<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>MOT!VANK</title>
        <link>https://velog.io/</link>
        <description>만들고 나누며, 세상을 이롭게 하고 싶습니다.</description>
        <lastBuildDate>Tue, 14 Mar 2023 14:41:53 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>MOT!VANK</title>
            <url>https://velog.velcdn.com/images/eunho_dev/profile/5e2b919c-0074-4e1a-82cd-62f750b04e8a/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. MOT!VANK. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/eunho_dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[코틀린 기본기 - 예외]]></title>
            <link>https://velog.io/@eunho_dev/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B8%B0%EB%B3%B8%EA%B8%B0-%EC%98%88%EC%99%B8</link>
            <guid>https://velog.io/@eunho_dev/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B8%B0%EB%B3%B8%EA%B8%B0-%EC%98%88%EC%99%B8</guid>
            <pubDate>Tue, 14 Mar 2023 14:41:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 최태현 님의 <a href="https://www.inflearn.com/course/java-to-kotlin/dashboardhttps://www.inflearn.com/course/java-to-kotlin/dashboard">&#39;자바 개발자를 위한 코틀린 입문&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.</p>
</blockquote>
<h2 id="try-catch-finally">try catch finally</h2>
<pre><code class="language-kotlin">fun parseIntOrThrow(str : String): Int {
    try {
        return str.toInt()
    } catch (e : NumberFormatException) {
        throw IllegalArgumentException(&quot;${str}은 숫자가 아닙니다.&quot;)
    }
}</code></pre>
<p>위의 예제와 같이 Java에서의<code>try-catch</code>문과 큰 차이는 없다. 단 <strong>타입이 뒤에 위치</strong>하고, <strong>new 키워드를 사용하지 않는다</strong>는 차이가 존재한다.</p>
<pre><code class="language-kotlin">fun parseIntOrThrow(str : String): Int? {
    return try {
        str.toInt()
    } catch (e : NumberFormatException) {
        null
    }
}</code></pre>
<p>또한 <code>try-catch</code>문 또한 표현식(expression)이기 때문에 구문 자체를 <code>return</code>할 수 있다. </p>
<h2 id="checked-exception--unchecked-exdception">Checked Exception &amp; Unchecked Exdception</h2>
<p>Kotlin에는 <code>throws</code> 구문이 없다. <code>Checked Exception</code>과 <code>Unchecked Exception</code>을 구분하지 않고 <strong>모든 예외를 <code>Unchecked Exception</code>으로 간주</strong>하기 때문이다.</p>
<h2 id="try-with-resources">try with resources</h2>
<p>Kotlin에는 <code>try with resources</code>구문이 존재하지 않고 <code>use</code>라는 inline 확장 함수를 사용해야 한다. <code>use</code>함수 사용시 <code>close</code>는 내부에서 처리해준다.</p>
<pre><code class="language-kotlin">fun readFile(path: String) {
    BufferedReader(FileReader(path)).use { reader -&gt; 
        println(reader.readLine())
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘으로 기강 잡기 - 그래프]]></title>
            <link>https://velog.io/@eunho_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EA%B0%95-%EC%9E%A1%EA%B8%B0-%EA%B7%B8%EB%9E%98%ED%94%84</link>
            <guid>https://velog.io/@eunho_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EA%B0%95-%EC%9E%A1%EA%B8%B0-%EA%B7%B8%EB%9E%98%ED%94%84</guid>
            <pubDate>Wed, 08 Mar 2023 13:03:42 GMT</pubDate>
            <description><![CDATA[<h2 id="그래프의-표현">그래프의 표현</h2>
<p>그래프는 노드(데이터를 표현하는 단위)와 에지(노드를 연결하는 선)로 구성된 집합이다. 그래프를 구현하는 방법은 크게 세 가지가 존재한다.</p>
<h3 id="1-에지-리스트">1. 에지 리스트</h3>
<p>에지를 중심으로 그래프를 표현하는 방법이다. 배열에 출발 노드, 도착 노드를 저장하여 에지를 표현하거나 출발 노드, 도착 노드, 가중치를 저장하여 가중치가 있는 에지를 표현한다.</p>
<h4 id="가중치가-없는-그래프">가중치가 없는 그래프</h4>
<p>가중치가 없는 그래프는 출발 노드와 도착 노드만 표현하기 때문에 배열의 행은 2개면 충분하다. 노드는 여러 자료형을 사용할 수 있다.
<img src="https://velog.velcdn.com/images/eunho_dev/post/282feaeb-d392-4c50-9917-4f0355e488cd/image.png" alt=""></p>
<h4 id="가중치가-있는-그래프">가중치가 있는 그래프</h4>
<p>가중치가 있는 그래프는 행을 3개로 늘려 3번째 행에 가중치를 저장한다.</p>
<p>에지 리스트는 구현하기 쉽지만 특정 노드와 관련되어 있는 에지를 탐색하기 쉽지 않다. 에지 리스트는 벨만-포드나 크루스칼 알고리즘에 사용되며, 노드 중심 알고리즘에는 잘 사용하지 않는다.</p>
<h3 id="2-인접-행렬">2. 인접 행렬</h3>
<p>인접 행렬은 2차원 배열을 자료구조로 이용하여 그래프를 표현한다. 에지 리스트와는 다르게 노드 중심으로 그래프를 표현한다.</p>
<h4 id="가중치가-없는-그래프-1">가중치가 없는 그래프</h4>
<p>아래 그림과 같이 에지를 A행 B열에 1을 저장하는 방식으로 표현한다. 1을 저장하는 이유는 가중치가 없기 때문이다.
<img src="https://velog.velcdn.com/images/eunho_dev/post/89b85c69-39d1-4063-bd00-fdac289d5815/image.png" alt=""></p>
<h4 id="가중치가-있는-그래프-1">가중치가 있는 그래프</h4>
<p>가중치가 있는 그래프는 앞의 가중치가 없는 그래프에서 에지의 위치에 가중치를 기록한다.</p>
<p>인접 행렬의 구현은 쉽고, 두 노드를 연결하는 에지의 여부와 가중치 값을 배열에 직접 접근하여 바로 확인할 수 있다는 장점이 있다. </p>
<p>그러나 노드와 관련되어 있는 에지를 탐색하기 위해서 N번 접근해야 하며, 노드 개수에 비해 에지가 적을 때 공간 효율성이 떨어진다. 또한 노드 개수가 많은 겨우 2차원 배열 선언 자체를 할 수 없는 결함도 있다.(노드 개수가 3만개가 넘을 경우 Java 힙 스페이스 에러 발생)</p>
<h3 id="3-인접-리스트">3. 인접 리스트</h3>
<p>인접 리스트는 <code>ArrayList</code>로 그래프를 표현한다. 노드 개수만큼 ArrayList를 선언하고, 알맞는 자료형을 사용한다.</p>
<h4 id="가중치가-없는-그래프-2">가중치가 없는 그래프</h4>
<p>인접 리스트에는 N번 노드와 연결되어 있는 노드를 배열의 위치 N에 연결된 노드 개수만큼 배열을 연결하는 방식으로 표현한다.
<img src="https://velog.velcdn.com/images/eunho_dev/post/5ca37582-c315-44a7-8b90-d28ae87adc90/image.png" alt=""></p>
<h4 id="가중치가-있는-그래프-2">가중치가 있는 그래프</h4>
<p>가중치가 있는 경우 자료형을 클래스로 사용한다. Node 클래스를 선언하여 <code>ArrayList</code>에 사용한다.</p>
<p>다른 방법들에 비해 인접 리스트를 이용한 그래프 구현은 복잡한 편이지만, 노드와 연결되어 있는 에지를 탐색하는 시간이 매우 뛰어나며, 노드 개수가 커도 공간 효율이 좋아 메모리 초과 에러도 발생하지 않는다.</p>
<h3 id="그래프-표현-구현">그래프 표현 구현</h3>
<p><a href="https://www.acmicpc.net/problem/1707">백준 1707 - 이분 그래프</a></p>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {

    static ArrayList&lt;Integer&gt;[] A ;  // 그래프 데이터 저장 인접 리스트
    static int[] check;              // 이분 그래프 체크 배열
    static boolean[] visited;        // 방문 기록 저장 배열
    static boolean isEven;           // 이분 그래프 여부

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

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

        for (int t = 0; t &lt; testCase; t++) {
            String[] s = br.readLine().split(&quot; &quot;);
            int v = Integer.parseInt(s[0]);    // 노드 개수
            int e = Integer.parseInt(s[1]);    // 에지 개수

            A = new ArrayList[v + 1];    // 노드는 무조건 1번 부터 시작
            visited = new boolean[v + 1];
            check = new int[v + 1];
            isEven = true;

            for (int i = 1; i &lt;= v; i++) {
                A[i] = new ArrayList&lt;Integer&gt;();
            }

            // 에지 데이터 저장
            for (int i = 0; i &lt; e; i++) {
                s = br.readLine().split(&quot; &quot;);
                int start = Integer.parseInt(s[0]);
                int end = Integer.parseInt(s[1]);
                A[start].add(end);
                A[end].add(start);
            }

            // 모든 노드에서 DFS 실행
            for (int i = 1; i &lt;= v; i++) {
                if (isEven) {
                    DFS(i);
                } else {
                    break;
                }
            }

            if (isEven)  System.out.println(&quot;YES&quot;);
            else         System.out.println(&quot;NO&quot;);
        }
    }

    public static void DFS(int start) {
        visited[start] = true;
        for (int i : A[start]) {    // 인접 리스트로 받아서 start에서 연결된 모든 노드 탐색
            if (!visited[i]) {
                check[i] = (check[start] + 1) % 2;
                DFS(i);
            } else {
                if (check[start] == check[i]) {
                    isEven = false; 
                }
            }
        }
    }
}</code></pre>
<h2 id="유니온-파인드">유니온 파인드</h2>
<p>그래프에서의 유니온 파인드는 그래프의 사이클이 생성되는지 판별하는 알고리즘이다.</p>
<h2 id="위상-정렬">위상 정렬</h2>
<p>사이클이 없고, 방향이 있는 그래프를 정렬하는 알고리즘으로 그 결과가 한 개 이상일 수 있다.</p>
<h2 id="다익스트라">다익스트라</h2>
<p>최단거리 알고리즘으로, 시작점이 있고 다른 모든 노드로 가는 최단거리를 구하는 알고리즘이다. 단, 음수간선이 존재하면 안된다.</p>
<h2 id="벨만-포드">벨만-포드</h2>
<p>최단거리 알고리즘으로, 시작점이 있고 다른 모든 노드로 가는 최단거리를 구하는 알고리즘이다. 음수간선이 존재해도 구할 수 있다. 음수 사이클이 존재하는지 체크하는데 많이 사용된다. </p>
<p>노드 수를 V, 에지 수를 E라고 했을 때 O(VE)의 시간 복잡도를 갖는다.</p>
<h3 id="핵심-이론">핵심 이론</h3>
<h4 id="1-에지-리스트로-그래프-구현-후-최단-경로-리스트-조회">1. 에지 리스트로 그래프 구현 후 최단 경로 리스트 조회</h4>
<p>벨만-포드 알고리즘은 에지를 중심으로 동작하기 때문에 그래프를 에지 리스트로 구현한다. 또한 최단 경로 리스트를 출발 노드는 0, 나머지 노드는 무한대(적당히 큰 수)로 초기화 한다. </p>
<h4 id="2-모든-에지를-확인하며-정답-리스트-업데이트">2. 모든 에지를 확인하며 정답 리스트 업데이트</h4>
<p>최단 거리 리스트에서 업데이트 반복 횟수는 노드 개수 -1이다.(이 이상일 경우 무조건 사이클이 생긴다.) 업데이트 반복 횟수가 K번이라면 해당 시점에 정답 리시트의 값은 시작점에서 K개의 에지를 사용했을 때 각 노드에 대한 최단 거리이다.</p>
<blockquote>
<h4 id="업데이트-조건과-방법">업데이트 조건과 방법</h4>
<p><strong>시작점의 값 != ∞</strong>이며, <strong>도착점의 값 &gt; 시작점의 값 + 가중치</strong>일 때 <strong>도착점의 값 = 시작점의 값 + 가중치</strong>로 리스트 값을 업데이트한다.</p>
</blockquote>
<p>음수 사이클이 없을 때 N - 1번 에지 사용 횟수를 반복하면 출발 노드와 모든 노드 사이의 최단 거리를 구할 수 있다. 이렇게 완성된 정답 리스트를 통해 해당 그래프에 음수 사이클이 존재하는지 여부를 확인해야 한다.</p>
<h4 id="3-음수-사이클-유무-확인">3. 음수 사이클 유무 확인</h4>
<p>음수 사이클 유무를 확인하기 위해 모든 에지를 한 번씩 다시 사용해 업데이트되는 노드가 발생하는지 확인한다. 업데이트되는 노드가 있다면 음수 사이클이 존재한다는 의미이며, 최단 거리를 찾을 수 없는 그래프라는 뜻이 된다.(음수 사이클이 존재할 경우 무한하게 돌며 가중치가 감소하므로 구할 수 없다.)</p>
<h2 id="플로이드-워셜">플로이드-워셜</h2>
<p>시작점이 존재하지 않고, 어느 노드에서 시작해도 최단거리를 구할 수 있는 알고리즘으로, 음수 가중치 에지가 있어도 수행할 수 있지만 음수 사이클이 존재하면 안된다. 단 시간복잡도가 좋지 않다.(O(노드의 수³))</p>
<h3 id="핵심-이론-1">핵심 이론</h3>
<p>플로이드-워셜의 핵심 원리는 A 노드에서 B 노드까지 최단 경로를 구했다고 가정했을 때 최단 경로 위에 K 노드가 존재한다면 그것일 이루는 부분 경로 역시 최단 경로라는 것이다.</p>
<blockquote>
<h4 id="플로이드-워셜-점화식">플로이드-워셜 점화식</h4>
<p>D[S][E] = Math.min(D[S][E], D[S][K] + D[K][E])</p>
</blockquote>
<h4 id="1-리스트-선언-후-초기화">1. 리스트 선언 후 초기화</h4>
<p>D[S][E]는 노드 S에서 노드 E까지의 최단 거리를 저장하는 리스트라 정의한다. S와 E의 값이 같은 칸은 0, 다른 칸은 무한대로 초기화 한다. 여기서 S == E는 자기 자신에게 가는데 걸리는 최단 경로값을 의미하기 때문이다.</p>
<h4 id="2-최단-거리-리스트에-그래프-데이터-저장">2. 최단 거리 리스트에 그래프 데이터 저장</h4>
<p>출발 노드는 S, 도착 노드는 E, 가중치 W일 때 D[S][E] = W로 에지의 정보를 리스트에 입력한다.(인접 행렬) </p>
<h4 id="3-점화깃으로-리스트-업데이트">3. 점화깃으로 리스트 업데이트</h4>
<p>기존에 구했던 점화식을 3중 for문의 형태로 반복하면서 리스트의 값을 업데이트한다.</p>
<pre><code class="language-java">for 경유지 K에 관해 (1 ~ N)
    for 출발 노드 S에 관해 (1 ~ N)
        for 도착 노드 E에 관해 (1 ~ N)
            D[S][E] = Math.min(D[S][E], D[S][K] + D[K][E])</code></pre>
<h2 id="최소-신장-트리">최소 신장 트리</h2>
<p>그래프의 모든 노드를 연결하는데 사용된 에지들의 가중치의 합을 최소로 하는 트리이다. 크루스칼 알고리즘과 프림 알고리즘을 사용할 수 있다.</p>
<p><strong>사이클이 포함되면 가중치의 합이 최소가 될 수 없기 때문에 사이클을 포함하지 않으며</strong>, N개의 노드가 있으면 최소 신장 트리를 구성하는 에지의 개수는 항상 N-1 개이다.</p>
<h3 id="핵심-이론-2">핵심 이론</h3>
<h4 id="1-에지-리스트로-그래프를-구현한-후-유니온-파인드-리스트-초기화">1. 에지 리스트로 그래프를 구현한 후 유니온 파인드 리스트 초기화</h4>
<p>최소 신장 트리는 데이터를 노드가 아닌 <strong>에지 중심으로 저장</strong>하기 때문에 인접 리스트가 아닌 <strong>에지 리스트로 저장</strong>한다. 일반적으로 <strong>노드 변수 2개와 가중치 변수로 구성</strong>되며, 사이클 처리를 위한 <strong>유니온 파인드 리스트도 함께 초기화</strong> 한다. 리스트의 <strong>인덱스는 해당 자리의 값으로 초기화</strong>한다.</p>
<h4 id="2-가중치-기준으로-그래프-정렬">2. 가중치 기준으로 그래프 정렬</h4>
<p>에지 리스트에 담긴 그래프 데이터를 가중치 기준으로 <strong>오름차순 정렬</strong>한다.</p>
<h4 id="3-가중치가-낮은-에지부터-연결-시도">3. 가중치가 낮은 에지부터 연결 시도</h4>
<p>가중치가 낮은 에지부터 순서대로 연결을 시도하며 바로 연결하는 것이 아닌 이 에지를 연결했을 때 그래프에 사이클 형성 유무를 find 연산을 이용해 확인한 후 사이클이 형성되지 않을 때만 union연산을 이용해 두 노드를 연결한다.</p>
<h4 id="4-앞의-단계-3을-반복">4. 앞의 단계 3을 반복</h4>
<p>전체 노드의 개수가 N개이면 연결한 에지의 개수가 N-1이 될 때까지 과정 3을 반복한다.</p>
<h4 id="5-총-에지-비용-출력">5. 총 에지 비용 출력</h4>
<p>에지의 개수가 N-1이 되면 알고리즘을 종료하고, 완성된 최소 신장 트리의 총 에지 비용을 출력한다.</p>
<p>최소 신장 트리는 다른 그래프 알고리즘과 달리 에지 리스트의 형태를 이용해 데이터를 담는다. 또한 사이클이 존재하면 안되기 때문에 사이클 판별을 위한 유니온 파인드 알고리즘을 내부에 구현해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘으로 기강 잡기 - 정수론]]></title>
            <link>https://velog.io/@eunho_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EA%B0%95-%EC%9E%A1%EA%B8%B0-%EC%A0%95%EC%88%98%EB%A1%A0</link>
            <guid>https://velog.io/@eunho_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EA%B0%95-%EC%9E%A1%EA%B8%B0-%EC%A0%95%EC%88%98%EB%A1%A0</guid>
            <pubDate>Tue, 07 Mar 2023 13:06:42 GMT</pubDate>
            <description><![CDATA[<h2 id="소수-구하기">소수 구하기</h2>
<p>소수를 구하는 대표적인 판별법으로 <strong>에라토스테네스의 체</strong>가 있다. 에라토스테네스의 체 원리는 다음과 같다.
<img src="https://velog.velcdn.com/images/eunho_dev/post/71f84ac2-a5b9-4da0-a5ed-e42df27e04b9/image.png" alt=""></p>
<h3 id="에라토스테네스의-체-핵심-원리">에라토스테네스의 체 핵심 원리</h3>
<ol>
<li>구하고자 하는 소수의 범위만큼 1차원 배열을 생성</li>
<li><strong>2</strong> 부터 시작하여 현재 숫자가 지워지지 않을 때, 현재 선택된 숫자의 배수에 해당하는 수를 배열에서 끝까지 탐색하며 지운다. 이 때 <strong>처음으로 선택된 수는 지우지 않는다.</strong></li>
<li>배열의 끝까지 두 번째 단계를 반복한 후 배열에서 남아 있는 모든 수를 출력</li>
</ol>
<p>에라토스테네스의 체를 사용할 때 시간 복잡도는 최적화 정도에 따라 다르지만, 일반적으로 <strong>O(Nlog(logN))</strong>이다.</p>
<h3 id="소수-구하기-구현">소수 구하기 구현</h3>
<p><a href="https://www.acmicpc.net/problem/1929">백준 1929번 - 소수 구하기</a></p>
<pre><code class="language-java">import java.util.*;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);    
        int m = sc.nextInt();
        int n = sc.nextInt();
        int[] a = new int[n + 1];

        for (int i = 1; i &lt;= n; i++) {
            a[i] = i;
        }

        for (int i = 2; i &lt;= Math.sqrt(n); i++) {
            if (a[i] == 0) continue;
            for (int j = i+i; j &lt;= n; j += i) {
                a[j] = 0;
            }
        }

        for (int i = m; i &lt;= n; i++) {
            if (a[i] &gt; 1) {
                System.out.println(a[i]);
            }
        }
    }
}</code></pre>
<h2 id="오일러-피">오일러 피</h2>
<p>오일러 피 함수 P[N]의 정의는 1 부터 N까지의 범위에서 N과 서로소(공약수가 1이외에 없는 수)인 자연수의 개수를 뜻한다.</p>
<h3 id="오일러-피-핵심-이론">오일러 피 핵심 이론</h3>
<ol>
<li>구하고자 하는 오일러 피의 범위만큼 자기 자신의 인덱스값으로 초기화</li>
<li>2 부터 시작하여 <strong>배열의 값과 인덱스가 같으면(소수일 때)</strong> 현재 <strong>선택된 수(K)의 배수</strong>에 해당하는 수를 배열에 끝까지 탐색하여 <strong>P[i] = P[i] - P[i]/k 연산 수행</strong></li>
<li>배열의 끝까지 두 번째 단계를 반복하며 오일러 피 함수를 완성</li>
</ol>
<h2 id="유클리드-호제법">유클리드 호제법</h2>
<p>두 수의 최대 공약수를 구하는 알고리즘으로, 소인수 분해를 이용한 공통된 소수들의 곱으로 표현하는 것 보다 좀 더 간단한 방법을 제시한다.</p>
<h3 id="유클리드-호제법-핵심-이론">유클리드 호제법 핵심 이론</h3>
<p>유클리드 호제법을 수행하기 위해서는 먼저 최대 공약수를 구하는데 사용할 <strong>MOD 연산</strong>(두 값을 나눈 나머지를 구하는 연산 - <code>10 % 4 = 2</code>)을 이해하고 있어야 한다.</p>
<h4 id="mod-연산을-통해-구현하는-유클리드-호제법">MOD 연산을 통해 구현하는 유클리드 호제법</h4>
<ol>
<li>큰 수를 작은 수로 나누는 MOD 연산 수행</li>
<li>이전 단계에서의 작은 수와 MOD 연산의 결과값으로 MOD 연산 수행</li>
<li>두 번째 단계를 반복하며 <strong>나머지가 0이 되는 순간의 작은 수를 최대 공약수로 선택</strong></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘으로 기강 잡기 - 그리디 알고리즘 (탐욕법)]]></title>
            <link>https://velog.io/@eunho_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EA%B0%95-%EC%9E%A1%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EB%94%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%83%90%EC%9A%95%EB%B2%95</link>
            <guid>https://velog.io/@eunho_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EA%B0%95-%EC%9E%A1%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EB%94%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%83%90%EC%9A%95%EB%B2%95</guid>
            <pubDate>Tue, 07 Mar 2023 12:05:34 GMT</pubDate>
            <description><![CDATA[<h2 id="그리디-알고리즘">그리디 알고리즘</h2>
<p>그리디 알고리즘은 현재 상태에서 선택할 수 있는 선택 중 최선의 선택지가 전체 선택지 중 최선의 선택지라고 가정하는 알고리즘이다.</p>
<h3 id="핵심-이론">핵심 이론</h3>
<ol>
<li>해 선택 : 현재 상태에서 가장 최선이라고 생각되는 해를 선택</li>
<li>적절성 검사 : 현재 선택한 해가 전체 문제의 제약 조건에 벗어나지 않는지 검사</li>
<li>해 검사 : 현재까지 선택한 해 집합이 전체 문제를 해결할 수 있는지 검사, 전체 문제를 해결하지 못한다면 1로 돌아가 같은 과정을 반복 수행</li>
</ol>
<h3 id="그리디-알고리즘-구현">그리디 알고리즘 구현</h3>
<p><a href="https://www.acmicpc.net/problem/11047">백준 11047번 - 동전 0</a></p>
<pre><code class="language-java">import java.util.*;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        int[] a = new int[n];

        for (int i = 0; i &lt; n; i++) {
            a[i] = sc.nextInt();
        }

        int count = 0;
        for (int i = n - 1; i &gt;= 0; i--) {
            if (a[i] &lt;= k) {
                count += (k / a[i]);
                k = k % a[i];
                if (k == 0) break; 
            }
        }

        System.out.println(count);
    }
}</code></pre>
<p><a href="https://www.acmicpc.net/problem/1541">백준 1541번 - 잃어버린 괄호</a></p>
<pre><code class="language-java">import java.util.*;

