<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>고민저장소</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 30 Jun 2024 11:54:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>고민저장소</title>
            <url>https://velog.velcdn.com/images/wnso-kim/profile/25e82f36-bcd8-4a94-a8b6-6ab83f4a0e63/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 고민저장소. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/wnso-kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[백준] 22866 - 탑 보기]]></title>
            <link>https://velog.io/@wnso-kim/%EB%B0%B1%EC%A4%80-22866-%ED%83%91-%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@wnso-kim/%EB%B0%B1%EC%A4%80-22866-%ED%83%91-%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 30 Jun 2024 11:54:58 GMT</pubDate>
            <description><![CDATA[<h2 id="링크">링크</h2>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/22866">https://www.acmicpc.net/problem/22866</a></p>
</blockquote>
<h2 id="문제">문제</h2>
<blockquote>
<p>일직선으로 다양한 높이의 건물이 총 $N$개가 존재한다. 
각 건물 옥상에서 양 옆에 존재하는 건물의 옆을 몇 개 볼 수 있는지 궁금해졌다.
$i$번째 건물 기준으로 $i - 1$, $i - 2$, ..., 
$1$번째 건물은 왼쪽에, $i + 1$, $i + 2$, ..., 
$N$번째 건물은 오른쪽에 있다. 각 건물 사이의 거리는 다 동일하다.</p>
</blockquote>
<p>현재 있는 건물의 높이가 $L$이라고 가정하면 높이가 $L$보다 큰 곳의 건물만 볼 수 있다.</p>
<blockquote>
</blockquote>
<p>바라보는 방향으로 높이가 $L$인 건물 뒤에 높이가 $L$이하인 건물이 있다면 가려져서 보이지 않는다.</p>
<blockquote>
</blockquote>
<p>각 건물에서 볼 수 있는 건물들이 어떤것이 있는지 구해보자.</p>
<h2 id="입력">입력</h2>
<blockquote>
</blockquote>
<p>첫번째 줄에 건물의 개수 $N$이 주어진다.</p>
<blockquote>
</blockquote>
<p>두번째 줄에는 $N$개의 건물 높이가 공백으로 구분되어 주어진다.</p>
<h2 id="출력">출력</h2>
<blockquote>
<p> 
$i(1 \le i \le N)$번째 건물에서 볼 수 있는 건물의 개수를 출력한다.</p>
</blockquote>
<p>만약 볼 수 있는 건물의 개수가 1개 이상이라면 $i$번째 건물에서 거리가 가장 가까운 건물의 번호 중 작은 번호로 출력한다.</p>
<h2 id="풀이">풀이</h2>
<blockquote>
</blockquote>
<h4 id="1-나이브한-접근">[1] 나이브한 접근</h4>
<p><code>z번째 빌딩</code>보다 왼쪽에 있는 빌딩 중 높은 빌딩(y)을 찾는다.<br><code>y번째 빌딩</code>보다 왼쪽에 있는 빌딩 중 높은 빌딩(x)을 찾는다.<br><code>x번째 빌딩</code>보다 왼쪽에 있는 빌딩 중 높은 빌딩(w)을 찾는다.</p>
<blockquote>
</blockquote>
<p>위를 반복하면 z번째 건물에서 볼 수 있는 왼쪽 건물들을 알 수 있다.</p>
<blockquote>
</blockquote>
<h4 id="2-dp-관점">[2] DP 관점</h4>
<p>w &gt; x &gt; y &gt; z 인 건물이 순서대로 주어 졌을 때,<br>각 건물에서 볼 수 있는 왼쪽 건물의 수는 아래 표와 같다.   </p>
<blockquote>
</blockquote>
<p><img src="https://github.com/BE-Archive/Algorithm-Study/assets/109727039/6334a369-bb38-4d5d-b4ed-5260f13a5ec8" alt="image"></p>
<blockquote>
</blockquote>
<p>만약 중간에 가장 높은 건물이 추가 된다면 어떻게 될까?</p>
<blockquote>
</blockquote>
<p><img src="https://github.com/BE-Archive/Algorithm-Study/assets/109727039/99055cad-31ac-4add-92e8-47e8e57628d8" alt="image"></p>
<blockquote>
</blockquote>
<p>위의 그림과 같이 y와 z의 결과가 바뀌게 된다.</p>
<blockquote>
</blockquote>
<p><code>특정 빌딩(z)</code> 보다 왼쪽에 <code>높은 빌딩(y)</code>이 있는 경우,<br><code>높은 빌딩(y)</code>의 dp값 +1이 <code>특정 빌딩(x)</code>의 dp값이 된다.</p>
<blockquote>
</blockquote>
<h4 id="3-결과">[3] 결과</h4>
<p>왼쪽 빌딩의 결과를 구한 것과 같이, 오른쪽 빌딩의 결과 또한 DP로 구한다.<br>출력 조건 중 아래와 같은 문구가 있다.</p>
<blockquote>
<p>만약 볼 수 있는 건물의 개수가 1개 이상이라면 $i$번째 건물에서 거리가 가장 가까운 건물의 번호 중 작은 번호로 출력한다. 
DP 테이블이 갱신 되는 경우에 <code>높은 빌딩(y)</code>의 인덱스를 저장해두면 된다.</p>
</blockquote>
<pre><code class="language-java">// 왼쪽 빌딩 확인 코드 중 일부
if(buildings[j] &gt; buildings[i]){
    dp[i][0] = dp[j][0]+1;
    dp[i][2] = j+1; // 인덱스 저장
    break;
}
&gt;
// 오른쪽 빌딩 확인 코드 중 일부
if(dp[i][0] == 0 || (i - dp[i][2]+1) &gt; (j-i)){
    dp[i][2] = j+1;
}</code></pre>
<blockquote>
</blockquote>
<h4 id="4-최적화">[4] 최적화</h4>
<p><code>특정 빌딩(z)</code> 보다 왼쪽에 <code>높은 빌딩(y)</code>이 있는 경우는 반복문을 통해 알 수 있다.
단, <code>특정 빌딩(z)</code>이 왼쪽에 있는 모든 빌딩 보다 높은 빌딩이라면 어떻게 될까?<br>0번째 빌딩까지 높이를 다 확인해야 한다!</p>
<blockquote>
</blockquote>
<p>이는 0부터 i번째 빌딩까지 높이를 확인할 때, 가장 높은 빌딩의 높이를 변수에 저장해두면 최적화 할 수 있다.</p>
<pre><code class="language-java">if(max &lt; buildings[i]){
    max = buildings[i];
    continue;
}</code></pre>
<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class BOJ_22866_탑보기 {

    static int N;
    static int[] buildings;
    static int[][] dp;

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

        // 입력| N, buildings
        N = Integer.parseInt(br.readLine()); 
        StringTokenizer stk = new StringTokenizer(br.readLine());

        buildings = new int[N];
        for(int i=0; i&lt;N; i++)  buildings[i] = Integer.parseInt(stk.nextToken());

        // 초기화| dp
        dp = new int[N][3]; // [i][0]: i기준 왼쪽 빌딩, [i][1]: i기준 오른쪽 빌딩, [i][2]: 가장 가까운 빌딩

        // 왼쪽 빌딩 확인
        int max = buildings[0];
        for(int i=1; i&lt;N; i++){
            if(max &lt; buildings[i]){
                max = buildings[i];
                continue;
            }

            for(int j=i-1; j&gt;=0; j--){
                if(buildings[j] &gt; buildings[i]){
                    dp[i][0] = dp[j][0]+1;
                    dp[i][2] = j+1;
                    break;
                }
            }
        }

        // 오른쪽 빌딩 확인
        max = buildings[N-1];
        for(int i=N-2; i&gt;=0; i--){
            if(buildings[i] &gt; max){
                max = buildings[i];
                continue;
            }

            for(int j=i+1; j&lt;N; j++){
                if(buildings[i] &lt; buildings[j]){
                    dp[i][1] = dp[j][1]+1;

                    if(dp[i][0] == 0 || (i - dp[i][2]+1) &gt; (j-i)){
                        dp[i][2] = j+1;
                    }

                    break;
                }
            }
        }

        // 결과 출력
        StringBuilder sb = new StringBuilder();
        for(int i=0; i&lt;N; i++){
            int sum = dp[i][0] + dp[i][1];

            sb.append(sum).append(&quot; &quot;);
            if(sum != 0) sb.append(dp[i][2]);
            sb.append(&quot;\n&quot;);
        }

        System.out.println(sb);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] 스프링 웹 컨텐츠]]></title>
            <link>https://velog.io/@wnso-kim/Spring-Boot-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-Gradle</link>
            <guid>https://velog.io/@wnso-kim/Spring-Boot-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-Gradle</guid>
            <pubDate>Sun, 10 Mar 2024 15:37:11 GMT</pubDate>
            <description><![CDATA[<h2 id="-정적-컨텐츠-">[ 정적 컨텐츠 ]</h2>
<p>말 그대로 정적인 HTML 컨텐츠를 제공하는 것이다.Spring boot는 <code>tomcat</code>에 의해 적절한 컨트롤러가 로드된다. 
클라이언트의 요청과 맞는 스프링 컨테이너가 없다면 <code>정적 컨텐츠</code>가 로드된다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/3f02e2a4-9c47-4046-8ff8-44406ed1bfa2/image.png" alt=""></p>
<p>기존에 작성된 HTML을 따로 가공하지 않고, 순수 제공하는 것으로 <code>Web Server</code>에서 제공하는 경우 사용할 수 있다.
<a href="https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-static-content">Spring 정적 컨텐츠</a>는 resources하위 폴더를 칭한다. 단순 static 폴더만을 지칭하지 않는다. 물론 변경도 가능하다.</p>
<pre><code class="language-js">spring.mvc.static-path-pattern=/resources/**</code></pre>
<blockquote>
<p>나중에 Web Serverd와 WAS를 정리해둬야겠다.</p>
</blockquote>
<h2 id="-mvc--템플릿-엔진">[ MVC &amp; 템플릿 엔진]</h2>
<p><code>MODEL</code> <code>VIEW</code> <code>CONTROLLER</code>의 약자로, 컨텐츠 제공을 위한 3가지 컴포넌트를 활용한다.
Model: 컨텐츠의 정보를 나타내며, 데이터의 가공을 책임지는 컴포넌트이다.
View: 컨텐츠의 사용자 인터페이스 부분을 담당한다. 데이터를 기반으로 사용자들이 볼 수 있는 화면을 구성한다.
Controller: 데이터와 사용자 인터페이스를 연결한다. 즉, 모델과 뷰를 연결한다.</p>
<p>스프링에서는 아래의 사진과 같이 <code>tomcat</code>에 의해 스프링 컨테이너가 적절한 <code>controller</code>를 로드한다.
controller는 모델을 viewResolver에게 전달한다. 템플릿 엔진(Thymeleaf)가 동적으로 view를 처리하고 사용자에게 보여진다.
즉, 서버 사이드 랜더링 방식으로 동작한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/9f762e1d-56f3-41b4-84f8-3806656e0856/image.png" alt=""></p>
<h2 id="-api-">[ API ]</h2>
<p>클라이언트에게 데이터만을 보낸다. XML, JSON과 같은 데이터 형식을 보냄으로서 클라이언트에게 랜더링을 맡긴다. 즉, 클라이언트 사이드 랜더링 방식으로 동작하게끔 한다.</p>
<p>스프링은 객체를 <code>ResponseBody</code>에 반환한다. 이때, 당연하게도 <strong>객체를 바로 보낼 순 없다.</strong>
스프링 컨테이너는 <code>HttpMessageConverter</code>를 선택해 객체를 컨버팅 할 수 있도록한다.
기본 문자 처리는 <code>StringHttpMessageConverter</code>, 기본 객체 처리는 <code>MappingJackson2HttpMessageConverter</code>를 사용한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/bad8a688-43ec-493d-98f1-03037a982cc8/image.png" alt=""></p>
<h3 id="참고">참고</h3>
<p>김영한님의 <a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard">스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술</a>을 참고해 작성했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Sprinig Boot] Spring Initializr]]></title>
            <link>https://velog.io/@wnso-kim/Sprinig-Boot-Spring-Initializr</link>
            <guid>https://velog.io/@wnso-kim/Sprinig-Boot-Spring-Initializr</guid>
            <pubDate>Sat, 09 Mar 2024 14:29:30 GMT</pubDate>
            <description><![CDATA[<p>스프링 부트 프로젝트를 생성할 때 마다, 이름을 어떻게 작성해야하는지 긴가민가하다.
미래의 나도 그럴 확률이 높기에, 두고두고 보기 위해 작성한다.</p>
<h2 id="-프로젝트-생성-">[ 프로젝트 생성 ]</h2>
<p><a href="https://start.spring.io">start spring io</a>로 접속한다. 아래의 사진으로 한번에 이해 가능하다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/d4afca47-26e5-4e1e-a161-4ba7f10cde87/image.png" alt=""></p>
<p>위의 사진처럼 뭐가 많지만 사실 간단하다. 5가지만 잘 선택해주면 된다.</p>
<ul>
<li>project</li>
<li>Language</li>
<li>Spring Boot 버전</li>
<li>Project Metadata</li>
<li>Dependencies</li>
</ul>
<h2 id="-project-">[ Project ]</h2>
<p><strong>Project를</strong> 통해 <code>빌드 관리 도구</code>를 선택할 수 있다.
Gradle과 Maven의 차이는 <a href="https://velog.io/@leesomyoung/Maven%EA%B3%BC-Gradle%EC%9D%98-%EC%B0%A8%EC%9D%B4-%EB%B0%8F-%EB%B9%84%EA%B5%90">Maven과 Gradle의 개념 및 비교</a>를 참고</p>
<h2 id="-spring-boot-">[ Spring Boot ]</h2>
<p><strong>Spring Boot</strong>에서 스프링 부트의 버전을 선택한다.</p>
<ul>
<li><strong>Snapshot</strong>: 데일리 빌드 버전, 안정화가 되어있지 않다.</li>
<li><strong>GA(General Availability)</strong>: 정식 릴리즈 버전</li>
<li><strong>M(Milestone)</strong>: 프로젝트의 주기마다 배포하는 버전, 주요 기능이 구현되면 릴리즈를 진행하고 개발자들에게 피드백을 받는다.</li>
<li><strong>RC(Release Cadidate)</strong>: M의 상위 버전으로, 정식 릴리즈는 아니다. RC버전을 거치고 GA로 배포된다.</li>
</ul>
<h2 id="-project-metadata-">[ Project Metadata ]</h2>
<p>프로젝트를 설명하는 데이터이다.</p>
<ul>
<li>Group: 기업 도메인명</li>
<li>Artifact: Build 결과물(프로젝트명)</li>
<li>Name: Build 결과물(프로젝트명)</li>
<li>Description: 프로젝트 설명</li>
<li>Package name: 루트 패키지. <code>Group.Artifact</code>가 자동으로 입력된다.</li>
</ul>
<h2 id="-dependencies-">[ Dependencies ]</h2>
<p>프로젝트에 필요한 라이브러리 선택한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Github] 스터디에서 깃 관리]]></title>
            <link>https://velog.io/@wnso-kim/Github-%EC%8A%A4%ED%84%B0%EB%94%94%EC%97%90%EC%84%9C-%EA%B9%83-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@wnso-kim/Github-%EC%8A%A4%ED%84%B0%EB%94%94%EC%97%90%EC%84%9C-%EA%B9%83-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Thu, 07 Mar 2024 15:36:55 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/BE-Archive/Algorithm-Study">알고리즘 스터디</a>를 위한 깃허브에서 <code>이슈</code>와 <code>Pull Request</code>를 어떻게 관리 할 것인지에 관한 문서이다.
<strong>깃 관리자</strong>는 <code>매주</code> 바뀌며, <strong>관리자</strong>는 아래의 내용을 참고해 <code>이슈와 PR</code>을 관리한다.</p>
<h4 id="스터디-형식">스터디 형식</h4>
<ul>
<li>문제는 <strong>주5회</strong> 출제(스터디원)</li>
<li>각 문제마다 <strong>이슈</strong>로 생성(관리자)</li>
<li>주차별 <strong>마일스톤</strong>을 생성하고, 해당 주의 이슈와 연결(관리자)</li>
<li>문제풀이 Pull Request(스터디원)</li>
<li>Approve된 PR merge(관리자)</li>
</ul>
<h2 id="1-마일스톤-생성">1. 마일스톤 생성</h2>
<p>월요일마다 마일스톤을 생성한다. Github-Issue-Milestones에서 생성할 수 있다.
Title은 주차에 맞게 설정하고, Due date를 일요일로 설정한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/4b0e07d3-25c7-4ee6-847c-a479831207e3/image.png" alt=""></p>
<h2 id="2-이슈-생성">2. 이슈 생성</h2>
<p>평일 스터디원이 문제를 출제하면 Issue를 생성하고 마일스톤과 연결한다.
Github-Issue에서 생성하고, 템플릿을 사용해 문제에 맞게 {}를 수정한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/69ea5b97-d291-43d6-b096-d17bf7bee63b/image.png" alt=""></p>
<h2 id="3-pull-request-merge">3. Pull Request Merge</h2>
<p>스터디원이 문제를 해결하고 PR을 요청하면, 관리자는 확인하고 merge한다.
PR마다 지정된 페어가 코드 리뷰를 통해 Approve를 해주어야 merge 가능하다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/d5655724-5a39-4dab-93cc-d4ddf1ac5ff2/image.png" alt=""></p>
<p>Files changed에서 파일명(네이밍,주차,특수문자 여부)이 적절한지 확인한다.
위의 조건들이 만족 했다면, 아래 3가지를 설정한 뒤 merge한다.</p>
<ul>
<li><code>Labels</code>: 풀이에 사용된 알고리즘</li>
<li><code>Assignees</code>: PR작성자</li>
<li><code>Milestone</code>: 해당 주차</li>
</ul>
<blockquote>
<p>Action을 사용해 Assignees와 Milestone 할당을 자동화하려했지만 실패했다.. 성공하면 수정해야지 </p>
</blockquote>
<br>


<h2 id="4-이슈--마일스톤-close">4. 이슈 &amp; 마일스톤 close</h2>
<p>일주일이 마무리 되면 생성한 이슈와 마일스톤을 close한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/2b4f3358-8b18-47f7-9011-88a3b04de9ca/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ToDo] 20231208 정리]]></title>
            <link>https://velog.io/@wnso-kim/ToDo-20231208-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@wnso-kim/ToDo-20231208-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 08 Dec 2023 02:56:41 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘-해결해야-할-일">오늘 해결해야 할 일</h2>
<ol>
<li>[PerfumePedia] SearchResultService 테스트</li>
<li>[PerfumePedia] PerfumeDetailService 테스트</li>
<li>[SSAFY] PT면접 주제(OTT) 내용 추가</li>
<li>[SSAFY] 면접 예상 질문 확정답안 준비</li>
</ol>
<h2 id="해결한-일">해결한 일</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] JPA - JPQL 동적쿼리]]></title>
            <link>https://velog.io/@wnso-kim/Spring-Boot-JPA-JPQL-%EB%8F%99%EC%A0%81%EC%BF%BC%EB%A6%AC</link>
            <guid>https://velog.io/@wnso-kim/Spring-Boot-JPA-JPQL-%EB%8F%99%EC%A0%81%EC%BF%BC%EB%A6%AC</guid>
            <pubDate>Thu, 07 Dec 2023 11:18:05 GMT</pubDate>
            <description><![CDATA[<p>프로젝트의 Service 테스트중 repository의 쿼리를 줄일 수 있는 방법이 생각났다.
동적쿼리가 사용된 Repository를 개선해보자</p>
<h3 id="기존-코드">기존 코드</h3>
<p>매개변수로 <strong>객체의 타입</strong>과 해당 <strong>객체의 ID</strong>가 전달된다.
WordType은 PERFUME, BRAND, NOTE를 name으로 갖는 enum이다</p>
<pre><code class="language-java">List&lt;Word&gt; findByTypeAndTypeId(Long typeId, WordType wordType)</code></pre>
<br>
코드는 Word 테이블의 [perfume, brand, note] 열에서 타입과 맞는 열을 찾고, 해당 열과 ID값을 비교해 조회 결과를 LIST로 반환한다.

<pre><code class="language-java">public List&lt;Word&gt; findByTypeAndTypeId(Long typeId, WordType wordType){
    String jpql = &quot;SELECT w FROM Word w &quot; +
            &quot;LEFT JOIN w.brand b &quot; +
            &quot;LEFT JOIN w.note n &quot; +
            &quot;LEFT JOIN w.perfume p &quot; +
            &quot;WHERE w.wordType = :wordType &quot; +
            &quot;AND (b.id = :id OR n.id = :id OR p.id = :id)&quot;;

    return em.createQuery(jpql, Word.class)
            .setParameter(&quot;wordType&quot;, wordType)
            .setParameter(&quot;id&quot;, typeId)
            .getResultList();
}
</code></pre>
<p>Join을 사용했고, 결과를 정상적으로 반환 받을 수 있었다.
하지만 Hibernate에 의해 binding 되는 값이 4개이고, where절의 and와 or를 줄일 수 있을 것 같았다.</p>
<h3 id="수정된-코드">수정된 코드</h3>
<p>WordType의 name을 적극 활용하기로 했다.</p>
<p>WordType은 PERFUME, BRAND, NOTE를 name으로 갖기에, 
Word 테이블의 perfume, brand, note 열의 이름과 같다.(대문자,소문자 차이)</p>
<pre><code class="language-java">public List&lt;Word&gt; findByTypeAndTypeId(Long typeId, WordType wordType) {
        String jpql = &quot;select w from Word w where &quot; + &quot;w.&quot; +
                wordType.name().toLowerCase() +
                &quot;.id = :id&quot;;

        return em.createQuery(jpql, Word.class)
                .setParameter(&quot;id&quot;, typeId)
                .getResultList();
    }</code></pre>
<p>Word 테이블에서 Type에 맞는 열만을 비교하므로, 쿼리를 줄일 수 있었다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/7cadb8bf-4dfc-4c85-8ae5-5f0a3dbf1806/image.png" alt=""></p>
<h3 id="결론">결론?</h3>
<p>이 방법이 더 나은 로직인지는 확실치 않다. 
다만, 확실히 쿼리가 줄었고 고민해서 코드를 고안해냈으니 다음엔 더 좋은 방법이 떠오르지 않을까.. 하는 기대를 가져보자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ToDo] 20231207 정리]]></title>
            <link>https://velog.io/@wnso-kim/ToDo-20231207-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@wnso-kim/ToDo-20231207-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 07 Dec 2023 10:15:25 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘-해결해야-할-일">오늘 해결해야 할 일</h2>
<p><del>1. [PerfumePedia] Contoller Service 테스팅(진행중)</del>
2. [SSAFY] PT면접 주제(OTT) 내용 추가</p>
<h2 id="해결한-일">해결한 일</h2>
<h3 id="1-controller-service-테스팅">1. Controller Service 테스팅</h3>
<ul>
<li><p>WordRepository JPQL 쿼리 수정 </p>
<ul>
<li><p>Join 사용 대신, Enum의 값 자체를 사용하도록 수정했다.</p>
<pre><code class="language-java">public List&lt;Word&gt; findByTypeAndTypeId(Long typeId, WordType wordType) {
  String jpql = &quot;select w from Word w where &quot; + 
          &quot;w.&quot; + wordType.name().toLowerCase() +
          &quot;.id = :id&quot;;

  return em.createQuery(jpql, Word.class)
          .setParameter(&quot;id&quot;, typeId)
          .getResultList();
}</code></pre>
</li>
<li><p><a href="https://velog.io/@wnso-kim/Spring-Boot-JPA-JPQL-%EB%8F%99%EC%A0%81%EC%BF%BC%EB%A6%AC">[Spring Boot] JPA - JPQL 동적쿼리</a>에서 자세히 볼 수 있다.</p>
</li>
<li><p>AutoCompleteService 테스트 완료</p>
<ul>
<li>자동완성 기능에서 반환값 수정: <strong>별칭 값</strong>에서 <strong>이름 값</strong>으로 수정</li>
</ul>
</li>
<li><p>자동완성 기능 → 정상</p>
</li>
<li><p>가중치에 따른 정렬 → 정상</p>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] 커밋 순서가 바뀌었을 때]]></title>
            <link>https://velog.io/@wnso-kim/Git-%EC%BB%A4%EB%B0%8B-%EC%88%9C%EC%84%9C%EA%B0%80-%EB%B0%94%EB%80%8C%EC%97%88%EC%9D%84-%EB%95%8C</link>
            <guid>https://velog.io/@wnso-kim/Git-%EC%BB%A4%EB%B0%8B-%EC%88%9C%EC%84%9C%EA%B0%80-%EB%B0%94%EB%80%8C%EC%97%88%EC%9D%84-%EB%95%8C</guid>
            <pubDate>Sun, 26 Nov 2023 14:50:15 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-발생">문제 발생</h2>
<p>프로젝트 진행 중 A브랜치와 B브랜치를 병합하려 했는데 오류가 발생했다.
그리고 브랜치 그래프가 아래와 같았다.
(사실 오류는 다른 부분에서 발생했다. 방법은 <a href="#%ED%95%B4%EA%B2%B0">해결로!</a>)
<img src="https://velog.velcdn.com/images/wnso-kim/post/b887286f-361e-4862-9449-d89c0cf9ece2/image.png" alt=""></p>
<p>동일한 내용의 커밋이 각 브랜치에 따로 존재했다. 이유를 알아보자</p>
<h2 id="커밋-순서가-바뀌었다">커밋 순서가 바뀌었다.</h2>
<p>커밋을 자세히 보면 순서가 이상하다. 
브랜치별로 구분하기 편하도록 색상으로 나눴다. 
<img src="https://velog.velcdn.com/images/wnso-kim/post/8b708bee-79f0-49cd-a6e7-d9722ec9732b/image.png" alt=""></p>
<p><strong>refactor: AutoCompleteWordDto 수정</strong> 커밋 내용이
<span style="color:lightgreen">A브랜치</span>에서는 <strong>feat: Service 기반 작성</strong> 커밋 보다 하위에 위치하고
<span style="color:lightblue">B브랜치</span>에서는 <strong>feat: Service 기반 작성</strong> 커밋 보다 상위에 위치한다.</p>
<h3 id="왜-바뀌었지">왜 바뀌었지?</h3>
<p>왜 이러한 문제가 발생한건지 chatGPT에 물어봤는데 아래와 같았다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/5f301ce6-c35b-468e-b525-6a9374905089/image.png" alt=""></p>
<p>chatGPT의 답변을 기반으로 했을때, 가능성 있는건 Rebase, Amend, ForcePush이다.
확실치는 않지만 commit중 amend를 진행한 적이 있는데, 그때 순서가 바뀌었을 것이라고 추측한다.</p>
<h3 id="해결">해결</h3>
<p><span style="color:orange">기본적으로 커밋 순서가 다르다 해서 git의 병합이 실패하지는 않는다.
그냥 merge해도 잘 작동한다.</span></p>
<p>나도 병합할 때 오류가 발생해서 원인이 커밋 순서에 있는 줄 알았지만, 사실 문제는 따로 있었다.</p>
<pre><code># Conflicts:
#    src/main/java/com/perfumepedia/PerfumePedia/service/SearchResultService.java</code></pre><p>각 브랜치에서 SearchResultService.java 코드가 둘 다 수정되어 오류가 발생했을 뿐... 
문제를 알았으니 됐다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ToDo] 20231126 정리]]></title>
            <link>https://velog.io/@wnso-kim/ToDo-20231126-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@wnso-kim/ToDo-20231126-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 26 Nov 2023 11:26:51 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘-해야할-일">오늘 해야할 일</h2>
<p><del>1. [PerfumePedia] Controller 브랜치 병합 충돌 해결(완료)</del>
2. [PerfumePedia] Service 기반 작성</p>
<h2 id="해결한-일">해결한 일</h2>
<h3 id="1-controller-브랜치-병합-충돌">1. Controller 브랜치 병합 충돌</h3>
<ul>
<li><p>동일한 브랜치를 팀원과 같이 수정하다보니, 브랜치 병합에서 충돌이 일어났다.
git에서 병합오류와, 사진과 같이 같은 내용의 커밋이 2번 나타났다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/80320df2-3bee-409e-aaf4-bf47b0b97b5a/image.png" alt=""></p>
</li>
<li><p>git rebase를 통해 main과 병합 시켜 해결했다.
<a href="https://velog.io/@wnso-kim/Git-%EC%BB%A4%EB%B0%8B-%EC%88%9C%EC%84%9C%EA%B0%80-%EB%B0%94%EB%80%8C%EC%97%88%EC%9D%84-%EB%95%8C">[Git] 원격 저장소의 브랜치가 origin에 없을 때 해결</a></p>
</li>
<li><p>모든 브랜치를 다 rebase 시켜서 늘어난 내 PR은 덤..
<img src="https://velog.velcdn.com/images/wnso-kim/post/f3488d83-8ec9-40cc-a772-82f32273c947/image.png" alt=""></p>
<br>

</li>
</ul>
<h3 id="2-service-기반-작성">2. Service 기반 작성</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ToDo] 20231124 정리]]></title>
            <link>https://velog.io/@wnso-kim/ToDo-20231124-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@wnso-kim/ToDo-20231124-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 24 Nov 2023 07:10:49 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘-해야할-일">오늘 해야할 일</h2>
<p><del>1. [PerfumePedia] React - Spring boot Cors 해결 (완료)</del>
<del>2. [PerfumePedia] Controller를 위한 Service 기반 작성 (진행중)</del></p>
<h2 id="해결한-일">해결한 일</h2>
<h3 id="1-react---spring-boot-cors-해결">1. React - Spring boot Cors 해결</h3>
<ul>
<li><p>React에 아래의 코드 추가</p>
<pre><code class="language-jsx">const { createProxyMiddleware } = require(&#39;http-proxy-middleware&#39;);</code></pre>
</li>
<li><p>[Spring &amp; React 개발환경 연동] (<a href="https://velog.io/@kyeongzow/Spring-React-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%97%B0%EB%8F%99)%EC%97%90%EC%84%9C">https://velog.io/@kyeongzow/Spring-React-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%97%B0%EB%8F%99)에서</a> 자세히 볼 수 있음(경은 땡큐)</p>
<br>

</li>
</ul>
<h3 id="2-controller를-위한-service-기반-작성">2. Controller를 위한 Service 기반 작성</h3>
<ul>
<li><p>Dto를 감싸기 위한 Record ResponseData 생성
Record에 대해 공부 필요.    </p>
<pre><code class="language-java">public record ResponseData&lt;T&gt;(T data) {
  /**
   * 생성 및 초기화 진행
   * @param data T 객체
   */
  public ResponseData {
  }
}
</code></pre>
</li>
<li><p>자동완성 서비스 작성</p>
<pre><code class="language-java">@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class AutoCompleteService {

  private final WordRepository wordRepository;

  /**
   * 사용자가 입력한 keyword에 해당하는 검색어목록을 반환하는 함수입니다.
   * &lt;p&gt; word 엔티티로부터 weight 기준 내림차순으로 반환합니다.
   * @param keyword 사용자가 입력한 keyword입니다. 예) 조말론
   * @return AutoCompleteWordDto 객체를 반환합니다.
   */
  public ResponseData findKeywords(String keyword){
      AutoCompleteWordDto autoCompleteWordDto = new AutoCompleteWordDto();

      // WordRepository #findByAlias 사용
      // #findByAlias에 의해 Word 객체 List 반횐됨
      // 자동으로 5개 이하의 결과가 반환되며, desc sort 돼 있음
      List&lt;Word&gt; words = wordRepository.findByAlias(keyword);

      // AutoCompleteWordDto #set 메소드들을 활용해 초기화 진행

      // ResponseData 객체화
      ResponseData data = new ResponseData(autoCompleteWordDto);

      // 객체화한 ResponseData 반환
      return data;
  }
}</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ToDo] 20231121 정리]]></title>
            <link>https://velog.io/@wnso-kim/ToDo-20231121-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@wnso-kim/ToDo-20231121-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 21 Nov 2023 11:45:42 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘-해결해야할-일">오늘 해결해야할 일</h2>
