<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>meluu_.log</title>
        <link>https://velog.io/</link>
        <description>열심히 살자</description>
        <lastBuildDate>Fri, 24 May 2024 09:28:10 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>meluu_.log</title>
            <url>https://velog.velcdn.com/images/meluu_/profile/d73b9733-8bc2-4aec-9434-d1be8bc80b43/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. meluu_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/meluu_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[게임 맵 최단거리 level 2 자바]]></title>
            <link>https://velog.io/@meluu_/%EA%B2%8C%EC%9E%84-%EB%A7%B5-%EC%B5%9C%EB%8B%A8%EA%B1%B0%EB%A6%AC-level-2-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@meluu_/%EA%B2%8C%EC%9E%84-%EB%A7%B5-%EC%B5%9C%EB%8B%A8%EA%B1%B0%EB%A6%AC-level-2-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Fri, 24 May 2024 09:28:10 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/1844"
   title="문제">게임 맵 최단거리 level 2</a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/cde3a5c8-5d69-4c80-8faa-4b41729ce772/image.png" alt=""><img src="https://velog.velcdn.com/images/meluu_/post/50390a44-9873-4ee6-88c4-7e3381ac157f/image.png" alt=""></p>
<p>dfs/bfs 문제이다. bfs로 풀면 좋을것 같아서 bfs로 방향을 잡았다.
방문한 좌표에 대해 check를 해주고, 갈 수 있는 곳을 check해주는 것이 핵심이다. </p>
<p>처음에 visited를 q에서 뽑고나서 true로 해주어서 무한 루프에 빠졌었는데 
중복 좌표값이 Queue 에 추가되서 그런거였다. 
1 2
3 4 
그래프가 이렇게 있고 
Queue에 2,3 순으로 들어가 있다고 하자.</p>
<p>2가 나와서 4를 추가한다. 
3이 나와서 4를 추가한다. &lt;&lt; 이런 중복 좌표값이 생긴다.
이를 해결하기 위해서 while문 밖에서 시작지점에 true를 미리 넣어주고 
가능한 좌표일때 방문 표시를 즉시 해줌으로써 해결하였다.</p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<pre><code class="language-java">import java.util.*;

class Solution {
    static int[] dxarr = {0, 0, 1, -1};
    static int[] dyarr = {1, -1, 0, 0};

    public int solution(int[][] maps) {
        boolean[][] visited = new boolean[maps.length][maps[0].length];
        int answer = bfs(visited, maps);
        return answer;
    }