class Main {
    public static void main(String[] args) {
        int answer = 0;
        Scanner sc = new Scanner(System.in);
        String str = sc.nextLine();
        String[] strs = str.split(&quot;-&quot;);

        for (int i = 0; i &lt; strs.length; i++) {
            int num = sumNumber(strs[i]);
            if (i == 0) answer += num;
            else        answer -= num;
        }

        System.out.println(answer);
    }    

    public static int sumNumber(String str) {
        int sum = 0;
        String[] strs = str.split(&quot;[+]&quot;);

        for (int i = 0; i &lt; strs.length; i++) {
            sum += Integer.parseInt(strs[i]);
        }

        return sum;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 프레임워크(Spring Framework) 톺아보기 - 스프링이 지원하는 프록시]]></title>
            <link>https://velog.io/@eunho_dev/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%ACSpring-Framework-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%B4-%EC%A7%80%EC%9B%90%ED%95%98%EB%8A%94-%ED%94%84%EB%A1%9D%EC%8B%9C</link>
            <guid>https://velog.io/@eunho_dev/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%ACSpring-Framework-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%B4-%EC%A7%80%EC%9B%90%ED%95%98%EB%8A%94-%ED%94%84%EB%A1%9D%EC%8B%9C</guid>
            <pubDate>Sat, 04 Mar 2023 02:47:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 김영한 님의 <a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard">&#39;스프링 핵심원리 고급편&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.  </p>
</blockquote>
<h2 id="프록시-팩토리">프록시 팩토리</h2>
<h3 id="동적-프록시-사용의-문제점">동적 프록시 사용의 문제점</h3>
<p>동적 프록시를 사용할 때 인터페이스가 있는 경우에는 JDK 동적 프록시를, 구체 클래스는 CGLIB를 적용해야 한다. 이를 자동화할 수 있는 방법이 필요하며, 이 때 JDK 동적 프록시가 제공하는 <code>InvocationHandler</code>와 CGLIB가 제공하는 <code>MethodInterceptor</code>를 각각 중복으로 개발하여 관리해야 하는지, 특정 조건에 부합할 때 프록시 로직을 적용하는 공통 기능 제공 등 고민해야 할 부분이 존재한다.</p>
<h3 id="프록시-팩토리-1">프록시 팩토리</h3>
<p>스프링은 유사한 구체적인 기술들이 존재할 때 이를 통합해서 일관성 있게 접근하고 편리하게 사용할 수 있도록 추상화된 기술을 제공하며, 동적 프록시를 통합해서 관리하기 위해 <strong>프록시 팩토리(<code>ProxyFactory</code>)</strong>를 제공한다.</p>
<p>프록시 팩토리를 사용할 경우 인터페이스는 JDK 동적 프록시를, 구체 클래스만 존재할 경우 CGLIB를 사용한다. 또한 이 설정을 변경할 수도 있게 해준다.</p>
<h3 id="advice">Advice</h3>
<p>또한 두 기술을 함께 사용할 때 부가 기능을 적용하기 위해 각 기술을 중복해서 개발하지 않도록 <code>Advice</code>라는 새로운 개념을 도입했다. </p>
<p>개발자는 <code>InvocationHandler</code>와 <code>MethodInterceptor</code>를 구분하지 않고 <code>Advice</code> 하나만을 만들면 프록시 팩토리가 기술에 맞게 해당 <code>Advice</code>를 호출하는 전용 <code>InvocationHandler</code>와 <code>MethodInterceptor</code>를 내부적으로 사용한다.</p>
<h4 id="advice-구현-및-프록시-팩토리-적용">Advice 구현 및 프록시 팩토리 적용</h4>
<p><code>Advice</code>를 만드는 방법은 기본적으로 스프링 AOP 모듈에서 제공하는 <code>org.aopalliance.intercept</code> 패키지의 <code>MethodInterceptor</code>를 구현하면 된다.</p>
<pre><code class="language-java">public class TestAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println(&quot;TestProxy Run&quot;);

        Object result = invocation.procced();    
        /* 
         * target 클래스 호출 및 결과 반환
         * target 클래스의 정보는 MethodInvocation 안에 모두 포함,
         * ProxyFactory를 생성하는 단계에 이미 target 정보를 파라미터로 전달
         */

        System.out.println(&quot;TestProxy Stop&quot;);
        return result;
    }

}

public class ProxyFactoryTest {

    public void interfaceProxy() {    // JDK 동적 프록시 적용
        TestInterface target = new TestInterface();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        // target 정보를 파라미터로 전달받는다.
        proxyFactory.addAdvice(new TestAdvice());

        TestInterface proxy = (TestInterface) proxyFactory.getProxy();

        proxy.execute();
    }

    public void concreteProxy() {    // CGLIB 적용
        TestClass target = new TestClass();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        // target 정보를 파라미터로 전달받는다.
        proxyFactory.addAdvice(new TestAdvice());

        TestClass proxy = (TestClass) proxyFactory.getProxy();

        proxy.execute();
    }