<p><del>1. [PerfumePedia] Repository 코드 작성(완료)</del>
<del>2. [PerfumePedia] Service 코드 틀 작성(완료)</del></p>
<h2 id="해결한-일">해결한 일</h2>
<h3 id="1-repository-코드-작성">1. Repository 코드 작성</h3>
<ul>
<li>@Repository 를 사용해 작성 완료</li>
<li><a href="https://joyous-entrance-cb7.notion.site/2e625795affc492a9889b34c5233ca2d?pvs=4">Notion - Repository 설계 참고</a></li>
<li>추후 extends로 수정 여부 판별 및 test 예정</li>
</ul>
<h3 id="2-service-코드-틀-작성">2. Service 코드 틀 작성</h3>
<ul>
<li>수집한 데이터를 DB에 저장하기 위한 Service 작성<ul>
<li>Brand Service</li>
<li>Note Service</li>
<li>Perfume Service</li>
<li>PerfumeNote Service</li>
<li>Word Service</li>
</ul>
</li>
</ul>
<ul>
<li>깃 원격 저장소 새로운 브랜치 생성시 local 파일의 remote와 연동되지 않는 문제 해결
<a href="https://velog.io/@wnso-kim/Git-%EC%9B%90%EA%B2%A9-%EC%A0%80%EC%9E%A5%EC%86%8C%EC%9D%98-%EB%B8%8C%EB%9E%9C%EC%B9%98%EA%B0%80-origin%EC%97%90-%EC%97%86%EC%9D%84-%EB%95%8C-%ED%95%B4%EA%B2%B0">[Git] 원격 저장소의 브랜치가 origin에 없을 때 해결</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] 원격 저장소의 브랜치가 origin에 없을 때 해결]]></title>
            <link>https://velog.io/@wnso-kim/Git-%EC%9B%90%EA%B2%A9-%EC%A0%80%EC%9E%A5%EC%86%8C%EC%9D%98-%EB%B8%8C%EB%9E%9C%EC%B9%98%EA%B0%80-origin%EC%97%90-%EC%97%86%EC%9D%84-%EB%95%8C-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@wnso-kim/Git-%EC%9B%90%EA%B2%A9-%EC%A0%80%EC%9E%A5%EC%86%8C%EC%9D%98-%EB%B8%8C%EB%9E%9C%EC%B9%98%EA%B0%80-origin%EC%97%90-%EC%97%86%EC%9D%84-%EB%95%8C-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Tue, 21 Nov 2023 06:53:31 GMT</pubDate>
            <description><![CDATA[<p>PerfumePedia 프로젝트 진행 중 브랜치를 새로 생성하고 작업해야할 일이 생겼다.</p>
<p>원격 저장소에 &#39;feature/service&#39; 브랜치를 생성했고, 이를 이용해서 local 브랜치도 생성할 생각이었다.</p>
<p>하지만, 기존에 작업하던 파일에는 새로 생성한 &#39;feature/service&#39; 브랜치가 보이지 않았다...
<img src="https://velog.velcdn.com/images/wnso-kim/post/bedf4700-2de0-4e1a-9d6f-34867a75f427/image.png" alt=""></p>
<h2 id="멍청한-해결clone">멍청한 해결(Clone)</h2>
<p>원격 저장소에서 새로 Clone 해온다!
간단 명료하다.</p>
<p>기존에 작업하던걸 Commit &amp; Push 하고, 새로 Clone 하면 된다.
근데.. 이럴거면 Git 왜 쓰지?</p>
<h2 id="해결update">해결(Update)</h2>
<p>origin을 업데이트 시키면 된다. </p>
<p>커맨드 라인으로 처리한다면 아래의 명령어를 입력하면 된다.</p>
<pre><code>git remote update</code></pre><br>

<p>Intellij를 사용중이라면 하단 오른쪽 Git Branches에서 <span style="color:lightblue">파란색 화살표</span>를 클릭하면 된다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/ed395fc6-6043-4203-afc9-4bbec4565911/image.png" alt=""></p>
<p>그럼 아래와 같이 Remote Branches에 &#39;feature/service&#39;가 보인다!
<img src="https://velog.velcdn.com/images/wnso-kim/post/4012fc63-a2a8-499a-b9b2-1f568966f85c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PerfumePedia] JPA Service 설계]]></title>
            <link>https://velog.io/@wnso-kim/PerfumePedia-JPA-Service-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@wnso-kim/PerfumePedia-JPA-Service-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Mon, 20 Nov 2023 10:31:03 GMT</pubDate>
            <description><![CDATA[<p>기존 역할 배분은 이랬다.
나: Domain, Repository, Service 작성, Swagger 작성
재우: MySQL과 수집한 데이터 삽입에 대한 코드 작성
경은: API Controller 및 DTO 작성</p>
<p>이 중 Service 작성을 재우에게 맡기기로 했다.</p>
<h2 id="service의-역할">Service의 역할</h2>
<h3 id="1-수집-데이터-저장">1. 수집 데이터 저장</h3>
<p>Domain과 Repository를 기반으로 수집한 데이터 저장 로직을 처리해야한다.</p>
<ul>
<li>Json파일을 읽고, 데이터베이스에 저장해야하는데 이 과정에서 검증이 필요하다.</li>
<li>기존에 데이터베이스에 저장된 내용인지, 수정된 내용이 있는지 확인 해야한다.</li>
</ul>
<br>

<h3 id="2-dto-생성-및-초기화">2. DTO 생성 및 초기화</h3>
<p>데이터베이스에 저장된 데이터를 DTO에 저장하고, 반환할 수 있어야한다.</p>
<ul>
<li>Repository를 이용해 데이터베이스에 저장된 값을 Domain으로 읽고 이를 DTO에 저장해야한다.</li>
<li>Controller에 따라 필요한 DTO가 다르므로, Controller와 상의해서 미리 DTO를 설계 해야한다. -&gt; 이부분은 경은이가 미리 작성해 뒀다.<img src="https://velog.velcdn.com/images/wnso-kim/post/9cc71f3a-0839-427b-9c5f-629b3f8765b1/image.png" alt=""></li>
</ul>
<h2 id="고려사항">고려사항</h2>
<h3 id="1-tdd">1. TDD</h3>
<p>코드 작성시 누락된 부분이 생길 수 있으므로 TDD를 기반으로 작성한다.</p>
<ul>
<li>기능 테스트 케이스를 작성한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/93b181a5-5bf0-48f4-9c26-4f10a90dc039/image.png" alt=""></li>
<li>Service 코드를 아직 작성하지 않았으니, 오류가 발생한다.</li>
<li>기본적인 Service 코드를 작성하고, 오류를 해결한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/2b1b5efb-01bb-4651-8c83-35b7cbf5ce2a/image.png" alt=""></li>
<li>refactoring을 통해 코드를 개선한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/639ad202-4139-41e7-a7e9-b95d0efbb10e/image.png" alt=""></li>
<li>다음 기능을 위와 같이 반복하여 작성한다.</li>
</ul>
<h3 id="2-주석작성">2. 주석작성</h3>
<p>자바의 /**/ 주석을 이용하여 작성한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/13f31076-2437-4639-b5de-56bd05c1b7d1/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/0235804b-c729-42ba-986d-2349d5d84c93/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ToDo] 20231120 정리]]></title>
            <link>https://velog.io/@wnso-kim/ToDo-20231120-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@wnso-kim/ToDo-20231120-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 20 Nov 2023 08:52:00 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘-해야할-일">오늘 해야할 일</h2>
<p><del>1. 카카오 인턴십 지원(완료)</del>
<del>2. PerfumePedia 프로젝트 feature/domain 병합(완료)</del>
<del>3. PerfumePedia 프로젝트 Service 설계 필수 내용 작성 및 전달(Service 코드 팀원이 작성 예정)(완료)</del>
4. PerfumePedia 프로젝트 Repository 코드 작성</p>
<h2 id="해결한-일">해결한 일</h2>
<h3 id="1-카카오-인턴십-지원">1. 카카오 인턴십 지원</h3>
<ul>
<li>서버 포지션으로 지원 완료<br>

</li>
</ul>
<h3 id="2-perfumepedia-프로젝트-featuredomain-병합">2. PerfumePedia 프로젝트 feature/domain 병합</h3>
<ul>
<li>feature/domain 브랜치를 main 브랜치로 병합 완료
<img src="https://velog.velcdn.com/images/wnso-kim/post/7ad25d39-901b-4985-84f9-3102313d33be/image.png" alt=""></li>
<li>MySQL Workbanch에서 스키마 및 테이블 생성 확인 완료
<img src="https://velog.velcdn.com/images/wnso-kim/post/3d5ad91d-9151-4792-bb0b-61304468de44/image.png" alt=""><br>

</li>
</ul>
<h3 id="3-perfumepedia-프로젝트-service-설계-필수-내용-작성-및-전달">3. PerfumePedia 프로젝트 Service 설계 필수 내용 작성 및 전달</h3>
<ul>
<li>설계에 필요한 TDD와 주석 관련 내용 정리
<a href="https://velog.io/@wnso-kim/PerfumePedia-JPA-Service-%EC%84%A4%EA%B3%84">[PerfumePedia] JPA Service 설계</a> </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] JPA - Entity 설계에서 Long과 long의 차이]]></title>
            <link>https://velog.io/@wnso-kim/Spring-Boot-JPA-Entity-%EC%84%A4%EA%B3%84%EC%97%90%EC%84%9C-Long%EA%B3%BC-long%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@wnso-kim/Spring-Boot-JPA-Entity-%EC%84%A4%EA%B3%84%EC%97%90%EC%84%9C-Long%EA%B3%BC-long%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Wed, 15 Nov 2023 11:14:01 GMT</pubDate>
            <description><![CDATA[<p>Spring boot JPA를 활용한 단위 테스트 중 무의식 중에 assertNull을 사용했는데</p>
<p><strong>내가 왜 이걸 사용했지? 그리고 왜 id를 long이 아닌 Long이지?</strong>
라는 의문이 들었다.</p>
<pre><code class="language-java">    @Entity
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class Perfume {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = &quot;PERFUME_ID&quot;)
        private Long id;
    }
</code></pre>
<pre><code class="language-java">    @Test
    public void ID_자동생성_확인() throws Exception{
        //given
        String name = &quot;향수이름&quot;;

        //when
        Perfume perfume = new Perfume(name);

        //then
        assertNull(&quot;id는 자동 생성됩니다.&quot;, perfume.getId());
    }</code></pre>
<h3 id="long과-long의-차이">Long과 long의 차이</h3>
<p>long은 원시 타입(primitive type), Long은 래퍼 클래스(Wrapper class) 이다.</p>
<p>id를 long으로 사용하는 경우, 별도의 초기화 과정이 없다면 0으로 초기화 된다.
반면, Long으로 사용하는 경우엔 초기값은 Null이다.</p>
<br>
즉, id타입으로 Long을 사용하는 경우 Null이라면, id가 없다는 것을 보장할 수 있다

<p>그래서 test case에서 assertNull을 사용했다!</p>
<h3 id="참조">참조</h3>
<p><a href="https://www.inflearn.com/questions/35759/long-%ED%83%80%EC%9E%85%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A7%88%EB%AC%B8%EC%9E%85%EB%8B%88%EB%8B%A4">인프런 질문 - Long 타입에 대한 질문입니다.</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ToDo] 231115 정리]]></title>
            <link>https://velog.io/@wnso-kim/ToDo-231115-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@wnso-kim/ToDo-231115-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 15 Nov 2023 05:01:10 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘-해야할-일">오늘 해야할 일</h2>