    public int bfs(boolean[][] visited, int[][] map) {
        Queue&lt;int[]&gt; q = new LinkedList&lt;&gt;();
        visited[0][0] = true;
        q.add(new int[]{0,0});

        while(!q.isEmpty()) {
            int[] coord = q.poll();

            for(int i = 0; i &lt; 4; i++) {
                int dx = coord[1] + dxarr[i]; // 열
                int dy = coord[0] + dyarr[i]; // 행

                if (dx &gt;= 0 &amp;&amp; dx &lt; map[0].length &amp;&amp; dy &gt;= 0 &amp;&amp; dy &lt; map.length) {
                    if(!visited[dy][dx] &amp;&amp; map[dy][dx] &gt;= 1) {
                        map[dy][dx]+= map[coord[0]][coord[1]]; // 이전 거리를 더한다.
                        q.add(new int[]{dy, dx});
                        visited[dy][dx] = true;
                    }
                } 
            }
        }

        int endPoint = map[map.length - 1][map[0].length - 1];
        return  endPoint == 1 ? -1 : endPoint;
    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/310ac4f7-b8bb-4c70-bb0c-2325e31882a6/image.png" alt=""></p>
<br>


<hr>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<ol>
<li>깊이/너비 탐색문제 오랜만에 시도해보니 좀 어려웠다. 다시 개념을 정리하고 많이 풀어보자.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[행렬의 곱셈 level 2 자바]]></title>
            <link>https://velog.io/@meluu_/%ED%96%89%EB%A0%AC%EC%9D%98-%EA%B3%B1%EC%85%88-level-2-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@meluu_/%ED%96%89%EB%A0%AC%EC%9D%98-%EA%B3%B1%EC%85%88-level-2-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Sat, 18 May 2024 04:40:50 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12949"
   title="문제">행렬의 곱셈 level 2</a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/253b9724-48bb-48bd-bba8-c809e95774f2/image.png" alt=""></p>
<p>arr1 x arr2 = arr3
axn * nxb = axb인 행렬이 생긴다.</p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<pre><code class="language-java">class Solution {
    public int[][] solution(int[][] arr1, int[][] arr2) {
        int[][] answer = new int[arr1.length][arr2[0].length];

        for (int i = 0; i &lt; arr1.length; i++) {
            for(int j = 0; j &lt; arr2[0].length; j++) {
                for (int k = 0; k &lt; arr2.length; k++) {
                    answer[i][j] += arr1[i][k] * arr2[k][j];
                }
            }
        }
        return answer;
    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/d8f15d88-4383-4ebf-859c-eb37f6a802cc/image.png" alt=""></p>
<p>arr1 = [[1,2],[7,8]] arr2 =[[3,5],[4,6]]
answer[i][j] = C
arr1[i][k] = [1,2]
arr2[k][j] = [3][4] 
i는 answer와 arr1의 행을 가리킨다.
j는 arr2의 열을 가리킨다. [1,2] 가 1열 [3][4], 2열 [4][6]을 곱연산하게 끔한다. 
k는 arr1의 열, arr2의 행을 가리킨다.</p>
<p>제일 좋은 방법은 직접 손으로 행렬을 그려보는 것이다. </p>
<br>

<h4 id="처음-시도">처음 시도</h4>
<pre><code class="language-java">class Solution {
    public int[][] solution(int[][] arr1, int[][] arr2) {
        int[][] answer = new int[arr1.length][arr2.length];

        for (int i = 0; i &lt; arr1.length; i++) {
            for(int j = 0; j &lt; arr2.length; j++) {
                for (int k = 0; k &lt; arr2.length; k++) {
                    answer[i][j] += arr1[i][k] * arr2[k][j];
                }
            }
        }
        return answer;
    }
}</code></pre>
<p>문제 예시에서 행과 열이 같은 경우만 주어져서 이렇게 생각한 것 같다...
당연히 실패했다.</p>
<hr>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<ol>
<li>수학 문제는 항상 개념을 확실하게 이해할 필요가 있다. 그렇지 않으면 시간낭비일 뿐이다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[n^2 배열 자르기 level 2 자바 ]]></title>
            <link>https://velog.io/@meluu_/n2-%EB%B0%B0%EC%97%B4-%EC%9E%90%EB%A5%B4%EA%B8%B0-level-2-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@meluu_/n2-%EB%B0%B0%EC%97%B4-%EC%9E%90%EB%A5%B4%EA%B8%B0-level-2-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Thu, 16 May 2024 12:14:18 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/87390"
   title="문제">n^2 배열 자르기 level 2</a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/6a394402-6dee-48d5-986d-d3fa3772637c/image.png" alt=""></p>
<p>nxn크기의 2차원 배열을 i행 i열에 i 값을 넣는다. 
123
223
333
이런식으로 
그다음 각 행을 모두 이어붙이고 left~right까지만 배열로 반환</p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<pre><code class="language-java">class Solution {
    public int[] solution(int n, long left, long right) {
        int[] answer = new int[(int)(right-left+1)];

        for (int i = 0; i &lt; answer.length; i++) {
            answer[i] = (int)(Math.max((left + i) / n, (left + i) % n) + 1);
        }

        return answer;
    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/79ddc3e4-bdac-475e-8ab5-a96f3ee3589f/image.png" alt=""></p>
<p>1차시도때 2차원 배열 만들고 값을 하나씩 넣은다음 그 중에서 left~right 배열을 반환하려 했으나 보면 알겠지만 n의 값이 최대 10^7이고 right - left 는 10^5 미만이다. 2차원 배열을 만들 경우 n *n 인 10^14크기의 배열을 만들어야하므로 메모리 초과가 뜬다. </p>
<p>무작정 풀게 아니라 우리에게는 아주 좋은 나누기 연산과 나머지 연산이 있다. 
굳이 한 배열로 이어붙이는 로직을 짤 필요가 없다는 의미이다. </p>
<p>left ~right까지 반복하게 끔 반복문을 만든 후 
left / n 과 left % n 중 가장 큰 값이 처음 i행 i열에 i 값을 넣은 그 값이다. 
물론 +1을 해주어야한다. 나는 0행, 0열부터 시작하게끔 설정하였으므로..</p>
<p>입출력 예 1번을 예시로 풀어보면
left=2이고 max(2/3, 2%3) 이면 0, 2이므로 arr[0][2] 의 값이라는 뜻이다. 
여기서 우리는 처음 열,행이 0부터 시작이므로 최대값 2에 +1을 해준 3이 left=2에 해당하는 값이다.</p>
<br>

<h4 id="처음-시도">처음 시도</h4>
<pre><code class="language-java">class Solution {
    public int[] solution(int n, long left, long right) {
        // 메모리 초과
        int[][] arr = new int[n][n];
        int[] answer = new int[(int)(right-left+1)];

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

        for (int index = 0; index &lt;= right - left; index++) {
            answer[index] = arr[(int)(left + index) / n][(int)(left + index) % n];
        }

        return answer;
    }
}</code></pre>
<p>2차원 배열을 만들기만 해도 메모리초과가 떠서 2차원배열로 푸는 문제가 아니라는 것을 깨달았다. </p>
<hr>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<ol>
<li>무작정 풀어보고 생각을 다듬는 것이 좋아보인다. </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[H-Index level 2 자바 ]]></title>
            <link>https://velog.io/@meluu_/H-Index-level-2-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@meluu_/H-Index-level-2-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Wed, 15 May 2024 15:11:25 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42747"
   title="문제">H-Index level 2</a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/aecd054e-2bcf-4bdd-bf2c-fec5fe9f88c8/image.png" alt=""></p>
<p>문제를 잘 못 이해해서 삽질했다.
n편 중 h 번 이상 인용된 논문이 h편 이상이고,
나머지 논문은 개수에 상관없이 h 번 이하 인용되었다면 h의 최댓값이 H-Index이다.</p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<pre><code class="language-java">import java.util.Arrays;

class Solution {
    public int solution(int[] citations) {
        Arrays.sort(citations);
        int max = 0;

        // i = 논문 인용 횟수
        for (int i = 0; i &lt; citations[citations.length - 1]; i++) {
            int count = 0;
            // 인용횟수 h 이상인 논문 카운트
            for (int j = 0; j &lt; citations.length; j++) {
                if ( i &lt;= citations[j]) count++;   
            }
            // 인용횟수 h이상인 논문이 h편 이상인지 판별
            if (i &lt;= count) max = i;
            else break;

        }

        // n편중 h번 이상이 h 개 이상, 나머지는 h번 이하 인 h의 최댓값

        return max;
    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/6fd988b4-217a-4ac2-9560-87b2a9bf7ddd/image.png" alt=""></p>
<p>흠.. 느리다. </p>
<br>

<h4 id="프로그래머스-좋아요-1등-풀이">프로그래머스 좋아요 1등 풀이</h4>
<pre><code class="language-java">import java.util.*;

class Solution {
    public int solution(int[] citations) {
        Arrays.sort(citations);

        int max = 0;
        for(int i = citations.length-1; i &gt; -1; i--){
            int min = (int)Math.min(citations[i], citations.length - i);
            if(max &lt; min) max = min;
        }

        return max;
    }
}</code></pre>
<h4 id="이해-해보기">이해 해보기</h4>
<p>예제 [1,1,1,7,8,10] 이고 정답은 3이다.</p>
<p><img src="https://velog.velcdn.com/images/meluu_/post/8fd4b813-05bc-46d0-8671-cb645f6e9ad3/image.png" alt=""></p>
<p>결국에는 citations 길이 안에서 결정이 난다. 왜냐하면 h 개 이상의 논문이 있어야하기때문이다.</p>
<p>쉽게 보자면 
10 이상 인용된 논문의 개수 : 1
8 이상 인용된 논문의 개수 : 2
7 이상 인용된 논문의 개수 : 3
...</p>
<p> citations의 요소가 인용된 횟수이며, 그 횟수 이상이 논문의 개수여야한다. 
 인용횟수와 논문 개수중 더 작은 것을 찾아내고 그 중에서 최댓값을 선정한다.</p>
<hr>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<ol>
<li>저런 발상을 하는 연습이 필요한 것 같다...</li>
<li>내가 짠게 뭔가 이해가 더 잘되는 것 같다. 풀이는 내가하던대로 하되 나중에 더 최적화해야겠다 싶을 때 분석해서 코드를 리팩토링하는게 맞는 것 같다. </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[영어 끝말잇기 level 2 자바]]></title>
            <link>https://velog.io/@meluu_/%EC%98%81%EC%96%B4-%EB%81%9D%EB%A7%90%EC%9E%87%EA%B8%B0-level-2-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@meluu_/%EC%98%81%EC%96%B4-%EB%81%9D%EB%A7%90%EC%9E%87%EA%B8%B0-level-2-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Wed, 03 Apr 2024 04:02:17 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12981"
   title="문제">영어 끝말잇기 level 2</a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/d1334722-3220-409a-ad68-d24136e2b185/image.png" alt=""></p>
<p>핵심 
말 그대로 끝말잇기이며 앞 뒤 문자가 이어지는 지 확인
중복 단어 불가</p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<pre><code class="language-java">import java.util.*;

class Solution {

    // 1번 부터 순서대로 한사람씩 단어
    // 마지막 다음 1번부터 다시시작
    // 마지막 문자로 시작하는 단어
    // 중복 단어 불가
    // 한 글자 불가

    public int[] solution(int n, String[] words) {
        Map&lt;String, Boolean&gt; map = new HashMap&lt;&gt;();
        char lastWord = words[0].charAt(0);

        for (int i = 0; i &lt; words.length; i++) {          
            if(map.getOrDefault(words[i], false)) 
                return new int[]{(i % n) + 1 , (int)Math.ceil((float)(i + 1) / n)};

            else if (lastWord != words[i].charAt(0)) 
                return new int[]{(i % n) + 1 , (int)Math.ceil((float)(i + 1) / n)};

            map.put(words[i], true);
            lastWord = words[i].charAt(words[i].length() - 1);
        }

        return new int[]{0,0};
    }
}

</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/3b409664-ab39-4a85-9d9c-d3ca472f1ed0/image.png" alt=""></p>
<br>

<h4 id="다른사람풀이를-보며">다른사람풀이를 보며</h4>
<p>왜 Math.round Ceil을 쓸생각을 했을까
없어도 충분히 되는데...</p>
<pre><code class="language-java">import java.util.*;

class Solution {

    // 1번 부터 순서대로 한사람씩 단어
    // 마지막 다음 1번부터 다시시작
    // 마지막 문자로 시작하는 단어
    // 중복 단어 불가
    // 한 글자 불가

    public int[] solution(int n, String[] words) {
        Map&lt;String, Boolean&gt; map = new HashMap&lt;&gt;();
        char lastWord = words[0].charAt(0);

        for (int i = 0; i &lt; words.length; i++) {          
            if(map.getOrDefault(words[i], false)) 
                return new int[]{(i % n) + 1, (i  / n) + 1};

            else if (lastWord != words[i].charAt(0)) 
                return new int[]{(i % n) + 1 , (i / n) + 1};

            map.put(words[i], true);
            lastWord = words[i].charAt(words[i].length() - 1);
        }

        return new int[]{0,0};
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/c168884d-4630-4447-baba-c87c45256c02/image.png" alt=""></p>
<hr>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<ol>
<li>처음에 Math.round를 사용했는데 생각해보니 반올림이 아닌 올림을 해줘야하는 것이였다. ceil()메서드를 기억하자. </li>
<li>올림, 반올림같은 메서드는 실수형을 매개변수에 넣어야한다. 사용시 캐스팅해주자.</li>
<li>중복체크에 적합한 Set이 있다.. </li>
<li>굳이 반올림, 올림 안해도 충분한데 이걸 생각못했다. 잘 기억해두고 써먹자.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[카펫 level 2 자바]]></title>
            <link>https://velog.io/@meluu_/%EC%B9%B4%ED%8E%AB-level-2-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@meluu_/%EC%B9%B4%ED%8E%AB-level-2-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Tue, 02 Apr 2024 09:54:27 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42842"
   title="문제">카펫 level 2</a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/bd238895-5627-4cb9-b449-97a951646295/image.png" alt=""></p>
<p>핵심 
가로 길이 &gt;= 세로길이
가로 세로 길이를 구하되 갈색과 노란색의 개수가 맞아야함</p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<p>좀 이상하게 풀었는데 
우선 가로 세로를 찾기위해 약수를 구한다. 약수가 가로 세로가 된다.
약수를 check 메서드에 넘긴다. </p>
<p>check 메서드는 
가로가 위아래로 2개 있으므로 가로 길이 x 2를 해준 다음 전체 갈색개수에서 빼준다. 
이를 2로 나누면 사각형에서 양 옆의 개수가 된다. 
<img src="https://velog.velcdn.com/images/meluu_/post/668a4076-fb99-430a-b547-1db4d1a01157/image.jpg" alt="">
위 예시와 같이 4 / 2 = 2 세로 옆구리가 2다. </p>
<p>이 값이 매개변수로 넘어온 약수로 찾은 가로세로중 세로 길이 - 2 한 값보다 작거나 같으면 
정상적으로 사각형이 성립된다.</p>
<p>만약 옆구리가 2 고 약수로 찾은 세로가 1이면 갈색이 1 남는다.. 이러면 아닌 것이다.</p>
<pre><code class="language-java">class Solution {
    public int[] solution(int brown, int yellow) {

        // 너비는 brown + yellow 와 같다. 
        // 가로 길이 &gt;= 세로
        int area = brown + yellow;

        for (int i = 3; i &lt; 5001; i++) {
                                    // 이과정은 세로를 구하는 과정 (가로 x 세로 = 넓이)
            if (area % i == 0 &amp;&amp; (area / i) &lt;= i ) {   
                                // i :가로, area/i :세로
                if (check(new int[] {i, area/i}, brown, yellow)) 
                    return new int[] {i, area/i};     
            }
        }
        return null;
    }

    public boolean check(int[] arr, int br, int ye) {
        int temp = (br - (arr[0] * 2)) / 2;
        if (temp &lt;= arr[1] - 2)
            return true;

        return false;
    }
}

</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/9e15a1d5-d5cf-4280-8371-aad8ff8326af/image.png" alt=""></p>
<br>

<h4 id="다른-사람-풀이를-보며">다른 사람 풀이를 보며</h4>
<p>(가로 - 2 ) x (세로 - 2)= 노란색 넓이
이걸 생각해내지 못했다...ㅠㅠ</p>
<p>약수를 찾아서 가로 세로를 구하고 
(가로 -2) x (세로 -2) == 노란색 넓이 로 조건문을 걸어서 참이면 그것이 가로세로 정답이다. </p>
<hr>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<ol>
<li>나름 생각해서 원칙을 찾아본다고 한게 저 풀이인데 많이 아쉽다. 하지만 풀었으니 다행이다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JadenCase 문자열 만들기 level 2 자바]]></title>
            <link>https://velog.io/@meluu_/JadenCase-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%A7%8C%EB%93%A4%EA%B8%B0-level-2-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@meluu_/JadenCase-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%A7%8C%EB%93%A4%EA%B8%B0-level-2-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Wed, 27 Mar 2024 11:29:57 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12951"
   title="문제">JadenCase 문자열 만들기 level 2</a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/bf03d0a0-8085-4489-8134-6143b615f881/image.png" alt=""></p>
<p>핵심 
공백문자가 연속해서 나올 수 있다.
첫 문자는 대문자 그외에는 소문자</p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<h4 id="처음-시도">처음 시도</h4>
<pre><code class="language-java">import java.util.*;

class Solution {
    public String solution(String s) {
        s += &quot;#&quot;;

        String[] str = s.split(&quot; &quot;);
        char temp;
        String answer = &quot;&quot;;

            for (int i = 0; i &lt; str.length; i++) {

                if (str[i].isBlank()) {
                    answer += &quot; &quot;;
                    continue;
                }

                temp = str[i].charAt(0);
                if (97 &lt;= temp &amp;&amp; temp &lt;= 122) {
                    answer += ((char)(temp-32) + str[i].substring(1).toLowerCase());
                } else {
                    answer += str[i] ;
                }

                if (i &lt; str.length) {
                    answer += &quot; &quot;;
                }
            }

        return answer.substring(0, answer.length() - 2);
    }
}
</code></pre>
<p>매우 부끄럽게도 왜 안되지 이해를 못했는데 직접 테스트 케이스를 만들다가 알게되었다...
if문에서 temp가 소문자일 때는 대문자로 바꾸고 뒤에 것을 tolowerCase로 갖다 붙이는데 
정작 temp가 대문자이고 그 뒤에 문자열도 대문자일 때를 생각하지 못했다...</p>
<h4 id="2차-시도--정답">2차 시도 / 정답</h4>
<pre><code class="language-java">class Solution {
    public String solution(String s) {
        s += &quot;#&quot;;

        String[] str = s.split(&quot; &quot;);
        char temp;
        String answer = &quot;&quot;;

            for (int i = 0; i &lt; str.length; i++) {  
                // 기존 s 에서 공백 2개일때 answer에 공백 추가 
                // split(&quot; &quot;)을 하게되면 입력이 &quot;  a&quot; 이렇게 연속 공백이면 &quot; &quot;, &quot;a&quot; 이런식으로 저장되기때문에 따로 공백을 추가해주었다.  
                if (str[i].isBlank()) {
                    answer += &quot; &quot;;
                    continue;
                }

                temp = str[i].charAt(0);
                if (97 &lt;= temp &amp;&amp; temp &lt;= 122) {
                    answer += ((char)(temp-32) + str[i].substring(1).toLowerCase());
                } else {
                    answer += str[i].charAt(0) + str[i].substring(1).toLowerCase();    
                }

                if (i &lt; str.length) {
                    answer += &quot; &quot;;
                }
            }

        return answer.substring(0, answer.length() - 2);
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/b9a798dc-ea37-4df2-8d2c-4208cec4cb8e/image.png" alt=""></p>
<br>

<blockquote>
<p>맞추긴 했는데 느리다. ㅠㅠ 문자열을 다루는 문제에 약하다. 
문자열 문제를 좀 집중적으로 풀어볼 필요가 있다. </p>
</blockquote>
<hr>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<ol>
<li>고민중인 것은 일단 무작정 풀기 or 최고의 효율을 생각해보고 풀기이다. </li>
<li>물론 무작정 풀기가 시간면에서 나을 수도 있으나 시험이 아니니 이럴때 효율적으로 풀다보면 무작정 풀때 효율적으로 풀던 방식이 습관적으로 작용하지 않을까 싶다. 그러니 최대한 효율성도 챙기자.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 PCCP 기출문제 2번 / 석유 시추 ]]></title>
            <link>https://velog.io/@meluu_/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-PCCP-%EA%B8%B0%EC%B6%9C%EB%AC%B8%EC%A0%9C-2%EB%B2%88-%EC%84%9D%EC%9C%A0-%EC%8B%9C%EC%B6%94</link>
            <guid>https://velog.io/@meluu_/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-PCCP-%EA%B8%B0%EC%B6%9C%EB%AC%B8%EC%A0%9C-2%EB%B2%88-%EC%84%9D%EC%9C%A0-%EC%8B%9C%EC%B6%94</guid>
            <pubDate>Sat, 23 Mar 2024 01:39:29 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/250136"
   title="문제">프로그래머스 PCCP 기출문제 2번 / 석유 시추 </a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/f998375f-84d8-45c0-af69-583d800efd67/image.png" alt=""></p>
<p>핵심 
그래프 bfs or dfs을 사용하여 각 석유 덩어리에 개수를 따로 저장하고 번호를 매긴다. 각 열이 어떤 번호를 가졌는지 저장한다음 반복문으로 각 열을 돌아서 가진 번호에 대한 석유 덩어리 개수들을 뽑아서 최대값을 찾는다. </p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<p>row = 깊이 
col = 1 2 3 4 .. 등 시추할 위치 열</p>
<pre><code class="language-java">import java.util.*;

class Solution {
    private static int[][] graph;
    private static int[] upDown = {0, 0, 1, -1};
    private static int[] leftRight = {1, -1, 0, 0};

    public int solution(int[][] land) {
        graph = land;
        int number = 2;
        int maxAmount = 0;
        int tempAmount = 0;
        HashMap&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();
        Set&lt;Integer&gt; numberSet = new HashSet&lt;&gt;();
        int[] depth = new int[land[0].length];

        for (int col = 0; col &lt; land[0].length; col++) {
            for (int row = 0; row &lt; land.length; row++) {

                // 석유 덩어리 넘버링 및 개수 map에 저장
                if (graph[row][col] == 1) {
                    // 석유 덩이리인 number에 대한 값을 가짐
                    map.put(number, bfs(row,col,number,tempAmount));
                    number++;

                }
                // set에 한 열에 대한 덩어리 넘버링을 저장
                if (graph[row][col] &gt; 1) {
                    numberSet.add(graph[row][col]);
                }

            }
            // 저장해뒀던 넘버링을 꺼내서 각 열에 저장
            for (Integer i : numberSet) {
                depth[col] += map.getOrDefault(i, 0);
            }

            numberSet.clear();
        }

        // int max = 0;
        // for (int ia : depth) {
        //     max = Math.max(max, ia);
        // }

        // return max;

        return Arrays.stream(depth)
                .max()
                .getAsInt();
    }

    public  int bfs(int row, int col, int number, int temp) {

        Queue&lt;int[]&gt; q = new LinkedList&lt;&gt;();
        q.add(new int[]{row, col});
        graph[row][col] = number;

        while (!q.isEmpty()) {
            int[] poll = q.poll();
            temp++;

            for (int i = 0; i &lt; 4; i++) {
                int tempCol = poll[0] + upDown[i];
                int tempRow = poll[1] + leftRight[i];

                if (tempCol&gt;= 0 &amp;&amp; tempRow &gt;= 0 &amp;&amp; tempCol &lt; graph.length &amp;&amp; tempRow &lt; graph[0].length) {
                    if(graph[tempCol][tempRow] == 1) {
                        q.add(new int[]{tempCol, tempRow});
                        graph[tempCol][tempRow] = number;
                    }

                }
            }
        }
        return temp;
    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/087e861b-c10d-4f83-a50c-4b1a566f1184/image.png" alt=""></p>
<blockquote>
<p>스트림 연습한다고 return에 깨알 스트림을 사용했다ㅎㅎㅎㅎㅎ</p>
</blockquote>
<br>

<hr>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<p>처음에 할 때는 정확성만 통과하고 효율성에서 다 시간초과로 떴는데 이유는 </p>
<ol>
<li>넘버링 하지 않았기에 같은 석유 덩어리 라인에 대한 중복 조회가 계속 이루어졌다.</li>
<li>덩어리 값을 반복문 안에서 더하고 Math.max계속 호출하였다.</li>
</ol>
<p>이 두가지 인 것 같다. </p>
<p>넘버링 하고 그런 것은 처음 문제 볼때 생각하긴 했었는데 일단 그냥 해보자 해서 정확성 매우빠르게 나오고 통과하길래 잘된줄 알았더니 효율성에서 시간초과가 떴다. </p>
<p>효율성 테스트에서는 항상 최소한의 반복문, 참조, 접근으로 코드를 짜주자..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 PCCP 기출문제 10번 / 데이터 분석 자바]]></title>
            <link>https://velog.io/@meluu_/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-PCCP-%EA%B8%B0%EC%B6%9C%EB%AC%B8%EC%A0%9C-10%EB%B2%88-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@meluu_/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-PCCP-%EA%B8%B0%EC%B6%9C%EB%AC%B8%EC%A0%9C-10%EB%B2%88-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Wed, 20 Mar 2024 08:51:58 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/250137"
   title="문제">프로그래머스 PCCP 기출문제 10번 / 데이터 분석 </a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/0b8657d3-f4b4-4f43-abd5-f3a7fface8fc/image.png" alt=""></p>
<p>핵심 
val_ext 보다 ext가 더 작은 데이터를 뽑고, 거기서 sort_by에 해당하는 값을 기준으로 오름차순 </p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<pre><code class="language-java">import java.util.*;

class Solution {
    public  int[][] solution(int[][] data, String ext, int val_ext, String sort_by) {
        int[][] answer;
        int filterIndex = indexTranslator(ext);
        List&lt;int[]&gt; filterList = new ArrayList&lt;&gt;();

        for (int[] datum : data) {
            if (datum[filterIndex] &lt; val_ext)
                filterList.add(datum);
        }


        filterList.sort((o1, o2) -&gt; {
            if (o1[indexTranslator(sort_by)] - o2[indexTranslator(sort_by)] &lt; 0) return -1;
            else {
                return 1;
            }
        });

        answer = new int[filterList.size()][4];

        for (int i = 0; i &lt; filterList.size(); i++) {
            answer[i] = filterList.get(i);
        }

        return  answer;
    }

    private  int indexTranslator(String value) {
        switch (value) {
            case  &quot;code&quot; : return 0;
            case &quot;date&quot; : return 1;
            case  &quot;maximum&quot; : return 2;
            default: return 3;
        }
    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/12065cf3-25bc-4b72-9e6c-25675b65e621/image.png" alt=""></p>
<blockquote>
<p>사실 스트림, 람다식 사용할려고 했는데 2차원 배열을 처리할 때 스트림 최종연산에서 아무리해도 안되서 포기하고 저렇게 한것이였다. 다른 사람들 풀이를 보니 스트림으로만 푸신분들이 있어서 보고 배워야겠다.</p>
</blockquote>
<h4 id="스트림-람다식으로만-해서-다시-풀이">스트림, 람다식으로만 해서 다시 풀이</h4>
<pre><code class="language-java">import java.util.*;

class Solution {
    public  int[][] solution(int[][] data, String ext, int val_ext, String sort_by) {
        int filterIndex = indexTranslator(ext);
        return Arrays.stream(data).filter(x -&gt; x[filterIndex] &lt; val_ext).sorted((o1, o2) -&gt; o1[indexTranslator(sort_by)] - o2[indexTranslator(sort_by)]).toArray(int[][]::new);
    }
    private  int indexTranslator(String value) {
        switch (value) {
            case  &quot;code&quot; : return 0;
            case &quot;date&quot; : return 1;
            case  &quot;maximum&quot; : return 2;
            default: return 3;
        }
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/meluu_/post/0284c61e-2b8c-43ad-8f27-e787e0cb0ceb/image.png" alt=""></p>
<blockquote>
<p>근데 역시 스트림은 느린것 같다. </p>
</blockquote>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<ol>
<li>ToArray(int[][]::new)를 배웠다.</li>
<li>x-&gt; x[] 처럼 x가 배열이지만 이렇게 사용할 수 있다. 파라미터이니까</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PCCP 기출문제] 1번 / 붕대 감기 자바 ]]></title>
            <link>https://velog.io/@meluu_/PCCP-%EA%B8%B0%EC%B6%9C%EB%AC%B8%EC%A0%9C-1%EB%B2%88-%EB%B6%95%EB%8C%80-%EA%B0%90%EA%B8%B0-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@meluu_/PCCP-%EA%B8%B0%EC%B6%9C%EB%AC%B8%EC%A0%9C-1%EB%B2%88-%EB%B6%95%EB%8C%80-%EA%B0%90%EA%B8%B0-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Tue, 19 Mar 2024 11:23:39 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/250137"
   title="문제">프로그래머스 PCCP 기출문제 1번 / 붕대감기</a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/dc8b31cc-e2ea-4601-a3a7-3547758824c6/image.png" alt=""></p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<pre><code class="language-java">class Solution {
    public static int solution(int[] bandage, int health, int[][] attacks) {
        int time = 0;
        int timeIndex = 0;
        int healthTime = 0;
        int maxHp = health;

        //공격 시간 
        int attackTime = attacks[timeIndex][0];

        while(health &gt; 0) {
            time++;

            if (timeIndex == attacks.length ) {
                return health;
            }
            // 공격
            if (time == attackTime) {
                health -= attacks[timeIndex++][1];
                if (timeIndex != attacks.length) {
                    attackTime = attacks[timeIndex][0];
                }
                healthTime = 0;

                // 회복
            } else {
                if (maxHp &gt; health + bandage[1]) {
                    health += bandage[1];
                    healthTime++;
                } else {
                    health = maxHp;
                }

                if (healthTime == bandage[0]) {
                    healthTime = 0;

                    // 추가 회복량 
                    if (maxHp &gt;= health + bandage[2]) {
                        health += bandage[2];
                    } else {
                        health = maxHp;
                    }
                }
            }

        }
        return -1;

    }
}
</code></pre>
<blockquote>
<p>문제를 제대로 이해하지 못해서 좀 애먹었다. 핵심은 회복량이 최대 체력을 넘길양이면 최대체력까지만 회복하고 더이상 회복하지 않는다는 것이다..</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/meluu_/post/e2bbe539-461b-4ade-9613-40c5f2106c6b/image.png" alt=""></p>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<ol>
<li>Math 클래스를 잘 이용해보자. </li>
<li>문제를 수학적으로 한 번 생각해 볼 필요가 있다. </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[SpringJpaRepository Failed to load ApplicationContext]]></title>
            <link>https://velog.io/@meluu_/SpringJpaRepository-Failed-to-load-ApplicationContext</link>
            <guid>https://velog.io/@meluu_/SpringJpaRepository-Failed-to-load-ApplicationContext</guid>
            <pubDate>Thu, 14 Mar 2024 05:54:52 GMT</pubDate>
            <description><![CDATA[<p>강의 실습 중 스프링 db 2편 SpringDataJpa 실습중에 에러가 났다. <img src="https://velog.velcdn.com/images/meluu_/post/f4b17fd0-5835-494c-a25b-bae98407459c/image.png" alt=""></p>
<p>@Import도 확인하고 Config도 확인했는데 자꾸 저렇게 떠서 JpaRepository 상속받은 SpringDataJpaItemRepository 에 가보니 
노란줄이 있었다. 
<img src="https://velog.velcdn.com/images/meluu_/post/9be2cdfc-c234-4541-8e85-0ce2ca3d34f1/image.png" alt=""></p>
<p>알고보니 itemName을 넘겨주지 않아서 생긴 문제였다. 이런게 안맞으면 스프링 에서 프록시를 생성하지 못하나 보다. 그래서 스프링 빈에 없었던 것이었고.. 놓친 부분이 있나 항상 잘 확인하자 .</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 실버1 숨바꼭질 1697 자바 ]]></title>
            <link>https://velog.io/@meluu_/%EB%B0%B1%EC%A4%80-%EC%8B%A4%EB%B2%841-%EC%88%A8%EB%B0%94%EA%BC%AD%EC%A7%88-1697-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@meluu_/%EB%B0%B1%EC%A4%80-%EC%8B%A4%EB%B2%841-%EC%88%A8%EB%B0%94%EA%BC%AD%EC%A7%88-1697-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Thu, 14 Mar 2024 05:51:25 GMT</pubDate>
            <description><![CDATA[<h2 id="🧫-문제-분석">🧫 문제 분석</h2>
<h3 id="✔️-출처">✔️ 출처</h3>
<p><a href="https://www.acmicpc.net/problem/1697"
   title="문제">숨바꼭질 1697번 - 백준</a></p>
<h3 id="📖-문제">📖 문제</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/9c08226c-8f90-4dca-9a7a-267586e8749b/image.png" alt=""></p>
<hr>
<h3 id="🔅-문제-풀이">🔅 문제 풀이</h3>
<p>수빈이의 위치 x
동생의 위치 k</p>
<p>수빈이는 -1, +1, *2 로 이동할 수 있고 이는 1초를 사용한다.</p>
<p>graph를 조건에 만족하는 최대 크기로 만들어서 각 위치에 따른 초를 적을 것이다. 
처음 수빈이가 있는 장소는 1로 초기화한다. </p>
<p>너비우선탐색으로 하되 문제에서 주어진 위치에 대한 조건식을 걸어주고 (0 &lt;= x, k &lt;= 100000)
이동하려는 곳에 이전에 이동한 흔적이 있는지 확인하여 걸린시간이 덮어씌어지는 것을 방지한다.</p>
<p>x-1, x+1, x*2  3번의 위치이동을 하고 graph 인덱스의 값에 현재 위치의 걸린시간 +1 초를 더해 이동한 흔적을 남긴다. 그리고 이동한 위치를 queue에 넣는다. 방문하지 않은 곳은 걸린시간이 0이다. 왜냐하면 graph를 만들 때 수빈이의 위치말곤 따로 값을 초기화해주지 않았기 때문이다. 
루프를 돌면서 queue에서 꺼낸 위치가 동생 위치일 때, 수빈이가 동생에게 도달했다는 것이고, 
그 위치에는 분명 이동한 흔적이 있을 것이다. 그것이 가장 빨리 도달한 시간이다. </p>
<pre><code class="language-java">import java.io.*;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
    static int answer;

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int[] graph = new int[100001];

        int x = Integer.parseInt(st.nextToken());
        int k = Integer.parseInt(st.nextToken());


        graph[x] = 1;

        bfs(x, graph, k);
        bw.write(answer+&quot;\n&quot;);
        bw.flush();
        bw.close();

    }

    private static void bfs(int x, int[] graph, int k) {
        Queue&lt;Integer&gt; q = new LinkedList&lt;&gt;();
        q.add(x);

        while (!q.isEmpty()) {
            int current = q.poll();
            int count = graph[current];

            if (current == k) {
                answer = count - 1;
                return;
            }

            if (current - 1 &gt;= 0 &amp;&amp; graph[current - 1] == 0) {
                graph[current - 1] = count + 1;
                q.add(current - 1);
            }
            if (current + 1 &lt; graph.length &amp;&amp; graph[current + 1] == 0) {
                graph[current + 1] = count + 1;
                q.add(current + 1);
            }
            if (current * 2 &lt; graph.length &amp;&amp; graph[current * 2] == 0) {
                graph[current * 2] = count + 1;
                q.add(current * 2);
            }
        }

    }
}

</code></pre>
<blockquote>
<p>이런 문제 잘 못풀어서 힘들었다. </p>
</blockquote>
<h3 id="❗-오답노트---필요한-지식">❗ 오답노트 /  필요한 지식</h3>
<p>bfs, dfs 에 대한 특징에 대해 아직은 학습이 부족한 것 같다. 더 공부해야한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 DB - 테스트]]></title>
            <link>https://velog.io/@meluu_/%EC%8A%A4%ED%94%84%EB%A7%81-DB-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@meluu_/%EC%8A%A4%ED%94%84%EB%A7%81-DB-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 12 Mar 2024 08:51:10 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="🌿-시작하기-앞서">🌿 시작하기 앞서</h2>