    public void interfaceProxyTargetClass() {    
    // Interface가 존재하지만 CGLIB 적용
        TestInterface target = new TestInterfaceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        // target 정보를 파라미터로 전달받는다.
        proxyFactory.setProxyTargetClass(true);    // 구현체에 적용
        proxyFactory.addAdvice(new TestAdvice());

        TestInterface proxy = (TestInterface) proxyFactory.getProxy();

        proxy.execute();
    }

}</code></pre>
<h3 id="pointcut">Pointcut</h3>
<p>스프링은 <code>Pointcut</code>이라는 개념을 도입해 특정 조건에 부합할 경우에만 프록시 부가 기능이 적용되도록 설정해줄 수 있게 해준다.</p>
<h4 id="pointcut-advice-advisor">Pointcut, Advice, Advisor</h4>
<p>프록시 팩토리는 역할과 책임을 명확하게 분리하기 위해 다음과 같은 단어들을 사용한다.</p>
<ul>
<li>Pointcut : 프록시 부가 기능을 적용할지 여부를 판단하는 필터링 로직. 주로 클래스와 메서드 이름으로 필터링 한다.</li>
<li>Advice : 프록시가 호출하는 부가 기능. 프록시 로직</li>
<li>Advisor : 하나의 포인트컷과 하나의 어드바이스를 포함한 개념</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘으로 기강 잡기 - 탐색]]></title>
            <link>https://velog.io/@eunho_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EA%B0%95-%EC%9E%A1%EA%B8%B0-%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@eunho_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EA%B0%95-%EC%9E%A1%EA%B8%B0-%ED%83%90%EC%83%89</guid>
            <pubDate>Thu, 02 Mar 2023 12:09:17 GMT</pubDate>
            <description><![CDATA[<h2 id="dfs깊이-우선-탐색">DFS(깊이 우선 탐색)</h2>
<p>깊이 우선 탐색은 그래프 완전 탐색 기법 중 하나로, 그래프의 시작 노드에서 출발하여 <strong>탐색할 한 쪽 분기를 정하여 최대 깊이까지 탐색을 마친 후 다른 쪽 분기로 이동하여 다시 탐색을 수행</strong>하는 알고리즘이다. </p>
<p>특징으로는 <strong>재귀 함수로 구현</strong>되며, <strong>스택 자료구조를 이용</strong>한다. 노드 개수를 V, 에지 개수를 E라고 할 경우 시간 복잡도는 O(V + E)이다.</p>
<p>깊이 우선탐색은 재귀 함수를 이용하므로 스택 오버플로우(stack overflow)에 유의해야 한다. </p>
<h3 id="핵심-이론">핵심 이론</h3>
<p><img src="https://velog.velcdn.com/images/eunho_dev/post/3f8e622d-6571-445d-9a72-5aaac8c38c0f/image.png" alt=""></p>
<h4 id="1-dfs-시작-노드를-정한-후-사용할-자료구조-초기화">1. DFS 시작 노드를 정한 후 사용할 자료구조 초기화</h4>
<p>DFS를 위한 초기 작업은 인접 리스트로 그래프를 표현하고, 방문했던 노드를 체크하기 위한 방문 배열을 초기화한 후 시작 노드 스택에 한다. </p>
<h4 id="2-스택에서-노드를-꺼낸-후-꺼낸-노드의-인접-노드를-다시-스택에-삽입">2. 스택에서 노드를 꺼낸 후 꺼낸 노드의 인접 노드를 다시 스택에 삽입</h4>
<p>스택에 들어있던 노드를 꺼낸 후 탐색 순서에 기입하고, 인접 리스트의 인접 노드를 스택에 삽입하며 방문 배열을 체크한다.</p>
<h4 id="3-스택-자료구조에-값이-없을-때-까지-반복">3. 스택 자료구조에 값이 없을 때 까지 반복</h4>
<p>위의 과정을 스택 자료구조에 값이 없을 때까지 반복한다. 이 때 이미 다녀간 노드는 방문 배열을 바탕으로 재삽입하지 않는 것이 핵심이다.</p>
<h3 id="dfs-구현">DFS 구현</h3>
<p><a href="https://www.acmicpc.net/problem/11724">백준 11724번 - 연결 요소의 개수</a></p>
<pre><code class="language-java">import java.io.*;
import java.util.*;

class Main {
    static boolean[] VISITED;
    static ArrayList&lt;Integer&gt;[] A;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());
        VISITED = new boolean[n + 1];
        A = new ArrayList[n + 1];

        for (int i = 1; i &lt; n + 1; i++) {
            A[i] = new ArrayList&lt;&gt;();
        }

        for (int i = 0; i &lt; m; i++) {
            st = new StringTokenizer(br.readLine());
            int s = Integer.parseInt(st.nextToken());
            int e = Integer.parseInt(st.nextToken());
            A[s].add(e);
            A[e].add(s);
        }
        int count = 0;
        for (int i = 1; i &lt; n + 1; i++) {
            if (!VISITED[i]) {
                count++;
                DFS(i);
            }
        }
        System.out.println(count);
    }    

    public static void DFS(int v) {
        if (VISITED[v]) {
            return;
        }
        VISITED[v] = true;
        for (int i : A[v]) {
            if (!VISITED[i]) {
                DFS(i);
            }
        }
    }
}</code></pre>
<h2 id="bfs너비-우선-탐색">BFS(너비 우선 탐색)</h2>
<p>그래프 완전 탐색 기법 중 하나로, 시작 노드에서 출발해 <strong>가까운 노드를 먼저 방문하면서 탐색하는 알고리즘</strong>이다. 노드 개수를 V, 에지 개수를 E라고 할 경우 시간 복잡도는 O(V + E)이다.</p>
<p><strong>선입선출 방식</strong>으로 탐색하므로 <strong>큐를 이용해 구현</strong>하며, 탐색 시작 노드와 가까운 노드를 우선 탐색하므로 <strong>목표 노드에 도착하는 경로가 여러 개일 때 최단 경로를 보장</strong>한다. </p>
<h3 id="핵심-이론-1">핵심 이론</h3>
<p><img src="https://velog.velcdn.com/images/eunho_dev/post/1b646949-c1fb-4fa2-88ce-7510a600a502/image.png" alt=""></p>
<h4 id="1-bfs-시작-노드를-정한-후-사용할-자료구조-초기화">1. BFS 시작 노드를 정한 후 사용할 자료구조 초기화</h4>
<p>인접 리스트로 그래프를 표현하고, 방문했던 노드를 체크하기 위한 방문 배열을 초기화한 후 시작 노드 큐에 한다. </p>
<h4 id="2-큐에서-노드를-꺼낸-후-꺼낸-노드의-인접-노드를-다시-큐에-삽입">2. 큐에서 노드를 꺼낸 후 꺼낸 노드의 인접 노드를 다시 큐에 삽입</h4>
<p>큐에 들어있던 노드를 꺼낸 후 탐색 순서에 기입하고, 인접 리스트의 인접 노드를 큐에 삽입하며 방문 배열을 체크한다.</p>
<h4 id="큐-자료구조에-값이-없을-때-까지-반복">큐 자료구조에 값이 없을 때 까지 반복</h4>
<p>위의 과정을 큐 자료구조에 값이 없을 때까지 반복한다. 이 때 이미 다녀간 노드는 방문 배열을 바탕으로 재삽입하지 않는 것이 핵심이다.</p>
<h3 id="bfs-구현">BFS 구현</h3>
<p><a href="acmicpc.net/problem/2178">백준 2178번 - 미로 탐색</a></p>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    static int[] DX = {0, 1, 0, -1};
    static int[] DY = {1, 0, -1, 0};

    static boolean[][] VISITED;
    static int[][] A;
    static int N, M;

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

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        A = new int[N][M];
        VISITED = new boolean[N][M];

        for (int i = 0; i &lt; N; i++) {
            st = new StringTokenizer(br.readLine());
            String line = st.nextToken();
            for (int j = 0; j &lt; M; j++) {
                A[i][j] = Integer.parseInt(line.substring(j, j + 1));
            }
        }

        BFS(0, 0);
        System.out.println(A[N - 1][M - 1]);
    }

    private static void BFS(int i, int j) {
        Queue&lt;int[]&gt; queue = new LinkedList&lt;&gt;();
        queue.offer(new int[] {i, j});
        VISITED[i][j] = true;

        while (!queue.isEmpty()) {
            int now[] = queue.poll();

            for (int k = 0; k &lt; 4; k++) {
                int x = now[0] + DX[k];
                int y = now[1] + DY[k];

                if (x &gt;= 0 &amp;&amp; y &gt;= 0 &amp;&amp; x &lt; N &amp;&amp; y &lt; M) {
                    if (A[x][y] != 0 &amp;&amp; !VISITED[x][y]) {
                        VISITED[x][y] = true;
                        A[x][y] = A[now[0]][now[1]] + 1;
                        queue.add(new int[] {x, y});
                    }
                } 
            }
        }
    }
}</code></pre>
<h2 id="이진-탐색">이진 탐색</h2>
<p><strong>이진 탐색(binary search)</strong>은 데이터가 <strong>정렬되어 있는 상태</strong>에서 원하는 값을 찾아내는 알고리즘으로, 대상 <strong>데이터의 중앙값과 찾고자 하는 값을 비교해 데이터의 크기를 절반씩 줄이면서 대상을 찾는 알고리즘</strong>이다.</p>
<h3 id="핵심-이론-2">핵심 이론</h3>
<p><img src="https://velog.velcdn.com/images/eunho_dev/post/97bbbccb-0c80-49b7-aab2-cab84c57e05f/image.webp" alt=""></p>
<ol>
<li>현재 데이터셋의 중앙값을 선택</li>
<li><strong>중앙값 &gt; 타깃 데이터</strong>일 경우 중앙값 기준으로 왼쪽 데이터셋 선택</li>
<li><strong>중앙값 &lt; 타깃 데이터</strong>일 경우 중앙값 기준으로 오른쪽 데이터셋 선택</li>
<li>1 ~ 3을 반복하며 <strong>중앙값 == 타깃 데이터</strong>일 때 탐색 종료</li>
</ol>
<h3 id="이진-탐색-구현">이진 탐색 구현</h3>
<p><a href="https://www.acmicpc.net/problem/1920">백준 1920번 - 수 찾기</a></p>
<pre><code class="language-java">import java.util.*;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int[] A = new int[N];
        for (int i = 0; i &lt; N; i++) {
            A[i] = sc.nextInt();
        }
        Arrays.sort(A);

        int M = sc.nextInt();
        for (int i = 0; i &lt; M; i++) {
            boolean find = false;
            int target = sc.nextInt();
            int start = 0;
            int end = A.length - 1;
            while (start &lt;= end) {
                int mid = (start + end) / 2;
                int midValue = A[mid];
                if (midValue &gt; target) {
                    end = mid - 1;
                } else if (midValue &lt; target) {
                    start = mid + 1;
                } else {
                    find = true;
                    break;
                }
            }
            if (find) System.out.println(1);
            else      System.out.println(0);
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘으로 기강 잡기 - 정렬]]></title>
            <link>https://velog.io/@eunho_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EA%B0%95-%EC%9E%A1%EA%B8%B0-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@eunho_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EA%B0%95-%EC%9E%A1%EA%B8%B0-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Mon, 27 Feb 2023 13:39:13 GMT</pubDate>
            <description><![CDATA[<h2 id="버블-정렬">버블 정렬</h2>
<p>버블 정렬은 loop를 돌며 데이터의 인접 요소끼리 비교하고, swap 연산을 수행하면 정렬하는 방식이다. 간단하게 구현할 수 있지만, 시간 복잡도가 O(n²)으로 다른 정렬 알고리즘보다 속도가 느린 편이다.</p>
<h3 id="버블-정렬-과정">버블 정렬 과정</h3>
<ol>
<li>비교 연산이 필요한 루프 범위 설정</li>
<li>인접한 데이터 값 비교</li>
<li>swap 조건에 부합하면 swap 연산 수행</li>
<li>루프 범위가 끝날 때까지 2~3번 반복</li>
<li>정렬된 정렬 영역을 설정하여 다음 루프 실행시 이 영역 제외</li>
<li>비교 대상이 존재하지 않을 때 까지 위의 과정 반복
<img src="https://velog.velcdn.com/images/eunho_dev/post/3c338255-6f41-4f1d-8430-99c332adf4f3/image.png" alt=""></li>
</ol>
<p>만약 특정한 루프의 전체 영역에서 swap이 한 번도 발생하지 않았다면 그 영역 뒤에 있는 데이터가 모두 정렬되었다는 뜻이 되며, 프로세스를 종료해도 된다.</p>
<h3 id="버블-정렬-구현">버블 정렬 구현</h3>
<p><a href="https://www.acmicpc.net/problem/2750">백준 2705번 - 수 정렬하기</a></p>
<pre><code class="language-java">import java.util.*;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] a = new int[n];

        for (int i = 0; i &lt; n; i++) {
            a[i] = sc.nextInt();
        }

        for (int i = 0; i &lt; n - 1; i++) {
            for (int j = 0; j &lt; n - 1 - i; j++) {
                if (a[j] &gt; a[j + 1]) {
                    int num = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = num;
                }
            }
        }

        for (int i = 0; i &lt; n; i++) {
            System.out.println(a[i]);
        }
    }
}</code></pre>
<h2 id="선택-정렬">선택 정렬</h2>
<p>선택 정렬은 대상 데이터에서 최대나 최소 데이터를 데이터가 나열된 순으로 찾아가며 선택하는 방법이다. 구현 방법이 복잡하고, 시간 복잡도도 O(n²)으로 비효율적이다.
<img src="https://velog.velcdn.com/images/eunho_dev/post/9ffe72d8-0424-404e-a667-d7bfc2931ba2/image.png" alt=""></p>
<h3 id="선택-정렬-구현">선택 정렬 구현</h3>
<p><a href="https://www.acmicpc.net/problem/1427">백준 1427번 - 소트인사이드</a></p>
<pre><code class="language-java">import java.util.*;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str = sc.next();
        int[] a = new int[str.length()];
        for (int i = 0; i &lt; str.length(); i++) {
            a[i] = Integer.parseInt(str.substring(i, i+1));
        }

        for (int i = 0; i &lt; a.length; i++) {
            int max = i;
            for (int j = i + 1; j &lt; a.length; j++) {
                if (a[max] &lt; a[j]) {
                    max = j;
                }
            }
            if (a[i] &lt; a[max]) {
                int num = a[i];
                a[i] = a[max];  
                a[max] = num;
            }
        }

        for (int i = 0; i &lt; a.length; i++) {
            System.out.print(a[i]);
        }
    }
}</code></pre>
<h2 id="삽입-정렬">삽입 정렬</h2>
<p>이미 정렬된 데이터 범위에 정렬되지 않은 데이터를 적절한 위치에 삽입시켜 정렬하는 방식으로 O(n²)의 시간 복잡도로 느린 편이지만 구현이 쉽다. 선택 데이터를 현재 정렬된 데이터 범위 내 적절한 위치에 삽입하는 것이 핵심이다.</p>
<h3 id="삽입-정렬-과정">삽입 정렬 과정</h3>
<ol>
<li>현재 index에 있는 데이터 값 선택</li>
<li>선택한 데이터가 정렬된 데이터 범위에 삽입될 위치 탐색</li>
<li>삽입 위치부터 index까지 shif연산 수행</li>
<li>삽입 위치에 현재 선택한 데이터를 삽입한 후 index++</li>
<li>선택할 데이터가 없을 때 까지 반복(전체 데이터의 크기만큼 index가 커질 때 까지)
<img src="https://velog.velcdn.com/images/eunho_dev/post/fd9d7cca-171a-48f1-9e76-4e832c625b81/image.png" alt="">
적절한 삽입 위치를 탐색할 때 이진 탐색과 같은 탐색 알고리즘을 사용하면 시간 복잡도를 줄일 수 있다.</li>
</ol>
<h2 id="퀵-정렬">퀵 정렬</h2>
<p>기준값을 선정해 해당 값보다 작은 데이터와 큰 데이터로 분류하는 것을 반복하여 정렬한다. 기준값(pivot) 선정 기준이 시간 복잡도에 많은 영향을 미치며, 평균 시간 복잡도는 O(nlogn)이며, 최악의 경우 O(n²)이 된다. </p>
<h3 id="퀵-정렬-과정"><a href="https://www.programiz.com/dsa/quick-sort">퀵 정렬 과정</a></h3>
<p><img src="https://velog.velcdn.com/images/eunho_dev/post/0a8b8a60-ecfd-4770-bda1-7221ac3350c6/image.png" alt=""></p>
<ol>
<li>데이터를 분할하는 pivot 설정</li>
<li>pivot을 기준으로 다음 과정을 거쳐 데이터를 2개의 집합으로 분리<ul>
<li>L이 가리키는 데이터가 pivot이 가리키는 데이터보다 작으면 L을 오른쪽으로 한칸 이동</li>
<li>R이 가리키는 데이터가 pivot이 가리키는 데이터보다 크면 R을 왼쪽으로 한칸 이동</li>
<li>L이 가리키는 데이터가 pivot이 가리키는 데이터보다 크고, R이 가리키는 데이터가 pivot이 가리키는 데이터보다 작으면 L, R이 가리키는 데이터를 swap하고, L은 오른쪽, R은 왼쪽으로 한칸씩 이동</li>
<li>L과 R이 만날 때 까지 위의 과정을 반복</li>
<li>L과 R이 만나면 만난 지점에서 가리키는 데이터와 pivot이 가리키는 데이터를 비교하여 pivot이 가리키는 데이터가 크면 만난 지점의 오른쪽에, 작으면 만난 지점의 왼쪽에 pivot이 가리키는 데이터를 삽입</li>
</ul>
</li>
<li>분리 집합에서 각각 다시 pivot을 선정</li>
<li>분리 집합이 한개 이하가 될 때까지 위의 모든 과정을 반복</li>
</ol>
<h2 id="병합-정렬">병합 정렬</h2>
<p>분할 정복 방식을 사용해 데이터를 분할하고, 분할한 집합을 정렬하며 합치는 정렬방법이며, 시간 복잡도는 O(nlogn)이다.</p>
<h3 id="두-그룹을-병합하는-과정">두 그룹을 병합하는 과정</h3>
<p><img src="https://velog.velcdn.com/images/eunho_dev/post/66d3e654-29af-4da9-baa8-a54a670d7afb/image.png" alt="">
투 포인터 개념을 사용하여 두 그룹을 병합한다. 왼쪽 포인터(i)와 오른쪽 포인터(j)의 값을 비교하여 작은 값을 결과 배열에 추가하고 포인터를 오른쪽으로 이동시킨다.</p>
<h3 id="병합-정렬-구현">병합 정렬 구현</h3>
<pre><code class="language-java">public class MergeSort {
   public static void main(String[] args) {
    int[] arr = { 9, 7, 3, 1, 6, 2, 8, 4, 5 };
    mergeSort(arr, 0, arr.length - 1);
    for (int i : arr) {
      System.out.print(i + &quot; &quot;);
    }
  }


  public static void mergeSort(int[] arr, int left, int right) {
    if (left &lt; right) {
      int mid = (left + right) / 2;  // 중간 지점 계산
      mergeSort(arr, left, mid);     // 왼쪽 반 정렬
      mergeSort(arr, mid + 1, right);// 오른쪽 반 정렬
      merge(arr, left, mid, right);  // 정렬된 반들을 병합
    }
  }

  public static void merge(int[] arr, int left, int mid, int right) {
    int[] tempArr = new int[right - left + 1]; // 병합을 위한 임시 배열 생성
    int i = left, j = mid + 1, k = 0;

    while (i &lt;= mid &amp;&amp; j &lt;= right) { // 병합
      if (arr[i] &lt;= arr[j]) {
        tempArr[k++] = arr[i++];
      } else {
        tempArr[k++] = arr[j++];
      }
    }

    // 왼쪽 반 나머지 항목들 복사
    while (i &lt;= mid) {
      tempArr[k++] = arr[i++];
    }

    // 오른쪽 반 나머지 항목들 복사
    while (j &lt;= right) {
      tempArr[k++] = arr[j++];
    }

    // 임시 배열을 복사하여 정렬된 배열로 업데이트
    for (i = left, k = 0; i &lt;= right; i++, k++) {
      arr[i] = tempArr[k];
    }
  }
}
</code></pre>
<h2 id="기수-정렬">기수 정렬</h2>
<p>값을 비교하지 않고 비교할 자릿수를 정한 후 해당 자릿수만 비교하는 정렬 방식으로 시간 복잡도는 O(kn)이다. k는 데이터의 자릿수를 의미한다.</p>
<p>기수 정렬은 10개의 큐를 이용(0 ~ 9의 수를 담기 위해)하며, 각 큐는 값의 자릴수를 대표한다.
<img src="https://velog.velcdn.com/images/eunho_dev/post/0a761025-eaac-4a62-be99-75301d41a680/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 프레임워크(Spring Framework) 톺아보기 - 동적 프록시]]></title>
            <link>https://velog.io/@eunho_dev/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%ACSpring-Framework-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EB%8F%99%EC%A0%81-%ED%94%84%EB%A1%9D%EC%8B%9C</link>
            <guid>https://velog.io/@eunho_dev/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%ACSpring-Framework-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EB%8F%99%EC%A0%81-%ED%94%84%EB%A1%9D%EC%8B%9C</guid>
            <pubDate>Mon, 27 Feb 2023 11:23:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 김영한 님의 <a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard">&#39;스프링 핵심원리 고급편&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.  </p>
</blockquote>
<h2 id="리플렉션">리플렉션</h2>
<p>자바의 리플렉션 기술은 클래스나 메서드의 메타정보를 동적으로 획득하고, 코드도 동적으로 호출할 수 있다. </p>
<p>리플렉션을 사용하는 방법은 다음과 같다.</p>
<ul>
<li><code>Class test = Class.forName(&quot;패키지경로.클래스명{$내부 클래스}&quot;)</code> : 클래스 메타정보를 획득 </li>
<li><code>Method execute = test.getMethod(&quot;execute&quot;)</code> : 해당 클래스의 <code>execute()</code> 메서드 메타정보를 획득</li>
<li><code>execute.invoke(target)</code> : 획득한 메서드 메타정보로 실제 인스턴스(<code>target</code>)의 메서드를 호출한다.</li>
</ul>
<p>이렇듯 리플렉션을 사용하면 클래스나 메서드 정보를 동적으로 변경할 수 있으며, 이를 통해 비지니스 로직의 공통된 부분을 뽑아 공통 로직을 만들 수 있게 된다.</p>
<h3 id="리플렉션-사용시-주의사항">리플렉션 사용시 주의사항</h3>
<p>리플렉션을 사용하면 어플리케이션을 동적으로 유연하게 만들 수 있다. 그러나 리플렉션은 런타임에 동작하기 때문에 <strong>컴파일 시점에 오류를 잡을 수 없다</strong>는 단점이 존재한다. 이는 굉장히 치명적인 오류로 발생할 수 있다.</p>
<p>따라서 리플렉션은 일반적으로 사용하지 않으며, 프레임워크나 매우 일반적인 공통 처리가 필요할 때 부분적으로 주의해서 사용해야 한다.</p>
<h2 id="jdk-동적-프록시">JDK 동적 프록시</h2>
<p>프록시 패턴을 적용하기 위해서는 적용 대상의 수 만큼 많은 프록시 클래스를 만들어야 했다. 이는 적용 대상의 차이가 존재하기 때문에 발생하는 문제이며, 동적 프록시 기술을 통해 이를 해결할 수 있다.</p>
<p>동적 프록시 기술을 사용하면 개발자가 직접 프록시 객체를 만들지 않아도 런타임에 프록시 객체를 대신 만들어주며, 원하는 실행 로직을 지정해줄 수 있다.</p>
<pre><code class="language-java">public interface TestInterface { void execute(); }