<p><del>1. 당근 Winter Tech Internship 지원하기(완료)</del>
<del>2. PerfumePedia 프로젝트 Domain 코드 작성(완료)</del>
3. PerfumePedia 프로젝트 Repository 코드 작성
3. PerfumePedia 프로젝트 Service 설계</p>
<h2 id="해결한-일">해결한 일</h2>
<h3 id="1-당근-winter-tech-internship-지원하기">1. 당근 Winter Tech Internship 지원하기</h3>
<h3 id="2-perfumepedia-프로젝트-domain-코드-작성">2. PerfumePedia 프로젝트 Domain 코드 작성</h3>
<p>Domain 작성 중 정리한 내용</p>
<ul>
<li><a href="https://velog.io/@wnso-kim/Spring-Boot-JPA-Entity-%EC%84%A4%EA%B3%84%EC%97%90%EC%84%9C-Long%EA%B3%BC-long%EC%9D%98-%EC%B0%A8%EC%9D%B4">[Spring Boot] JPA - Entity 설계에서 Long과 long의 차이</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NextBus] 데이터 전처리, 데이터베이스 설계]]></title>
            <link>https://velog.io/@wnso-kim/NextBus-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%84%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@wnso-kim/NextBus-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%84%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Sat, 28 Oct 2023 10:32:24 GMT</pubDate>
            <description><![CDATA[<h3 id="1-데이터-전처리">1. 데이터 전처리</h3>
<p>[<a href="https://www.notion.so/5d8ca7de205e4587bd09bb6e689f839f?pvs=21">데이터 전처리</a>]에서 자세히 볼 수 있다.</p>
<p>수집한 데이터를 기반으로 학생들이 이용하는 정류소와 이를 경유하는 노선을 추렸다.
정류소 8개와 이를 경유하는 노선 8개이다.</p>
<p><strong>정류소</strong></p>
<ul>
<li>중화중학교삼거리(면목본동파출소 방면)</li>
<li>용마문화복지센터(서일대학교 방면)</li>
<li>용마문화복지센터(종점 방면)</li>
<li>서일대정문(중화중학교삼거리 방면)</li>
<li>서일대학교(혜원여중고후문 방면)</li>
<li>서일대학교(용마문화복지센터 방면)</li>
<li>새마을금고(서일대정문 방면)</li>
<li>공판장(새마을금고 방면)</li>
</ul>
<p><strong>노선</strong></p>
<ul>
<li>271</li>
<li>320</li>
<li>1213</li>
<li>2012</li>
<li>2013</li>
<li>2212</li>
<li>2230</li>
<li>중랑02</li>
</ul>
<p>이에 대한 정보를 JSON파일로 저장했다. 형식은 아래와 같다.</p>
<pre><code class="language-json">{
  &quot;bus_stops&quot;: [
    {
      &quot;stop_name&quot;: &quot;정류소 이름&quot;,
      &quot;stop_number&quot;: &quot;정류소 번호&quot;,
            &quot;x_coordinate&quot;: &quot;정류소 x 좌표 값&quot;,
            &quot;y_coordinate&quot;: &quot;정류소 y 좌표 값&quot;,
      &quot;next_stop_name&quot;: &quot;다음 정류소 이름&quot;,
      &quot;routes&quot;: [
        {
          &quot;route_name&quot;: &quot;노선명&quot;,
          &quot;route_number&quot;: &quot;노선 고유번호&quot;,
                    &quot;departure&quot;:  &quot;기점 출발여부&quot;
        },
        {
          &quot;route_name&quot;: &quot;노선명&quot;,
          &quot;route_number&quot;: &quot;노선 고유번호&quot;,
                    &quot;departure&quot;:  &quot;기점 출발여부&quot;
        }, ...(다음 노선)...
      ]
    },
    ...(다음 정류소)...
  ]
}</code></pre>
<h3 id="2-데이터베이스-설계">2. 데이터베이스 설계</h3>
<p>[<a href="https://www.notion.so/e27e4aff938d447cac9ddeea2f123c61?pvs=21">데이터베이스 설계</a>]에서 자세히 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/22cc0413-e5cc-4eb7-b59a-99c0ec8caa82/image.png" alt=""></p>
<p>“정류소”와 “노선”에 대해 테이블을 설계했다.
“정류소”와 “노선”의 다대다 관계를 해결하기 위해 “경유노선” 테이블을 설계했다.</p>
<p>기점 출발 노선인 경우 BIS를 사용해 정보를 요청 하더라도 대부분 “도착 정보 없음” 이라는 값만 출력되므로 이를 해결하기 위해 “배차정보”테이블을 설계했다.
“경유노선”의 ‘기점 출발 노선’ 값이 true인 경우에 한하여 “배차정보”테이블에 값을 채울 예정이다.</p>
<p>20초마다 BIS에 접속해 배차가 되었는지 값을 얻어와 ‘접속시간’, ‘남은시간’을 저장한다.
이를 이용해 인공지능을 학습해 남은시간을 예측한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NextBus] 프로젝트 기획]]></title>
            <link>https://velog.io/@wnso-kim/NextBus-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%ED%9A%8D</link>
            <guid>https://velog.io/@wnso-kim/NextBus-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%ED%9A%8D</guid>
            <pubDate>Fri, 27 Oct 2023 14:32:04 GMT</pubDate>
            <description><![CDATA[<h3 id="시작">시작</h3>
<p>매번 버스를 통해 통학하면서 불편한점이 있다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/89da0706-7138-467d-b4f4-cae951ceb7b2/image.png" alt=""></p>
<p>도착 정보가 없다.... 아무리 새로고침을 해도 버스가 언제 올지 알 수가 없다..
학교 바로앞 버스 정류소가 기점이기에, 내가 타는 2013번 버스는 언제 올지 알 수 없고 나는 그저 기다렸다.</p>
<p>3년동안 너무나 불편했다 그러니까 내가 고쳐야지</p>
<p>모바일 기반 웹 서비스를 만들어보자!
우리대학 주변 정류소를 경유하는 버스의 도착 예정 시간을 알려주고, 도착 정보가 없다면 인공지능의 힘을 빌려보자</p>
<h2 id="1-문제점-파악">1. 문제점 파악</h2>
<p><a href="https://m.blog.naver.com/koti10/221993667772"><strong>버스 운행정보 소외지역을 위한 버스정보시스템(BIS) 개선에 대한 제언</strong></a> 을 참고해 작성했다.</p>
<h3 id="11-bis버스정보시스템의-한계">1.1. BIS(버스정보시스템)의 한계</h3>
<p>각 지자체에서는 버스정보시스템(Bus Information System, 이하 BIS)를 제공하고있다.
BIS는 해당 정류소 이전에 운행중인 버스가 있는 경우에 한하여 버스 도착예정 시간을 제공한다.</p>
<p>이는 정류소 이전에 운행중인 버스가 없는 경우, 버스의 도착예정시간을 제공할 수 없다는 문제가 있다.</p>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/8e694d9e-abff-4929-af8c-68af763a3f8c/image.png" alt=""></p>
<p>기점 주변 정류소는 이러한 문제로 인해 도착예정정보를 얻지 못한채 “도착 예정 정보 없음”이라는 문구만을 반복한다.</p>
<h3 id="22-서일대학교-주변-버스">2.2. 서일대학교 주변 버스</h3>
<p>서일대학교의 경우 근처 2개의 기점이 존재한다.
<strong>진로아파트앞.종점</strong>과 <strong>용마문화복지센터</strong>
<img src="https://velog.velcdn.com/images/wnso-kim/post/14ce0b09-01a3-489d-b5db-3338871fca36/image.png" alt=""></p>
<p>2개의 기점으로부터 출발하는 버스는 <strong>총 4개</strong>이다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/91893a82-0ce2-4b64-8d7c-22bfcf7f59d0/image.png" alt=""></p>
<h3 id="33-결론">3.3. 결론</h3>
<p>서일대학교를 중랑02, 271, 1213, 2013을 이용해 통학하는 학생들의 경우
버스의 도착예정시간을 확인하지 못한채 기다리는 경우가 허다하다.</p>
<p>서울의 경우 버스의 운행시간과 배차간격에 대한 정보만을 제공하기에, 기점 주변 정류소는 도착 예정 시간을 제공할 수 없고 이로인해 해당 정류소를 이용하는 사람들은 기다림에만 집중할 수 밖에 없다.</p>
<p>대략적인 도착 예정 시간을 알 수 있다면 다른 교통수단을 이용하거나 시간을 효율적으로 사용할 수 있을 것이다.</p>
<p>그렇기에 기점에서 출발하는 버스의 시간을 예측하는 서비스가 필요하다.</p>
<h2 id="2-서비스-기능">2. 서비스 기능</h2>
<ul>
<li><p>정류소 위치 제공</p>
<ul>
<li>네이버 혹은 카카오 지도를 활용해 주변 정류소 위치 제공</li>
</ul>
</li>
<li><p>버스 도착 예정 시간 제공</p>
<ul>
<li>정류소별 버스 도착 예정 시간 제공</li>
<li>BIS를 통해 예정 시간을 제공할 수 있는 경우, BIS 활용</li>
</ul>
</li>
<li><p>버스 도착 예측 시간 제공</p>
<ul>
<li>BIS를 통해 제공할 수 없는 경우, 인공지능을 통해 예측</li>
<li>예측 시간 제공시, 예측 확률 같이 제공</li>
<li>ℹ️와 같은 아이콘을 추가해 예측 확률을 확인할 수 있도록 함</li>
</ul>
</li>
<li><p>선호 정류소 및 노선 저장 기능</p>
<ul>
<li>사용자가 선호하는 정류소를 체크한 경우 웹 페이지 접속 시 해당 정류소를 먼저 제공함</li>
<li>사용자가 선호하는 노선이 있는 경우 정류소 선택 시 해당 노선의 정보가 최상단에 위치함</li>
</ul>
</li>
</ul>
<blockquote>
<p>사실 기능상 별 내용은 없다. BIS를 사용해서 도착 정보를 제공하는 서비스는 워낙 많기 때문.</p>
</blockquote>
<p>주 기능은 예측 시간 제공에 있다. 예정이아닌 &#39;예측&#39;! 
인공지능이 잘 작동하리라 믿어보자..</p>
<h2 id="3-예상-레이아웃">3. 예상 레이아웃</h2>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/55135a96-e5de-4599-97da-bc11cd2ded56/image.png" alt=""></p>
<p>왼쪽 레이아웃 선택해 제작하기로 했다. </p>
<blockquote>
<p>오른쪽은 기획하자마자 내가 디자인 한건데
동기가 공대냄새 난다고 바꿔줬다. 고마워...</p>
</blockquote>
<h2 id="4-데이터-수집">4. 데이터 수집</h2>
<p><a href="https://www.data.go.kr/">공공데이터</a>와 네이버 지도에서 버스 정류소와 버스에 대한 정보를 수집했다. </p>
<h3 id="41-file-버스-노선별-id-수집">4.1. FILE: 버스 노선별 ID 수집</h3>
<ul>
<li><a href="https://www.data.go.kr/data/15051743/fileData.do">공공데이터 - 서울특별시_버스노선ID 정보</a>에서 버스 노선별 ID값이 포함된 엑셀 파일 수집 했다.</li>
<li>2023년 10월 26일자 최신 데이터 다운로드</li>
<li>파일 이름은 [서울시버스노선ID정보(20231026)]이며, 내용은 아래와 같다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/021631f8-9ef8-4489-b7e9-5e3ccd1c653f/image.png" alt=""></li>
</ul>
<h3 id="42-file-버스-정류소-id-수집">4.2. FILE: 버스 정류소 ID 수집</h3>
<ul>
<li><a href="https://www.data.go.kr/data/15051741/fileData.do">서울특별시_버스정류소 좌표 데이터</a>에서 버스 정류소별 번호값이 포함된 엑셀 파일 수집했다.</li>
<li>2023년 10월 26일자 최신 데이터 다운로드</li>
<li>파일 이름은 [서울시버스정류소위치정보(20231026)]이며, 내용은 아래와 같다.<img src="https://velog.velcdn.com/images/wnso-kim/post/3ec84c69-a294-4ec2-b8e7-df44e7f4e932/image.png" alt=""></li>
</ul>
<h3 id="43-api-노선의-전체-정류소-도착-예정-정보-조회">4.3. API: 노선의 전체 정류소 도착 예정 정보 조회</h3>
<ul>
<li><a href="https://www.data.go.kr/data/15000314/openapi.do">서울특별시_버스도착정보조회 서비스</a>에서 노선별 정류소 도착 예정 정보를 얻을 수 있다.</li>
<li>한 번의 검색을 통해 한 노선(버스)의 전체 위치를 확인할 수 있다.</li>
<li>API의 [getArrInfoByRouteAll] 이용
<img src="https://velog.velcdn.com/images/wnso-kim/post/7bb5c277-4131-4fc8-b2e0-4ca326c29969/image.png" alt=""></li>
</ul>
<h3 id="44-api-정류소-버스-도착-정보--조회">4.4. API: 정류소 버스 도착 정보  조회</h3>
<ul>
<li><a href="https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15000303">서울특별시_정류소정보조회 서비스</a>에서 정류소별 버스 도착 정보를 얻을 수 있다.</li>
<li>API의 [getStationByUid] 이용
<img src="https://velog.velcdn.com/images/wnso-kim/post/f686c2b9-0226-42fc-a5b0-eb9d422fcb5e/image.png" alt=""></li>
</ul>
<h3 id="45-기타">4.5. 기타</h3>
<p>학교 근처에 어떠한 정류소와 노선이 있는지 확인하기 위해 네이버 지도를 활용했다.</p>
<ul>
<li>정류소: 중화중학교삼거리[07436: 면목본동파출소 방면]<ul>
<li>노선: 320, 2012, 중랑02</li>
</ul>
</li>
<li>정류소: 면목본동파출소[07315: 중화중학교.다문화가족지원센터 방면]<ul>
<li>노선: 320, 2012</li>
</ul>
</li>
<li>정류소: 용마문화복지센터[07195: 서일대학교 방면]<ul>
<li>노선: 271, 1213, 2013, 2212, 2230, 중랑02
등등 여러개</li>
</ul>
</li>
</ul>
<h2 id="마무리">마무리</h2>
<p>사실 9월에 이 프로젝트를 시작 했다가 바로 폐기했었다. 기획없이 바로 도메인부터 우다다 작성했었는데 서비스에서 생각해야될게 많아지니까 하기가 싫어지더라..</p>
<p>현재 팀 프로젝트를 하는 것이 있는데, 기획부터 쌓아올리니까 팀원들이랑 소통도 잘 되고 고려해야할게 명확히 보이니까 오히려 편했다.</p>
<p>그래서 이번에 기획, 설계부터 차근차근 시작해보려 한다.</p>
<p>인공지능을 잘 학습시킬 수 있을지 모르겠지만.. 우선 해보자!</p>
<h3 id="프로젝트-노션-주소">프로젝트 노션 주소</h3>
<p><a href="https://incredible-crepe-3bf.notion.site/NextBus-031254ddf37b4ed7be89dddb88287164?pvs=4">여기!</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] 카카오 OAuth2.0 체험하기]]></title>
            <link>https://velog.io/@wnso-kim/Spring-Boot-%EC%B9%B4%EC%B9%B4%EC%98%A4-OAuth2.0-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@wnso-kim/Spring-Boot-%EC%B9%B4%EC%B9%B4%EC%98%A4-OAuth2.0-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 26 Oct 2023 06:28:42 GMT</pubDate>
            <description><![CDATA[<p>로그인 및 회원가입 기능 개발시 보안을 위해 OAuth를 사용할 수 있다.
다음 프로젝트를 위해 미리 사용해보고 정리해보자.</p>
<p><a href="https://developers.kakao.com/docs/latest/ko/kakaologin/common">카카오 로그인 공식문서</a>와 <a href="https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-code">카카오 로그인 REST API</a>에서 OAuth2.0 로그인에 대해 자세한 내용을 볼 수 있다.</p>
<h2 id="1-카카오-개발자-페이지">1. 카카오 개발자 페이지</h2>
<h3 id="11-회원가입-및-로그인">1.1. 회원가입 및 로그인</h3>
<p><a href="https://developers.kakao.com/">Kakao Developers</a>에 접속해 회원가입을 하고 로그인한다.</p>
<h3 id="12-애플리케이션-추가">1.2. 애플리케이션 추가</h3>
<p>카카오 개발자 기능을 사용하기 위해 애플리케이션을 추가한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/be14c553-4ea6-4263-8896-6253f4b2c101/image.png" alt=""></p>
<blockquote>
<p>아이콘은 선택사항이며, 추후 비지니스 앱으로 전환시 아이콘은 필수다.
비지니스 앱으로 전환시 사용자의 정보를 더 요구할 수 있다.(이메일, 이름 등..)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/dd9248aa-5c52-498d-a840-7708e4aebb4c/image.png" alt=""></p>
<p><strong>앱 설정 &gt; 앱 키</strong>에서 Rest API 키를 확인할 수 있다.
Rest API 키가 서버에서 사용할 client_id값으로 사용된다.</p>
<h3 id="13-로그인-활성화-및-redirect-uri-설정">1.3. 로그인 활성화 및 Redirect URI 설정</h3>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/8739eef0-2b8e-4a7e-abd4-2aed9074f28c/image.png" alt=""></p>
<p><strong>제품 설정 &gt; 카카오 로그인</strong>에서 활성화를 설정하고 Redirect URI를 설정한다.
local 환경에서 테스트하기 위해 host와 port를 localhost와 8080으로, path는 /local/oauth2/code/kakao로 설정한다.</p>
<h3 id="14-보안-설정">1.4. 보안 설정</h3>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/45d8fc44-b2b9-4fb3-a465-97187292e1de/image.png" alt=""></p>
<p><strong>제품 설정 &gt; 보안</strong>에서 client secret을 발급받고, 활성화 상태를 사용함으로 설정한다.
서버에서 client_secret 값으로 사용된다.</p>
<h3 id="15-동의항목-설정">1.5. 동의항목 설정</h3>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/2a1dd690-1a80-4233-b602-5949b1cce3e8/image.png" alt=""></p>
<p><strong>제품 설정 &gt; 동의항목</strong>에서 필수 동의 항목과 선택 동의 항목을 설정한다.
닉네임과 프로필 사진은 접근 권한이 있으나 이메일, 이름 등 사용자의 정보를 더 얻고싶다면 비지니스 앱으로 등록해야한다.</p>
<blockquote>
<p>아이콘을 등록하면 이메일까지는 얻을 수 있지만 그 이후에는 사업장을 등록해야하더라.. 우선 이메일까지만 해보자..</p>
</blockquote>
<h2 id="2-spring-boot-설정">2. Spring boot 설정</h2>
<h3 id="21-의존성-추가">2.1. 의존성 추가</h3>
<p>설정한 의존성은 아래와 같다.</p>
<ul>
<li>Spring Web</li>
<li>Thymeleaf</li>
<li>Spring Security</li>
<li>OAuth2 Client</li>
</ul>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/c2f97f00-c1c7-454c-9125-e6084d4acf8b/image.png" alt=""></p>
<h3 id="22-yml-수정">2.2. yml 수정</h3>
<pre><code class="language-yml">spring:
  security:
    oauth2:
      client:
        provider:
          kakao:
            authorization-uri: https://kauth.kakao.com/oauth/authorize
            token-uri: https://kauth.kakao.com/oauth/token
            user-info-uri: https://kapi.kakao.com/v2/user/me
            user-name-attribute: id
        registration:
          kakao:
            client-id: 여기에 Client Id 입력
            client-secret: 여기에 Security Id 입력
            client-authentication-method: client_secret_post
            redirect-uri: https://localhost:8080/login/oauth2/code/kakao
            authorization-grant-type: authorization_code
            client-name: kakao
            scope:
              - profile_nickname
              - profile_image
              - account_email(필요시 동의항목에서 설정한 ID 임력)</code></pre>
<p>client-id와 security-id, redirect-uri를 개발자 페이지에서 설정한 것과 같은 값으로 설정하면 된다.</p>
<blockquote>
<p>마지막 scope 값은 동의항목에서 필수 또는 선택으로 설정한 항목의 ID를 입력하면 된다.
비지니스 앱을 등록한 경우 profile 외의 다른 값들을 요청할 수 있다.</p>
</blockquote>
<h3 id="23-로그인-페이지-렌더링-contoller와-html">2.3. 로그인 페이지 렌더링: Contoller와 html</h3>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/25c40505-6c10-4070-9f2b-9096b040bbcc/image.png" alt=""></p>
<p>LoginContoller.java와 login.html 파일을 생성한다.
html파일의 경우 src/main/resources/templates 디렉토리에 생성한다.</p>
<p>카카오 로그인을 클릭시 /oauth2/autorization/kakao로 요청을 보내게 된다.
이는 <strong>OAuth2 Client</strong> 의존성 기본 설정 경로이며, 필요시 수정할 수 있다.</p>
<h3 id="24-service">2.4. Service</h3>
<pre><code class="language-java">package com.oauth_kakao.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class OAuth2UserService extends DefaultOAuth2UserService {
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);

        // Role generate
        List&lt;GrantedAuthority&gt; authorities = AuthorityUtils.createAuthorityList(&quot;ROLE_ADMIN&quot;);

        // nameAttributeKey
        String userNameAttributeName = userRequest.getClientRegistration()
                .getProviderDetails()
                .getUserInfoEndpoint()
                .getUserNameAttributeName();

        // DB 저장로직이 필요하면 추가

        return new DefaultOAuth2User(authorities, oAuth2User.getAttributes(), userNameAttributeName);
    }
}</code></pre>
<p>OAuth2UserService 클래스를 생성한다.(DefaultOAuth2UserService 클래스 상속)</p>
<pre><code class="language-java">package com.oauth_kakao.security;