<hr>
<blockquote>
<p>스프링 부트 2.6.5 버전을 기준으로 작성됨
  H2 데이터베이스 Version 2.2.224 (2023-09-17)
  강의에서 제공하는 기본 소스코드에서 DB 관련 기술만 학습</p>
</blockquote>
<p><code>application.properties</code> 는 <code>src/main</code> 과 <code>src/test</code>는 다른 구역이므로 테스트에서도 동일한 환경을 사용한다면 <code>src/test</code>의 <code>application.properties</code>도 설정해주자. </p>
<pre><code class="language-java">spring.profiles.active=test
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa

logging.level.org.springframework.jdbc=debug</code></pre>
<p>dataSource가 이 정보를 토대서 생성됨</p>
<br>

<h2 id="🌱-테스트">🌱 테스트</h2>
<hr>
<h3 id="✔️-로컬-db-테스트">✔️ 로컬 DB 테스트</h3>
<p><code>@SpringBootTest</code> 애노테이션은 <code>@SpringBootApplication</code>을 찾아서 설정으로 사용한다.</p>
<p>H2 데이터베이스로 테스트를 실패할 경우 2가지를 확인하자</p>
<ol>
<li>H2 데이터베이스 실행 여부</li>
<li>H2 데이터베이스의 데이터 초기화 여부</li>
</ol>
<p>로컬에서 사용하는 애플리케이션 서버와 테스트에서 같은 데이터베이스를 사용해서 문제가 발생한다.</p>
<p>가장 간단한 방법
테스트 전용 데이터베이스를 별도로 운영</p>
<ul>
<li><code>jdbc:h2:tcp://localhost/~/test local</code>에서 접근하는 서버 전용 데이터베이스</li>
<li><code>jdbc:h2:tcp://localhost/~/testcase test</code> 케이스에서 사용하는 전용 데이터베이스</li>
</ul>
<p>생성방법은 
서버 재실행 -&gt; url에 testcase등으로 바꾸고 연결 하면 <code>testcase.mv.db</code>가 생성된다. 
이제 <code>jdbc:h2:tcp://localhost/~/testcase</code> 이 url로 접속</p>
<p>src/test appliation.properties도 변경</p>
<pre><code>spring.profiles.active=test
spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
spring.datasource.username=sa</code></pre><h4 id="테스트의-중요한-원칙">테스트의 중요한 원칙</h4>
<ul>
<li>테스트는 다른 테스트와 격리해야 한다.</li>
<li>테스트는 반복해서 실행할 수 있어야 한다.</li>
</ul>
<p>테스트가 끝날때마다 delete sql을 사용하는 것은 테스트 도중 예외 발생시 애플리케이션이 종료되버리면 delete sql을 실행할 수 없기 때문에 좋지 않다.</p>
<br>