public class TestImple implements TestInterface {
    @Override
    public void execute() { System.out.println(&quot;Test Execute&quot;); }
}</code></pre>
<p>JDK 동적 프록시는 인터페이스를 기반으로 프록시를 동적으로 만들어주기 때문에 인터페이스가 필수적이다. 위의 인터페이스와 구현체에 JDK 동적 프록시를 이용해 공통 로직을 적용하기 위해서는 <code>InvocationHandler</code> 인터페이스를 구현하면 된다.</p>
<pre><code class="language-java">public class TestInvocationHandler implemnts InvocationHandler {

    private final Object target;    // 동적 프록시가 호출할 대상

    public TestInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method,
                         Object[] args) throws Throwable {
                         // args : 메서드 호출시 넘겨줄 인수
        System.out.println(&quot;TestProxy Run&quot;);

        Object result = method.invoke(target, args);

        System.out.println(&quot;TestProxy End&quot;);
        return result;
    }
}</code></pre>
<p>이렇게 구현된 프록시는 아래와 같이 사용할 수 있다.</p>
<pre><code class="language-java">void test() {
    TestInterface target = new TestImpl();
    TestInvocationHandler handler = new TestInvocationHandler(target);

    TestInterface proxy = (TestInterface) Proxy.newProxyInterface(TestInterface.class.getClassLoader(), new Class[]{TestInterface.class}, handler);

    proxy.execute();
}</code></pre>
<p>위 메서드의 실행 결과는 다음과 같다.</p>
<pre><code>TestProxy Run
Test Execute
TestProxy End</code></pre><p>위 로직의 실행 순서는 다음과 같다.</p>
<ul>
<li>클라이언트는 JDK 동적 프록시의 <code>execute()</code>를 실행한다.</li>
<li>JDK 동적 프록시는 <code>InvocationHandler</code>의 <code>invoke()</code>를 호출하며, 그 구현체가 <code>TestInvocationHandler</code>이므로 <code>TestInvocationHandler.invoke()</code>가 호출된다.</li>
<li><code>TestInvocationHandler</code>가 내부 로직을 수행하고, <code>method.invoke(target, args)</code>를 호출하여 <code>target</code>의 실제 객치인 <code>TestImpl</code>를 호출한다.</li>
<li><code>TestImpl</code>의 <code>execute()</code>가 실행된다.</li>
<li><code>TestImpl</code>의 <code>execute()</code>가 종료되면 <code>TestInvocationHandler</code>로 응답이 돌아와 나머지 로직을 수행하고 그 결과를 반환한다.</li>
</ul>
<h3 id="jdk-동적-프록시의-효용과-한계">JDK 동적 프록시의 효용과 한계</h3>
<p>이렇게 살펴본 JDK 동적 프록시의 장점은 <strong>같은 부가 기능 로직을 한번만 개발해서 공통으로 사용할 수 있다</strong>는 것이다. 위의 코드에서 인터페이스 <code>TestInterface</code>와 구현체인 <code>TestImpl</code>의 위치에 어느 인터페이스와 그 구현체가 와도 프록시 부분은 재사용이 가능하다. 따라서 <strong>적용 대상만큼 프록시 객체를 만들지 않아도 된다.</strong> 결과적으로 부가 기능 로직도 하나의 클래스에 모아 *<em>단일 책임 원칙(SRP)도 지킬 수 있게 되었다. *</em> </p>
<p>그러나 JDK 동적 프록시는 인터페이스가 필수이기 때문에 <strong>구현체를 만들기 앞서 항상 인터페이스를 생성해주어야 한다는 단점이 존재</strong>한다. 이를 해결하기 위해서는 <strong>CGLIB</strong>라는 바이트코드를 조작하는 특별한 라이브러리를 사용해야 한다.</p>
<h2 id="cglib">CGLIB</h2>
<h3 id="cglib--code-generator-library">CGLIB : Code Generator Library</h3>
<p>CGLIB는 바이트코드를 조작해서 동적으로 클래스를 생성하는 기술을 제공하는 라이브러리이다. 이를 사용하면 인터페이스가 없이 구체 클래스만 가지고 동적 프록시를 만들어낼 수 있다. CGLIB를 직접 사용하는 경우는 거의 없고, 스프링의 <code>ProxyFactory</code>가 이 기술을 편리하게 사용할 수 있게 해준다.</p>
<pre><code class="language-java">public class TestClass {

    public void execute() { 
        System.out.println(&quot;Test Execute&quot;); 
    }

}</code></pre>
<p>위의 콘크리트 클래스에 동적 프록시 로직을 적용하기 위해서는 <code>MethodInterceptor</code>를 사용하면 된다.</p>
<pre><code class="language-java">public class TestMethodInterceptor implements MethodInterceptor {

    private final Object target;

    public TestMethodInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercpet(Object obj, Method method, 
                            Object[] args, MethodProxy proxy
    ) throws Throwable {
        System.out.println(&quot;TestProxy Run&quot;);

        Object result = method.invoke(target, args);

        System.out.println(&quot;TestProxy End&quot;);
        return result;
    }

}</code></pre>
<p>위의 예제를 살펴보면 알 수 있듯 기본적으로 JDK 동적 프록시와 그 사용에 있어 큰 차이가 존재하지 않는다. 그러나 이를 실행하는데 있어 약간의 차이가 있다.</p>
<pre><code class="language-java">void test {
    TestClass target = new TestClass();

    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(TestClass.class);
    enhancer.setCallback(new TestMethodInterceptor(target));

    TestClass proxy = (TestClass) enhancer.create();

    proxy.execute();
}</code></pre>
<p>코드를 살펴보면 CGLIB는 <code>Enhancer</code>를 사용해 프록시를 생성하는 것을 알 수 있다. 이 때 프록시를 생성하고자 하는 구체 클래스(<code>TestClass</code>)를 상속 받아서 프록시를 생성한다. </p>
<h3 id="cglib의-제약">CGLIB의 제약</h3>
<p>CGLIB는 클래스 기반으로 상속을 사용하기 때문에 몇가지 제약이 존재한다. </p>
<ul>
<li>부모 클래스의 생성자를 체크해야 하며, 자식 클래스를 동적으로 생성하기 때문에 <strong>기본 생성자가 필요</strong>하다.</li>
<li>클래스에 final 키워드가 붙으면 상속이 불가능하고 예외가 발생한다.</li>
<li>메서드에 final 키워드가 붙으면 해당 메서드를 오버라이딩할 수 없기 때문에 프록시 로직이 동작하지 않는다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 톺아보기 - JPQL JOIN]]></title>
            <link>https://velog.io/@eunho_dev/JPA-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-JPQL-JOIN</link>
            <guid>https://velog.io/@eunho_dev/JPA-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-JPQL-JOIN</guid>
            <pubDate>Sat, 25 Feb 2023 10:43:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 김영한 님의 <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">&#39;자바 ORM 표준 JPA 프로그래밍 - 기본편&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.</p>
</blockquote>
<h2 id="jpql-조인">JPQL 조인</h2>
<h3 id="내부-조인과-외부-조인">내부 조인과 외부 조인</h3>
<p>JPQL의 조인은 SQL과 기능이 같지만, 문법에 약간의 차이가 있다. JPQL 조인의 가장 큰 특징은 다른 엔티티와 연관관계를 가지기 위해 사용하는 필드인 <strong>연관 필드를 사용해 조인한다</strong>는 것이다.</p>
<pre><code class="language-sql">SELECT ... FROM Member m JOIN m.team t</code></pre>
<p>외부 조인 또한 기능상 SQL의 외부 조인과 같다. </p>
<pre><code class="language-sql">SELECT m FROM Member m LEFT JOIN m.team t</code></pre>
<h3 id="컬렉션-조인">컬렉션 조인</h3>
<p>일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인하는 것을 컬렉션 조인이라 한다. </p>
<ul>
<li>회원 -&gt; 팀 : 다대일 조인, 단일 값 연관 필드(<code>m.team</code>) 사용</li>
<li>팀 -&gt; 회원 : 일대다 조인, 컬렉션 값 연관 필드(<code>m.members</code>) 사용</li>
</ul>
<h3 id="세타-조인">세타 조인</h3>
<p>전혀 관련 없는 엔티티도 세타 조인을 통해 조인할 수 있다. 세타 조인은 내부 조인만을 지원한다.</p>
<pre><code class="language-sql">SELECT COUNT(m) FROM Member m, Team t WHERE m.username = t.name</code></pre>
<h3 id="on">ON</h3>
<p>ON 절을 사용하면 조인 대상을 필터링하고 조인할 수 있다. 내부 조인의 ON절은 WHERE 절과 같은 결과를 반환하므로 보통 외부 조인에서만 사용한다.</p>
<pre><code class="language-sql">SELECT m, t FROM Memeber m LEFT JOIN m.team t ON t.name = &quot;A&quot;</code></pre>
<h2 id="페치-조인">페치 조인</h2>
<p>페치 조인은 JPQL에서 성능 최적화를 위해 제공하는 기능으로 <strong>연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능</strong>이다. 페치 조인의 문법은 다음과 같다.</p>
<pre><code class="language-sql">[ LEFT [OUTER] | INNER ] JOIN FETCH 조인 경로</code></pre>
<h3 id="엔티티-페치-조인">엔티티 페치 조인</h3>
<pre><code class="language-sql">SELECT m FROM Member m JOIN FETCH m.team</code></pre>
<p>위와 같은 JPQL 쿼리를 사용할 경우 연관된 엔티티나 컬렉션을 함께 조회할 수 있다. 일반적인 JPQL 조인과는 다르게 m.team 다음에 별칭이 존재하지 않으며, <strong>페치 조인은 별칭을 사용할 수 없다.</strong>(하이버네이트는 허용)</p>
<p>페치 조인을 사용하면 <strong>지연 로딩으로 설정된 엔티티 객체를 조회할 때도 프록시가 아닌 실제 엔티티 객체를 조회할 수 있다.</strong> 그렇기에 <strong>준영속 상태가 되더라도 조회 된 범위 내에서 객체 그래프 탐색이 가능</strong>하다.</p>
<h3 id="컬렉션-페치-조인">컬렉션 페치 조인</h3>
<pre><code class="language-sql">SELECT t FROM Team t JOIN FETCH t.members WHERE t.name = &#39;TeamA&#39;</code></pre>
<p>위의 쿼리문 실행 시 팀을 조회하며 연관된 회원 컬렉션도 함께 조회한다. 조회된 회원 컬렉션의 각 객체들은 <strong>같은 팀 객체를 참조</strong>하게된다.</p>
<h3 id="페치-조인과-distinct">페치 조인과 DISTINCT</h3>
<p>JPQL의 <code>DISTINCT</code>는 SQL에 DISTINCT를 추가함과 동시에 <strong>어플리케이션에서 한 번 더 중복을 제거</strong>한다. </p>
<h3 id="페치-조인과-일반-조인">페치 조인과 일반 조인</h3>
<p>JPQL은 결과를 반환할 때 SELECT절에 지정한 엔티티만을 조회할 뿐 연관관계까지 고려하지 않는다. 그렇기 때문에 연관관계 설정시 지연 로딩으로 설정하면 프록시나 아직 초기화되지 않은 컬렉션 래퍼를 반환한다. 반면 즉시 로딩으로 설정할 경우 해당 컬렉션을 즉시 로딩하기 위해 쿼리를 한 번 더 실행하게 된다. 하지만 페치 조인을 사용하면 연관된 엔티티를 함께 조회한다. </p>
<h3 id="페치-조인의-특징과-한계">페치 조인의 특징과 한계</h3>
<p>페치 조인 사용시 SQL을 최적화하여 호출 횟수를 줄일 수 있다. 페치 조인은 글로벌 로딩 전략보다 우선시 되기에 기본적으로 <strong>글로벌 로딩 전략을 지연 로딩으로 설정하고, 최적화가 필요한 조회시 페치 조인을 적용하는 것</strong>이 좋다.</p>
<p>그러나 페치 조인은 조인 대상에 별칭을 줄 수 없어 <strong>SELECT, WHERE, 서브 쿼리에서 페치 조인 대상을 사용할 수 없다.</strong> 또한 <strong>둘 이상의 컬렉션을 페치할 수 없다</strong>는 점과 <strong>페이징 API를 사용할 수 없다</strong>는 단점이 존재한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코틀린 기본기 - 제어문]]></title>
            <link>https://velog.io/@eunho_dev/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B8%B0%EB%B3%B8%EA%B8%B0-%EC%A0%9C%EC%96%B4%EB%AC%B8</link>
            <guid>https://velog.io/@eunho_dev/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B8%B0%EB%B3%B8%EA%B8%B0-%EC%A0%9C%EC%96%B4%EB%AC%B8</guid>
            <pubDate>Sat, 25 Feb 2023 03:49:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 최태현 님의 <a href="https://www.inflearn.com/course/java-to-kotlin/dashboardhttps://www.inflearn.com/course/java-to-kotlin/dashboard">&#39;자바 개발자를 위한 코틀린 입문&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.</p>
</blockquote>
<h2 id="조건문">조건문</h2>
<h3 id="if문">if문</h3>
<pre><code class="language-kotlin">fun validationScore(score: Int) {
    if (score &lt; 0) {
        throw IllegalArgumentException(&quot;${score}는 0보다 작을 수 없습니다.&quot;);
    }
}</code></pre>
<p>위의 코드 예제를 통해 알 수 있는 코틀린과 자바의 차이는 함수를 선언하기 위해 <code>fun</code>키워드를 사용하며, <code>Exception</code>을 <code>throw</code>할 때 <code>new</code> 키워드를 생략한다는 것이다. </p>
<p><code>if</code>문은 기본적으로 자바와 사용 방법이 동일하다. 그러나 자바에서 <code>if-else</code>는 <strong>Statement</strong>이지만 코틀린에서는 <strong>Expression</strong>이라는 차이가 있다.</p>
<h3 id="expression문과-statement표현식">Expression(문)과 Statement(표현식)</h3>
<p><img src="https://velog.velcdn.com/images/eunho_dev/post/c94b4840-6018-4f93-b16c-741952de90c0/image.png" alt="">
<strong>Expression</strong>은 &#39;수식&#39;이라는 뜻으로 <strong>하나 이상의 값으로 표현될 수 있는 코드</strong>를 뜻한다. 변수와 연산을 비롯해 함수 콜, 변수 이름과 같은 식별자, 배열등의 할당 연산자까지도 포함한 식을 의미한다.</p>
<p><strong>Statement</strong>는 프로그래밍에서 <strong>실행가능한 최소의 독립적인 코드 조각</strong>을 말한다. 컴파일러가 이해하고 실행할 수 있는 모든 구문은 statement이며 한 개 이상의 expression과 프로그래밍 키워드를 포함한다.</p>
<pre><code class="language-java">int num = 30 + 40;    // Expression &amp; Statement

boolean isPass = if (num &gt; 50) { &quot;Pass&quot;; }
// if문을 하나의 값으로 취급하지 않기 때문에 에러 발생, !Expression &amp; Statement</code></pre>
<p>위의 예제와 같이 자바에서 <code>if-else</code>문은 statement이다. 그러나 코틀린에서는 expression이기 때문에 아래와 같이 사용할 수 있다.</p>
<pre><code class="language-kotlin">val isPass: Boolean = if (score &gt; 50) { &quot;Pass&quot; } else { &quot;Fail&quot; }</code></pre>
<p>이처럼 조건문을 expression으로 사용할 수 있기 때문에 <strong>코틀린에는 삼항 연산자가 존재하지 않는다.</strong></p>
<p>또한<code>if (0 &lt;= score &amp;&amp; score &lt;= 100)</code>과 같은 범위에 대한 조건식을 <code>if (score in 0..100)</code>과 같이 <code>in</code>을 사용하여 간단하게 표기할 수 있다.</p>
<h3 id="switch와-when">switch와 when</h3>
<p>코틀린에서는 자바의 <code>switch</code>문이 <code>when</code>으로 대체되었다. </p>
<pre><code class="language-kotlin">when(값) {
    조건부 -&gt; 어떠한 구문
    조건부 -&gt; 어떠한 구문
    else -&gt; 어떠한 구문
}</code></pre>
<p>조건부에는 어떠한 expression이라도 들어갈 수 있으며, 여러 개의 조건을 <code>,</code>로 구분하여 동시에 검사할 수 있다. 값 부분이 없을 경우 early return 처럼 동작하며, <code>when</code> 역시 expression으로 사용 가능하다. Enum, Sealed Class와 함께 사용할 경우 시너지가 좋다. </p>
<h2 id="반복문">반복문</h2>
<h3 id="forfor-each">for/for-each</h3>
<p>기본적으로 for문과 for-each문 모두 자바와 거의 동일하게 동작하지만 문법에 약간의 차이가 있다.</p>
<h4 id="for-each">for-each</h4>
<p>자바와 동일하게 Iterable이 구현된 타입이라면 모두 for-each문을 사용할 수 있다. <code>:</code> 대신 <code>in</code>을 사용한다는 차이가 존재한다.</p>
<pre><code class="language-kotlin">for (number in numbers) {
    println(number)
}</code></pre>
<h4 id="for">for</h4>
<pre><code class="language-kotlin">for (i in 1..3) {
    println(i)
}</code></pre>
<p>위의 코드는 자바의 <code>for (int i = 0; i &lt;= 3; i++)</code>와 동일하게 동작한다. i가 증감식이 감소되는 경우에는 <code>for (i in 3 downTo 1)</code>과 같이 표현할 수 있으며, 증가되는 수가 2 이상일 경우 <code>for (i in 1..5 step 2)</code>와 같이 표현할 수 있다. 여기서 사용되는 <code>downTo</code>와 <code>step</code> 모두 함수이며, 중위 호출 함수라 한다. </p>
<p>코틀린은 <code>for</code>문 사용 시 <code>1..3</code>과 같은 범위 연산자와 <code>downTo</code>, <code>step</code>과 같은 함수를 이용하여 <strong>등차수열</strong>을 만들어낸다. <code>1..3</code>, <code>3 downTo 1</code>을 통해 시작값과 끝값을 표현하고 <code>step</code>을 통해 공차를 표현해주는 것이다.</p>
<p>이 외의 반복문인 <code>while</code>, <code>do while</code>문은 <strong>자바와 완전히 동일</strong>하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 톺아보기 - JPQL]]></title>
            <link>https://velog.io/@eunho_dev/JPA-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%BF%BC%EB%A6%AC</link>
            <guid>https://velog.io/@eunho_dev/JPA-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%BF%BC%EB%A6%AC</guid>
            <pubDate>Fri, 24 Feb 2023 15:17:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 김영한 님의 <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">&#39;자바 ORM 표준 JPA 프로그래밍 - 기본편&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.</p>