import com.oauth_kakao.service.OAuth2UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;

@Configuration
@EnableMethodSecurity
public class SecurityConfig {
    private final OAuth2UserService oAuth2UserService;

    public SecurityConfig(OAuth2UserService oAuth2UserService) {
        this.oAuth2UserService = oAuth2UserService;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeHttpRequests(config -&gt; config.anyRequest().permitAll());
        http.oauth2Login(oauth2Configurer -&gt; oauth2Configurer
                .loginPage(&quot;/login&quot;)
                .successHandler(successHandler())
                .userInfoEndpoint()
                .userService(oAuth2UserService));

        return http.build();
    }

    @Bean
    public AuthenticationSuccessHandler successHandler() {
        return ((request, response, authentication) -&gt; {
            DefaultOAuth2User defaultOAuth2User = (DefaultOAuth2User) authentication.getPrincipal();

            String id = defaultOAuth2User.getAttributes().get(&quot;id&quot;).toString();
            String body = &quot;&quot;&quot;
                    {&quot;id&quot;:&quot;%s&quot;}
                    &quot;&quot;&quot;.formatted(id);

            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());

            PrintWriter writer = response.getWriter();
            writer.println(body);
            writer.flush();
        });
    }
}
</code></pre>
<p>Security config를 작성한다.
로그인 페이지를 /login으로 설정하고, 정상 실행시 id를 반환한다.</p>
<h3 id="25-실행">2.5. 실행</h3>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/cc7d63e4-72c8-4c7f-87a6-ce9fbca3f475/image.png" alt=""></p>
<p>localhost:8080/login 접속시 왼쪽과 같은 창이 출력된다.
카카오 로그인 클릭후 로그인 하게 되면 우측과 같이 id값이 반환된다.</p>
<h4 id="참고자료">참고자료</h4>
<p><a href="https://developers.kakao.com/docs/latest/ko/kakaologin/common">카카오 로그인 공식문서</a>
<a href="https://kim-jong-hyun.tistory.com/150">Spring Boot 3.x 버전에서 OAuth2 라이브러리를 이용하여 카카오 로그인 구현 및 OAuth2 동작원리 살펴보기</a>
<a href="https://velog.io/@leesomyoung/Spring-Boot-%EC%B9%B4%EC%B9%B4%EC%98%A4-OAuth2.0-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0">[Spring Boot] 카카오 OAuth2.0 구현하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 14002 - 가장 긴 증가하는 부분 수열 4]]></title>
            <link>https://velog.io/@wnso-kim/%EB%B0%B1%EC%A4%80-14002-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-4</link>
            <guid>https://velog.io/@wnso-kim/%EB%B0%B1%EC%A4%80-14002-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-4</guid>
            <pubDate>Tue, 12 Sep 2023 04:54:29 GMT</pubDate>
            <description><![CDATA[<h3 id="링크">링크</h3>
<p><a href="https://www.acmicpc.net/problem/14002">https://www.acmicpc.net/problem/14002</a></p>
<h3 id="문제">문제</h3>
<blockquote>
</blockquote>
<p>수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.</p>
<blockquote>
</blockquote>
<p>예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {<strong>10</strong>, <strong>20</strong>, 10, <strong>30</strong>, 20, <strong>50</strong>} 이고, 길이는 4이다.</p>
<h3 id="입력">입력</h3>
<blockquote>
<p>첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000)이 주어진다.</p>
</blockquote>
<p>둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000)</p>
<h3 id="출력">출력</h3>
<blockquote>
</blockquote>
<p>첫째 줄에 수열 A의 가장 긴 증가하는 부분 수열의 길이를 출력한다.</p>
<blockquote>
</blockquote>
<p>둘째 줄에는 가장 긴 증가하는 부분 수열을 출력한다. 
그러한 수열이 여러가지인 경우 아무거나 출력한다.</p>
<h3 id="풀이">풀이</h3>
<blockquote>
</blockquote>
<p>이전 문제 <a href="https://velog.io/@wnso-kim/%EB%B0%B1%EC%A4%80-11053-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4">가장 긴 증가하는 부분 수열</a>과 같이 DP를 사용한다.</p>
<blockquote>
</blockquote>
<p>수열에서 <strong>어떠한 수(x)</strong>는 왼쪽에 위치한 수들과 비교해 큰 경우 &quot;<strong>증가하는 부분 수열</strong>&quot;을 이룰 수 있다.</p>
<blockquote>
</blockquote>
<p>이때, &quot;<strong>가장 긴</strong>&quot; 증가하는 부분 수열을 찾아야 하므로 왼쪽에 위치한 수들 중 &quot;<strong>가장 길고, 어떠한 수(x)보다 작은</strong>&quot; 수을 찾아 연결해야 한다.
<br></p>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/79cc4e7c-8897-4765-be2d-99e9a5e2db75/image.png" alt=""></p>
<blockquote>
</blockquote>
<p>그림에서 30은 10, 20, 10과 부분수열을 이룰 수 있다.
그리고 20은 10과 부분수열을 이룰 수 있다.
30을 기준으로 10-20-30이 &quot;<strong>가장 긴 증가하는 부분수열</strong>&quot;이다.</p>
<blockquote>
<p>아래의 패턴을 반복하면 <strong>특정 수(x) 기준 가장 긴 증가하는 부분 수열</strong>을 찾을 수 있다.</p>
</blockquote>
<ol>
<li>왼쪽의 수들 중 특정 수(x) 보다 작은 수(y)를 찾는다.</li>
<li>작은 수(y)들 중 가장 긴 수열을 찾는다.</li>
<li>가장 긴 수열을 찾기 위해 작은 수(y)를 기준으로 1, 2를 반복한다.<blockquote>
</blockquote>
그러나 위의 패턴은 부분 수열의 마지막 값(최대값)을 알아야한다.
최대값을 모르는 경우 특정 수가 바뀌므로, 이전에 했던 작업을 다시 수행해야한다.</li>
</ol>
<blockquote>
<p>이를 배열에 값을 저장하는 방법으로 해결할 수 있다.
인덱스와, 값, 값이 포함된 수열의 이전 인덱스, 수열의 길이를 저장하는 배열을 선언한다.
각 수는 <strong>자기자신이 부분 수열</strong>을 이루며, 이전 수의 인덱스를 자기 인덱스로 한다. <img src="https://velog.velcdn.com/images/wnso-kim/post/56826579-7eec-4272-a23d-5878980af536/image.png" alt=""></p>
</blockquote>
<p>1번 인덱스부터 시작해 5번 인덱스까지 왼쪽의 수들과 비교해 값을 채운다.</p>
<ol>
<li>왼쪽 수의 <strong>값이 작아야 한다.</strong></li>
<li>왼쪽 수의 <strong>부분 수열의 길이</strong>가 <strong>같거나 커야한다.</strong><blockquote>
</blockquote>
1,2번을 만족하는 경우 <strong>이전 수의 인덱스</strong>를 <strong>왼쪽 수의 인덱스</strong>로 채우고,</li>
</ol>
<p><strong>부분 수열의 길이</strong>를 <strong>왼쪽 수의 부분 수열의 길이 값+1</strong>로 저장한다.</p>
<blockquote>
</blockquote>
<p>마지막 인덱스까지 비교한 후, <strong>부분 수열의 길이</strong> 중 가장 큰 값을 출력한다.
그리고, 가장 큰 값의 <strong>이전 수의 인덱스</strong>를 이용해 역으로 값을 출력한다.</p>
<blockquote>
<p>20(1: 괄호 안 숫자는 인덱스 번호)과 왼쪽 숫자 비교
10(0)과 비교시 1,2번 조건을 만족하므로 20(1)의 <strong>이전 수 인덱스</strong>와 <strong>부분 수열의 길이를 수정</strong>한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/bac73d17-3b34-4ec2-8abc-204fabeffbb2/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>10(2)과 왼쪽 숫자 비교
왼쪽 숫자들과 비교시 조건을 만족하는 숫자가 없으므로 수정하지 않는다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/f79b37b9-6723-4575-bf31-31d067761327/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>30(3)과 왼쪽 숫자 비교
10(0), 20(1)과 비교시 1,2번 조건을 만족하므로 30(3)의 <strong>이전 수 인덱스</strong>와 <strong>부분 수열의 길이를 수정</strong>한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/b71b8c93-723d-4c5b-a847-4ac79a581d00/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>20(4)과 왼쪽 숫자 비교
10(0), 10(2)과 비교시 1,2번 조건을 만족하므로 20(4)의 <strong>이전 수 인덱스</strong>와 <strong>부분 수열의 길이를 수정</strong>한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/d1f5b8dd-cce6-4329-8f64-5f20f01ddd64/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>50(5)과 왼쪽 숫자 비교
10(0), 20(2), 30(3)과 비교시 1,2번 조건을 만족하므로 50(5)의 <strong>이전 수 인덱스</strong>와 <strong>부분 수열의 길이를 수정</strong>한다.
<img src="https://velog.velcdn.com/images/wnso-kim/post/b77b349b-75ef-4361-86b7-1fcc5597245a/image.png" alt=""></p>
</blockquote>
<h3 id="코드">코드</h3>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

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

        // input | N -&gt; int, stk -&gt; stringToken
        int N = Integer.parseInt(br.readLine());
        StringTokenizer stk = new StringTokenizer(br.readLine());

        /** init | Ai = int[N][3]
         * [][0]: 수열
         * [][1]: 길이
         * [][2]: 이전 수(인덱스)
         */
        int[][] Ai = new int[N][3];
        for(int i=0; i&lt;N; i++){
            Ai[i][0] = Integer.parseInt(stk.nextToken());
            Ai[i][1] = 1;
            Ai[i][2] = i;
        }

        // solve
        // len: 가장 긴 수열의 길이
        // index: 가장 긴 수열의 마지막 인덱스
        int len = 1; 
        int index = 0;
        //loop | 왼쪽 수들과 비교 
        for(int i=1; i&lt;N; i++){
            for(int j=0; j&lt;i; j++){
                if(Ai[j][0]&lt;Ai[i][0] &amp;&amp; Ai[j][1]&gt;=Ai[i][1]){
                    Ai[i][1] = Ai[j][1]+1;
                    Ai[i][2] = j;

                    if(Ai[i][1]&gt;len){
                        len = Ai[i][1];
                        index = i;
                    }
                }
            }
        }

        // make indexs | 
        int[] indexs = new int[len];
        for(int i=len-1; i&gt;=0; i--){
            indexs[i] = index;
            index = Ai[index][2];
        }


        // print
        String answer = &quot;&quot;;
        for(int i=0; i&lt;len; i++)
            answer +=  Ai[indexs[i]][0] + &quot; &quot;;

        System.out.println(len);
        System.out.println(answer);
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/wnso-kim/post/2e7fd963-5573-42a8-95ba-2c6e20a1674b/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>