<h3 id="✔️-데이터-롤백">✔️ 데이터 롤백</h3>
<hr>
<p>트랜잭션을 사용하면 해결된다.</p>
<pre><code>1. 트랜잭션 시작
2. 테스트 A 실행
3. 트랜잭션 롤백

4. 트랜잭션 시작
5. 테스트 B 실행
6. 트랜잭션 롤백

.
.
.</code></pre><p>테스트는 각각의 테스트 실행 전 후로 동작하는 <code>@BeforeEach</code> , <code>@AfterEach</code> 라는 편리한 기능을 제공</p>
<pre><code class="language-java">//트랜잭션 관련 코드
@Autowired
PlatformTransactionManager transactionManager; //스프링 부트 자동 빈 등록 
TransactionStatus status;

@BeforeEach
void beforeEach() {
    //트랜잭션 시작
    status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}

@AfterEach
void afterEach() {
    //MemoryItemRepository 의 경우 제한적으로 사용
    if (itemRepository instanceof MemoryItemRepository) {
        ((MemoryItemRepository) itemRepository).clearStore();
    }

    //트랜잭션 롤백
     transactionManager.rollback(status);
 }</code></pre>
<br>

<h3 id="✔️-transactional">✔️ @Transactional</h3>
<hr>
<p>사실 더 좋은 방법이 있는데 <code>@Transactional</code> 애노테이션 하나만 붙이면 된다. 이러면 각 테스트 전 후로 트랜잭션 시작, 롤백을 자동으로 해준다. 해당 기능은 test에서만 적용된다. </p>
<p><img src="https://velog.velcdn.com/images/meluu_/post/05a0f1a0-ccbb-4fe9-b73f-7ee9404dd3a0/image.png" alt=""></p>
<p>메서드나 클래스에 <code>@Transactional</code> 이 있으면  트랜잭션을 시작한다.
테스트 메서드가 끝나면 트랜잭션을 강제로 롤백한다.</p>
<blockquote>
<p>참고
트랜잭션을 테스트에서 시작하기 때문에 서비스, 리포지토리에 있는 @Transactional 도 테스트에서
시작한 트랜잭션에 참여한다. 라고 하는데 트랜잭션 전파라는 것을 배워야 하나보다. 같은 트랜잭션 범위에 들어간다 라고 알아두자</p>
</blockquote>
<br>

<h4 id="강제-커밋-commit">강제 커밋 <code>@Commit</code></h4>
<p><code>@Commit</code> 애노테이션을 클래스 또는 메서드에 붙이면 테스트 종료 후 롤백 대신 커밋이 호출된다.
<code>@Rollback(value = false)</code> 도 마찬가지이다.</p>
<br>

<h3 id="✔️-임베디드-모드-db">✔️ 임베디드 모드 DB</h3>
<hr>
<p>테스트 케이스 실행을 위해 별도의 데이터베이스 설치, 설정, 운영은 번잡한 작업이다.</p>
<p>H2 데이터베이스는 자바로 개발되었고, JVM 안에서 메모리 모드로 동작하는 특별한 기능을 제공한다. 
즉, JVM 메모리에 H2 데이터베이스를 포함해서 함께 실행이 가능하다. 이를 <code>임베디드 모드</code> 라 한다.
(DB를 애플리케이션에 내장)</p>
<p>애플리케이션이 종료되면 임베디드 모드로 동작하는 H2 데이터베이스도 함께 종료, 데이터도 모두 삭제</p>
<pre><code class="language-java">// h2 임베디드 메모리 DB 사용
@Bean
@Profile(&quot;test&quot;)
public DataSource dataSource() {
    log.info(&quot;메모리 데이터베이스 초기화&quot;);
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(&quot;org.h2.Driver&quot;);
    dataSource.setUrl(&quot;jdbc:h2:mem:db;DB_CLOSE_DELAY=-1&quot;); // 임베디드 메모리 모드
    dataSource.setUsername(&quot;sa&quot;);
    dataSource.setPassword(&quot;&quot;);
    return dataSource;
}</code></pre>
<ul>
<li><code>@Profile(&quot;test&quot;)</code><ul>
<li>프로필이 test인 경우에만 데이터소스를 스프링 빈으로 등록</li>
<li>테스트 케이스에서만 이 데이터소스를 스프링 빈으로 등록해서 사용하겠다는 뜻</li>
</ul>
</li>
<li><code>dataSource()</code><ul>
<li><code>jdbc:h2:mem:db</code> : 중요! 데이터소스를 만들때 이렇게만 적으면 임베디드 모드(메모리 모
드)로 동작하는 H2 데이터베이스를 사용 가능</li>
<li><code>DB_CLOSE_DELAY=-1</code> : 임베디드 모드에서는 데이터베이스 커넥션 연결이 모두 끊어지면 데이터베이스도 종료되는 것을 방지하는 설정</li>
</ul>
</li>
</ul>
<br> 

<h3 id="✔️-스프링-부트와-임베디드-모드">✔️ 스프링 부트와 임베디드 모드</h3>
<hr>
<p>스프링 부트는 임베디드 데이터베이스에 대한 설정도 기본으로 제공한다. 
데이터베이스에 대한 별다른 설정이 없으면 임베디드 데이터베이스를 사용</p>
<p>데이터베이스에 접근하는 DataSource 설정 정보도 주석처리</p>
<p>application.properties의 datasource 설정 정보도 주석처리</p>
<p>이로써 스프링부트가 임베디드 데이터베이스를 제공한다. </p>
<pre><code> conn0: url=jdbc:h2:mem:0e4053f9-xxxx-xxxx-xxxx-xxxxxxxxxxx user=SA] </code></pre><p>테스트를 실행하면 이런 식으로 임베디드 모드가 연결된 것을 확인 할 수 있다.</p>
<br>

<h3 id="✔️-스프링-부트---기본-sql-스크립트를-사용한-데이터베이스-초기화-기능">✔️ 스프링 부트 - 기본 SQL 스크립트를 사용한 데이터베이스 초기화 기능</h3>
<hr>
<p>메모리 DB는 애플리케이션 종료될 때 함께 사라지기에 실행 시점에 데이터베이스 테이블도 새로 만들어 줘야한다.</p>
<p>스프링 부트는 SQL 스크립트를 실행해서 애플리케이션 로딩 시점에 데이터베이스를 초기화하는 기능 제공</p>
<p><code>src/test/resource/schema.sql</code> 해당 위치에 <code>schema.sql</code> 파일을 생성</p>
<pre><code class="language-sql">drop table if exists item CASCADE;
create table item
(
    id bigint generated by default as identity,
    item_name varchar(10),
    price integer,
    quantity integer,
    primary key (id)
);</code></pre>
<p>sql문을 입력해놓으면 된다. </p>
<hr>
<p>🔖 학습내용 출처 </p>
<p><a href="https://inf.run/wREBZ"
   title="강의">스프링 DB 2편 - 데이터 접근 활용 기술</a> </p>
<!--
<span style="color: red">red</span>
<span style="color: #0000FF">파랑</span>
<span style="color: #008000">초록</span>
<span style="color: #2D3748; background-color:#fff5b1;"> Strong</span>
<span style="color: #808080">그레이</span>
<span style="color: #ffd33d">노랑</span>

<span style='background-color: #fff5b1'>노란형광펜</span>
<span style='background-color: #f6f8fa'>회색형광펜</span>
<span style='background-color: #f1f8ff'>파랑형광펜</span>
<span style='background-color: #ffdce0'>빨강형광펜</span>
<span style='background-color: #dcffe4'>초록형광펜</span>
<span style='background-color: #f5f0ff'>보라형광펜</span>
<span style='background-color: #F7DDBE'>주황형광펜</span>
-->]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 DB - 스프링 JdbcTemplate]]></title>
            <link>https://velog.io/@meluu_/%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%8A%A4%ED%94%84%EB%A7%81-JdbcTemplate</link>
            <guid>https://velog.io/@meluu_/%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%8A%A4%ED%94%84%EB%A7%81-JdbcTemplate</guid>
            <pubDate>Wed, 06 Mar 2024 08:59:34 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="🌿-시작하기-앞서">🌿 시작하기 앞서</h2>
<hr>
<blockquote>
<p>스프링 부트 2.6.5 버전을 기준으로 작성됨
  H2 데이터베이스 Version 2.2.224 (2023-09-17)</p>
</blockquote>
<p>SQL을 직접 사용하는 경우 좋은 DB접근 기술이다. </p>
<br>

<h2 id="🌱-jdbctemplate-기본-사용">🌱 JdbcTemplate 기본 사용</h2>
<hr>
<h4 id="장점">장점</h4>
<ul>
<li>설정의 편리함<ul>
<li>JdbcTemplate은 <code>spring-jdbc</code> 라이브러리에 포함 (스프링 기본 사용 라이브러리)</li>
<li>별도의 복잡한 설정 없이 바로 실행 가능</li>
</ul>
</li>
</ul>
<ul>
<li>반복 문제 해결<ul>
<li>템플릿 콜백 패턴</li>
<li>개발자는 SQL 작성, 전달 파라미터 정의, 응답 값 매핑 까지만 하면 된다.</li>
<li>대부분의 반복 작업 대신 처리<ul>
<li>커넥션 획득</li>
<li><code>statement</code> 를 준비 및 실행</li>
<li>결과를 반복하도록 루프 실행</li>
<li>커넥션 종료, <code>statement</code>, <code>resultset</code> 종료</li>
<li>트랜잭션 다루기 위한 커넥션 동기화</li>
<li>예외 발생시 스프링 예외 변환기 실행</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li>동적 SQL 해결이 어려움</li>
</ul>
<h4 id="jdbctemplate를-dependencies에-추가">JdbcTemplate를 dependencies에 추가</h4>
<pre><code class="language-java">build.gradle

//JdbcTemplate 추가
implementation &#39;org.springframework.boot:spring-boot-starter-jdbc&#39;</code></pre>
<br>

<h3 id="✔️-jdbctemplate-적용">✔️ JdbcTemplate 적용</h3>
<hr>
<h4 id="기본">기본</h4>
<pre><code class="language-java">private final JdbcTemplate template;

public JdbcTemplateItemRepository(DataSource dataSource) {
    this.template = new JdbcTemplate(dataSource);
}</code></pre>
<p><code>JdbcTemplate</code>는 <code>dataSource</code>가 필요
<code>dataSource</code>를 의존 관계 주입을 받고 생성자 내부에서 <code>JdbcTemplate</code> 생성 (관례상 많이 사용)</p>
<br>

<hr>
<h4 id="save">save</h4>
<pre><code class="language-java">KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(connection -&gt; {
//자동 증가 키
PreparedStatement ps = connection.prepareStatement(sql, new String[] {&quot;id&quot;}); 
    //ps.setXxx로 파라미터 바인딩
    return ps;
    }, keyHolder);

long key = keyHolder.getKey().longValue();</code></pre>
<p>PK 생성에 identity (auto increment) 방식을 사용했기에 id 값을 비워두고 저장
하지만 이러면 insert 가 완료되어야 생성된 PK id값을 확인 가능
-&gt; <code>KeyHolder</code> 와 <code>connection.prepareStatement(sql, new String[] {&quot;id&quot;})</code> 를 사용해서 id를 지정해주면 insert 쿼리 실행 이후에 DB에서 생성된 id 값 조회 가능</p>
<br>

<h5 id="preparestatement-메서드-살펴보기">prepareStatement 메서드 살펴보기</h5>
<pre><code class="language-java">// 여러 개가 있지만 여기서 사용된 메서드 
PreparedStatement prepareStatement(String sql, String columnNames[]) throws SQLException;</code></pre>
<ol>
<li>sql 문</li>
<li>자동 생성되는 키들의 컬럼 이름</li>
</ol>
<br>

<hr>
<h4 id="findbyid">findById()</h4>
<pre><code class="language-java">@Override
public Optional&lt;Item&gt; findById(Long id) {
    String sql = &quot;select id, item_name, price, quantity from item where id = ?&quot;;
    try {
        Item item = template.queryForObject(sql, itemRowMapper(), id);
         return Optional.of(item);
     } catch (EmptyResultDataAccessException e) {
         return Optional.empty();
     }
 }</code></pre>
<br>