</blockquote>
<p>JPA는 엔티티 객체를 중심으로 개발이 가능하게 도와준다. 검색 쿼리 또한 테이블이 아닌 엔티티 객체를 대상으로 검색을 하는데 현실적으로 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능하다. 때문에 필요한 데이터만 DB에서 불러오기 위해서는 조건이 포함된 SQL이 필요하다.</p>
<h2 id="jpql">JPQL</h2>
<p>JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공한다. SQL 문법과 유사(SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN)하며, 데이터베이스 테이블을 대상으로 쿼리를 보내는 SQL과 달리 객체를 대상으로 쿼리를 보낸다. 또한 SQL을 추상화해서 사용하기 때문에 특정 데이터베이스의 SQL 문법에 의존적이지 않다. </p>
<h3 id="jpql-문법">JPQL 문법</h3>
<p><code>SELECT m FROM Member m WHERE m.age &gt; 20</code>과 같이 엔티티와 속성은 대소문자를 구분하며, JPQL 키워드는 구분하지 않는다. 또한 객체를 대상으로 검색하기 때문에 테이블명이 아닌 엔티티 객체의 이름을 사용해야하며, 별칭(Alias, as는 생략 가능)을 반드시 명시해야 한다.</p>
<p>기본적으로 COUNT, SUM, AVG, MAX, MIN과 같은 집합 함수나 GROUP BY, HAVING과 같은 집합, ORDER BY와 같은 정렬 조건도 지원한다.(UNION은 지원하지 않는다.)</p>
<h3 id="typequery-query">TypeQuery, Query</h3>
<p>TypeQuery 객체는 반환 타입이 명확할 때 사용하며 <code>TypedQuery&lt;Member&gt; query = em.createQuery(&quot;SELECT m FROM Member m&quot;, Member.class)</code>과 같이 제네릭으로 타입을 명시해줄 수 있다. 반면 반환 타입을 명확하게 지정할 수 없는 경우 Query 객체를 아래와 같이 사용할 수 있다.</p>
<pre><code class="language-java">Query query = em.createQuery(&quot;SELECT m.username, m.age FROM Member m&quot;)

List resultList = query.getResultList();
for (Object o : resultList) {
    Object[] result = (Object[]) o; // 결과가 둘 이상이면 배열 반환
    System.out.println(&quot;username = &quot; + result[0]);
    System.out.println(&quot;age = &quot; + result[1]);
}</code></pre>
<h3 id="결과-조회">결과 조회</h3>
<p>다음 메서드들을 호출하면 실제 쿼리를 실행해서 데이터베이스를 조회한다.</p>
<ul>
<li><code>query.getResultList();</code><ul>
<li>결과를 컬렉션으로 반환</li>
<li>결과가 없으면 빈 컬렉션을 반환한다.</li>
</ul>
</li>
<li><code>query.getSingleResult();</code><ul>
<li>결과가 정확히 하나일 때 사용한다.</li>
<li>결과가 없으면 <code>NoResultException</code> 예외가 발생</li>
<li>결과가 한개보다 많으면 <code>NonUniqueResultException</code> 예외가 발생한다. 따라서 사용에 주의해야 한다.</li>
</ul>
</li>
</ul>
<h3 id="파라미터-파인딩">파라미터 파인딩</h3>
<p><del>JDBC는 위치 기준 파라미터 파인딩만을 지원하지만</del>(NamedParameterJdbcTemplate을 통해 지원한다.) JPQL은 이름 기준 파라미터 바인딩도 지원한다.</p>
<h4 id="이름-기준-파라미터-바운딩">이름 기준 파라미터 바운딩</h4>
<p>이름 기준 파라미터 바운딩은 기준 파라미터 앞에 <code>:</code>를 붙여서 사용한다.</p>
<pre><code class="language-java">List&lt;Member&gt; members = em.createQuery(&quot;SELECT m FROM Member m WHERE m.username = :username&quot;, Member.class)
                          .setParameter(&quot;username&quot;, &quot;test&quot;)
                          .getResultList();</code></pre>
<h4 id="위치-기준-파라미터-바인딩">위치 기준 파라미터 바인딩</h4>
<p>위치 기준으로 사용하려면 <code>?1</code>과 같이 ? 뒤에 위치 값을 지정하고 파라미터 값을 넣어주면 된다. 위치 값은 1부터 시작된다.</p>
<h3 id="프로젝션">프로젝션</h3>
<p>SELECT 절에 조회할 대상을 지정하는 것을 프로젝션(projection)이라 한다. 프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터 타입)이 있다.</p>
<pre><code class="language-SQL">SELECT {프로젝션 대상} FROM ...</code></pre>
<h4 id="엔티티-프로젝션">엔티티 프로젝션</h4>
<pre><code>SELECT m FROM Member m        // 회원
SELECT m.team FROM Member m    // 팀</code></pre><p>위 예시에서 프로젝션은 모두 엔티티이다. 컬럼을 나열해서 조회해야 하는 SQL과 달리 객체 단위로 바로 조회가 가능하다. 이렇게 조회된 엔티티는 모두 영속성 컨텍스트에 의해서 관리된다. </p>
<h4 id="임베디드-타입-프로젝션">임베디드 타입 프로젝션</h4>
<p>임베디드 타입은 엔티티와 거의 비슷하게 사용된다. 그러나 조회의 시작점이 될 수 없다는 제약이 있기 때문에 엔티티를 통해 임베디드 타입을 조회해야 한다.</p>
<pre><code>// Address = 임베디드 타입
SELECT a FROM Address a            // 조회 불가
SELECT m.address FROM Member m    // 엔티티를 통해 조회 가능</code></pre><p>임베디드 타입은 엔티티 타입이 아닌 값 타입이기 때문에 직접 조회한 임베디드 타입은 영속성 컨텍스트의 관리 대상이 되지 않는다.</p>
<h4 id="new-명령어">new 명령어</h4>
<p>엔티티 객체나 임베디드 타입이 아닌 필드를 프로젝션할 경우 타입을 지정할 수 없어 TypeQuery를 사용할 수 없다. 이후 Object로 반환 받은 값을 DTO로 변환해서 사용하게 되는데, new 명령어를 통해 DTO 객체를 타입으로 지정하고 TypeQuery를 사용할 수 있다.</p>
<pre><code class="language-java">List&lt;MemberDTO&gt; memberList = em.createQuery(
        &quot;SELECT new 패키지경로.MemberDTO(m.username, m.age) FROM Member m&quot;,
        MemberDTO.class
    )
    .getResultList();</code></pre>
<p>new 명령어 사용시 <strong>패키지 명을 포함한 전체 클래스 명을 입력</strong>해야 하며, <strong>순서와 타입이 일치하는 생성자가 필요</strong>하다.</p>
<h3 id="페이징-api">페이징 API</h3>
<p>JAP는 페이징을 <code>setFirstResult(int startPosition);</code>과 <code>setMaxResults(int maxResult)</code> 두 API로 추상화하였다. FirstResult의 시작은 0이다.</p>
<h3 id="집합과-정렬">집합과 정렬</h3>
<p>일반적으로 사용되는 COUNT, SUM, AVG, MAX, MIN과 같은 집합 함수를 모두 사용할 수 있다. 다음은 집합 함수 사용 시 참고사항을 정리한 것이다.</p>
<ul>
<li>NULL 값은 무시하기 때문에 통계에 잡히지 않는다. DISTINCT가 정의되어 있어도 무시된다.</li>
<li>값이 없을 때 사용하면 COUNT를 제외한 함수는 NULL을, COUNT는 0을 반환한다.</li>
<li>DISTINCT를 집합 함수 안에 사용해서 중복된 값을 제거한 후 집합을 구할 수 있다.<ul>
<li><code>SELECT COUNT(DISTINCT m.idx) FROM Member m</code></li>
</ul>
</li>
<li>DISTINCT를 COUNT에서 사용할 때 임베디드 타입은 지원하지 않는다.</li>
</ul>
<p>GROUP BY와 HAVING, ORDER BY 모두 일반적인 SQL과 동일하게 사용할 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘으로 기강 잡기 - 시간 복잡도]]></title>
            <link>https://velog.io/@eunho_dev/%EC%8B%9C%EA%B0%84%EB%B3%B5%EC%9E%A1%EB%8F%84</link>
            <guid>https://velog.io/@eunho_dev/%EC%8B%9C%EA%B0%84%EB%B3%B5%EC%9E%A1%EB%8F%84</guid>
            <pubDate>Thu, 23 Feb 2023 15:12:17 GMT</pubDate>
            <description><![CDATA[<h2 id="시간-복잡도">시간 복잡도</h2>
<p>시간 복잡도는 주어진 문제를 해결하기 위해 수행한 연산 횟수를 의미하며, 일반적으로 1억 번의 연산을 1초의 시간으로 간주하여 예측한다.</p>
<h3 id="시간-복잡도-유형">시간 복잡도 유형</h3>
<pre><code class="language-java">int findNumber = (int)(Math.random() * 100);

for (int i = 0; i &lt; 100; i++) {
    if (i == findNumber) {
        System.out.println(i);
        break;
    }
}</code></pre>
<p>위 코드 예제를 통해 시간 복잡도를 살펴보면 다음과 같이 표기할 수 있다.</p>
<ul>
<li><strong>빅-오메가(Ω(n) = 1번)</strong> : 최선일 때(best case)의 연산 횟수</li>
<li><strong>빅-세타(Θ(n) = 2/N번 = 50번)</strong> : 보통일 때(average case)의 연산 횟수</li>
<li><strong>빅-오(O(n) = N번 = 100번)</strong> : 최악일 때(worst case)의 연산 횟수</li>
</ul>
<p>항상 최악의 경우를 염두해야하기 때문에 <strong>빅-오 표기법을 사용</strong>해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/eunho_dev/post/59ea21ce-d00a-4fa9-a599-a4d57ad9656b/image.png" alt=""></p>
<h3 id="시간-복잡도-도출-기준">시간 복잡도 도출 기준</h3>
<ul>
<li>상수는 시간 복잡도 계산에서 제외한다.</li>
<li>가장 많이 중첩된 반복문의 수행 횟수가 시간 복잡도의 기준이 된다.<pre><code class="language-java">int N = 100_000;
int cnt = 0;
</code></pre>
</li>
</ul>
<p>for (int i = 0; i &lt; N; i++) {
    System.out.println(&quot;연산 횟수 : &quot; + cnt++);
}</p>
<pre><code>
```java
int N = 100_000;
int cnt = 0;

for (int i = 0; i &lt; N; i++) {
    System.out.println(&quot;연산 횟수 : &quot; + cnt++);
}

for (int i = 0; i &lt; N; i++) {
    System.out.println(&quot;연산 횟수 : &quot; + cnt++);
}

for (int i = 0; i &lt; N; i++) {
    System.out.println(&quot;연산 횟수 : &quot; + cnt++);
}</code></pre><p>위의 두 예제의 연산 횟수는 <strong>N번과 3N번으로 3배의 차이</strong>가 난다. 그러나 상수는 일반적으로 시간 복잡도 계산에서 제외되므로 <strong>두 코드 모두 O(N)의 시간 복잡도</strong>를 갖는다. </p>
<pre><code class="language-java">int N = 100_000;
int cnt = 0;

for (int i = 0; i &lt; N; i++) {
    for (int j = 0; j &lt; N; j++) {
        System.out.println(&quot;연산 횟수 : &quot; + cnt++);
    }
}</code></pre>
<p>그러나 위의 코드에서는 중첩된 반복문을 기준으로 시간 복잡도가 도출되므로 <strong>N²</strong>이 된다.</p>
<p>그렇기에 효율적인 알고리즘을 짜기 위해서는 시간 복잡도를 고려하여 <strong>알맞은 알고리즘을 선택</strong>하고, 비효율 적인 로직을 찾아 <strong>효율적으로 최적화</strong>하는 작업이 필요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[My(private)SQL - WAL, Index]]></title>
            <link>https://velog.io/@eunho_dev/MyprivateSQL</link>
            <guid>https://velog.io/@eunho_dev/MyprivateSQL</guid>
            <pubDate>Tue, 21 Feb 2023 16:08:35 GMT</pubDate>
            <description><![CDATA[<h2 id="wal">WAL</h2>
<h3 id="데이터-베이스의-성능을-향상시키려면">데이터 베이스의 성능을 향상시키려면...</h3>
<p>데이터베이스의 데이터는 결국 디스크에 저장된다. 즉 영속성이 있고 저렴하지만, 느리다는 성능에는 치명적인 단점이 있기에 데이터베이스 성능의 핵심은 디스크 I/O(접근)를 최소화 하는 것이라 할 수 있다.</p>
<p>디스크 접근을 줄이기 위해서는 메모리에 올라온 데이터로 최대한 요청을 처리해야한다. 즉 메모리 캐시 히트율을 높이는 것인데, 이는 읽기 뿐만 아니라 쓰기 시에도 동일하다.</p>
<p>그러나 메모리는 전원이 공급되지 않으면 휘발되는 데이터로, 전원 공급이 중단되거나 에러로 인해 데이터베이스 접속이 끊기게 된다면 데이터가 유실될 수 있다. </p>
<p>이러한 메모리의 데이터 유실을 막기위해 데이터베이스는 <strong>WAL(Write-Ahead-Logging)</strong>을 사용한다.</p>
<h3 id="wal의-기능">WAL의 기능</h3>
<p>WAL을 사용하는 시스템이서 모든 수정은 적용전 먼저 로그에 기록된다. 트랜잭션 발생시 로그에 기입하여 기록을 남기고 적용을 지연시켜 랜덤I/O를 줄이고 순차적 I/O를 통해 정합성을 유지하며 <strong>일관성(Consistency)을 보장</strong>한다. 또한 서버가 다운되는 상황에서 기입된 로그를 통해 데이터의 유실을 막고, 중단된 지점 부터 다시 실행하여 <strong>원자성(Atomicity)을 보장</strong>할 수 있다. </p>
<h2 id="index">Index</h2>
<p>디스크 보다 메모리가 빠르다지만 모든 데이터를 메모리에 올려놓고 사용하는 것은 물리적으로 불가능에 가깝다. 그렇다면 어떻게 조회 속도를 향상시킬 수 있을까? </p>
<p>데이터베이스는 데이터와 데이터의 위치를 포함한 자료구조를 생성하여 빠르게 조회할 수 있도록 돕는다. 이것이 바로 인덱스(Index)이다.</p>
<p><img src="https://velog.velcdn.com/images/eunho_dev/post/fa5fffa7-188a-4f0d-a639-c761a8938c88/image.png" alt=""></p>
<h3 id="index의-자료구조">Index의 자료구조</h3>
<p>데이터를 담기 위한 자료구조는 Map, List, Tree등 여러 선택지가 있을 것이다. 그렇다면 인덱스는 어떠한 자료구조로 이루어져있을까? </p>
<h4 id="map">Map</h4>
<p>먼저 Map형태는 단건 검색의 시간 복잡도가 O(1)로 월등히 빠르다는 장점이 있다. 그러나 범위 탐색은 O(N)의 시간 복잡도를 가지며, 무엇보다 <code>LIKE &#39;TEST%&#39;</code>와 같은 <strong>전방 일치 탐색이 불가능</strong>하다는 치명적인 단점이 있다.</p>
<h4 id="list">List</h4>
<p>List는 정렬되지 않은 리스트의 탐색이 O(N), 정렬된 리스트의 탐색은 O(logN)의 시간 복잡도를 가진다. 정렬된 탐색이 매우 빠르지만 위의 <strong>장점을 살리기 위해서는 데이터가 정렬되어야 한다는 전제 조건</strong>이 있다. 그렇기에 실제 데이터를 정렬하고 탐색하는 시간 복잡도는 O(N) ~ O(N * logN)이 되어버린다. 또한 <strong>삽입과 삭제 비용이 매우 높다</strong>는 단점이 있어 적합하지 않다.</p>
<h4 id="tree">Tree</h4>
<p>Tree 구조는 <strong>검색 속도와 삽입, 삭제 모두 효율적</strong>이다. 그러나 트리 높이에 따라 시간 복잡도가 결정되며 빠른 탐색을 위해서는 <strong>트리의 높이를 최소화</strong>하고, 한쪽으로 <strong>노드가 치우치지 않도록 균형</strong>을 잡아주어야 한다. </p>
<p>그렇기에 삽입/삭제시 항상 균형을 이루며 하나의 노드가 여러 자식 노드를 가질 수 있고, 마지막 리프노드에만 데이터가 존재하여 연속적인 데이터 접근시 유리한 <strong><a href="https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html">B+Tree</a>가 인덱스의 자료구조로 적합</strong>하다. </p>
<h3 id="index-사용시-주의점">Index 사용시 주의점</h3>
<p>인덱스는 데이터를 정렬하여 하나의 테이블로 생성하는 것이다. 그렇기에 기본적으로 <strong>디스크의 메모리를 차지</strong>하며, 데이터의 삽입/삭제시 인덱스 테이블에도 값이 추가되어야 하는 <strong>추가적인 연산</strong>이 들어가게 된다. 즉, 조회 성능을 얻는 대신 쓰기 성능을 희생하는 trade-off가 있는 것이다. 그렇기에 꼭 <strong>인덱스로만 해결할 수 있는 문제인가를 고려</strong>해보아야 한다.</p>
<p>또한 <strong>하나의 쿼리에는 하나의 인덱스</strong>만 타며, 여러 인덱스를 동시에 탐색하지 않는다. 그렇기에 해당 쿼리에 맞지 않는 인덱스를 타게 되면 오히려 <strong>성능이 저하되는 역효과가 발생</strong>할 수 있다. 특히 WHERE, ORDER BY, GROUP BY를 혼합해서 사용할 때에는 인덱스를 잘 고려해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 프레임워크(Spring Framework) 톺아보기 - 프록시(프록시 패턴과 데코레이터 패턴)]]></title>
            <link>https://velog.io/@eunho_dev/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%ACSpring-Framework-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%ED%94%84%EB%A1%9D%EC%8B%9C-%ED%8C%A8%ED%84%B4%EA%B3%BC-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@eunho_dev/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%ACSpring-Framework-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%ED%94%84%EB%A1%9D%EC%8B%9C-%ED%8C%A8%ED%84%B4%EA%B3%BC-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Tue, 21 Feb 2023 14:56:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 김영한 님의 <a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard">&#39;스프링 핵심원리 고급편&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.  </p>
</blockquote>
<h2 id="프록시">프록시</h2>
<p>클라이언트와 서버의 관계에서 클라이언트는 서버를 직접 호출하고, 처리 결과를 직접 받는 <strong>직접 호출</strong>이 일반적이다. 그러나 클라이언트에서 서버에 직접 요청하지 않고 어떤 대리자(Proxy)를 통해서 간접적으로 할 수도 있다.</p>
<p>대리자를 통해 호출할 경우 대리자가 캐싱과 같은 <strong>접근 제어</strong>나 <strong>부가 기능</strong>을 수행할 수 있다. 또한 대리자를 통해 다른 대리자를 호출하는 것도 가능하다. </p>
<p>중요한 것은 <strong>클라이언트는 자신이 보낸 요청이 대리자에게 보낸 것인지, 서버에 보낸 것인지 몰라야 한다는 것</strong>이다. 즉 서버와 프록시는 같은 객체를 사용해야 하며, 서버 객체를 프록시 객체로 교체해도 <strong>클라이언트 코드를 변경하지 않고 동작이 가능</strong>해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/eunho_dev/post/8332bff9-a5fd-4dd9-bd1c-601951db64d5/image.png" alt="">
위의 그림은 프록시의 개념을 도표화 한 것이다. 이를 코드로 구현한다면 아래와 같다.</p>
<pre><code class="language-java">public class Client {

    pivate Subject subject;

    public Client(Subject subject) {
        this.subject = subject;
    }

    public void execute() {
        subject.doSomething();
    }

}

public interface Subject {
    void doSomething();
}

public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        ...
    }
}

public class Proxy implements Subject {

    @Object
    public void doSomething() {
        somethingElse();
        ...
    }

    private void somethingElse() {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
</code></pre>
<p>클라이언트(<code>Client</code>)는 서버 인터페이스(<code>Subject</code>)에만 의존하며, 해당 인터페이스를 서버(<code>RealSubject</code>)와 프록시(<code>Proxy</code>)가 같이 사용한다. 런타임에 클라이언트 객체에 DI를 사용해서 서버 객체가 아닌 프록시 객체로 의존관계를 변경해도 클라이언트 코드를 전혀 변경하지 않을 수 있으며, 클라이언트는 인터페이스만을 의존하기 때문에 변경 사실 조차 알 수 없다.</p>
<h3 id="프록시의-주요-기능">프록시의 주요 기능</h3>
<p>프록시를 통해 할 수 있는 일은 <strong>접근 제어</strong>와 <strong>부가 기능 추가</strong>로 구분할 수 있다. 이를 상세하게 보면 아래와 같다.</p>
<ul>
<li><strong>접근 제어</strong><ul>
<li>권한에 따른 접근 차단</li>
<li>캐싱</li>
<li>지연 로딩<ul>
<li><strong>부가 기능 추가</strong></li>
</ul>
</li>
<li>기존 서버가 제공하는 기능에 추가적인 기능 수행</li>
<li>요청 값, 응답 값 변형</li>
<li>로깅</li>
</ul>
</li>
</ul>
<p>GOF 디자인 패턴에서는 이 둘을 의도에 따라 접근 제어가 목적이면 프록시 패턴, 새로운 기능 추가가 목적이면 데코레이터 패턴으로 구분한다.</p>
<h2 id="구체-클래스-기반-프록시">구체 클래스 기반 프록시</h2>
<p>Java의 다형성은 인터페이스를 구현하든 클래스를 상속하든 상위 타입만 맞으면 적용된다. 그렇기에 인터페이스가 아닌 구체 클래스를 사용해서도 프록시를 생성할 수 있다.</p>
<pre><code class="language-java">public class Client {

    pivate Subject subject;

    public Client(Subject subject) {
        this.subject = subject;
    }

    public void execute() {
        subject.doSomething();
    }

}

public class Subject {
    public void doSomething() {
        ...
    }
}

public class RealSubject extends Subject {
    @Override
    public void doSomething() {
        ...
    }
}

public class Proxy extends Subject {

    @Object
    public void doSomething() {
        somethingElse();
        ...
    }

    private void somethingElse() {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
</code></pre>
<p>구현이 아닌 상속이라는 것 말고는 기존 인터페이스 기반 프록시와 차이가 없는 것을 확인할 수 있다. 다만 Java의 기본 문법으로 인해 클래스를 생성할 때마다 <strong>항상 <code>super()</code>를 통해 부모 클래스의 생성자를 호출해야 한다</strong>는 점과 <strong><code>final</code> 키워드가 붙은 클래스나 메서드는 상속과 오버라이딩이 불가능</strong>하다는 단점이 있다.</p>
<p>이렇듯 인터페이스 기반의 프록시는 상속이라는 제약에서 자유롭고, 역할과 구현을 명확하게 나눌 수 있어 보다 좋은 선택이 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 프레임워크(Spring Framework) 톺아보기 - 템플릿 메서드 패턴과 전략 패턴, 그리고 콜백 패턴]]></title>
            <link>https://velog.io/@eunho_dev/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%ACSpring-Framework-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%8C%A8%ED%84%B4%EA%B3%BC-%EC%BD%9C%EB%B0%B1-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@eunho_dev/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%ACSpring-Framework-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%8C%A8%ED%84%B4%EA%B3%BC-%EC%BD%9C%EB%B0%B1-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Mon, 13 Feb 2023 14:21:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 김영한 님의 <a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard">&#39;스프링 핵심원리 고급편&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.  </p>
</blockquote>
<h2 id="핵심-기능과-부가-기능">핵심 기능과 부가 기능</h2>
<p><strong>핵심 기능</strong>은 해당 객체가 제공하는 <strong>고유의 기능</strong>을 말하며, <strong>부가 기능</strong>은 <strong>핵심 기능을 보조하기 위해 제공되는 기능</strong>을 말한다. 필요에 따라 부가 기능이 늘어나게 되면서 객체 내에 핵심 기능 제공을 위한 코드보다 부가 기능을 위한 코드가 많아지는 경우가 생기게 된다. 또한 여러 객체에서 비슷한 부가 기능을 위한 중복되는 코드가 많아지면서 리소스를 낭비하고, 유지/보수가 어렵게 만든다.</p>
<p>그렇기에 핵심 기능과 부가 기능을, 변하는 것과 변하지 않는 것을 분리하는 것이 좋은 객체 지향 설계이지만, 막상 여러 기능이 혼재한 상황에서 단순하게 기능별로 메서드를 추출하는 것은 어려울 때가 많다. </p>
<p>이 때 변하는 부가 기능과 변하지 않는 핵심 기능을 분리하여 모듈화하여 이러한 문제를 해결 하기 위해 사용되는 디자인 패턴이 템플릿 메서드 패턴(Template Method Pattern)이다.</p>
<h2 id="템플릿-메서드-패턴template-method-pattern">템플릿 메서드 패턴(Template Method Pattern)</h2>
<p><img src="https://velog.velcdn.com/images/eunho_dev/post/846f264f-0ece-427e-b14d-647b52d8a243/image.gif" alt="">
템플릿 메서드 패턴은 이름 그대로 변하지 않는 부분을 템플릿이라는 틀에 몰아두고, 일부 변하는 부분을 별도로 호출해서 해결하는 방식을 이야기한다.</p>
<pre><code class="language-java">public abstract class AbstractClass {

    public void templateMethod() {
        // 변하지 않는 기능
       ...
       // 변하는 기능
        primitiveOperation1();
        primitiveOperation2();
    }

    protected abstract primitiveOperation1();

    protected abstract primitiveOperation2();

}

public class ConcreteClass extends AbstractClass {

    @Override
    protected void primitiveOperation1() {
        // 변하는 기능
    }

    @Override
    protected void primitiveOperation2() {
        // 변하는 기능
    }

}</code></pre>
<p>위의 코드는 처음 그림을 코드로 작성한 예제이다. 변하지 않는 기능을 부모 클래스에 몰아 넣고 하나의 템플릿으로 만든다. 그리고 변하는 부분을 템플릿 안에서 호출하도록 처리한 뒤 자식 클래스에서 <strong>상속</strong>과 <strong>오버라이딩</strong>을 통해 <strong>구현</strong>한다.</p>
<p>즉 클라이언트에서 템플릿 메서드를 호출하면 템플릿을 포함한 부모 클래스가 그 구현체인 자식 클래스를 호출하여 오버라이딩 된 부가 기능 메서드를 호출하는 것이다.</p>
<h3 id="템플릿-메서드-패턴의-단점과-해결">템플릿 메서드 패턴의 단점과 해결</h3>
<p>템플릿 메서드 패턴은 부가 기능을 위해 자식 객체를 계속 만들어야 한다는 단점이 존재한다. 그러나 아래 예제와 같이 익명 내부 클래스를 사용하여 이러한 단점을 해결할 수 있다.</p>
<pre><code class="language-java">AbstractClass template = new AbstractClass() {
    @Override
    protected void primitiveOperation1() {
        ...
    }

    @Override
    protected void primitiveOperation2() {
        ...
    }
}

template.templateMethod();</code></pre>
<p>템플릿 메서드 패턴은 부모 클래스에서 알고리즘의 골격인 템플릿을 정의하고 변경되는 로직을 자식 클래스에서 정의하는 방식이다. 다형성을 통해 자식 클래스가 알고리즘 전체 구조를 변경하지 않은채 특정 부분만을 재정의함으로 <strong>단일 책임 원칙을 지킬 수 있게 해준다.</strong></p>
<p>그러나 상속을 이용한다는 점에서 부모 클래스와 자식 클래스가 컴파일 시점에 강하게 결합되며 부모 클래스의 기능을 전혀 사용하지 않음에도 <strong>부모 클래스를 강하게 의존</strong>하게 된다. 그렇기에 <strong>부모 클래스의 기능 수정이 자식 클래스에도 영향을 끼칠 수 있다.</strong> 이러한 문제점들이 존재하기에 이를 개선한 디자인 패턴이 등장했으며, 바로 <strong>전략 패턴(Stragy Pattern)</strong>이다. </p>
<h2 id="전략-패턴strategy-pattern">전략 패턴(Strategy Pattern)</h2>
<p>템플릿 메서드 패턴의 단점들은 대부분 부모 클래스를 상속해서 사용한다는데서 온다. 이러한 단점을 극복하기 위해서는 변하는 부분을 상속이 아닌 다른 방법으로 해결해야 하는데, 전략 패턴은 이를 <strong>위임</strong>으로 해결한다.</p>
<p>전략 패턴은 변하지 않는 부분을 <code>Context</code>에 두고, 변하는 부분을 <code>Stratgy</code>라는 인터페이스를 만들고 이를 구현하도록 해서 문제를 해결한다. 
<img src="https://velog.velcdn.com/images/eunho_dev/post/407f0466-9edd-4662-9c65-c0102b7721c0/image.png" alt=""></p>
<p>위의 예제를 코드로 구현해보면 아래와 같다.</p>
<pre><code class="language-java">public interface Strategy {
    void execute();
}

public class StrategyA implements Strategy {
    @Override
    public void call() {
        //...
    }
}

public class StrategyB implements Strategy {
    @Override
    public void call() {
        //...
    }
}

public class SomeClass {    // Context

    private Strategy strategy;

    public SomeClass(strategy) {
        this.strategy = strategy;
    }

    public void execute() {
        //...
        strategy.
    }
}</code></pre>
<p>전략 패턴의 핵심은 <code>Context</code>는 <code>Strategy</code> 인터페이스에만 의존한다는 점이다. <code>Strategy</code>의 구현체를 변경하거나 새로 만들지라도 <code>Context</code>에는 영향을 주지 않는다. 스프링에서 사용되는 의존관계 주입 방식이 바로 전략 패턴이다.</p>
<h3 id="선조립-후-실행">선조립 후 실행</h3>
<p>전략 패턴은 <code>Context</code> 내부 필드에 <code>Strategy</code>를 두고 사용한다. 이 방식을 통해 <code>Context</code>와 <code>Strategy</code>를 실행 전에 원하는 모양으로 조립해두고, 실행 시점에 <code>Context</code>만 실행하면 된다. </p>
<p>스프링도 이처럼 어플리케이션 로딩 시점에 의존관계 주입을 통해 필요한 의존관계를 모두 맺어두고 실제 요청을 처리한다.</p>
<h2 id="콜백-패턴callback-pattern">콜백 패턴(Callback Pattern)</h2>
<p>전략 패턴의 단점은 <code>Context</code>와 <code>Strategy</code>를 조립한 이후에 전략을 변경하기가 번거롭다는 점이다. 특히 <code>Context</code>를 싱글톤으로 사용할 때 동시성 이슈 등 고려할 점이 많다. </p>
<p>이를 좀 더 유연하게 만들기 위해서는 <code>Strategy</code>를 실행 시점에 직접 파라미터로 전달해서 사용하면 된다. 그리고 이러한 방법을 콜백 패턴이라고 한다.</p>
<h3 id="콜백">콜백</h3>
<p>프로그래밍에서 콜백(callback) 또는 콜 애프터 함수(call-after-function)는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 즉시/나중에 실행할 수 있다.</p>
<p>자바에서 콜백은 보통 하나의 메서드를 가진 인터페이스를 구현하고, 이를 익명 내부 클래스로 구현하여 사용했다. 자바8 이후로는 주로 람다를 사용한다.</p>
<h3 id="템플릿-콜백-패턴">템플릿 콜백 패턴</h3>
<p>스프링에서 <code>Strategy</code>를 파라미터로 받는 전략 패턴을 템플릿 콜백 패턴이라고 한다. <code>Context</code>가 템플릿 역할을, <code>Strategy</code> 부분이 콜백으로 넘어온다. 스프링에서 이름에 <code>...Template</code>가 붙어 있다면 보통 템플릿 콜백 패턴으로 만들어져 있다고 볼 수 있다.
<img src="https://velog.velcdn.com/images/eunho_dev/post/ae9fe7bf-d274-40e1-8c8c-caf906284d51/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코틀린 기본기 - 변수, Type, 연산자]]></title>
            <link>https://velog.io/@eunho_dev/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B8%B0%EB%B3%B8%EA%B8%B0-%EB%B3%80%EC%88%98</link>
            <guid>https://velog.io/@eunho_dev/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B8%B0%EB%B3%B8%EA%B8%B0-%EB%B3%80%EC%88%98</guid>
            <pubDate>Sat, 11 Feb 2023 14:46:33 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 최태현 님의 <a href="https://www.inflearn.com/course/java-to-kotlin/dashboardhttps://www.inflearn.com/course/java-to-kotlin/dashboard">&#39;자바 개발자를 위한 코틀린 입문&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.</p>
</blockquote>
<h2 id="변수-선언">변수 선언</h2>
<p>코틀린은 모든 변수에 수정 가능 여부를 명시해주어야 한다. <code>val</code> 키워드는 불변이나, 컬렉션에는 <code>element</code>를 추가할 수 있다.</p>
<pre><code class="language-kotlin">var num1 = 10L    // var : 가변 
val num2 = 10L    // val : 불변</code></pre>
<p>또한 타입을 명시적으로 선언하지 않아도 타입을 추론하며, 명시적으로 선언해줄 수도 있다. </p>
<pre><code class="language-kotlin">var num1: Long = 10L
val num2: Long = 10L</code></pre>
<p>모든 변수는 우선 불변인 <code>val</code>로 선언하고 필요할 경우에만 <code>var</code>로 변경하는 것이 좋다.</p>
<p>코틀린에서 변수는 기본적으로 null이 허용되지 않으며 nullable 변수를 생성하기 위해서는 <code>타입?</code>과 같이 명시적으로 선언해줘야 한다.</p>
<pre><code class="language-kotlin">var num: Long?</code></pre>
<p>또한 객체를 인스턴스화 할 때 <code>new</code>를 붙이지 않는다.</p>
<pre><code class="language-kotlin">val obj = Object(&quot;New Object&quot;);</code></pre>
<p>코틀린에서는 <strong>기본 자료형과 참조 자료형의 구분이 감춰져있다.</strong> 일반적으로는 자료형의 구별 없이 사용할 수 있으며, <strong>null이 허용된다면 참조형, 아니라면 기본형으로 선언</strong>된다. 즉 boxing/unboxing을 고려하지 않아도 되도록 코틀린이 처리 해준다.</p>
<h2 id="null-안정성">null 안정성</h2>
<p>코틀린은 null이 가능한 타입을 완전히 다르게 취급하며, 한 번 null check를 하면 컴파일러가 non-null임을 알 수 없다.(특히 Java의 코드를 사용하는 플랫폼 타입일 경우) 따라서 이를 위한 기능을 제공한다.</p>
<h3 id="안전한-호출safe-call">안전한 호출(safe call)</h3>
<p>안전한 호출은 체인(chain)할 때 <code>?.</code>를 사용하여 null이 아니면 해당 체인의 연산을 실행하고, null이면 실행하지 않는다. nullable 타입일 경우 안전한 호출을 통해서만 실행이 가능하다.</p>
<pre><code class="language-kotlin">val str: String? = &quot;ABC&quot;
str.length // str이 nullable이기 때문에 실행 불가능
str?.length</code></pre>
<h3 id="elvis-연산자">Elvis 연산자</h3>
<p>엘비스 연산자는 <code>?:</code>를 사용하여 앞의 연산 결과가 null이면 뒤의 내용을 호출해준다.</p>
<pre><code class="language-kotlin">str?.length ?: 0 // str.length의 값이 null일 경우 0 실행</code></pre>
<p>엘비스 연산은 early return에도 사용할 수 있다.</p>
<pre><code class="language-kotlin">fun calculate(number: Long?): Long {
    number ?: return 0
    ... null이 아닐 경우 로직
}</code></pre>
<h3 id="null-아님-단언">null 아님 단언</h3>
<p>nullable 타입이지만 null이 될 수 없는 경우 <code>!!</code>를 사용할 수 있다.</p>
<pre><code class="language-kotlin">fun startsWithA(str: String?): Boolean {
    return str!!.startsWith(&quot;A&quot;)
}</code></pre>
<h2 id="type">Type</h2>
<p>코틀린은 선언된 기본값을 보고 타입을 추론하지만 타입간의 변환은 언제나 명시적으로 이루어져야 한다.</p>
<pre><code class="language-kotlin">val num1 = 4
val num2: Long = num1 // Type mismatch
val num2: Long = num1.toLong()    // 명시적 형변환 필요</code></pre>
<p>Java에서의 <code>instanceof</code>에 해당하는 키워드로 <code>is</code> 키워드가 있으며, 일반 타입은<code>as</code>를 통해 형변환이 가능하다.(기본 타입도 가능)</p>
<pre><code class="language-kotlin">fun printAgeIfPerson(obj: Any) {
    if (obj is Person) {    
    // is == java: instance of
    // val person = obj as Person  
    // as = java: (Object) obj, 생략 가능
        println(obj.age)
    }
}</code></pre>
<p>또한 nullable 변수에 대해 적절한 처리가 필요하다.</p>
<pre><code class="language-kotlin">val num1: Int? = 3
val num2: Long = num1?.toLong() ?: 0L

fun printAgeIfPersonNullable(obj: Any?) {
    val person: Person? = obj as? Person    
    // as? : obj가 해당 타입이면 형변환, null이거나 해당 타입이 아니면 null을 반환
    println(person?.age)
}</code></pre>
<h3 id="any">Any</h3>
<p>Java의 Object 처럼 모든 객체의 최상위 타입으로, 모든 기본 자료형의 최상위 타입 또한 Any이다. Any 자체로는 null을 포함할 수 없으며, <code>Any?</code>를 통해 포함시킬 수 있다. 기본적으로 <code>equals</code>, <code>hashCode</code>, <code>toString</code>이 존재한다.</p>
<h3 id="unit">Unit</h3>
<p>Java의 void와 동일한 역할이지만, void와는 다르게 Unit 그 자체를 타입 인자로 사용 가능하다.</p>
<h3 id="nothing">Nothing</h3>
<p>함수가 정상적으로 끝나지 않았다는 사실을 표현하는 역할로, 무조건 예외를 반환하거나 무한 루프 함수 등에 사용된다.</p>
<pre><code class="language-kotlin">fun fail(message: String): Nothin {
    throw IllegalArgumentException(message)
}</code></pre>
<h3 id="string-interpolation">String interpolation</h3>
<p>코틀린은 문자열의 동적인 가공을 위해 아래와 같이 String interpolation을 지원한다.</p>
<pre><code class="language-kotlin">val person = Person(&quot;test&quot;, 100)
println(&quot;이름: ${person.name} / 나이: ${person.age}&quot;)</code></pre>
<p><code>${변수}</code>를 사용하면 해당 변수의 값이 포함되며 <code>$변수</code>를 사용할 경우 해당 변수명이 포함된다.</p>
<p>또한 <code>&quot;&quot;&quot;문자열&quot;&quot;&quot;.trimIndent()</code>을 통해 편리하게 여러 줄의 문자열을 가공할 수 있게 해준다.</p>
<h3 id="string-indexing">String indexing</h3>
<p>문자열에서 문자를 가져올 때 <code>[index]</code>를 통해 Java의 배열과 같이 가져올 수 있다.</p>
<h2 id="연산자">연산자</h2>
<h3 id="단항-연산자--산술-연산자">단항 연산자 / 산술 연산자</h3>
<p>단항/산술 연산자는 Java와 동일하다. <code>++</code>, <code>--</code>로 단항 연산을 할 수 있으며 <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>%</code>로 산술 연산을, <code>+=</code>, <code>-=</code>, <code>*=</code>, <code>/=</code>, <code>%/</code>로 산술 대입 연산이 가능하다.</p>
<h3 id="비교-연산자">비교 연산자</h3>
<p>비교 연산자 또한 Java와 동일하게 <code>&gt;</code>, <code>&lt;</code>, <code>&gt;=</code>, <code>&lt;=</code>, <code>==</code> 으로 할 수 있다. 단, Java와 다르게 객체를 비교할 때 비교 연산자를 사용하면 자동으로 <code>compareTo</code>를 호출해준다.</p>
<p>두 객체의 값이 같은지 비교하는 동등성 비교에 <code>equals</code>, 주소가 같은 완전히 동일한 객체인지 비교하는 동일성 비교에 <code>==</code>를 사용하는 Java와는 다르게 동등성에 <code>==</code>를 사용하여 간접적으로 <code>equals</code>를 호출하며, 동일성 비교는 <code>===</code>을 사용한다.</p>
<h3 id="논리-연산자">논리 연산자</h3>
<p><code>&amp;&amp;</code>, <code>||</code>, <code>!</code>을 통해 논리 연산이 가능하며, Java와 같이 Lazy 연산을 수행한다.</p>
<p>이 외에 코틀린에만 존재하는 연산자로는 컬렉션의 범위에 포함되어있는지의 여부를 리턴하는 <code>in</code>/<code>!in</code>, a에서 b까지의 범위 객체를 생성하는 <code>a..b</code>, <code>a[i]</code>, <code>a[i] = b</code>로 a에서 특정 index i를 통해 값을 처리할 수 있는 연산자가 존재한다.</p>
<h3 id="연산자-오버로딩">연산자 오버로딩</h3>
<p>코틀린에서는 아래와 같이 객체마다 연산자를 직접 정의할 수 있다.</p>
<pre><code class="language-kotlin">data class Money(
    val amount: Long
) {

    operator fun plus(other: Money): Money {
        return Money(this.amount + other.amount)
    }

}
...
val money1 = Money(1_000L)
val money2 = Money(2_000L)
print(money1 + money2) // Money(3_000L)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 톺아보기 - 값 타입]]></title>
            <link>https://velog.io/@eunho_dev/JPA-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EA%B0%92-%ED%83%80</link>
            <guid>https://velog.io/@eunho_dev/JPA-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EA%B0%92-%ED%83%80</guid>
            <pubDate>Thu, 09 Feb 2023 00:58:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 김영한 님의 <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">&#39;자바 ORM 표준 JPA 프로그래밍 - 기본편&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.</p>
</blockquote>
<p>JPA의 데이터 타입은 크게 <strong>엔티티 타입</strong>과 <strong>값 타입</strong>으로 나눌 수 있다. 엔티티 타입은 <code>@Entity</code>로 매핑되는 테이블 객체이고, 값 타입은 단순히 값으로 사용되는 자바 기본 타입이나 객체를 말한다.</p>
<p>엔티티 타입은 식별자가 있어 추적이 가능하지만, 값 타입은 속성만 존재하므로 추적이 불가능하다. 값 타입은 다음과 같이 분류할 수 있다.</p>
<ul>
<li><p><strong>기본값 타입(basic value type)</strong></p>
<ul>
<li>Java 기본 타입(int, double...)</li>
<li>래퍼 클래스(Integer...)</li>
<li>String</li>
</ul>
</li>
<li><p><strong>임베디드 타입(embedded type, 복합 값 타입)</strong></p>
</li>
<li><p><strong>컬렉션 타입(collection value type)</strong></p>
<h2 id="기본값-타입">기본값 타입</h2>
<pre><code class="language-java">@Entity
public class Example {

  @Id @GeneratedValue
  private Long id;

  // 기본 값 타입
  private String name;
  private int number;
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>위 예제의 String, int가 값 타입이며, Example 엔티티는 id라는 식별자 값과 생명주기가 있지만, name, number와 같은 값 타입은 식별자가 없으며, 생명주기 또한 엔티티에 의존한다. 또한 **값 타입은 java의 기본 타입(primitive type)과 같이 공유되면 안된다**.
## 임베디드 타입(복합 값 타입)
JPA에서는 새로운 값 타입을 직접 정의해서 사용할 수 있으며, 이를 임베디드 타입(embedded type)이라고 한다.

```java
@Entity
public class Member {

    @Id @GeneratedValue
    private Long id;

    private String name;

    // 주소
    private String city;
    private String street;
    private String zipcode;

}</code></pre><p>위의 Member 엔티티가 주소 값을 가지고 있는 것 처럼 엔티티 객체가 상세 데이터를 그대로 가지고 있는 것은 객체지향적이지 않으며, 응집력만 떨어뜨린다. 이를 아래와 같이 임베디드 타입으로 분리시킬 수 있다.</p>
<pre><code class="language-java">@Entity
public class Member {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @Embedded
    private Address address;

}

@Embeddable
public class Address {

    @Column(name = &quot;city&quot;)
    private String city;
    private String street;
    private String zipcode;

}</code></pre>
<p>새로 정의한 값 타입은 재사용이 가능하며, 응집도도 아주 높다. 또한 값 타입 객체 내에 해당 값 타입만 사용하는 의미 있는 메서드들도 만들 수 있다. 임베디드 타입을 사용하기 위한 어노테이션을 다음과 같으며, 둘 중 하나는 생략이 가능하다.</p>
<ul>
<li><code>@Embeddable</code> : 값 타입을 정의하는 곳에 표시</li>
<li><code>@Embedded</code> : 값 타입을 사용하는 곳에 표시</li>
</ul>
<p>임베디드 타입은 기본 생성자가 필수이다.</p>
<h3 id="임베디드-타입과-테이블-매핑">임베디드 타입과 테이블 매핑</h3>
<p>임베디드 타입은 엔티티의 값일 뿐이기에 값이 속한 엔티티의 테이블에 매핑되며, 사용하기 전과 후의 테이블의 형태는 같다. 임베디드 타입을 통해 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능해지기에 보다 객체지향 모델을 설계하기에 편리해진다. </p>
<p>임베디드 타입이 null일 경우 매핑한 컬럼 값은 모두 null이 된다.</p>
<h3 id="임베디드-타입과-연관관계">임베디드 타입과 연관관계</h3>
<p>임베디드 타입은 아래 예제와 같이 값 타입을 포함하거나 엔티티를 참조할 수 있다.</p>
<pre><code class="language-java">@Entity
public class Member {

    ...
    @Embedded
    private Address address;

}

@Embeddable
public class Address {

    private String city;
    private String street;
    @Embedded
    private Zipcode zipcode;

}

@Embeddable
public class Zipcode {
    private Zip zip;
    private String plusFour;
}

@Entity
public class Zip {
    @Id
    private String code;
}</code></pre>
<h3 id="attributeoverride--속성-재정의"><code>@AttributeOverride</code> : 속성 재정의</h3>
<p>임베디드 타입에 정의한 매핑 정보를 <code>@AttributeOverride</code>로 재정의할 수 있다.</p>
<pre><code class="language-java">@Entity
public class Member {

    ...
    @Embedded
    private Address homeAddress;

    @Embedded
    @AttributeOverrides({
        AttributeOverride(name = &quot;city&quot;, columne = @Column(name = &quot;COMPANY_CITY&quot;),
        AttributeOverride(name = &quot;street&quot;, columne = @Column(name = &quot;COMPANY_STREET&quot;),
        AttributeOverride(name = &quot;zipcode&quot;, columne = @Column(name = &quot;COMPANY_ZIPCODE&quot;))
    })
    private Address companyAddress;

}</code></pre>
<h2 id="값-타입과-불변-객체">값 타입과 불변 객체</h2>
<p>값 타입은 객체를 조금이라도 단순화하기 위해 만든 개념이다. 그렇기에 언제나 단순하고 안전하게 다룰 수 있어야 한다.</p>
<h3 id="값-타입-공유-참조">값 타입 공유 참조</h3>
<p>임베디드 타입 같은 값 타입을 여러 엔티티에서 그대로 참조해서 사용할 경우 값 타입의 값 변경시 해당 인스턴스를 참조하는 모든 엔티티의 값이 변경되는 사이드 이펙트가 발생할 수 있다. 그렇기에 하나의 값 타입을 여러 엔티티에서 사용할 경우 참조가 아닌 복사를 통해 사용해야 한다. </p>
<h3 id="값-타입-복사">값 타입 복사</h3>
<p>자바는 기본 타입에 값을 대입하면 항상 값을 복사해서 전달한다. 그렇기에 원본과 복사본은 완전히 독립된 값을 가지며, 부작용도 없다.</p>
<p>그러나 객체 타입은 항상 참조값을 전달하며, 원본과 복사본이 같은 인스턴스를 공유한다. 그렇기에 원복 값 변경시 복사본의 값 또한 변경된다. 이를 막기 위해 객체를 대입할 때마다 항상 인스턴스를 복사해서 대입하면 되지만, <strong>자바에서 복사하지 않고 원본의 참조 값을 직접 넘기는 것을 막을 방법이 없다</strong>는 근본적인 문제가 있다. 이러한 근본적인 문제를 해결하기 위해서는 setter와 같은 수정자 메서드를 모두 제거해야 한다.</p>
<h3 id="불변-객체">불변 객체</h3>
<p>값 타입은 부작용 걱정 없이 사용해야 한다. 그렇기에 될 수 있으면 불변 객체(immutable Object)로 설계해야 한다. 불변 객체도 결국 객체기 때문에 인스턴스의 참조 값 공유를 피할 수 없지만 해당 값을 수정할 수 없기 때문에 부작용이 발생하지는 않는다. </p>
<h2 id="값-타입의-비교">값 타입의 비교</h2>
<p>자바가 제공하는 객체 비교는 인스턴스의 참조 값을 비교하는 <strong>동일성(Identity) 비교(==)</strong>와 인스턴스의 값을 비교하는 <strong>동등성(Equivalence) 비교(equals())</strong> 두 가지가 있다.</p>
<p>값 타입은 서로 다른 인스턴스일지라도 그 안에 값이 같으면 같은 것으로 봐야 한다. 따라서 값 타입의 <code>equals()</code> 메서드를 재정의하고, 동등성을 비교해야 한다. 또한 자바에서 <code>equals()</code> 메서드를 재정의하면 <code>hashCode()</code>도 재정의해야 해시를 사용하는 컬렉션(HashSet, HashMap)들이 정상 동작한다.</p>
<h2 id="값-타입-컬렉션">값 타입 컬렉션</h2>
<p>값 타입을 하나 이상 저장하기 위해서는 컬렉션에 보관하고 <code>@ElementCollection</code>, <code>@CollectionTable</code> 어노테이션을 사용한다.</p>
<pre><code class="language-java">@Entity
public class Member {

    ...
    @ElementCollection
    @CollectionTable(name = &quot;ADDRESS&quot;, 
        joinColumns = @JoinColumn(name = &quot;MEMBER_ID&quot;))
    private List&lt;Address&gt; addressHistory = new ArrayList&lt;Address&gt;();

}</code></pre>
<p>관계형 데이터베이스의 테이블은 컬럼 안에 컬렉션을 포함할 수 없기 때문에 위와 같은 값 타입 컬렉션을 가진 객체를 테이블로 매핑하려면 별도의 테이블을 추가하고 <code>@CollectionTable</code>을 사용해야 한다. </p>
<p>값으로 사용되는 컬럼이 하나일 경우 <code>@Column</code>을 사용해서 컬럼명을 지정할 수 있으며, <code>@AttributeOverride</code>를 사용해서 매핑정보를 재정의할 수도 있다.</p>
<h3 id="값-타입-컬렉션-사용">값 타입 컬렉션 사용</h3>
<p>값 타입 컬렉션은 영속성 전이와 고아 객체 제거 기능을 필수로 가지며, 조회시 페치 전략을 선택할 수 있다. 기본 전략은 LAZY이다.</p>
<p>값 타입은 불변해야 하기 때문에 엔티티의 값 타입 수정은 다음과 같다.</p>
<ol>
<li>임베디드 값 타입 : 엔티티에 새로운 임베디드 인스턴스 생성 후 엔티티만 UPDATE한다.</li>
<li>기본값 타입 컬렉션 : 수정하고자 하는 기본값 타입을 제거하고 새 값을 추가한다.</li>
<li>임베디드 값 타입 컬렉션 : 수정하고자 하는 임베디드 값 타입을 제거하고 새 값을 추가한다. <code>eqauls()</code>와 <code>hashCode()</code>를 꼭 구현해야 한다.</li>
</ol>
<h3 id="값-타입-컬렉션의-제약사항">값 타입 컬렉션의 제약사항</h3>
<p>값 타입은 식별자라는 개념이 없는 단순한 값들의 모음이기 때문에 값을 변경해버리면 데이터베이스에서 저장된 원본 데이터를 찾기 어렵다. </p>
<p>특정 엔티티에 소속된 값 타입은 소속된 엔티티를 데이터베이스에서 찾고 변경하면 되지만, 값 타입 컬렉션은 별도의 테이블에 보관되기 때문에 값이 변경되면 <strong>원본 데이터를 찾기 어렵다는 문제</strong>가 있다. 그렇기에 JPA 구현체들은 값 타입 컬렉션에 변경 사항이 발생하면 값 타입 컬렉션이 매핑된 테이블의 모든 연관 데이터를 삭제하고 현재 객체에 있는 값들을 다시 저장하며, 이 때 해당 <strong>컬렉션 길이만큼 INSERT</strong>된다. 그렇기에 값 타입 컬렉션이 매핑된 테이블에 데이터가 많다면 <strong>일대다 관계를 고려</strong>해야 한다.</p>
<p>또한 값 타입 컬렉션을 매핑하는 모든 컬럼을 묶어서 기본 키를 구성해야 하기 때문에 <strong>컬럼에 null을 입력할 수 없고, 중복된 값을 저장할 수 없다</strong>는 제약이 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 톺아보기 - 영속성 전이]]></title>
            <link>https://velog.io/@eunho_dev/JPA-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EC%98%81%EC%86%8D%EC%84%B1-%EC%A0%84%EC%9D%B4</link>
            <guid>https://velog.io/@eunho_dev/JPA-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EC%98%81%EC%86%8D%EC%84%B1-%EC%A0%84%EC%9D%B4</guid>
            <pubDate>Mon, 06 Feb 2023 15:19:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 김영한 님의 <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">&#39;자바 ORM 표준 JPA 프로그래밍 - 기본편&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.</p>
</blockquote>
<h2 id="영속성-전이">영속성 전이</h2>
<p>JPA에서 엔티티를 저장할 때 연관된 모든 엔티티가 영속 상태여야 한다. 이 때 영속성 전이(transitive persistence) 기능을 사용하며,<code>CASCADE</code> 옵션을 사용하면 부모 엔티티를 영속 상태로 만들 때 자식 엔티티를 함께 영속 상태로 만들 수 있다.</p>
<p>영속성 전이는 연관관계 매핑과는 아무 관련이 없으며, 엔티티를 영속화할 때 연관된 엔티티를 함께 영속화하는 기능만을 제공한다.</p>
<h3 id="저장">저장</h3>
<pre><code class="language-java">@Entity
public class Parent {

    @Id    @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = &quot;parent&quot;, cascade = CascadeType.PERSIST)
    private List&lt;Child&gt; children = new ArrayList&lt;Child&gt;();

}

@Entity
public class Child {

    @Id    @GeneratedValue
    privaet Long id;

    @ManyToOne
    private Parent parent;

}

public void save(EntityManager em) {

    Child child1 = new Child();
    Child child2 = new Child();

    Parent parent = new Parent();
    child1.setParent(parent);
    child2.setParent(parent);
    parent.getChildren().add(child1);
    parent.getChildren().add(child2);

      em.persist(parent);
}</code></pre>
<p>위의 예제를 통해 알 수 있듯이 <code>CascadeType.PERSIST</code>로 설정한 자식 엔티티는 부모 엔티티가 영속화할 때 함께 영속화되어 저장된다. </p>
<h3 id="삭제">삭제</h3>
<p><code>CascadeType.REMOVE</code> 속성을 사용할 경우 엔티티를 삭제할 때도 영속성 전이를 사용할 수 있다. 삭제 순서는 외래 키 제약조건을 고려해서 자식을 먼저 삭제하고 부모를 삭제한다.</p>
<h3 id="cascade">CASCADE</h3>
<p><code>CascadeType</code> 속성에는 다음과 같은 옵션이 있다.</p>
<ul>
<li><code>ALL</code> : 모두 적용</li>
<li><code>PERSIST</code> : 영속, flush 호출시 전이 발생</li>
<li><code>MERGE</code> : 병합</li>
<li><code>REMOVE</code> : 삭제, flush 호출시 전이 발생</li>
<li><code>REFRESH</code> : REFRESH</li>
<li><code>DETACH</code> : DETACH</li>
</ul>
<h2 id="고아-객체">고아 객체</h2>
<p>JPA의 <strong>고아 객체(ORPHAN) 제거</strong>는 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제해주는 기능이며, 영속성 컨텍스트를 플러시할 때 적용된다. 이를 활성화하기 위해서는 자식 엔티티를 참조하는 컬렉션에 <code>orphanRemoval = true</code>를 설정해주면 된다.</p>
<p>고아 객체 제거는 <strong>참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능</strong>이기 때문에 참조하는 곳이 하나일 때만 사용해야 한다. 이런 이유로 <code>orphanRemoval</code>은 <code>@OneToOne</code>, <code>@OneToOne</code>에서만 사용할 수 있다.</p>
<h3 id="영속성-전이--고아-객체-생명주기">영속성 전이 + 고아 객체 생명주기</h3>
<p><code>CascadeType.ALL</code>과 <code>orphanRemoval = true</code>를 동시에 사용할 경우 부모 엔티티를 통해서 자식 엔티티의 등록/삭제 등 생명주기를 관리할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 프레임워크(Spring Framework) 톺아보기 - 동시성 문제와 쓰레드 로컬(ThreadLocal)]]></title>
            <link>https://velog.io/@eunho_dev/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%ACSpring-Framework-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EC%93%B0%EB%A0%88%EB%93%9C-%EB%A1%9C%EC%BB%ACThreadLocal</link>
            <guid>https://velog.io/@eunho_dev/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%ACSpring-Framework-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%EC%93%B0%EB%A0%88%EB%93%9C-%EB%A1%9C%EC%BB%ACThreadLocal</guid>
            <pubDate>Fri, 03 Feb 2023 01:36:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 김영한 님의 <a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard">&#39;스프링 핵심원리 고급편&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.  </p>