<h5 id="queryforobject"><code>queryForObject()</code></h5>
<ul>
<li>결과 로우가 하나일 때 사용</li>
<li><code>RowMapper</code>는 데이터베이스의 반환 결과인 <code>ResultSet</code>을 객체로 변환</li>
<li>결과가 없으면 <code>EmptyResultDataAccessException</code> 예외 발생</li>
<li>결과가 둘 이상이면 <code>IncorrectResultSizeDataAccessException</code> 예외 발생</li>
</ul>
<p><code>ItemRepository.findById()</code> 인터페이스는 결과가 없을 때 <code>Optional</code>을 반환해야한다.
따라서 결과가 없으면 예외를 잡아서 <code>Optional.empty</code>를 대신 반환
<br></p>
<hr>
<h4 id="findall">findAll()</h4>
<pre><code class="language-java">@Override
public List&lt;Item&gt; findAll(ItemSearchCond cond) {
    String itemName = cond.getItemName();
    Integer maxPrice = cond.getMaxPrice();
    String sql = &quot;select id, item_name, price, quantity from item&quot;;
    // 동적 쿼리
    if (StringUtils.hasText(itemName) || maxPrice != null) {
        sql += &quot; where&quot;;
    }
    boolean andFlag = false;
    List&lt;Object&gt; param = new ArrayList&lt;&gt;();
    if (StringUtils.hasText(itemName)) {
        sql += &quot; item_name like concat(&#39;%&#39;, ?, &#39;%&#39;)&quot;;
        param.add(itemName);
        andFlag = true;
    }
    if (maxPrice != null) {
        if (andFlag) {
            sql += &quot; and&quot;;
        }
        sql += &quot; price &lt;= ?&quot;;
        param.add(maxPrice);
    }
    log.info(&quot;sql={}&quot;, sql);
    return template.query(sql, itemRowMapper(), param.toArray());
}</code></pre>
<p>데이터를 리스트로 조회, 검색 조건에 따른 데이터 조회
<code>template.query()</code></p>
<ul>
<li>결과가 하나 이상일 때 사용</li>
<li><code>RowMapper</code>는 데이터베이스의 반환 결과인 <code>ResultSet</code>을 객체로 변환</li>
<li>결과가 없으면 빈 컬렉션을 반환</li>
<li>동적 쿼리에 대한 부분을 직접 짜줘야함</li>
</ul>
<hr>
<h4 id="rowmapper">RowMapper()</h4>
<p>JdbcTemplate가 루프를 돌려주고, 개발자는 <code>RowMapper</code>를 구현해서 그 내부 코드만 채운다.</p>
<pre><code class="language-java">while(resultSet 이 끝날 때 까지) {
    rowMapper(rs, rowNum);
}</code></pre>
<br>


<h3 id="✔️-동적-쿼리-문제">✔️ 동적 쿼리 문제</h3>
<hr>
<p><code>findAll</code>에서 사용자가 검색하는 값에 따라 SQL이 동적으로 달라져야한다.
<code>select id, item_name, price, quantity from item</code></p>
<p><code>select id, item_name, price, quantity from item
where item_name like concat(&#39;%&#39;, ?, &#39;%&#39;)</code></p>
<p>... </p>
<p>검색 조건의 개수는
1개의 검색조건이 있다 없다 하여 2가지 경우의 수이고 
2개면 2x2 
3개면 2x2x2 
즉 검색 조건의 수가 n이라 했을 때 
SQL문의 경우의 수는 2^n 이 된다.</p>
<p>또한 2가지 이상의 조건이면 <code>and</code> 를 넣어줘야하고 골치아프다.</p>
<blockquote>
<p>MyBatis의 가장 큰 장점은 SQL을 직접 사용할 때 동적 쿼리를 쉽게 작성할 수 있다</p>
</blockquote>
<br>

<h3 id="✔️-구성과-실행">✔️ 구성과 실행</h3>
<hr>
<h4 id="jdbctemplatev1config">JdbcTemplateV1Config</h4>
<pre><code class="language-java">@Configuration
@RequiredArgsConstructor
public class JdbcTemplateV1Config {

    private final DataSource dataSource;

    @Bean
    public ItemService itemService() {
        return new ItemServiceV1(itemRepository());
    }

    @Bean
    public ItemRepository itemRepository() {
        return new JdbcTemplateItemRepositoryV1(dataSource);
    }
}</code></pre>
<h4 id="itemserviceapplication">ItemServiceApplication</h4>
<pre><code class="language-java">@Slf4j
//@Import(MemoryConfig.class)
@Import(JdbcTemplateV1Config.class)
@SpringBootApplication(scanBasePackages = &quot;hello.itemservice.web&quot;)
public class ItemServiceApplication {</code></pre>
<h4 id="데이터-베이스-접근-설정">데이터 베이스 접근 설정</h4>
<pre><code class="language-java">// application.properties              // src/main
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa</code></pre>
<p>스프링 부트가 해당 설정을 사용해서 커넥션 풀과 <code>DataSource</code>, 트랜잭션 매니저를 스프링 빈으로 자동 등록한다.</p>
<h4 id="jdbctemplate가-실행하는-sql-로그-확인-방법">JdbcTemplate가 실행하는 SQL 로그 확인 방법</h4>
<pre><code class="language-java"> // application.properties
#jdbcTemplate sql log
logging.level.org.springframework.jdbc=debug</code></pre>
<br>
<br>

<h2 id="📌-이름-지정-파라미터">📌 이름 지정 파라미터</h2>
<hr>
<h3 id="✔️-기존-순서대로-바인딩">✔️ 기존 순서대로 바인딩</h3>
<pre><code class="language-java">String sql = &quot;update item set item_name=?, price=?, quantity=? where id=?&quot;;
        template.update(sql,
                updateParam.getItemName(),
                updateParam.getPrice(),
                updateParam.getQuantity(),
                itemId);</code></pre>
<p>SQL 코드의 순서 변경시 </p>
<pre><code class="language-java">String sql = &quot;update item set item_name=?, price=?, quantity=? where id=?&quot;;
        template.update(sql,
                updateParam.getItemName(),
                updateParam.getQuantity(),
                updateParam.getPrice(),
                itemId);</code></pre>
<p>가격이 수량이 되고 수량이 가격이되어버리는 큰 사고 발생</p>
<blockquote>
<p>개발을 할 때는 코드를 몇줄 줄이는 편리함도 중요하지만, 모호함을 제거해서 코드를 명확하게 만드는 것이 유지보수 관점에서 매우 중요</p>
</blockquote>
<br>

<h3 id="✔️-이름-지정-바인딩">✔️ 이름 지정 바인딩</h3>
<hr>
<p>이러한 문제들을 해결하기위해 이름을 지정해서 파라미터를 바인딩 하는 기능을 제공
<code>NamedParameterJdbcTemplate</code>를 사용</p>
<pre><code>private final NamedParameterJdbcTemplate template;

public JdbcTemplateItemRepositoryV2(DataSource dataSource) {
    this.template = new NamedParameterJdbcTemplate(dataSource);
}</code></pre><p><code>NamedParameterJdbcTemplate</code>도 <code>DataSource</code>가 필요</p>
<h4 id="save-1">save</h4>
<pre><code class="language-java">@Override
public Item save(Item item) {
    String sql = &quot;insert into item(item_name, price, quantity) &quot; +
            &quot;values (:itemName, :price, :quantity)&quot;;

    BeanPropertySqlParameterSource param = new BeanPropertySqlParameterSource(item);

    KeyHolder keyHolder = new GeneratedKeyHolder();
    template.update(sql, param, keyHolder);

    long key = keyHolder.getKey().longValue();
    item.setId(key);

    return item;
}</code></pre>
<p>SQL 에서 <code>?</code> -&gt; <code>:파라미터이름</code>으로 받는다.</p>
<pre><code class="language-sql">insert into item(item_name, price, quantity) values (:itemName, :price, :quantity)</code></pre>
<br>
<br>

<h3 id="✔️-이름-지정-파라미터-전달">✔️ 이름 지정 파라미터 전달</h3>
<hr>
<p>이름 지정 파라미터를 전달하려면 <code>Map</code> 처럼 <code>Key</code>, <code>value</code> 데이터 구조를 만들어서 전달해야 한다.
<code>key</code> : <code>:파라미터이름</code>으로 지정한, 파라미터의 이름
<code>value</code> : 해당 파라미터의 값</p>
<h4 id="이름-지정-바인딩에서-자주-사용하는-파라미터-종류는-크게-3가지">이름 지정 바인딩에서 자주 사용하는 파라미터 종류는 크게 3가지</h4>
<ul>
<li><code>Map</code></li>
<li><code>SqlParameterSource</code><ul>
<li><code>MapSqlParameterSource</code></li>
<li><code>BeanPropertySqlParameterSource</code></li>
</ul>
</li>
</ul>
<h4 id="1-map">1. Map</h4>
<p>단순히 <code>Map</code> 사용</p>
<pre><code class="language-java">Map&lt;String, Object&gt; param = Map.of(&quot;id&quot;, id);
Item item = template.queryForObject(sql, param, itemRowMapper());

</code></pre>
<h4 id="2-mapsqlparametersource">2. MapSqlParameterSource</h4>
<p><code>Map</code>과 유사한데 SQL 타입을 지정할 수 있는 등 SQL에 좀 더 특화된 기능 제공
<code>SqlParameterSource</code> 인터페이스의 구현체
메서드 체인을 통해 편리한 사용법도 제공</p>
<pre><code class="language-java">SqlParameterSource param = new MapSqlParameterSource()
    .addValue(&quot;itemName&quot;, updateParam.getItemName())
    .addValue(&quot;price&quot;, updateParam.getPrice())
    // 파라미터 계속 추가
    .addValue(&quot;id&quot;, itemId); // id를 넣는 부분이 별도로 필요
template.update(sql, param);

</code></pre>
<h4 id="3-beanpropertysqlparametersource">3. BeanPropertySqlParameterSource</h4>
<p><code>SqlParameterSource</code> 인터페이스의 구현체
자바빈 프로퍼티 규약을 통해서 자동으로 파라미터 객체를 생성
<code>getXxx() -&gt; xxx, getItemName() -&gt; itemName</code></p>
<p>ex) getItemName()</p>
<ul>
<li><code>key=itemNamem value=상품명 값</code></li>
</ul>
<pre><code class="language-java">SqlParameterSource param = new BeanPropertySqlParameterSource(item);
KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(sql, param, keyHolder);</code></pre>
<p>매우 간편하다. </p>
<blockquote>
<p>여기서 주의할 점은 
<code>:id</code> 바인딩을 해주어야하는 상황에서 파라미터에서 넘어온 Dto에 id값이 없다면 사용할 수 없다. 이때는 <code>MapSqlParameterSource</code>를 사용하자</p>
</blockquote>
<br>

<hr>
<h4 id="beanpropertyrowmapper">BeanPropertyRowMapper</h4>
<p>기존 itemRowMapper 를 사용할 때는 람다식을 사용해서 하나하나 값을 세팅해주었다.</p>
<pre><code class="language-java">// 기존
private RowMapper&lt;Item&gt; itemRowMapper() {
    return ((rs, rowNum) -&gt; {
        Item item = new Item();
        item.setId(rs.getLong(&quot;id&quot;));
        // setter로 item 값 설정 ...
        return item;
    });
}</code></pre>
<p>이번에는 BeanPropertyRowMapper를 사용해서 간편해졌다.</p>
<pre><code class="language-java">// new
private RowMapper&lt;Item&gt; itemRowMapper() {
    return BeanPropertyRowMapper.newInstance(Item.class); // camel 변환 지원
}</code></pre>
<p><code>BeanPropertyRowMapper</code>는 <code>ResultSet</code>의 결과를 받아서 자바빈 규약에 맞추어 데이터를 반환</p>
<p>ex) DB 조회 결과가 <code>select id, itemName</code>이라고 한다면 다음과 같은 코드를 작성해준다. (실제로는 리플렉션 같은 기능을 사용)</p>
<pre><code class="language-java">Item item = new Item();
item.setId(rs.getLong(&quot;id&quot;);
item.setItemName(rs.getString(&quot;itemName&quot;);</code></pre>
<p>데이터베이스에서 조회한 결과 이름을 기반으로 <code>setId()</code>, <code>setPrice()</code> 처럼 자바빈 프로퍼티 규약에 맞춘 메서드를 호출</p>
<br>

<hr>
<h4 id="별칭">별칭</h4>
<p>컬럼 명에 <code>_</code> 이 중간에 포함되어 있거나
아예 다른 컬럼명이면 어떻게 해야할까?</p>
<p>이때는 <code>as</code> 를 사용하여 별칭을 붙여준다. 
<code>select item_name as itemName</code>
<code>select member_name as username</code></p>
<p>이러한 방법으로 자주 사용한다.</p>
<br>

<h4 id="관례의-불일치">관례의 불일치</h4>
<p><span style='background-color: #F7DDBE'>자바 객체</span>는 카멜 표기법
<span style='background-color: #ffdce0'>관계형 데이터베이스</span>는 스네이크 케이스 표기법(언더스코어 사용)</p>
<p><code>BeanPropertyRowMapper</code>는 언더스코어 표기법을 카멜로 자동 변환해준다.
<code>select item_name</code>으로 조회 -&gt; <code>setItemName()</code>에 문제 없이 값이 들어간다.</p>
<blockquote>
<p>참고
리플렉션이란?
구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API</p>
</blockquote>
<br>
<br>

<h3 id="✔️-simplejdbcinsert">✔️ SimpleJdbcInsert</h3>
<hr>
<pre><code class="language-java">private final NamedParameterJdbcTemplate template;
private final SimpleJdbcInsert jdbcInsert;

public JdbcTemplateItemRepositoryV3(DataSource dataSource) {
    this.template = new NamedParameterJdbcTemplate(dataSource);
    this.jdbcInsert = new SimpleJdbcInsert(dataSource)
            .withTableName(&quot;item&quot;)
            .usingGeneratedKeyColumns(&quot;id&quot;);
    //      .usingColumns(&quot;item_name&quot;, &quot;price&quot;, &quot;quantity&quot;); 생략 가능 심플 jdbc가 item table의 메타 데이터를 읽음

    }
</code></pre>
<p><code>SimpleJdbcInsert</code> 도 dataSource를 받는다.</p>
<ul>
<li><code>withTableName</code> : 데이터를 저장할 테이블 명 지정</li>
<li><code>usingGeneratedKeyColumns</code> : <code>key</code>를 생성하는 PK 컬럼 명을 지정</li>
<li><code>usingColumns</code> : INSERT SQL에 사용할 컬럼을 지정, 특정 값만 저장하고 싶을 때 사용, 모든 컬럼 저장시 생략가능</li>
</ul>
<p><code>SimpleJdbcInsert</code>는 생성 시점에 데이터베이스 테이블의 메타 데이터를 조회
어떤 컬럼이 있는지 확인하기에 <code>usingColumns</code>을 생략 가능</p>
<h4 id="save-2">save()</h4>
<p><code>jdbcInsert.executeAndReturnKey(param)</code>을 사용해서 INSERT SQL을 실행하고, 생성된 키 값도 매우 편리하게 조회 가능</p>
<pre><code class="language-java">public Item save(Item item) {
    SqlParameterSource param = new BeanParameterSource(item);
    Number key = jdbcInsert.executeAndReturnKey(param);
    item.setId(key.longValue());
    return item;
}</code></pre>
<p>나머지 코드 부분은 기존과 같다. </p>
<pre><code class="language-java">@Override
public Item save(Item item) {
    BeanPropertySqlParameterSource param = new BeanPropertySqlParameterSource(item);
    Number key = jdbcInsert.executeAndReturnKey(param);
    item.setId(key.longValue());
    return item;
}</code></pre>
<br>
<br>

<h2 id="🔅-jdbctemplate-기능-정리">🔅 JdbcTemplate 기능 정리</h2>
<hr>
<h3 id="✔️-주요-기능">✔️ 주요 기능</h3>
<ul>
<li><code>JdbcTemplate</code><ul>
<li>순서 기반 파라미터 바인딩 지원</li>
</ul>
</li>
<li><code>NamedParameterJdbcTemplate</code><ul>
<li>이름 기반 파라미터 바인딩을 지원 (권장)</li>
</ul>
</li>
<li><code>SimpleJdbcInsert</code><ul>
<li>INSERT SQL을 편리하게 사용 가능</li>
</ul>
</li>
<li><code>SimpleJdbcCall</code><ul>
<li>스토어드 프로시저를 편리하게 호출 가능</li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#jdbcsimple-jdbc-call-1">스토어드 프로시저 공식문서</a></li>
</ul>
</li>
</ul>
<br>

<h3 id="✔️-사용법-정리">✔️ 사용법 정리</h3>
<p><a href="https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#jdbcJdbcTemplate">스프링 JdbcTemplate 사용 방법 공식 메뉴얼</a></p>
<br>

<hr>
<p>🔖 학습내용 출처 </p>
<p><a href="https://inf.run/wREBZ"
   title="강의">스프링 DB 2편 - 데이터 접근 활용 기술</a> </p>
<p><a href="https://velog.io/@yeon/Reflection%EC%9D%B4%EB%9E%80">Reflection이란 / @yeon</a></p>
<!--
<span style="color: red">red</span>
<span style="color: #0000FF">파랑</span>
<span style="color: #008000">초록</span>
<span style="color: #2D3748; background-color:#fff5b1;"> Strong</span>
<span style="color: #808080">그레이</span>
<span style="color: #ffd33d">노랑</span>

<span style='background-color: #fff5b1'>노란형광펜</span>
<span style='background-color: #f6f8fa'>회색형광펜</span>
<span style='background-color: #f1f8ff'>파랑형광펜</span>
<span style='background-color: #ffdce0'>빨강형광펜</span>
<span style='background-color: #dcffe4'>초록형광펜</span>
<span style='background-color: #f5f0ff'>보라형광펜</span>
<span style='background-color: #F7DDBE'>주황형광펜</span>
-->]]></description>
        </item>
        <item>
            <title><![CDATA[could not autowired no beans of 'DataSource' 오류 해결]]></title>
            <link>https://velog.io/@meluu_/could-not-autowired-no-beans-of-DataSource-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@meluu_/could-not-autowired-no-beans-of-DataSource-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Tue, 05 Mar 2024 10:07:47 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/meluu_/post/8c6f4696-4699-416e-a023-768df0c006c7/image.png" alt=""></p>
<p>강의 따라 작성하는데 빨간줄이 뜨고 확인해보니 
Could not autowire. No beans of &#39;DataSource&#39; type found. 이라고 뜬다. </p>
<p><img src="https://velog.velcdn.com/images/meluu_/post/9ca89336-b306-4281-a0c8-d738304e6a6b/image.png" alt="">
여기서 Import를 Jdbcconfig로 바꾸니 </p>
<p><img src="https://velog.velcdn.com/images/meluu_/post/bd1abab4-c34a-4785-866a-fc7003c4de78/image.png" alt="">
해결되었다. </p>
<p><img src="https://velog.velcdn.com/images/meluu_/post/9c8a1e17-5fde-4c95-926c-f590f143b038/image.png" alt="">
알고보니 컴포넌트 스캔 범위를 줄여서 해당 config 클래스가 컨테이너에 빈으로 등록되지 않아서 그런거였다.... </p>
<p>그러니 @Import 애노테이션을 사용해서 JdbcTemplateV1Config를 Import하여 스프링 빈 컨테이너에 추가해준다. 
이로써 DataSource에 의존관계 주입이 가능해져 에러가 사라진다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Optional<T>]]></title>
            <link>https://velog.io/@meluu_/OptionalT</link>
            <guid>https://velog.io/@meluu_/OptionalT</guid>
            <pubDate>Tue, 05 Mar 2024 06:12:35 GMT</pubDate>
            <description><![CDATA[<h2 id="✔️-optional이란-">✔️ Optional이란 ?</h2>
<p>Optional은 NullPointException 발생 방지를 위해 사용 Java8</p>
<p><code>Optional&lt;T&gt;</code>는 null 이 올 수 있는 값을 감싸는 Wrapper 클래스</p>
<hr>
<h3 id="✔️-null-체크">✔️ null 체크</h3>
<p>기존에는 if문을 사용해서 null 체크 </p>
<pre><code class="language-java">List&lt;String&gt; list = store.get(&quot;itemName&quot;);
if ( list == null) {
    log.info(&quot;없는 상품&quot;);
    // 처리 
}</code></pre>
<pre><code class="language-java">public final class Optional&lt;T&gt; {
    /**
     *  빈 optional을 공통객체로서 가지고 있다. 
     */
    private static final Optional&lt;?&gt; EMPTY = new Optional&lt;&gt;(null);

    /**
     * null이 아닐 경우 값이 담기고, null 일 경우null 이 담긴다. 
     */
    private final T value;

    // 생성자 
    private Optional(T value) {
        this.value = value;
    }
}    
</code></pre>
<hr>
<h3 id="✔️-메서드">✔️ 메서드</h3>
<hr>
<h4 id="empty">empty()</h4>
<pre><code class="language-java">public static&lt;T&gt; Optional&lt;T&gt; empty() {
    @SuppressWarnings(&quot;unchecked&quot;) // 컴파일 경고를 사용하지 않도록 설정
    Optional&lt;T&gt; t = (Optional&lt;T&gt;) EMPTY;
    return t;
}</code></pre>
<p>공통 객체로 갖고 있는 빈 optional을 반환한다. </p>
<hr>
<h4 id="oft-value">of(T value)</h4>
<pre><code class="language-java">public static &lt;T&gt; Optional&lt;T&gt; of(T value) {
    return new Optional&lt;&gt;(Objects.requireNonNull(value));
}</code></pre>
<p>null 체크
value 가 null이면 requireNonNull()에서 NPE을 던진다. </p>
<hr>
<h4 id="ofnullablet-value">ofNullable(T value)</h4>
<pre><code class="language-java">public static &lt;T&gt; Optional&lt;T&gt; ofNullable(T value) {
    return value == null ? (Optional&lt;T&gt;) EMPTY : new Optional&lt;&gt;(value);
}</code></pre>
<p>null일 가능성이 있는 value를 파라미터로 넘긴다. 
<code>null</code> : <code>Optional EMPTY</code>를 반환 , (빈 공통 객체)
<code>non-null</code> : <code>new Optional&lt;&gt;(value)</code> 반환</p>
<hr>
<h4 id="ispresent">isPresent()</h4>
<pre><code class="language-java">public boolean isPresent() {
    return value != null;
}</code></pre>
<hr>
<h4 id="ifpresentorelseconsumer-super-t-action-runnable-emptyaction"><code>ifPresentOrElse(Consumer&lt;? super T&gt; action, Runnable emptyAction)</code></h4>
<pre><code class="language-java">public void ifPresentOrElse(Consumer&lt;? super T&gt; action, Runnable emptyAction) {
    if (value != null) {
        action.accept(value);
    } else {
        emptyAction.run();
    }
}</code></pre>
<p>처음 파라미터는 Optional 객체에 값이 null이 아닐때 실행되는 Consumer 를 받고,
두 번째 파라미터는 Optional 객체의 값이 null일 때 실행되는 Runnable을 받는다. </p>
<p>둘다 함수형 인터페이스이기에 람다식이 가능하다. </p>
<hr>
<h4 id="orelset-other">orElse(T other)</h4>
<pre><code class="language-java">public T orElse(T other) {
    return value != null ? value : other;
}</code></pre>
<p>Optional이 null 이 아니면 value 반환
null 이라면 파라미터로 들어온 other을 그대로 반환</p>
<hr>
<h4 id="orelsegetsupplier-extends-t-supplier"><code>orElseGet(Supplier&lt;? extends T&gt; supplier)</code></h4>
<pre><code class="language-java">public T orElseGet(Supplier&lt;? extends T&gt; supplier) {
    return value != null ? value : supplier.get();
}
</code></pre>
<p>Supplier 라는 함수형 인터페이스가 있기에 람다식을 사용할 수 있다. 
값이 Optonal 에 있으면 value를 반환,
없으면 Supplier 에서 생성된 결과 반환 </p>
<hr>
<h4 id="orelsethrow">orElseThrow()</h4>
<pre><code class="language-java">public T orElseThrow() {
    if (value == null) {
        throw new NoSuchElementException(&quot;No value present&quot;);
    }
    return value;
}
</code></pre>
<p>OPtional에 값이 있으면 반환
없으면 <code>NoSuchElementException</code>을 던진다.</p>
<hr>
<h4 id="optional은-반환형으로만-사용하자">Optional은 반환형으로만 사용하자</h4>
<p>반환타입으로 값이 있는지 여부를 확인하기 위해 탄생했다.</p>
<h4 id="그외">그외</h4>
<p>map, filter 등 많이 있다 필요할 때 찾아서 사용하자</p>
<hr>
<h4 id="✔️-다른-분이-정리하신-것">✔️ 다른 분이 정리하신 것</h4>
<p>[ 올바른 Optional 사용법 가이드 ]</p>
<ul>
<li>Optional 변수에 Null을 할당하지 말아라</li>
<li>값이 없을 때 Optional.orElseX()로 기본 값을 반환하라</li>
<li>단순히 값을 얻으려는 목적으로만 Optional을 사용하지 마라</li>
<li>생성자, 수정자, 메소드 파라미터 등으로 Optional을 넘기지 마라</li>
<li>Collection의 경우 Optional이 아닌 빈 Collection을 사용하라</li>
<li>반환 타입으로만 사용하라</li>
</ul>
<p>출처: <a href="https://mangkyu.tistory.com/203">https://mangkyu.tistory.com/203</a> [MangKyu&#39;s Diary:티스토리]</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 DB - 데이터 접근기술 ]]></title>
            <link>https://velog.io/@meluu_/%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC%EA%B8%B0%EC%88%A0</link>
            <guid>https://velog.io/@meluu_/%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC%EA%B8%B0%EC%88%A0</guid>
            <pubDate>Tue, 05 Mar 2024 05:13:14 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="🌿-시작하기-앞서">🌿 시작하기 앞서</h2>
<hr>
<blockquote>
<p>스프링 부트 2.6.5 버전을 기준으로 작성됨
  H2 데이터베이스 Version 2.2.224 (2023-09-17)</p>
</blockquote>
<br>

<h2 id="🌱-데이터-접근-기술">🌱 데이터 접근 기술</h2>
<hr>
<p>데이터 접근 기술</p>
<ul>
<li>Jdbc Template</li>
<li>Mybatis</li>
<li>JPA, Hibernate</li>
<li>스프링 데이터 JPA</li>
<li>Querydsl</li>
</ul>
<p>SQLMapper</p>
<ul>
<li>JdbcTemplate</li>
<li>MyBatis</li>
</ul>
<p>ORM 관련 기술</p>
<ul>
<li>JPA, Hibernate</li>
<li>스프링 데이터 JPA</li>
<li>Querydsl</li>
</ul>
<br>

<h4 id="sqlmapper-주요기능">SQLMapper 주요기능</h4>
<ul>
<li>개발자가 SQL만 작성 -&gt; 결과를 객체로 편리하게 매핑해준다.</li>
<li>JDBC를 직접 사용할 때 발생하는 여러 중복을 제거, 기타 개발자에게 여러 편리한 기능을 제공</li>
</ul>
<br>

<h4 id="orm-주요-기능">ORM 주요 기능</h4>
<ul>
<li>JPA는 기본적인 SQL은 JPA가 대신 작성 및 처리</li>
<li>개발자가 저장하고 싶은 객체를 마치 자바 컬렉션에 저장하고 조회하듯이 사용하면 ORM 기술이 데이터베이스에 해당 객체를 저장 및 조회 해준다.</li>
<li>JPA는 자바 진영의 ORM 표준 , Hibernate(하이버네이트)는 JPA에서 가장 많이 사용하는 구현체 <ul>
<li>JPA 인터페이스 사용 , Hibernate 구현체로 사용</li>
</ul>
</li>
<li>스프링 데이터 JPA, Querydsl은 JPA를 더 편리하게 사용 가능하게 도와주는 프로젝트 (거의 필수)</li>
</ul>
<br>


<h4 id="데이터-접근-기술-진행-방식">데이터 접근 기술 진행 방식</h4>
<ul>
<li>데이터 접근 기술에 대한 기본 이해와 전체 큰 그림을 그린다.</li>
<li>각 기술들의 핵심 기능 위주로 학습</li>
<li>각 기술들을 점진적으로 도입하는 과정을 통해서 각 기술의 특징과 장단점을 자연스럽게 이해</li>
</ul>
<br>

<h3 id="⚙️-프로젝트-구조-및-설정">⚙️ 프로젝트 구조 및 설정</h3>
<hr>
<p>Repository 는 향후 다양한 데이터 접근 기술 구현체로 손쉽게 변경하기 위해 인터페이스 도입</p>
<pre><code class="language-java">public interface ItemRepository {
    Item save(Item item);
    void update(Long itemId, ItemUpdateDto updateParam);
    Optional&lt;Item&gt; findById(Long id);
    List&lt;Item&gt; findAll(ItemSearchCond cond);
}</code></pre>
<h4 id="검색-수정">검색, 수정</h4>
<p>검색과 수정은 Dto 클래스를 각각 만들어서 처리
<code>ItemSearchCond</code>
<code>ItemUpdateDto</code></p>
<pre><code class="language-java">// Dto 사용
 @Override
 public void update(Long itemId, ItemUpdateDto updateParam) {
     Item findItem = findById(itemId).orElseThrow();
    findItem.setItemName(updateParam.getItemName());
     //...
 }
</code></pre>
<h4 id="dto-data-transfer-object">DTO (Data Transfer Object)</h4>
<ul>
<li>데이터 전송 객체</li>
<li>DTO는 기능은 없고 데이터를 전달만 하는 용도로 사용되는 객체</li>
<li>객체 이름에 DTO를 꼭 붙일 필요는 없지만 붙여두면 용도를 알 수 있다는 장점이 있다. </li>
</ul>
<br>

<h4 id="확인용-초기-데이터-추가">확인용 초기 데이터 추가</h4>
<pre><code class="language-java">@EventListener(ApplicationReadyEvent.class)
public void initData() {
    itemRepository.save(new Item(&quot;itemA&quot;, 10000, 10));
    // 추가할 데이터넣기 
}</code></pre>
<ul>
<li>애플리케이션을 실행할 때 초기 데이터를 저장</li>
<li><code>@EventListener(ApplicationReadyEvent.class</code>: 스프링 컨테이너가 완전히 초기화를 다 끝내고, 실행 준비가 되었을 때 발생하는 이벤트 <ul>
<li>스프링이 이 시점에 해당 애노테이션이 붙은 <code>initData()</code> 메서드를 호출해준다.</li>
<li><code>@PostConstruct</code>를 사용할 경우 AOP 같은 부분이 아직 다 처리되지 않은 시점에 호출될 수 있기에 간혹 문제 발생가능성이 있다. ex) <code>@Transactional</code> 과 관련된 AOP가 적용되지 않은 상태로 호출</li>
<li><code>@EventListener(ApplicationReadyEvent.class)</code> 는 AOP를 포함한 스프링 컨테이너가 완전히 초기화 된 이후에 호출되기 때문에 이런 문제가 발생하지 않는다. </li>
</ul>
</li>
</ul>
<br>


<h4 id="여러-설정">여러 설정</h4>
<pre><code class="language-java">@Import(MemoryConfig.class)
@SpringBootApplication(scanBasePackages = &quot;hello.itemservice.web&quot;)
public class ItemServiceApplication {

    public static void main(String[] args) {
    SpringApplication.run(ItemServiceApplication.class, args);
    }

    @Bean
    @Profile(&quot;local&quot;)
    public TestDataInit testDataInit(ItemRepository itemRepository) {
        return new TestDataInit(itemRepository);
    }
}</code></pre>
<ul>
<li><code>@Import</code> : 설정파일로 MemoryConfig 클래스를 사용</li>
<li><code>@scanBasePackages</code> : 컴포넌트 스캔 경로를 <code>hello.itemservice.web</code> 하위로 지정</li>
<li><code>@Profile</code> : 특정 프로필의 경우에만 해당 스프링 빈을 등록 <ul>
<li>여기서는 local이라는 이름의 프로필이 사용되는 경우에만 <code>testDataInit</code> 이라는 스프링 빈을 등록</li>
</ul>
</li>
</ul>
<br>

<h4 id="프로필">프로필</h4>
<p>스프링은 로딩 시점에 <code>application.properties</code>의 <code>spring.profiles.active</code> 속성을 읽어서 프로필을 사용</p>
<p>이 프로필은 로컬 (내 PC), 운영환경, 테스트 실행 등 다양한 환경에 따라 다른 설정을 할 때 사용하는 정보 
프로필을 사용함으로써 현재 환경에 따른 설정을 할 수 있다!</p>
<h5 id="main-프로필">main 프로필</h5>
<p><code>/src/main/resource</code> 하위의 <code>application.properties</code></p>
<pre><code class="language-java">spring.properties.active=local</code></pre>
<ul>
<li>이  위치의 <code>application.properties</code> 는 <code>/src/main</code> 하위의 자바 객체를 실행할 때 (주로 main() ) 동작하는 스프링 설정<br> 

</li>
</ul>
<h5 id="test-프로필">test 프로필</h5>
<p><code>/src/test/resources</code> 하위의 <code>application.properties</code></p>
<pre><code class="language-java">spring.profiles.active=test</code></pre>
<ul>
<li>이 위치의 <code>application.properties</code>는 <code>/src/test</code> 하위의 자바 객체를 실행할 때 동작하는 스프링 설정</li>
<li>주로 테스트 케이스를 실행할 때 동작</li>
<li>이 경우 local로 설정한 datainit는 빈에 등록되지 않는다. </li>
</ul>
<br>
<br>

<h3 id="✔️-권장하는-식별자-선택-전략">✔️ 권장하는 식별자 선택 전략</h3>
<hr>
<h4 id="데이터-베이스-기본-키는-다음-3가지-조건을-모두-만족해야-한다">데이터 베이스 기본 키는 다음 3가지 조건을 모두 만족해야 한다.</h4>
<ol>
<li><code>null</code> 값은 허용 x</li>
<li>유일해야 함</li>
<li>변해선 안 됨</li>
</ol>
<h4 id="테이블의-기본-키를-선택하는-전략-2가지">테이블의 기본 키를 선택하는 전략 2가지</h4>
<ul>
<li><p>자연 키 (natural key)</p>
<ul>
<li>비지니스에 의미가 있는 키</li>
<li>ex) 주민등록번호, 이메일, 전화번호</li>
</ul>
</li>
<li><p>대리 키 (surrogate key)</p>
<ul>
<li>비지니스와 관련 없는 임의로 만들어진 키, 대체 키로도 불린다. </li>
<li>ex) 오라클 시퀀스, auto_increment, identity, 키생성 테이블 사용</li>
</ul>
<Br>