</blockquote>
<h2 id="동시성-문제">동시성 문제</h2>
<p>여러 쓰레드가 동시에 같은 인스턴스의 필드 값을 변경하면서 발생하는 문제를 동시성 문제라고 하며 쓰레드가 많아질수록, 즉 트래픽이 늘어날수록 자주 발생하게 된다. 지역 변수와 같은 쓰레드 별 메모리 영역이 할당되는 값은 해당되지 않으며 스프링 빈, static 객체와 같이 싱글톤 객체의 필드를 변경하며 사용할 때 발생한다. 이를 해결하기 위해 사용하는 것이 <strong>쓰레드 로컬(ThreadLocal)</strong>이다.</p>
<p>동시성 문제에 대한 java에서의 처리 방법이 궁금해져서 OpenAi의 ChatGPT에게 물어보았더니 아래와 같은 답변을 얻었다.</p>
<h4 id="java에서-동시성-문제를-해결하는-방법에는-다음과-같은-것들이-있습니다">Java에서 동시성 문제를 해결하는 방법에는 다음과 같은 것들이 있습니다.</h4>
<ol>
<li><p><strong>Synchronized</strong> 키워드 : Java에서 synchronized 키워드를 사용하여 메소드 또는 블록을 잠글 수 있습니다.</p>
</li>
<li><p><strong>Lock 객체</strong> : Java에서 Lock 객체를 사용하여 동시적으로 접근하려는 자원에 대한 접근을 제어할 수 있습니다.</p>
</li>
<li><p><strong>Atomic 클래스</strong> : Java에서 Atomic 클래스를 사용하여 동시성 문제가 발생하는 경우 자동적으로 락을 걸어주고 해제하여 동시성 문제를 해결할 수 있습니다.</p>
</li>
<li><p><strong>Executor Framework</strong> : Java에서 Executor Framework를 사용하여 다중 스레드 환경에서 동시성 문제를 해결할 수 있습니다.</p>
</li>
</ol>
<p>이 외에도 Java 5에서 추가된 <strong>Concurrent Package 등의 라이브러리를 사용</strong>하여 동시성 문제를 해결할 수 있습니다.</p>
<h2 id="쓰레드-로컬threadlocal">쓰레드 로컬(ThreadLocal)</h2>
<p>쓰레드 로컬은 쓰레드 별로 생성되는 별도의 내부 저장소를 지칭한며, 다음과 같은 기능을 제공한다.</p>
<ul>
<li>값 저장 : <code>ThreadLocal.set()</code> </li>
<li>값 조회 : <code>ThreadLocal.get()</code></li>
<li>값 제거 : <code>ThreadLocal.remove()</code></li>
</ul>
<h3 id="예제-코드">예제 코드</h3>
<pre><code class="language-java">@Slf4j
public class ThreadLocalService {