</li>
</ul>
<h4 id="자연키-보다는-대리-키를-권장">자연키 보다는 대리 키를 권장</h4>
<p>이메일, 전화번호 등은 언제든 바뀔 수 있고 주민등록번호는 3가지 조건이 다 만족해보이는 것처럼 보이지만 <span style='background-color: #F7DDBE'>현실과 비지니스 규칙은 생각보다 쉽게 변하며</span> 주민등록 조차도 여러가지 이유로 변경될 수 있다. </p>
<blockquote>
<p>비지니스 환경은 언젠가 변한다.</p>
</blockquote>
<hr>
<p>🔖 학습내용 출처 </p>
<p><a href="https://inf.run/wREBZ"
   title="강의">스프링 DB 2편 - 데이터 접근 활용 기술</a> </p>
<!--
<span style="color: red">red</span>
<span style="color: #0000FF">파랑</span>
<span style="color: #008000">초록</span>
<span style="color: #2D3748; background-color:#fff5b1;"> Strong</span>
<span style="color: #808080">그레이</span>
<span style="color: #ffd33d">노랑</span>

<span style='background-color: #fff5b1'>노란형광펜</span>
<span style='background-color: #f6f8fa'>회색형광펜</span>
<span style='background-color: #f1f8ff'>파랑형광펜</span>
<span style='background-color: #ffdce0'>빨강형광펜</span>
<span style='background-color: #dcffe4'>초록형광펜</span>
<span style='background-color: #f5f0ff'>보라형광펜</span>
<span style='background-color: #F7DDBE'>주황형광펜</span>
-->]]></description>
        </item>
        <item>
            <title><![CDATA[Lambda & Stream]]></title>
            <link>https://velog.io/@meluu_/Lambda-Stream</link>
            <guid>https://velog.io/@meluu_/Lambda-Stream</guid>
            <pubDate>Mon, 04 Mar 2024 01:42:15 GMT</pubDate>
            <description><![CDATA[<h3 id="✔️-lambda람다">✔️ lambda(람다)</h3>
<hr>
<blockquote>
<p>람다식은 메서드로 전달할 수 있는 익명 함수를 단순화한 코드 블록
<code>() -&gt; {}</code>
<code>()</code> : 파라미터
<code>-&gt;</code> : 화살표
<code>{}</code> : 실행문 </p>
</blockquote>
<ul>
<li><p>람다식은 함수 자체를 전달 인자로 보내거나 변수에 저장하는 것이 가능</p>
</li>
<li><p>람다식에서 타입이 예측가능하다면 생략 가능</p>
</li>
<li><p>특정 람다식을 사용하여 변수에 담는데 그 변수의 타입은 함수형 인터페이스 타입이다. </p>
</li>
</ul>
<br>

<p><strong>함수형 인터페이스</strong> : 하나의 추상 메서드를 갖는 인터페이스 
<strong>함수 디스크립터</strong> : 함수형 인터페이스의 추상 메서드 시그니처
<strong>메소드 시그니쳐</strong> : 메소드의 이름 + 메소드의 파라미터 + 파라미터의 수</p>
<table>
<thead>
<tr>
<th align="center">함수형 인터페이스</th>
<th align="center">함수 디스크립터</th>
<th align="center">기본형 특화</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>Predicate&lt;T&gt;</code></td>
<td align="center">T -&gt; boolean</td>
<td align="center">IntPredicate, LongPredicate, DoublePredicate</td>
</tr>
<tr>
<td align="center"><code>Consumer&lt;T&gt;</code></td>
<td align="center">T -&gt; void</td>
<td align="center">IntConsumer, LongConsumer, DoubleConsumer</td>
</tr>
<tr>
<td align="center"><code>Function&lt;T, R&gt;</code></td>
<td align="center">T -&gt; R</td>
<td align="center"><code>IntFunction&lt;T&gt;, IntToDoubleFinction, ...</code></td>
</tr>
<tr>
<td align="center"><code>Supplier&lt;T&gt;</code></td>
<td align="center">() -&gt; T</td>
<td align="center">BooleanSupplier, IntSupplier</td>
</tr>
</tbody></table>
<p>더 있지만 자세한 건 강의 참조</p>
<p>기본형 특화의 경우 wrapper 클래스와 primitive 타입의 오토 언박싱을 문제없이 처리해주기 위해 특화된 것이다. </p>
<br>

<hr>
<h3 id="✔️-함수형-인터페이스를-알아야-하는-이유">✔️ 함수형 인터페이스를 알아야 하는 이유</h3>
<hr>
<p>람다 표현식은 함수 자체를 전달 인자 or 변수 저장하는게 가능하다 하였는데 </p>
<p>그렇다면 전달 인자나 변수의 타입이 정의되어야하기 때문이다. 
그렇기에 함수형 인터페이스를 변수의 타입이나 전달 인자에 명시해주면 된다. </p>
<p>람다식이 가능한 Stream의 filter 매개변수를 확인해 보니 진짜로 함수형 인터페이스를 매개변수로 받고 있다. </p>
<p><img src="https://velog.velcdn.com/images/meluu_/post/c6d0d2bf-09b4-496a-83e5-a1456e8514d0/image.png" alt=""></p>
<br>

<h3 id="〰️-stream">〰️ Stream</h3>
<hr>
<p>컬렉션이나 배열 등 데이터를 처리하는 기술
중간 연산을 통해 가공된 스트림은 마지막으로 최종 연산을 통해 요소를 소모하여 결과를 출력
한 번 생성한 스트림은 사용 후 재사용 불가, 전체 데이터에 대한 처리가 이루어지면 종료 -&gt; 다시 스트림을 생성해야 함 </p>
<p><code>Collection</code>의 Stream은 이미 데이터가 모여있는 형태의 스트림 객체 생성</p>
<p><img src="https://velog.velcdn.com/images/meluu_/post/6a3671ab-d522-4a78-962e-2cddce4fe2ca/image.png" alt="">
Collection의 stream() 메서드는 default로 정의하고 있다. 
List, Set, Queue, (Map.value()  -&gt;  <code>map.value().stream()</code>) 에서 사용가능</p>
<p>Array의 stream 은 <code>Arrays.stream(arr[])</code>로 사용 </p>
<h4 id="stream-사용방법">Stream 사용방법</h4>
<br> 

<p><strong>1. Stream.builder</strong> </p>
<pre><code class="language-java">Stream.Builder&lt;String&gt; builder = Stream.builder();
builder.accept(&quot;123&quot;);
builder.accept(&quot;456&quot;);

List&lt;Integer&gt; collect = builder.build().map(Integer::parseInt).toList();
for (Integer i : collect) {
    System.out.println(&quot;i = &quot; + i);
}
</code></pre>
<p>Stream 객체의 생성방식은 자체적으로 데이터 생성, 처리 가능  (많이 사용하지는 않는다.)</p>
<br>

<p>*<em>2. 람다식 사용 
*</em></p>
<pre><code class="language-java">List&lt;Integer&gt; list = new ArrayList&lt;&gt;();
list.add(1);
list.add(2);
list.add(3);


int[] array = list.stream().filter(x -&gt; x &lt; 3).mapToInt(x -&gt; x).toArray();

for (int i : array) {
    System.out.println(&quot;i = &quot; + i);
}</code></pre>
<hr>
<h4 id="스트림-연산-처리">스트림 연산 처리</h4>
<ol>
<li>스트림 객체 생성</li>
<li>중간 연산</li>
<li>최종 연산</li>
</ol>
<br>

<h4 id="중간-연산">중간 연산</h4>
<table>
<thead>
<tr>
<th align="left">연산</th>
<th>반환 형태</th>
<th align="center">연산 인수</th>
</tr>
</thead>
<tbody><tr>
<td align="left">filter</td>
<td><code>Stream&lt;T&gt;</code></td>
<td align="center"><code>Predicate&lt;T&gt;</code></td>
</tr>
<tr>
<td align="left">map</td>
<td><code>Stream&lt;T&gt;</code></td>
<td align="center"><code>Function&lt;T, R&gt;</code></td>
</tr>
<tr>
<td align="left">limit</td>
<td><code>Stream&lt;T&gt;</code></td>
<td align="center"></td>
</tr>
<tr>
<td align="left">sorted</td>
<td><code>Stream&lt;T&gt;</code></td>
<td align="center"><code>Comparator&lt;T&gt;</code></td>
</tr>
<tr>
<td align="left">distinct</td>
<td><code>Stream&lt;T&gt;</code></td>
<td align="center"></td>
</tr>
<tr>
<td align="left">peek</td>
<td><code>Stream&lt;T&gt;</code></td>
<td align="center"><code>Consumer&lt;T&gt;</code></td>
</tr>
<tr>
<td align="left">skip</td>
<td><code>Stream&lt;T&gt;</code></td>
<td align="center"></td>
</tr>
</tbody></table>
<p>중간 연산은 결과물이 Stream 이기에 반드시 최종 연산으로 결과를 반환해야한다. </p>
<br>


<h4 id="최종-연산">최종 연산</h4>
<p>중간 연산을 통해 가공된 스트림을 최종 연산을 통해 각 요소를 소모하여 결과를 출력</p>
<table>
<thead>
<tr>
<th align="left">연산</th>
<th>반환 형태</th>
</tr>
</thead>
<tbody><tr>
<td align="left">forEach</td>
<td>스트림의 각 요소를 소비하며 람다 적용, void 반환</td>
</tr>
<tr>
<td align="left">count</td>
<td>스트림 요소의 수를 Long 타입으로 반환</td>
</tr>
<tr>
<td align="left">collect</td>
<td>List, Map 형태의 컬렉션 반환</td>
</tr>
<tr>
<td align="left">sum</td>
<td>스트림의 모든 요소에 대한 합을 반환</td>
</tr>
<tr>
<td align="left">reduce</td>
<td>스트림의 요소를 하나씩 줄여가며 연산 수행 후 결가 반환, Optional 반환</td>
</tr>
</tbody></table>
<p>더 많이 있으니 찾아보자 </p>
<blockquote>
<p>Stream() 에서 sum을 사용하려면 숫자 연산 관련 자료형에 mapToInt등으로 스트림을 숫자 관련 자료형으로 Mapping 해주고 사용 </p>
</blockquote>
<blockquote>
<p><code>mapToInt -&gt; toArray()</code> 가능, 
List 나 Collection 으로 사용 불가능 
왜냐하면 int 형으로 mapping 되었기에 wrapper 클래스 타입을 받는 Collection을 사용할 수 없기 때문이다. </p>
</blockquote>
<br>

<h3 id="🟰-병렬-스트림">🟰 병렬 스트림</h3>
<hr>
<p>말 그대로 병렬로 처리한다. 
2개의 스레드로 처리해서 속도가 높아지지만 중복 제거의 경우 문제가 생길 수 있음 -&gt; <code>eqauls()</code> 사용</p>
<pre><code class="language-java">// List
list.parallelStream()

// Array
Arrays.stream(arr[]).parallel()</code></pre>
<br>

<h4 id="정렬">정렬</h4>
<p><code>sorted()</code>를 이용한 정렬 : 반드시 대상 객체들이 Comparable 인터페이스를 구현한 클래스
즉, 비교가 가능한 객체여야 한다.</p>
<p>혹은 sorted(Comparator.comparing(E))를 사용해서 정리 </p>
<br>

<h3 id="✔️-mapping">✔️ Mapping</h3>
<hr>
<p>스트림이 관리하는 데이터를 다른 형태의 데이터로 변환 </p>
<p>map, mpaToInt(), mapToDouble() 등</p>
<table>
<thead>
<tr>
<th align="left">연산</th>
<th>반환 형식</th>
</tr>
</thead>
<tbody><tr>
<td align="left">allMatch</td>
<td>파라미터로 전달되는 람다식 기준으로 스트림 데이터가 모두 일치하는지 확인</td>
</tr>
<tr>
<td align="left">anyMatch</td>
<td>파라미터로 전달되는 람다식 기준으로 스트림 데이터가 하나라도 일치하는지를 확인</td>
</tr>
<tr>
<td align="left">noneMatch</td>
<td>파라미터로 전달되는 람다식 기준으로 스트림 데이터가 모두 일치하지 않는지를 확인</td>
</tr>
<tr>
<td align="left">findFirst</td>
<td>스트림 데이터 중에서 가장 첫번째 데이터를 변환</td>
</tr>
<tr>
<td align="left">reduce</td>
<td>스트림 데이터 중에서 임의의 데이터를 변환</td>
</tr>
</tbody></table>
<hr>
<p>🔖 학습 참조</p>
<p><a href="https://www.youtube.com/@namoosori">유튜브 나무소리</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[IncorrectResultSetColumnCountException]]></title>
            <link>https://velog.io/@meluu_/IncorrectResultSetColumnCountException</link>
            <guid>https://velog.io/@meluu_/IncorrectResultSetColumnCountException</guid>
            <pubDate>Tue, 27 Feb 2024 08:59:04 GMT</pubDate>
            <description><![CDATA[<h3 id="예외-발생">예외 발생</h3>
<p><img src="https://velog.velcdn.com/images/meluu_/post/3b9280e0-f76e-419e-bcee-05958830dac0/image.png" alt=""></p>
<p>멤버 전체조회 코드를 짜고 테스트했는데
IncorrectResultSetColumnCountException  예외가 터졌다
예외에서 코드 따라가보니</p>
<pre><code class="language-java">public List&lt;Member&gt; findAll() {
    String sql = &quot;select  * from member&quot;;
    List&lt;Member&gt; members = template.queryForList(sql, Member.class);
    return members;
}</code></pre>
<p>여기서 문제가 발생했다. 
queryForList 메서드를 추적해서 가져왔다</p>
<pre><code class="language-java">@Override
public &lt;T&gt; List&lt;T&gt; queryForList(String sql, Class&lt;T&gt; elementType) throws DataAccessException {
    return query(sql, getSingleColumnRowMapper(elementType));
}</code></pre>
<p>즉 <code>getSingleColumnRowMapper</code> 한가지의 컬럼만 받아올 수 있다는 것이다.
내가 작성한 것은 모든 컬럼을 가져오는 것이었고 Member 테이블의 컬럼은 id, money 즉 2가지라서 actual 2가 뜬 것 같다.</p>
<hr>
<h4 id="결론">결론</h4>
<p>queryForList 를 사용하지 말고 
query메서드로 RowMapper 람다식을 사용해서 조회하자</p>
<pre><code class="language-java">public List&lt;Member&gt; findAll() {
    String sql = &quot;select  * from member&quot;;
    List&lt;Member&gt; members = template.query(sql, (rs, rowNum) -&gt; {
        Member member = new Member();
        member.setMemberId(rs.getString(&quot;member_id&quot;));
        member.setMoney(rs.getInt(&quot;money&quot;));
        return member;
    });
    return members;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JdbcTemplate query method]]></title>
            <link>https://velog.io/@meluu_/JdbcTemplate-query-method</link>
            <guid>https://velog.io/@meluu_/JdbcTemplate-query-method</guid>
            <pubDate>Tue, 27 Feb 2024 08:38:04 GMT</pubDate>
            <description><![CDATA[<p>스프링 DB 학습중 궁금한 것이 있어서 테스트해보고 찾아보고 학습한 내용을 정리한다.</p>
<hr>
<p><code>queryForObject(String sql, RowMapper&lt;T&gt; ,  @Nullable Object... args)</code></p>
<p>queryForObject 메서드는 여러가지가 있는데 그중 스프링 DB에서 썼던 것을 살펴보자</p>
<pre><code class="language-java">@Override
@Nullable
public &lt;T&gt; T queryForObject(String sql, RowMapper&lt;T&gt; rowMapper, @Nullable Object... args) throws DataAccessException </code></pre>
<p><code>sql</code> : sql 문
<code>RowMapper&lt;T&gt;</code> : ResultSet 데이터를 매핑하는데 사용
<code>arg</code> : sql 파라미터 </p>
<p><code>rs = pstmt.executeQuery()</code>
DB에서 조회한 값은 <code>ResultSet</code> 타입으로 반환된다. 
이때 객체로 반환받고 싶다면 RowMapper를 사용하면 된다.</p>
<p>단일 데이터 조회시 사용한다.</p>
<hr>
<h3 id="rowmapper">RowMapper</h3>
<pre><code class="language-java">@FunctionalInterface
public interface RowMapper&lt;T&gt; {

    @Nullable
    T mapRow(ResultSet rs, int rowNum) throws SQLException;

}</code></pre>
<p>인터페이스를 구현해서 사용한다면 
<code>mapRow</code> 메서드를 override 해서 사용</p>
<pre><code class="language-java">
@Override
public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
    Member member = new Member();
    member.setMemberId(rs.getString(&quot;member_id&quot;));
    member.setMoney(rs.getInt(&quot;money&quot;));
    return member;
}</code></pre>
<p>람다식으로 적용</p>
<pre><code class="language-java">
public Member findById(String memberId) {
    String sql = &quot;select  * from member where member_id = ?&quot;;
    return template.queryForObject(sql, memberRowMapper(), memberId);
}


private RowMapper&lt;Member&gt; memberRowMapper() {
    return (rs, rowNum) -&gt; {
        Member member = new Member();
        member.setMemberId(rs.getString(&quot;member_id&quot;));
        member.setMoney(rs.getInt(&quot;money&quot;));
        return member;
    };
}
</code></pre>
<p>구현해서 사용할려면 ResultSet이 필요하다.
그러면 커넥터, PreparedStatement 등이 필요하고 직접 코드로 짜줘야하기에 JdbcTemplate로 간단하게 할 수 있는 람다식이 더 편한 것 같다. </p>
<h3 id="멤버-전체-조회">멤버 전체 조회</h3>
<pre><code class="language-java">public List&lt;Member&gt; findAll() {
    String sql = &quot;select  * from member&quot;;

    List&lt;Member&gt; members = template.query(sql, (rs, rowNum) -&gt; {
        Member member = new Member();
        member.setMemberId(rs.getString(&quot;member_id&quot;));
        member.setMoney(rs.getInt(&quot;money&quot;));
        return member;
    });
     return members;
}</code></pre>
<h4 id="test">test</h4>
<pre><code class="language-java">package hello.jdbc.repository;

import hello.jdbc.domain.Member;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

@Slf4j
@SpringBootTest
public class MemberRepositoryfindAllTest {


    private final MemberRepositoryV5 repository;

    @Autowired
    public MemberRepositoryfindAllTest(MemberRepositoryV5 repository) {
        this.repository = repository;
    }


    @TestConfiguration
    static class TestConfig {

        private final DataSource dataSource;

        public TestConfig(DataSource dataSource) {
            this.dataSource = dataSource;
        }

        @Bean
        MemberRepositoryV5 memberRepository() {
            return new MemberRepositoryV5(dataSource);
        }


    }

    @Test
    void findAllMember() {
        repository.save(new Member(&quot;하나요&quot;, 11111));
        repository.save(new Member(&quot;둘이요&quot;, 22222));

        log.info(&quot;list={}&quot;, repository.findAll());

        repository.delete(&quot;하나요&quot;);
        repository.delete(&quot;둘이요&quot;);
    }
}
</code></pre>
<h4 id="결과">결과</h4>
<p><img src="https://velog.velcdn.com/images/meluu_/post/7f304dff-1d34-4e20-aea2-c38ffee9df57/image.png" alt=""></p>
<hr>
<p>🔖 참고내용 출처 </p>
<p><a href="https://inf.run/AomUA"
   title="강의">스프링 DB 1편 - 데이터 접근 핵심 원리</a> </p>
]]></description>
        </item>
    </channel>
</rss>