    private ThreadLocal&lt;String&gt; nameStore = new ThreadLocal&lt;&gt;();

     public String logic(String name) {
         log.info(&quot;저장 name={} -&gt; nameStore={}&quot;, name, nameStore.get());
         nameStore.set(name);

        sleep(1000);

        log.info(&quot;조회 nameStore={}&quot;,nameStore.get());

         return nameStore.get();
    }

     private void sleep(int millis) {
         try {
             Thread.sleep(millis);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
}

@Slf4j
public class ThreadLocalServiceTest {

     private ThreadLocalService service = new ThreadLocalService();

    @Test
     void threadLocal() {
         log.info(&quot;main start&quot;);

         Runnable userA = () -&gt; {
             service.logic(&quot;userA&quot;);
         };

         Runnable userB = () -&gt; {
             service.logic(&quot;userB&quot;);
         };

         Thread threadA = new Thread(userA);
         threadA.setName(&quot;thread-A&quot;);

         Thread threadB = new Thread(userB);
         threadB.setName(&quot;thread-B&quot;);

         threadA.start();
         sleep(100);

         threadB.start();
         sleep(2000);

         log.info(&quot;main exit&quot;);
     }

    private void sleep(int millis) {
         try {
             Thread.sleep(millis);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
}</code></pre>
<h3 id="실행-결과">실행 결과</h3>
<pre><code>[Test worker] main start
[Thread-A] 저장 name=userA -&gt; nameStore=null
[Thread-B] 저장 name=userB -&gt; nameStore=null
[Thread-A] 조회 nameStore=userA
[Thread-B] 조회 nameStore=userB
[Test worker] main exit</code></pre><h3 id="쓰레드-로컬-사용시-주의점">쓰레드 로컬 사용시 주의점</h3>
<p>쓰레드 로컬을 모두 사용 후 <code>ThreadLocal.remove()</code>를 통해 쓰레드 로컬에 저장된 값을 제거해주어야 한다. 쓰레드의 <strong>생성 비용은 비싸기 때문</strong>에 WAS는 사용이 끝난 쓰레드를 제거하지 않고, <strong>보통 쓰레드 풀을 통해 재사용</strong>한다. 그렇기에 사용 후 제거하지 않으면 심각한 문제가 발생할 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 톺아보기 - 프록시]]></title>
            <link>https://velog.io/@eunho_dev/JPA-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%ED%94%84%EB%A1%9D%EC%8B%9CProxy</link>
            <guid>https://velog.io/@eunho_dev/JPA-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-%ED%94%84%EB%A1%9D%EC%8B%9CProxy</guid>
            <pubDate>Thu, 02 Feb 2023 04:15:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 인프런에서 제공하는 김영한 님의 <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">&#39;자바 ORM 표준 JPA 프로그래밍 - 기본편&#39;</a>을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.</p>
</blockquote>
<h2 id="지연로딩">지연로딩</h2>
<p>엔티티가 조회될 때 연관관계에 있는 엔티티들을 사용하지 않음에도 함께 조회해 두는 것은 효율적이지 않다. 그렇기에 JPA는 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공하며, 이를 지연 로딩이라고 한다.</p>
<p>JPA의 구현체인 하이버네이트는 지연 로딩을 지원하기 위해 프록시를 사용하는 방법과 바이트코드를 수정하는 두 가지 방법을 제공하며, 복잡한 설정이 필요한 바이트코드 조작에 비해 프록시는 별도의 설정 없이 사용할 수 있다.</p>
<h2 id="프록시">프록시</h2>
<h3 id="프록시의-특징">프록시의 특징</h3>
<p>프록시는 실제 클래스를 상속 받아 만들어지며, 실제 클래스와 겉 모양이 같다. 그렇기에 사용자의 입장에서 해당 객체가 원본 객체인지, 프록시 객체인지 구분하지 않고 사용할 수 있다.</p>
<p>프록시 객체는 실제 객체에 대한 참조(target)를 보관하며, 프록시 객체의 메서드가 호출되면 참조를 통해 실제 객체의 메서드를 호출한다. 이 때 실제 객체가 생성되어있지 않다면 해당 객체에 대한 생성을 요청하며, 이를 초기화 한다.
<img src="https://velog.velcdn.com/images/eunho_dev/post/990ba37e-f6b4-47a5-966f-8bda3ef89e41/image.png" alt=""></p>
<p>JPA의 관점에서 본다면 아래와 같다.</p>
<ol>
<li><strong>프록시 객체 호출</strong></li>
<li><strong>실제 엔티티의 참조를 통한 호출</strong><ul>
<li>실제 엔티티가 존재하지 않을 경우 영속성 컨텍스트에 생성 요청(초기화) 후 멤버 변수에 담음</li>
<li>실제 엔티티 호출</li>
</ul>
</li>
<li><strong>호출에 대한 결과 반환</strong></li>
</ol>
<p>프록시의 특징을 정리하면 다음과 같다.</p>
<ul>
<li>프록시 객체는 처음 사용할 때 한 번만 초기화된다.</li>
<li>초기화 된 프록시는 실제 엔티티로 변환되는 것이 아닌 실제 객체에 대한 접근만 가능해진다.</li>
<li>프록시 객체는 원본 엔티티를 상속받았기에 타입 체크 시 주의해야 한다.</li>
<li>영속성 컨텍스트에 이미 실제 엔티티가 있다면 <code>em.getReference()</code> 호출시 프록시가 아닌 실제 엔티티를 반환한다.</li>
<li>초기화는 영속성 컨텍스트를 통해서만 가능하며, 준영속 상태의 프록시를 초기화할 경우 예외가 발생한다.</li>
</ul>
<h3 id="프록시와-식별자">프록시와 식별자</h3>
<p>엔티티를 프록시로 조회할 때 식별자(PK) 값을 파라미터로 전달 받아 프록시 객체 내부에 보관한다. 그렇기에 식별자 값만 호출된다면 프록시를 초기화 하지 않지만, 엔티티 접근 방식을 프로퍼티(<code>@Access(AccessType.PROPERTY)</code>)가 아닌 필드(<code>@Access(AccessType.FIELD)</code>)로 설정한 경우에는 초기화한다.</p>
<p>프록시를 사용하면 연관관계를 설정할 때 식별자 값만 사용하므로 데이터베이스 접근 횟수를 줄일 수 있다. 이러한 연관관계 설정시에는 엔티티 접근 방식을 필드로 설정해도 프록시를 초기화하지 않는다. </p>
<h3 id="프록시-확인">프록시 확인</h3>
<p>JPA가 제공하는 <code>PersistenceUnitUtil.isLoaded(Object entity)</code> 메서드를 사용하면 프록시 인스턴스의 초기화 여부를 확인할 수 있다. 초기화되었거나 프록시 인스턴스가 아닐 경우 true를 반환한다. 또한 하이버네이트의 <code>initialize()</code> 메서드를 통해 강제로 프록시를 초기화할 수도 있다.</p>
<h2 id="즉시-로딩과-지연-로딩">즉시 로딩과 지연 로딩</h2>
<p>JPA는 연관된 엔티티의 조회 시점을 선택할 수 있도록 엔티티를 조회할 때 연관된 엔티티도 함께 조회하는 <strong>즉시 로딩</strong>과 <strong>지연 로딩</strong> 두 가지 방법을 제공한다.</p>
<h3 id="즉시-로딩eager-loading">즉시 로딩(EAGER LOADING)</h3>
<p>즉시 로딩을 사용하기 위해서는 <code>@ManyToOne(fetch = FetchType.EAGER)</code>와 같이 연관관계 매핑의 <code>fetch</code> 속성을 <code>FetchType.EAGER</code>로 지정하면 된다.</p>
<p>JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다. 외래 키가 null을 허용할 경우를 대비해 JPA는 외부 조인(OUTER JOIN)을 기본적으로 사용한다. 따라서 null을 허용하지 않는 외래 키를 사용해 조회할 때 최적화를 위해 내부 조인(INNER JOIN)을 사용하기 위해서는 <code>@JoinColumn(..., nullable = false)</code>를 통해 명시해주어야 한다.</p>
<h3 id="지연-로딩lazy-loading">지연 로딩(LAZY LOADING)</h3>
<p>지연 로딩을 사용하기 위해서는 <code>@ManyToOne(fetch = FetchType.LAZY)</code>와 같이 연관관계 매핑의 <code>fetch</code> 속성을 <code>FetchType.LAZY</code>로 지정하면 된다.</p>
<p>지연 로딩을 사용할 경우 사용하지 않는 연관관계 객체에 프록시 객체를 넣어두고 사용하기 전까지 초기화를 미뤄둔다.</p>
<h2 id="지연-로딩-활용">지연 로딩 활용</h2>
<h3 id="프록시와-컬렉션-래퍼">프록시와 컬렉션 래퍼</h3>
<p>하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티 내부에 컬렉션이 있으면 이를 <strong>추적하고 관리할 목적</strong>으로 원본 컬렉션을 하이버네이트 내장 컬렉션을 변경하며, 이를 <strong>컬렉션 래퍼</strong>라 한다.</p>
<p>엔티티를 지연 로딩할 경우 프록시 객체를 사용하고, 컬렉션을 지연 로딩할 때는 컬렉션 래퍼가 지연 로딩을 처리한다.</p>
<h3 id="jpa-기본-페치-전략">JPA 기본 페치 전략</h3>
<p>JPA의 기본 페치(fetch) 전략은 연관된 엔티티가 하나일 경우 즉시 로딩을, 컬렉션일 경우 지연 로딩을 사용한다. 그러나 개발 단계에선 모든 연관관계를 지연 로딩으로 설정하고, 조회 빈도가 높은 곳에서만 즉시 로딩을 사용하도록 최적화하는 것이 좋다.</p>
<p>컬렉션을 하나 이상 즉시 로딩하는 것은 권장되지 않는다. 과도한 SQL 실행으로 어플리케이션의 성능을 저하시키는 <strong>1+N 문제</strong>가 발생할 수 있기 때문이다. 또한 컬렉션 즉시 로딩은 항상 외부 조인을 사용하기 때문에 이를 인지하고 사용해야 한다.</p>
]]></description>
        </item>
    </channel>
</rss>