<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>k_siik.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 17 Feb 2023 11:22:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>k_siik.log</title>
            <url>https://images.velog.io/images/k_siik/profile/9a301078-5923-441a-8310-c56c4c5b824a/KakaoTalk_20210226_163541278_25.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. k_siik.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/k_siik" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2023.02.17 TIL]]></title>
            <link>https://velog.io/@k_siik/2023.02.17-TIL</link>
            <guid>https://velog.io/@k_siik/2023.02.17-TIL</guid>
            <pubDate>Fri, 17 Feb 2023 11:22:44 GMT</pubDate>
            <description><![CDATA[<p>수료하고 한 달 이내로 취업하리라 다짐했지만 건강이 회복이 안돼서 취업은 커녕 공부도 제대로 못하고 있어서 걱정이다. 할 수 있는데까지 해보자..</p>
<p>저번 TIL에 스프링의 특징에 대해 간략히 알아본 것에 이어서 이번에는 JPA에 대해 되짚어보는 시간을 가지려고 한다. 공부를 했었는데도 머릿속에 정리가 안되다보니 말로 내뱉기가 힘들어서 머리 나쁜 나에겐 반복숙달만이 답이다.</p>
<h1 id="1-jpa">[1] JPA</h1>
<p>JPA는 Java Persistence API의 약자이다. JPA를 알기 위해서는 persistence라는 단어에 대해 알고 있어야만 한다. persistence는 고집, 없어지지 않고 오랫동안 지속됨이라는 사전적 의미를 가지고 있다. JPA에서는 이를 영속성이라고 번역해서 사용하는데 영속성이란 여러 의미들에서 유추할 수 있듯이 &#39;데이터를 생성한 프로그램이 종료되더라도 데이터가 사라지지 않는 특징&#39;이다. 영속성은 관계형 대이터베이스 등을 활용하여 구현한다. 영속성을 갖지 않은 데이터는 단지 메모리 내에만 존재하기 때문에 프로그램이 종료됨과 동시에 사라진다. </p>
<p>JPA는 자바 ORM에 대한 API 표준명세이고, 인터페이스의 모음이다. 따라서 구현체가 없고, 사용하기 위해서는 ORM 프레임워크를 선택해야 한다. 프레임워크 중 가장 대중적인 것이 하이버네이트이다. </p>
<p>그렇다면 ORM은 무슨 의미일까? Object Relational Mapping의 약자로 자바의 객체(Object)와 관계형 데이터베이스(RDBMS)의 데이터를 매핑한다는 의미이다. OOP는 클래스를 이용하고, RDBMS는 테이블을 이용하기 때문에 객체 모델과 관계형 모델의 패러다임 불일치가 존재한다. 예를 들면 테이블 내에는 객체 타입의 데이터를 저장할 수가 없다. 이를 해결하기 위해 나온 것이 ORM이다. 과거에는 기준을 RDBMS에 두고 객체를 만들어야 했기 때문에 객체지향적인 코드를 짜기 어려웠지만 지금은 ORM을 통해 프로그래밍 언어의 관점에서 데이터베이스에 접근할 수 있다. 또한 ORM을 통해 객체간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결할 수 있다. 과거에는 일일히 SQL문을 작성해야만 했기 때문에 비즈니스 로직에 집중하기 힘든 구조였다. </p>
<h1 id="2-http-vs-socket">[2] HTTP VS Socket</h1>
<h2 id="1-http">{1} HTTP</h2>
<p>HyperText Transfer Protocol의 약자로 말그대로 HTML 파일을 전달해주는 프로토콜이다. 웹브라우저와 웹서버간의 통신을 위해 설계되었다. HTML 파일을 전송하기 위한 목적으로 만들어졌으나 현재는 JSON, Image 파일도 전송 가능하다. </p>
<p>통신 방식은 클라이언트의 요청이 있을 경우에만 서버가 응답하고 바로 연결이 끊어지는 방식으로 즉, 단방향 통신이다. 계속 연결을 유지할 필요가 없으므로 부하가 적지만 클라이언트의 요청이 있을 때마다 연결을 해주어야하기 때문에 성능이 떨어질 수 있다.</p>
<h2 id="2-socket">{2} Socket</h2>
<p>소켓이란 두 프로그램이 서로 데이터를 주고 받을 수 있도록 양쪽에 생성되는 통신 단자이다. 즉, 웹소켓은 웹서버, 웹브라우저 양쪽에서 양방향 통신이 이루어져 서로 요청을 보낼 수 있는 통신 방법이다. 항상 통신을 유지하고 있어야 하기 때문에 HTTP 통신에 비해 리소스가 많이 소모되는 반면 통신을 유지해야만 하는 실시간 채팅, 스트리밍 등의 프로그램에서는 소켓통신을 이용하는 것이 적합하다. </p>
<h2 id="3-정리">{3} 정리</h2>
<p>따지고 보면 HTTP 통신도 결국 Socket 통신이다. 소켓은 IP와 Port Number을 사용해 만들어진 양 끝단을 의미하는데 IP와 Port Number을 활용하는 TCP 레이어 위에 올라간 HTTP 또한 같은 방식을 사용한다. HTTP 통신은 소켓통신을 활용한 통신의 일종이기 때문이다. 하지만 이 둘을 굳이 구분한 이유는 한쪽에서만 요청에 대한 응답을 하는 웹통신의 특성상 HTTP가 하나의 중요한 프로토콜로 구분되었기 때문이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023.02.09 TIL]]></title>
            <link>https://velog.io/@k_siik/2023.02.09-TIL</link>
            <guid>https://velog.io/@k_siik/2023.02.09-TIL</guid>
            <pubDate>Fri, 10 Feb 2023 07:21:18 GMT</pubDate>
            <description><![CDATA[<h1 id="1-스프링의-특징">[1] 스프링의 특징</h1>
<h4 id="-스프링은-프레임워크이다">* 스프링은 프레임워크이다.</h4>
<p>프레임워크 - 프로그램을 제작하기 쉽도록 어떠한 틀을 만들어놓고 그 안에서 프로그램을 만들 수 있도록 하는 툴.</p>
<h4 id="-스프링은-오픈소스이다">* 스프링은 오픈소스이다.</h4>
<h4 id="-스프링은-ioc-컨테이너를-가진다">* 스프링은 IoC 컨테이너를 가진다.</h4>
<p>IoC(Inversion of Control, 제어의 역전) - 객체의 생명주기를 개발자가 아닌 외부에 위임하는 설계원칙. 프레임워크없이 개발을 할 때엔 객체의 생성, 메서드의 호출, 객체의 소멸 등 객체의 생명주기를 개발자가 직접 관리했지만 스프링부트 프레임워크를 사용하면 객체의 생명주기를 프레임워크에 위임할 수 있다.</p>
<h4 id="-스프링은-di를-지원한다">* 스프링은 DI를 지원한다.</h4>
<p>DI(Dependency Injection, 의존성 주입) - 외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴으로 인터페이스를 사이에 둬서 클래스 레벨에서는 의존관계가 고정되지 않도록 하고, 런타임시에 관계를 동적으로 주입하여 의존성을 확보하고 결합도를 낮출 수 있게 해주는 것이다.</p>
<h4 id="-스프링은-수많은-필터를-가지고-있다">* 스프링은 수많은 필터를 가지고 있다.</h4>
<p>필터는 주로 클라이언트의 요청애 대한 인증, 권한 체크를 하는데에 쓰이는 문지기같은 역할을 한다. (필터에 대해서는 포스팅을 따로 빼서 정리할 예정이다.)         </p>
<h4 id="-스프링은-수많은-어노테이션을-가지고-있다">* 스프링은 수많은 어노테이션을 가지고 있다.</h4>
<p>어노테이션의 사전적 의미는 주석이지만 스프링 내에서의 어노테이션은 소스코드에 추가해서 사용할 수 있는 메타데이터의 일종이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023.02.08 TIL]]></title>
            <link>https://velog.io/@k_siik/2023.02.08-TIL</link>
            <guid>https://velog.io/@k_siik/2023.02.08-TIL</guid>
            <pubDate>Fri, 10 Feb 2023 02:16:53 GMT</pubDate>
            <description><![CDATA[<p>항해를 수료하고 건강이 나빠져서 골골대다가 결국 수술까지 받게 되었다. 건강 관리에 너무 소홀했던 것 같다. 수술 후 심신이 많이 약해진 느낌이다. 건강이 최고..</p>
<h1 id="1-mvc-패턴">[1] MVC 패턴</h1>
<p>MVC 패턴은 여러가지 디자인 패턴 중 하나이다. 디자인 패턴은 프로그램을 만들 때 상황에 따라 간편하게 적용해서 특정한 규약을 통해 쉽게 쓸 수 있는 형태로 만든 것이다. </p>
<p>여러 디자인 패턴 중 MVC 패턴은 애플리케이션를 구성할 때, MODEL, VIEW, CONTROLLER 세가지 역할로 구분한 패턴을 의미한다.</p>
<p>MODEL은 애플리케이션의 정보, 데이터, 데이터베이스 등을 지칭한다. </p>
<p>VIEW는 사용자에게 보여지는 화면, UI를 의미한다.</p>
<p>CONTROLLER는 MODEL과 VIEW를 제어해 둘이 서로 상호 통신을 하지 않도록 하는 역할을 한다. </p>
<h1 id="2-기사단원의-무기">[2] 기사단원의 무기</h1>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/136798">https://school.programmers.co.kr/learn/courses/30/lessons/136798</a> </p>
</blockquote>
<p>이 문제에서 잘몰랐던 것은 약수의 개수를 효율적으로 구하는 방법이다. 몰랐다기 보다는 전에 공부를 해서 알고는 있었지만 체화가 되질 않아 잊어버렸다는 표현이 맞다.</p>
<p>어찌됐던 딱 떠오르는 대로 N의 약수의 개수를 구해보면,</p>
<pre><code class="language-java">int cnt = 0;
for (int i=1; i &lt;= N; i++ {
    if (N % i == 0) cnt++;
}</code></pre>
<p>1부터 N까지 차례로 N과 나누어 떨어지는지 확인한 후, 나누어 떨어지면 약수의 개수를 올리는 형태의 코드이다.</p>
<p>이렇게 코드를 짜면 직관적이고 생각하기도 편하지만 숫자가 커지면 커질수록 시간복잡도가 증가하는 단점이 있다. 예를 들어 10000의 약수의 개수를 구하려고 한다면 for문을 10000번 돌아야만 한다.</p>
<p>시간복잡도를 낮추기 위해서는 다른 방법을 사용해야만 한다. 10000을 다시 예로 든다면 만일 10000에서 √10000 이후의 수를 나누었을 때 나누어 떨어지는 경우, 그 약수의 짝이 이미 약수이기 때문에 굳이 제곱근 이후의 수를 for문을 돌릴 필요가 없다.
그대신 약수의 개수를 더할 때 2를 더해야만 한다. 그리고 N이 제곱수인 경우에는 제곱근도 약수에 포함되므로 1을 더해야한다.
따라서 이 개념을 이용해서 다시 코드를 짠다면</p>
<pre><code class="language-java">int cnt = 0;
for (int i=1; i * i &lt;= N) {
    if (i * i = N) cnt++;
    else if (N % i = 0) cnt += 2;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[푸드 파이트 대회(StringBuilder)]]></title>
            <link>https://velog.io/@k_siik/%ED%91%B8%EB%93%9C-%ED%8C%8C%EC%9D%B4%ED%8A%B8-%EB%8C%80%ED%9A%8CStringBuilder</link>
            <guid>https://velog.io/@k_siik/%ED%91%B8%EB%93%9C-%ED%8C%8C%EC%9D%B4%ED%8A%B8-%EB%8C%80%ED%9A%8CStringBuilder</guid>
            <pubDate>Mon, 02 Jan 2023 01:37:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p>문제 링크 : <a href="https://school.programmers.co.kr/learn/courses/30/lessons/134240">https://school.programmers.co.kr/learn/courses/30/lessons/134240</a></p>
<p>문자열의 수정이 빈번할거라 판단해서 StringBuilder를 사용했다. 문제의 로직에 대해 설명하기 보다는 StringBuilder를 사용하면서 껶었던 이슈들에 대해 정리해 보려고 한다.</p>
<h1 id="1-repeat-메서드">[1] repeat 메서드</h1>
<p>자바 11부터 String 클래스에 추가된 메서드로 반복된 수만큼의 문자열을 삽입해주는 메서드이다.</p>
<p>매개변수 count에 </p>
<ul>
<li>음수를 넣으면, IllegalArgumentException이 터지고,</li>
<li>0을 넣으면, 빈문자열 반환,</li>
<li>1을 넣으면, 지정한 문자열을 한번만 반환,</li>
<li>2 이상을 넣을 시, Arrays.fill, System.arraycopy 메서드를 사용하여 문자열을 반복하여 삽입한다.</li>
</ul>
<h1 id="2-string-vs-stringbuilder">[2] String VS StringBuilder</h1>
<p>String과 StringBuilder의 차이 중 하나는 immutable한 특징의 여부이다. immutable(불변의)한 String 클래스의 경우, </p>
<pre><code class="language-java">String str = &quot;a&quot;;
str = &quot;ab&quot;;</code></pre>
<p>str 변수를 &quot;a&quot;에서 &quot;ab&quot;로 수정할 경우, str 변수가 참조하고 있던 주소에 &quot;a&quot; 값에서 &quot;ab&quot;값으로 바뀌는 것이 아니라, 새로운 주소에 &quot;ab&quot;가 생기고, str 변수가 그 새로운 주소를 참조하게 되고, &quot;a&quot; 인스턴스는 그대로 존재하고 나중에 garbage collector에 의해 처리된다.</p>
<p>반면, StringBuilder 클래스는 mutable하기 때문에 value값의 수정이 있더라도 새로운 인스턴스가 생성되는 것이 아닌 기존에 참조하던 주소의 값이 변경된다. </p>
<p>이러한 특징 때문에 String 클래스는 안정성 측면에서 더 좋고, 수정이 빈번한 경우에는 성능 상 StringBuilder 클래스가 더 좋다.</p>
<h1 id="3-reverse-메서드">[3] reverse 메서드</h1>
<p>위의 특징들을 간과한 채로 reverse 메서드를 사용했다가 오답이 계속 발생했었다. </p>
<p>문제 내에서 문자열을 뒤집는 로직이 필요해서 StringBuider의 reverse 메서드를 사용했다.</p>
<pre><code class="language-java">    public String solution(int[] food) {
        StringBuilder first = new StringBuilder();

        for (int i = 1; i &lt; food.length; i++) {
            first.append(String.valueOf(i).repeat(food[i] / 2));
        }

        return first + &quot;0&quot; + first.reverse();
    }</code></pre>
<p>위의 코드와 같이 StringBuilder 클래스의 first 변수를 reverse 메서드를 사용하니 return 값에 &quot;0&quot; 앞 부분까지 reverse 되는 현상이 발생했다. </p>
<p>이 이유가 2번 특징 때문인걸 꽤 나중에 알게 되었다.  first 변수가 참조하는 값 자체가 reverse되었기 때문에 &quot;0&quot; 문자열 앞뒤로 모두 문자열이 뒤집혀서 반환되었다.</p>
<pre><code class="language-java">    public String solution(int[] food) {
        StringBuilder foods = new StringBuilder();

        for (int i = 1; i &lt; food.length; i++) {
            foods.append(String.valueOf(i).repeat(food[i] / 2));
        }
        String player1 = foods.toString();
        String player2 = foods.reverse().toString();
        return player1 + &quot;0&quot; + player2;
    }</code></pre>
<p>이런 식으로 각각 String 클래스 변수에 문자열을 저장한 방식으로 해결하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.12.18 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.12.18-TIL</link>
            <guid>https://velog.io/@k_siik/2022.12.18-TIL</guid>
            <pubDate>Sun, 18 Dec 2022 13:31:51 GMT</pubDate>
            <description><![CDATA[<p>대망의 실전 프로젝트 발표가 끝이 났다. 3달간의 대장정이 마무리되는 순간이였다. 열심히 달려오다가 마지막에 끝맺음이 좋지 않아서 아쉬웠지만 3달 전에 비해 눈에 띄게 성장한 것만은 분명하다. 지금까지 너무나도 힘들었지만 앞으로는 더 힘들 예정이다. 공부에 끝이 없다는게 막막하기도 하지만 긍정적으로 생각하면 성장 가능성도 무궁무진하다는 의미이니까 하루하루 열심히 살아나가야 겠다. </p>
<p>오늘은 실전프로젝트에서 사용한 기술 스택에 대해 정리해 보는 시간을 가지려고 한다. 각각의 기술들을 가져다가 사용만 해봤지 정확히 어떤 녀석들인지는 제대로 파악하고 있지 못하다. 하나 하나 공부해 보면서 이 기술을 왜 사용했는지 선 사용 후 공부를 해볼 생각이다.</p>
<h1 id="1-기술-스택">[1] 기술 스택</h1>
<p><img src="https://velog.velcdn.com/images/k_siik/post/aea1b45b-a2fa-46d3-a7e5-ea1e4ef44788/image.png" alt=""></p>
<h2 id="1-spring">{1} Spring</h2>
<p> <img src="https://velog.velcdn.com/images/k_siik/post/6bb513db-2792-4b78-8c46-ff0d912f797e/image.png" alt="">
지금까지 스프링에 대해 잘 알지도 못하고 배워왔던 것 같은데 이번 기회에 스프링의 사용법이 아닌 스프링의 특징에 대해 알아보자.</p>
<h3 id="1-스프링이란">(1) 스프링이란?</h3>
<p>스프링은 자바 기반의 웹프레임워크로서 엔터프라이즈 급 개발을 하기 위한 모든 기능을 종합적으로 제공하는 경량화된 솔루션이다. 엔터프라이즈 급 개발이란 기업을 대상으로 하는 개발이라는 뜻으로 대규모 데이터 처리와 트랜잭션이 동시에 여러 사용자로 부터 행해지는 매우 큰 규모의 애플리케이션을 개발하기에 최적화되어있다. </p>
<h3 id="2-스프링-특징">(2) 스프링 특징</h3>
<blockquote>
<p>제어 반전(IoC : Inversion of Control)을 지원한다. </p>
</blockquote>
<p>프레임워크 없이 개발할 때에는 객체의 생성, 설정, 초기화, 메소드 호출, 소멸(이하 객체의 생명주기)을 프로그래머가 직접 관리한다.  하지만, 스프링 프레임워크를 사용하면 객체의 생명 주기를 모두 프레임워크에 위임할 수 있다. 즉, 외부 라이브러리가 프로그래머가 작성한 코드를 호출하고, 흐름을 제어한다.</p>
<p>특별한 객체에 모든 것을 위임하여 객체의 생성부터 생명주기 등 모든 객체에 대한 제어권이 넘어 간 것을 IOC, 제어의 역전이라고 한다.</p>
<blockquote>
<p>의존성 주입(DI : Dependency Injection)을 지원한다.</p>
</blockquote>
<p>객체의 의존성을 개발자가 아닌 외부(IOC Container)에서 주입함으로써 객체간의 결합을 약하게 해주며 유지보수가 좋은 코드를 만들어준다. 스프링에서는 스프링 컨테이너가 각 클래스의 의존관계를 Bean설정 정보를 바탕으로 자동으로 결정 및 연결해준다.</p>
<blockquote>
<p>관점 지향 프로그래밍(AOP : Aspect-Oriented Programming)을 지원한다.</p>
</blockquote>
<p>AOP, 관점 지향 프로그래밍은 흩어진 Aspect를 모듈화 할 수 있는 프로그래밍 기법을 말한다. 관점 지향은 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누고 관점을 기준으로 각각 모듈화하는 프로그래밍 기법이다.</p>
<h2 id="2-aws-ec2">{2} AWS EC2</h2>
<p><img src="https://velog.velcdn.com/images/k_siik/post/22158c41-b6f3-4834-8045-0d56b17cb9cc/image.png" alt=""></p>
<p>Amazon Elastic Compute Cloud (Amazon EC2)는 Amazon Web Services (AWS) 클라우드에서 독립된 가상의 컴퓨터를 임대해 주는 웹 서비스이다. 물리적인 컴퓨터가 아닌 리눅스, 윈도우와 같은 OS가 설치된 가상 머신을 빌려주는 것이기 때문에 사용자는 원격으로 해당 컴퓨터(인스턴스)를 제어하여 사용할 수 있다. </p>
<p>EC2의 장점으로는 </p>
<ul>
<li>용량을 늘리거나 줄일 수 있다. (탄력성)</li>
<li>사용한만큼 지불하므로 저렴하다.</li>
<li>사용자가 인스턴스를 완전히 제어할 수 있다.</li>
<li>보안 및 네트워크 구성, 스토리지 관리 효과적이다.</li>
</ul>
<h2 id="3-mysql">{3} MYSQL</h2>
<p><img src="https://velog.velcdn.com/images/k_siik/post/f661a17f-deee-466a-af57-ab1f9adc07e7/image.png" alt="">
세계에서 가장 많이 쓰이는 관계형 데이터베이스 관리 시스템(RDBMS)이다. 표준 데이터베이스 질의 언어 SQL(Structured Query Language)을 사용하고, 매우 빠르고, 유연하며, 사용하기 쉬운 특징이 있다. 다중사용자, 다중 쓰레드를 지원하고, C, C++, 자바, Pyton 스크립트 등을 위한 응용프로그램 인터페이스(API)를 제공한다. 유닉스나 리눅스, Windows 운영체제 등에서 사용할 수 있다.</p>
<h2 id="4-s3">{4} S3</h2>
<p><img src="https://velog.velcdn.com/images/k_siik/post/339acadb-aa74-4280-bd51-850db900236e/image.png" alt="">
AWS Simple Storage Service(S3)는 인터넷용 스토리지 서비스입니다. </p>
<p>S3의 특징으로는 </p>
<ul>
<li>제공하는 단순한 웹 서비스 인터페이스를 사용하여 웹에서 언제 어디서나 원하는 양의 데이터를 저장하고 검색할 수 있다.</li>
<li>높은 확장성과 신뢰성을 갖춘 빠르고 경제적인 데이터 스토리지 인프라에 액세스할 수 있다.</li>
<li>단독 스토리지로도 사용할 수 있으며 EC2, EBS, Glacier와 같은 다른 AWS 서비스와도 함께 사용할 수 있어 클라우드 어플리케이션, 컨텐츠 배포, 백업 및 아카이빙, 재해 복구 및 빅데이터 분석을 포함한 다양한 사례에 알맞다.</li>
<li>사용한 스토리지 만큼 요금이 청구되며 데이터 전송부분에서는 해당 리전 내에서는 데이터 송수신은 무료이고 S3에서 인터넷으로 데이터를 송수신 시에도 가격이 매우 저렴하다.</li>
</ul>
<h2 id="5-github-actions">{5} Github Actions</h2>
<p><img src="https://velog.velcdn.com/images/k_siik/post/0a8595a1-5f1a-4d54-befc-4d00ec165869/image.png" alt=""></p>
<p>GitHub Actions는 GitHub에서 제공하는 CI(Continuous Integration, 지속 통합)와 CD(Continuous Deployment, 지속 배포)를 위한 서비스이다.</p>
<p>GitHub Actions를 사용하면 자동으로 코드 저장소에서 어떤 이벤트(event)가 발생했을 때 특정 작업이 일어나게 하거나 주기적으로 어떤 작업들을 반복해서 실행시킬 수 있다. 예를 들어, 누군가가 코드 저장소에 Pull Request를 생성하게 되면 GitHub Actions를 통해 해당 코드 변경분에 문제가 없는지 각종 검사를 진행할 수 있다. 또 어떤 새로운 코드가 메인(main) 브랜치에 유입(push)되면 GitHub Actions를 통해 소프트웨어를 빌드(build)하고 상용 서버에 배포(deploy)할 수도 있다. 뿐만 아니라 매일 밤 특정 시각에 그날 하루에 대한 통계 데이터를 수집시킬 수도 있다.</p>
<h2 id="6-web-socket">{6} Web Socket</h2>
<p><img src="https://velog.velcdn.com/images/k_siik/post/f4a51232-f929-4e9b-93e2-db338e6ef70a/image.png" alt=""></p>
<p>HTTP를 이용한 실시간 통신의 문제를 해결하기 위해 HTML5부터 웹소켓이 등장했다. 웹소켓은 실시간 양방향 통신을 지원하며 한번 연결이 수립되면 클라이언트와 서버 모두 자유롭게 데이터를 보낼 수 있다. 이는 채팅과 같은 연속적인 통신에 대해 계속 유사한 통신을 반복하지 않게 해주어 통신의 효율성도 개선하였다.</p>
<h2 id="7-json-web-token">{7} Json Web Token</h2>
<p><img src="https://velog.velcdn.com/images/k_siik/post/f7bebe66-3e49-4063-b38d-8b8c57b1cb18/image.png" alt=""></p>
<p>JWT는 JSON 포맷을 이용해 인증정보와 같은 사용자에 대한 정보를 저장하는 Claim 기반의 Web Token이며 RFC7519 표준이다.
JWT는 서버와 클라이언트간에 정보를 주고 받을 때 HTTP Request Header에 JSON 토큰을 넣은 후 서버는 별도 인증과정없이 헤더에 포함되어 있는 JWT 정보를 통해 인증 및 인가과정을 수행한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.12.13 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.12.13-TIL</link>
            <guid>https://velog.io/@k_siik/2022.12.13-TIL</guid>
            <pubDate>Tue, 13 Dec 2022 15:52:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/k_siik/post/f92afc81-c65d-40e9-979b-95b88949b00f/image.png" alt=""></p>
<h4 id="오늘의-커밋">오늘의 커밋</h4>
<ul>
<li>for-loop -&gt; stream</li>
</ul>
<h1 id="1-성능비교">[1] 성능비교</h1>
<p>중간 발표 때 service 단 api들의 for 문으로 이루어진 로직들을 stream으로 바꿔 보라는 피드백을 받았다. 항해를 시작하기 이전부터 stream이 성능이 좋지 않다는 괴소문(?)을 들어왔던 터라 일부러 stream을 사용하지 않아왔었는데 이번 기회에 for-loop와 stream 성능 비교를 해보았다.</p>
<p>&#39;해시태그별 게시물 목록 조회&#39; api를 for-loop와 stream 각각의 로직을 System 클래스의 nanotime() 메서드를 이용하여 성능 비교를 진행하였다. (5회를 실시 한 후 평균을 내는 방식이고 시간은 ms 기준이다.)</p>
<table>
<thead>
<tr>
<th>횟수</th>
<th>for-loop</th>
<th>stream</th>
</tr>
</thead>
<tbody><tr>
<td>테스트1</td>
<td>664</td>
<td>1139</td>
</tr>
<tr>
<td>테스트2</td>
<td>909</td>
<td>806</td>
</tr>
<tr>
<td>테스트3</td>
<td>760</td>
<td>1012</td>
</tr>
<tr>
<td>테스트4</td>
<td>1052</td>
<td>1099</td>
</tr>
<tr>
<td>테스트5</td>
<td>853</td>
<td>833</td>
</tr>
<tr>
<td><strong>평균</strong></td>
<td><strong>847</strong></td>
<td><strong>977</strong></td>
</tr>
</tbody></table>
<p>평균치로 계산해 보았을 때 for-loop가 stream보다 약 1.15배 성능이 좋다. 성능 차이가 생각보다 크게 나지는 않았다. 그렇다면 stream이 성능이 좋지 않다는 괴소문은 어디서부터 비롯된 것일까? 공부를 해보니 stream이 성능이 좋지 않다는 것은 틀린 말은 아니였다. Wrapper 타입의 컬렉션의 비교보다 원시 타입의 배열의 성능 측정에서 성능 차이가 훨씬 더 두드러진다고 한다.</p>
<p>그렇다면 왜 for문이 stream보다 빠를까?</p>
<ol>
<li><p>for문은 단순 인덱스 기반이다.</p>
<p> stream을 이용하려면 stream 객체를 생성해야 하는데 객체 생성 과정에서 여러 작업들이 이루어지기 때문에 오버헤드가 발생하게 되지만, for문은 단순 인덱스 기반으로 도는 반복문이기 때문에 stream에 비해 빠르다.</p>
</li>
<li><p>for문은 컴파일러가 최적화를 시킨다.</p>
<p> for문의 경우 자바라는 언어의 탄생 이래로 함께 해온 문법이기 때문에 JVM에 최적화가 잘되어 있는 반면, stream은 자바8부터 나온 문법이기 때문에 최적화가 덜 되어있다.</p>
</li>
</ol>
<p>이 이외에도 for-loop가 stream에 비해 stack trace가 간단하고 명확하기 때문에 디버깅에 이점이 있기에 for-loop 역시 확실한 장점을 지니고 있다.</p>
<p>그럼에도 불구하고 멘토님께서 stream을 사용해 보라는 피드백을 주신데에는 이유가 있을거라고 생각하고 stream을 사용해야만 하는 이유에 대해 찾아보았다.</p>
<ol>
<li><p>가독성이 좋아진다.</p>
<p> 오늘날의 하드웨어는 충분히 빠르기 때문에 유의미하지 않은 성능 차이를 고려하기 보다는 가독성 및 유지보수를 위해 stream을 사용하곤 한다. for 문의 경우 로직에 따라 indent depth가 깊어져 가독성이 떨어질 수 있는 반면, stream은 보다 간결하게 표현이 가능하다.</p>
</li>
<li><p>코드로 작성해야하는 로직을 stream에서 제공해주는 함수로 간단하게 해결 가능하다.
 단순 forEach만을 사용하는 로직의 경우 for 문을 사용하는게 오히려 더 이득이 될 수 있지만, 로직이 복잡할 경우, filter, sorted, map 등의 stream에서 제공해주는 함수를 사용할 경우 코드를 더욱 간결하게 작성할 수 있다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.12.12 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.12.12-TIL</link>
            <guid>https://velog.io/@k_siik/2022.12.12-TIL</guid>
            <pubDate>Mon, 12 Dec 2022 13:37:07 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/k_siik/post/b29a9d99-8d12-41f1-9969-b7ac8062dab6/image.png" alt=""></p>
<h4 id="오늘의-커밋">오늘의 커밋</h4>
<ul>
<li>XSS 방어 설정</li>
</ul>
<h1 id="1-xss">[1] XSS</h1>
<h2 id="1-xss란">{1} XSS란?</h2>
<p>XSS란 Cross-Site scripting의 약자로, 악의적인 사용자가 공격하려는 사이트에 스크립트를 넣는 기법을 뜻한다. 공격에 성공하면 사이트에 접속한 사용자는 삽입된 코드를 실행하게 되며, 보통 의도치 않은 행동을 수행시키거나 쿠키나 세션 토큰 등의 민감한 정보를 탈취한다.</p>
<p>XSS를 방어하는 방법으로는</p>
<ol>
<li>서버 내에 Filter를 설정해서 XSS 공격 방어</li>
<li>스프링 시큐리티가 제공하는 XSS 방어 설정</li>
</ol>
<h2 id="2-xss-공격">{2} XSS 공격</h2>
<p>방어 설정을 하기 이전에 의도적으로 XSS 공격을 하여 어떤 현상이 발생하는지 확인해 보았다. 포스트맨으로 게시물을 작성할 때 내용 인풋값에 <code>&lt;script&gt;alert(&#39;XSS 공격&#39;)&lt;/alert&gt;</code> 을 작성하고 게시물 상세조회를 해 보았다. 정상적으로 XSS 공격이 먹히면 &#39;XSS 공격&#39;문구의 alert 창이 떠야하는데 문자열 그대로 출력이 되고 있었다. XSS 방어 설정을 해주지도 않았는데 XSS 공격이 먹히지 않은 것이다. 
<img src="https://velog.velcdn.com/images/k_siik/post/1a606b1f-8c3d-42ff-b79e-762290317819/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/k_siik/post/4e1015f3-db97-4288-9f01-974a02db2cbc/image.png" alt=""></p>
<h2 id="3-xss-공격-안먹힌-이유">{3} XSS 공격 안먹힌 이유</h2>
<p>XSS 공격이 먹히지 않은 이유에 대해 찾아보니 아래와 같이 헤더에 이미 XSS 방어 설정이 되어있었다.  </p>
<ul>
<li>X-XSS-Protection : 1; mode=block</li>
</ul>
<p>이미 방어 설정이 되어있었던 이유는 현재 대부분의 브라우저에서는 X-XSS-Protection이 자동으로 설정되어 브라우저 자체에서 스크립트를 필터링 해준다고 한다. 포스트맨은 크롬의 확장 프로그램이기 때문에 포스트맨으로 XSS 공격을 했을 때, 스크립트가 실행되지 않고 문자열로 출력되었던 것이다.</p>
<p>브라우저 상에서 X-XSS-Protection 설정이 자동적으로 되었던 것과는 별개로 더 확실히 하기 위해서 서버 내에서도 스프링 시큐리티가 제공하는 XSS 방어 설정을 해줌으로써 XSS 공격에 대한 방어를 견고히 헀다.</p>
<pre><code class="language-java">    @Bean
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors();

        // xss
        http
                .headers()
                .xssProtection()
                .and()
                .contentSecurityPolicy(&quot;script-src &#39;self&#39;&quot;);
                ....</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.12.11 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.12.11-TIL</link>
            <guid>https://velog.io/@k_siik/2022.12.11-TIL</guid>
            <pubDate>Sun, 11 Dec 2022 07:42:32 GMT</pubDate>
            <description><![CDATA[<h4 id="현재-프로젝트-진행-상황">현재 프로젝트 진행 상황</h4>
<ul>
<li>github actions로 자동 배포화(진행 중)</li>
<li>예외처리 및 버그 잡기(현재 프런트 단에서 동작하지 않은 부분이 많아서 아직 피드백이 많지는 않았지만 유지보수할 부분이 많을 것으로 예상된다.)</li>
<li>for 문 -&gt; stream</li>
<li>비효율적인 코드 querydsl로 작성</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.12.04 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.12.04-TIL</link>
            <guid>https://velog.io/@k_siik/2022.12.04-TIL</guid>
            <pubDate>Sun, 04 Dec 2022 12:12:01 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/k_siik/post/05f6fee9-6c5d-4df5-900e-39778efb39af/image.png" alt=""></p>
<h4 id="오늘의-커밋">오늘의 커밋</h4>
<ul>
<li>게시물 수정 시, 이미지 수정을 하지 않더라도 기존 이미지 유지</li>
</ul>
<h1 id="1-이미지-nullpointerexception">[1] 이미지 NullPointerException</h1>
<p>프로필, 게시물 등을 수정할 때 이미지를 변경하지 않은 경우, 즉, 이미지가 null값인 경우 기존 이미지를 유지하고 싶었는데 계속 nullPointerException이 발생했었다. 이 문제를 해결하기 위해서 코드를 이렇게 수정했다.</p>
<pre><code class="language-java">String profileImage = (companyUpdateRequest.getProfileImage() == null) ?
        member.getProfileImage() : s3Uploader.uploadFiles(companyUpdateRequest.getProfileImage(), &quot;company&quot;);</code></pre>
<p>이미지가 null이 들어올 경우 profileImage 문자열에 기존 이미지의 이미지 url을 저장하고, null이 아닌 경우, 이미지 파일을 버켓에 올려 저장한 후의 url을 저장하도록 했다.</p>
<p>이 방식으로 하게 되면 포스트맨으로는 잘 작동하는데 프런트 단에서 직접 요청을 할 경우에 자꾸 400 에러가 발생했다.</p>
<p>400에러는 BAD REQUEST 에러로서 잘못된 요청을 보내는 경우에 발생한다. 이 에러를 며칠동안 해결하지 못하고 있었는데 결국 백엔드 팀원분들과 모두 모여서 라이브 코딩을 하면서 결국 해결해 냈다.</p>
<p>백엔드 분들과 합심한 결과 포스트맨으로 요청할 때 file 형식으로 이미지를 요청하면 정상적으로 작동하는데 text 형식으로 이미지를 요청하면 똑같이 400 에러가 발생한다는 현상을 발견하게 되었다.</p>
<p>이 문제를 해결하기 위해 어떻게 해야할까 고민하다가 며칠전에 객체를 바인딩할 때 생성자를 사용해서 바인딩한다는 사실을 알게 되었었는데 이를 활용해보면 되겠다고 생각을 해서 닥치는대로 해보았다.</p>
<pre><code class="language-java">public BoardRequest(String title, String content, String dueDay, String startDate, String endDate,
                    String area, String detailArea, List&lt;Tag&gt; tags) {
    this.title = title;
    this.content = content;
    this.boardImage = null;
    this.dueDay = dueDay;
    this.startDate = startDate;
    this.endDate = endDate;
    this.area = area;
    this.detailArea = detailArea;
    this.tags = tags;
}</code></pre>
<p>이미지를 null로 받는 경우에 객체를 바인딩하기 위해 이미지를 null로 초기화시키는 생성자를 하나 만들었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.12.02 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.12.02-TIL</link>
            <guid>https://velog.io/@k_siik/2022.12.02-TIL</guid>
            <pubDate>Fri, 02 Dec 2022 09:41:47 GMT</pubDate>
            <description><![CDATA[<p>TIL을 작성하지 않은지 또 며칠이 흘렀다. 중간 발표 이 후 정신을 못차리고 있는데 진짜 각성하자..!</p>
<p><img src="https://velog.velcdn.com/images/k_siik/post/710146b0-f3dd-41bc-83a2-61f82d3b68c3/image.png" alt=""></p>
<h4 id="오늘의-커밋">오늘의 커밋</h4>
<ul>
<li><p>채팅방 생성을 위해서는 memberId가 필요한데 이를 위해 게시물 상세 조회 시 응답값으로 memberId를 리턴.</p>
</li>
<li><p>댓글 최신순 정렬</p>
</li>
<li><p>게시물 날짜 별 조회 시, 기존 방법으로 yyyy-MM-dd으로 요청하더라도 00시 00분부터 23시 59분까지 해당일 게시물 목록 모두 조회하도록 수정 </p>
</li>
</ul>
<h1 id="1-시간-쿼리">[1] 시간 쿼리</h1>
<p>이바울 멘토님께서 시간 다루는 쿼리가 힘들수도 있다고 하셨는데 진짜 힘들다.. 일단 시간 클래스에 대해서도 공부를 해야 하고, 쿼리를 날리는 방법도 공부를 해야 해서 요즘 프로젝트를 하고 있다기 보다는 공부를 하는 중이다. </p>
<h2 id="1-게시물-날짜-별-조회">{1} 게시물 날짜 별 조회</h2>
<p>현재 dueDay(봉사활동 시간)의 데이터 타입은 TimeStamp이고 양식은 &#39;yyyy-MM-dd hh:mm:ss&#39; 형태이다. 이중에서 날짜 타입인 &#39;yyyy-MM-dd&#39; 만으로 해당일의 게시물 목록을 조회하려고 한다. 여러가지 방법이 생각이 났지만 하나도 확신이 없는 방법이였다. 아직까지는 sql도 공부중이여서 쿼리메서드를 활용할 수 있는 방법을 찾아보다가 Between이라는 키워드를 알게 되었다. 매개변수로 처음값과 마지막값을 넣어주고 메서드명에 between을 선언해주면 처음값과 마지막값 사이의 데이터를 조회할 수 있다. </p>
<pre><code class="language-java">List&lt;Board&gt; findAllByDueDayBetween(Timestamp start, Timestamp end, Pageable pageable);</code></pre>
<p>요청받을 때는 &#39;yyyy-MM-dd&#39; 형태의 문자열을 요청받아서 이 문자열을 TimeStamp 데이터로 변환하는데 처음값을 00:00:00 마지막값을 23:59:59로 설정해서 변환을 하면 해당일의 게시물 목록을 모두 불러올 수 있어서 일단 이렇게 임시로 수정해 놓았다.</p>
<h1 id="2-댓글-최신순-정렬-이슈">[2] 댓글 최신순 정렬 이슈</h1>
<p>댓글을 최신순으로 정렬해야 최근 댓글이 맨 위에 보일 것 같다는 경일 님의 요청에 따라 최신순으로 정렬하려고 코드를 수정했다.
(왜 게시물은 최신순으로 정렬해놓고 댓글은 최신순으로 정렬할 생각을 하지 않았는지 모르겠다..)</p>
<pre><code class="language-java">public interface CommentRepository extends JpaRepository&lt;Comment, Long&gt; {

    // 게시물 상세조회 시 전체 댓글 조회
    List&lt;Comment&gt; findAllByOrderByCreatedAtDesc(Board board, Pageable pageable);</code></pre>
<p>코드를 수정하고 테스트를 해보는데 IllegalArgumentException과 함께 500 코드가 떴다. 에러 메세지를 읽어보니 </p>
<blockquote>
<p>At least 1 parameter(s) provided but only 0 parameter(s) present in query.</p>
</blockquote>
<p>쿼리를 날릴 때 적어도 하나의 파라미터는 조회를 해야하는데 하나도 조회하지 않다는 의미로 받아들였다.</p>
<p>쿼리메서드를 선언할 때, findAllBy 뒤에 Board가 빠져 있는걸 알아챘고 붙이고 실행을 해보니 정상적으로 작동했다.</p>
<pre><code class="language-java">public interface CommentRepository extends JpaRepository&lt;Comment, Long&gt; {

    // 게시물 상세조회 시 전체 댓글 조회
    List&lt;Comment&gt; findAllByBoardOrderByCreatedAtDesc(Board board, Pageable pageable);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.11.27 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.11.27-TIL</link>
            <guid>https://velog.io/@k_siik/2022.11.27-TIL</guid>
            <pubDate>Sun, 27 Nov 2022 07:18:57 GMT</pubDate>
            <description><![CDATA[<p>어제는 실전 프로젝트의 중간 발표 날이였다. 아주 중요한 날이였음에도 불구하고 며칠 전부터 너무 지치고 힘들어서 발표 준비를 제대로 하지 못했다. 트러블슈팅도 그 날 그 날 다 정리해놔서 정말 이야기할 것도 많았지만 갑자기 현타가 와서 중요해 보이는 트러블슈팅도 아닌 것 같고 어디까지 정리해야 할지도 모르겠어서 조금 소홀하게 준비했던 것 같다. 지금껏 열심히 하다가 중요한 날 바보같은 행동을 하게 되어서 속상하지만 지나간 일은 지나간 일이고, 앞으로는 컨디션 조절을 잘해서 최종 발표 때엔 후회없도록 최선을 다해야겠다. </p>
<p>나뿐만 아니라 모든 팀원들이 지쳤기 때문에 월요일까지 재충전하는 시간을 갖고 화요일에 다시 모여 앞으로의 3주간의 방향을 잡아보기로 했다. 백엔드 팀들과는 월요일에 잠깐 만나서 중간 발표 피드백을 회고하는 시간을 갖기로 했다. 피드백 회고록 관련 포스팅은 내일 작성하도록 하고 앞으로 3주간 하고 싶은 것들을 리스트업하는 것으로 TIL을 마무리하려고 한다.</p>
<h3 id="1-로깅">(1) 로깅</h3>
<p> 운영 중인 웹 어플리케이션이 문제가 발생했을 경우, 문제의 원인을 파악하려면 문제가 발생했을 때 당시의 정보가 필요하다. 이런 정보를 얻기 위해서 Exception이 발생했거나, 중요 기능이 실행되는 부분에서는 적절한 로그를 남겨야한다. </p>
<p>로깅에 대한 키워드에 대해서만 대강 아는 수준이고, @Slf4j
어노테이션에 대해서만 조금 아는 수준이다. 로깅을 할 줄 알게 되면 문제 원인을 파악하는데 도움이 된다고 하니 적용해 보면 좋을 것 같다.</p>
<h3 id="2-스캐줄러">(2) 스캐줄러</h3>
<p>한정적인 메모리를 프로세스가 효율적으로 사용 할 수있도록 작업을 할당시켜주는 역할을 한다. </p>
<p>스케줄러는 심화주차 때 추가 학습 주제였는데 기본적으로 공부해야할 부분을 따라가기도 벅차서 쳐다보지도 못했던 주제이다. 프로젝트 내에서 갈수록 쌓여가는 데이터를 관리하기 위해 스케줄러를 사용해서 주기적으로 데이터를 삭제해주는 기능을 구현하면 좋을 것 같다. </p>
<p>예를 들어, 봉사 활동이 끝난 게시물의 경우, 봉사 활동에 참여한 멤버에게 이력 정도만 남긴 상태로 게시물은 삭제하는걸 고려해봐도 좋을 것 같다.</p>
<h3 id="3-ci--cd">(3) CI / CD</h3>
<h4 id="ci">CI</h4>
<p>Continuous Integration의 약자로 지속적 통합이라는 뜻이다. 개발을 진행하면서도 품질을 관리할 수 있도록 하는 것으로 여러 명이 하나의 코드에 대해서 수정을 진행해도 지속적으로 통합하면서 관리할 수 있음을 의미한다.</p>
<h4 id="cd">CD</h4>
<p>Continuous Deployment의 약자로 지속적 제공이라는 의미이다. 
CI를 통해서 새로운 소스코드의 빌드와 테스트 병합까지 성공적으로 진행되었다면, 빌드와 테스트를 거쳐 github과 같은 저장소에 업로드하는 것을 의미한다.</p>
<h3 id="4-querydsl">(4) QueryDSL</h3>
<pre><code class="language-java">for (Board myBoard : myBoards) {
    List&lt;Enrollment&gt; enrollments = enrollRepository.findAllByBoard(myBoard);
    List&lt;Hashtag&gt; hashtags = hashtagRepository.findAllByBoardId(myBoard.getId());
    List&lt;String&gt; tags = new ArrayList&lt;&gt;();
    for (Hashtag hashtag : hashtags) {
        tags.add(hashtag.getTag().getMsg());
    }</code></pre>
<p>로직을 구현하면서 이중 for문을 처음으로 써봤다. 처음엔 아무것도 모르고 복잡한 로직을 구현했다는 것 자체에 초점이 맞춰져서 기분이 좋았었는데 알고 보니 매우 비효율적인 코드라는걸 알게 되었고, 주변 동료들로부터 QueryDSL을 사용해보라는 이야기를 들었다.</p>
<p>QueryDSL은 가장 하고 싶은 주제 중 하나이다. 처음 배운게 JPA라 JPA가 뭐가 좋은지도 글로만 배웠다. 복잡한 쿼리를 날리거나 동적 쿼리를 사용할 때 QueryDSL이 더 유용하다고는 들었는데 듣는거 말고 직접 체감하고 싶다. 다만, SQL도 아직 모르는 상태에서 QueryDSL을 배울 수 있는지에 대해서는 조금 생각해봐야겠다.</p>
<h3 id="5-swagger">(5) swagger</h3>
<p>Swagger는 REST API를 설계, 빌드, 문서화 및 사용하는 데 도움이되는 OpenAPI 사양을 중심으로 구축 된 오픈 소스 도구 세트이다.  </p>
<p>지금까지는 API 명세서를 노션에 작성하곤 했다. API 명세는 아무리 신중하게 짜더라도 수정 사항이 필수불가결로 생기곤 하는데 이럴 때마다 노션의 API 명세를 수정해 주는 것이 여간 귀찮은 일이 아니였을 뿐더러, 급하게 수정할 일이 생겼을 때 API 명세 수정하는 것을 깜빡하곤 해서 협업하는데 문제가 생긴 적도 있다.</p>
<p>스웨거 사용법을 좀 익숙하게 하고 싶은 생각이 있어서 스웨거도 적용해 보면 좋을 것 같다.</p>
<h3 id="6-jmeter">(6) Jmeter</h3>
<p>서버에 부하를 걸어서 성능을 분석할 수 있게 도와주는 툴이다. 하지만 이 툴을 사용해 보려면 성능 개선이 전제가 되어야 하는데 그러려면 성능 개선할 수 있는 방법을 공부해야만 한다.</p>
<h3 id="7-테스트-코드">(7) 테스트 코드</h3>
<p>테스트코드란 내가 작성한 메서드가 실제로 재대로 동작하는지 테스트 하는 코드이다. 테스트 코드를 작성하면 </p>
<ol>
<li>개발단계 초기에 문제를 발견하게 도와준다.</li>
<li>개발자가 나중에 코드를 리팩토링하거나 라이브러리 업그레이드 등에서 기존 기능이 올바르게 작동하는지를 확인 할 수 있다.</li>
<li>기능에 대한 불확실성을 감소시킬 수 있다.</li>
<li>시스템에 대한 실제 문서를 제공한다.</li>
</ol>
<p>TDD다 뭐다 하면서 테스트 코드가 중요하다는 말은 얼핏 듣기는 했지만, CRUD 공부가 우선이였기 때문에 우선순위를 뒤로 미뤄두었지만 공부의 필요성을 점점 체감하고 있다. 지금까지는 내가 구현한 코드가 어떤 상황에서 문제가 생기는지 파악하려면 포스트맨으로 그런 상황을 재연해내야만 해서 여간 불편한게 아니였다. </p>
<h3 id="8-xss-csrf">(8) xss, csrf</h3>
<p>이 이슈는 중간 발표 예상 질문으로 나왔던 키워드들이다. 아마 이런 질문을 주신데에는 프로젝트에 적용해 보라는 의미일 것이다. 아직도 여전히 Config 설정하는게 익숙치가 않지만 이번 기회에 조금 깊이 공부해서 xss와 csrf를 방어하는 방법에 대해서도 공부해 봐야 할 것 같다.</p>
<h3 id="마치며">마치며..</h3>
<p>하고 싶은건 8가지나 되는데 하나하나가 굵직한 주제들이 많기 때문에 3주라는 짧은 시간동안 어떤 공부를 하면 좋을지 신중하게 생각해서 선택해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.11.25 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.11.25-TIL</link>
            <guid>https://velog.io/@k_siik/2022.11.25-TIL</guid>
            <pubDate>Sat, 26 Nov 2022 07:37:47 GMT</pubDate>
            <description><![CDATA[<p>11월 26일은 대망의 중간 발표가 있는 날이다. 중간 발표는 지금까지 진행된 프로젝트에 대해 시니어 멘토 님께 코드 리뷰를 하고, 여러 질문들과 피드백을 받는 식으로 진행될 것이다. </p>
<p>저번 기수 때엔 멘토님께서 질문을 했을 때 대답을 잘 하지 못해서 이번 기수에는 예상 질문 목록을 미리 주시고 공부해올 수 있는 시간을 벌게 해주셨다. 질문 목록을 보니 아무런 준비가 되지 않은 상태에서 이런 질문을 갑자기 받았다면 나 역시 아무말도 못했을 것 같다. 미리 질문 목록을 받아 안도감이 들면서도 한편으로는 진짜 취업 면접 때 아무런 준비가 되지 않은 상태에서 이런 질문들을 받지 않도록 미리 준비해야겠다는 경각심도 들었다.</p>
<blockquote>
<p>질문 목록</p>
</blockquote>
<ol>
<li>스프링에서 트랜잭션을 구현하는 원리를 알고계신가요?</li>
<li>AOP 에 대해서 아는부분만 설명 부탁드립니다!</li>
<li>웹 공격기법중에서 XSS 에 대해서 설명 부탁드립니다.</li>
<li>웹 공격기법중 CSRF 공격기법에 대해 설명 부탁드립니다.</li>
<li>DI 는 무엇인지, 왜 쓰는지 설명 부탁드립니다.</li>
</ol>
<p>질문목록을 받고 충격받았던 사실은 트랙잭션, AOP, DI는 항해 강의 자료를 통해서, 혹은 WIL 과제 때문에라도 공부를 해야만 했었는데 CSRF, XSS는 단어 자체가 생소하다고 느꼈는데 구글링에 내가 검색한 기록이 이미 있었다. 사람은 역시 망각의 동물이 맞는 것 같고 기록의 중요성을 또 한 번 실감하게 되었다.</p>
<p>TIL 시작.</p>
<h1 id="1-트랜잭션">[1] 트랜잭션</h1>
<h2 id="1-트랜잭션의-의미">{1} 트랜잭션의 의미</h2>
<p>먼저 Transaction의 단어 뜻부터 살펴보면 표면적으로는 &#39;거래&#39; 라는 의미이고, 데이터베이스에서는 &#39;더 이상 쪼갤 수 없는 업무 처리의 최소 단위&#39; 라는 의미이다. </p>
<p>예를 들어서 설명하면 &#39;거래&#39; 라는 의미와 &#39;더 이상 쪼갤 수 없는 업무 처리의 최소 단위&#39;가 일맥상통하는 의미라는 사실을 알게 된다.
A라는 사람과 B라는 사람이 거래를 한다고 하자. 각자의 통장 잔고에는 10,000원이 있다. A가 B에게 5,000원을 준다고 하면 거래는 이렇게 성사될 것이다.</p>
<blockquote>
</blockquote>
<ol>
<li>A가 통장에서 5,000원을 인출 한다. (A 통장 잔고 : 5,000원)</li>
<li>A가 B의 통장에 5,000원을 송금한다.</li>
<li>B의 통장 내역에 15,000원이 찍힌다.</li>
</ol>
<p>이 일련의 과정들이 하나의 &#39;거래&#39;가 되는 것이다. 이 중 하나라도 처리가 되질 않는다면 &#39;거래&#39;가 성립되지 않는다. 이 일련의 과정이 &#39;더 이상 쪼갤 수 없는 업무 처리의 최소 단위&#39;가 되는 것이다.</p>
<p>만약 서버에서 &#39;더 이상 쪼갤 수 없는 업무 처리의 최소 단위&#39;를 잘 설계하지 않는다면 db에도 정상적으로 처리되지 않을 것이고, 클라이언트에게도 잘못된 데이터를 전달하게 될 가능성이 매우 크다. 위의 예시처럼 A가 B에게 5,000원을 송금했는데 B의 통장 내역에 5,000원이 추가되지 않았다면 매우 위험한 상황에 처하게 되는 것이다.</p>
<h2 id="3-트랜잭션의-특징">{3} 트랜잭션의 특징</h2>
<p>위와 같은 위험한 상황이 발생하지 않기 위해 트랜잭션이라는 장치를 둔 것이고, 트랜잭션은 다음과 같은 특징을 가지고 있다.</p>
<blockquote>
</blockquote>
<ul>
<li>원자성 (Atomicity)</li>
<li>일관성 (Consistency)</li>
<li>독립성 (Isolation)</li>
<li>영속성 (Durability)</li>
</ul>
<h3 id="1-원자성">(1) 원자성</h3>
<p>트랜잭션의 연산은 데이터베이스에 모두 반영되든지 아니면 전혀 반영되지 않아야 한다. 즉, commit 혹은 rollback이 이루어져야 한다. 트랜잭션 내의 모든 명령은 반드시 완벽히 수행(commit)되어야 하며, 모두가 완벽히 수행되지 않고 어느하나라도 오류가 발생하면 트랜잭션 전부가 취소(rollback)되어야 한다.</p>
<h3 id="2-일관성">(2) 일관성</h3>
<p>트랜잭션이 진행되는 동안에 데이터베이스가 변경 되더라도 업데이트된 데이터베이스로 트랜잭션이 진행되는것이 아니라, 처음에 트랜잭션을 진행 하기 위해 참조한 데이터베이스로 진행된다. </p>
<p>예를 들어, 댓글 테이블에 FK로 게시물 PK값이 걸려 있을 경우, 게시물이 삭제되어 게시물 PK값이 변경되면 댓글 테이블 내에 걸려있는 게시물 PK값 역시 변경되어 일관성을 맞춰줘야 한다는 의미이다. </p>
<h3 id="3-독립성">(3) 독립성</h3>
<p>트랜잭션이 실행하는 도중에 변경한 데이터는 이 트랜잭션이 완료될 때까지 다른 트랜잭션이 참조하지 못하게 하는 특성이다. 데이터베이스는 클라이언트들이 같은 데이터를 공유하는 것이 목적이므로 여러 트랜잭션이 동시에 수행되어야 한다. 이때 트랜잭션은 상호 간의 존재를 모르고 독립적으로 수행되어야 한다. 이를 유지하기 위해서는 여러 트랜잭션이 동시에 접근하는 데이터에 대한 제어가 필요하다. 여러 트랜잭션이 동시에 수행되더라도 각각의 트랜잭션은 다른 트랜잭션의 수행에 영향을 받지 않고 독립적으로 수행되어야 한다.</p>
<h3 id="4-영속성">(4) 영속성</h3>
<p>트랜잭션의 성공 결과 값은 심지어 시스템에 장애가 발생이 되더라도 변함없이 보관되어야 한다는 것으로 트랜잭션이 정상적으로 완료된 경우에는 db에 확실히 기록해야 하며, 부분 완료된 경우에는 작업을 취소하여야 한다. </p>
<h1 id="2-aop">[2] AOP</h1>
<h2 id="1-aop의-필요성">{1} AOP의 필요성</h2>
<p>객체지향적인 프로그래밍을 위해 어플리케이션을 설계할 때 책임과 관심사에 따라 클래스를 분리한다. 클래스가 하나의 책임만을 가지도록 분리함으로써 각 모듈의 응집도는 높아지고 결합도는 낮아지게 된다.</p>
<p>그러나 객체지향 설계 방식을 따르더라도 한 가지 아쉬운 점이 존재한다. 여러 클래스에 로깅이나 보안 및 트랜잭션 등 공통된 기능들이 흩어져 존재한다는 점이다. 이렇게 어플리케이션 전반에 걸쳐 흩어져있는 공통되는 부가 기능들을 관심사라고 한다. </p>
<p>만약 트랜잭션이나 로깅 및 보안 등의 부가 기능의 정책이나 API를 변경해야 한다면 이를 사용하는 모든 코드를 수정해야만 할 것이다. 이는 단일 책임 원칙을 위배하게 되고, 결국 서비스 클래스의 응집도가 떨어지면 가독성이 나빠지며, 변경할 부분이 명확하게 드러나지 않게 되는 등 유지보수 측면에서 아쉬운 점이 많아진다.</p>
<p>이러한 관심사를 어플리케이션의 핵심 비즈니스 로직 코드로부터 분리하는 방법론을 AOP, 관점지향 프로그래밍이라고 한다.</p>
<h2 id="2-aop-1">{2} AOP</h2>
<p>AOP란 OOP로 독립적으로 분리하기 어려운 부가 기능을 모듈화하는 방식이다. 위의 예시처럼 트랜잭션 관리와 같은 부분이 바로 부가 기능 모듈이며, 이를 Aspect라고 한다. 핵심 비즈니스 로직을 담고 있지는 않지만 어플리케이션에 부가됨으로써 의미를 갖는 특별한 모듈이다. AOP는 핵심 비즈니스 로직과 부가 기능 Aspect를 분리하는 등 OOP를 보완하는 역할을 한다.</p>
<h2 id="3-aop-사용법">{3} AOP 사용법</h2>
<blockquote>
</blockquote>
<ul>
<li>Spring AOP 의존성을 추가한다.</li>
<li>부가 기능에 해당하는 코드를 모듈화하기 위해 클래스를 만든다.</li>
<li>@Aspect를 설정하여 이 클래스가 Aspect임을 명시하고 @Component를 설정하여 스프링 빈으로 등록 한다.</li>
<li>AOP가 실행되는 시점을 설정해준다.</li>
</ul>
<h1 id="3-xss-공격-기법">[3] XSS 공격 기법</h1>
<p>XSS는 Cross-Site Scripting의 약자로 악의적인 사용자가 공격하려는 사이트에 스크립트를 넣는 기법을 말한다. 공격에 성공하면 사이트에 접속한 사용자는 삽입된 코드를 실행하게 되며, 보통 의도치 않은 행동을 수행시키거나 쿠키나 세션 토큰 등의 민감한 정보를 탈취한다. XSS는 크게 Reflected / Stored / DOM 3가지의 타입으로 나누어 볼 수 있다.</p>
<h2 id="1-reflected-xss">{1} Reflected XSS</h2>
<p>검색, 조회 기능과 같이 사용자로부터 입력 받은 데이터가 페이지에 반사되어 노출되는 경우, 공격코드가 포함된 URL을 타 사용자에게 전달하는 방식으로 공격을 수행한다.</p>
<h2 id="2-stored-xss">{2} Stored XSS</h2>
<p>게시글, 사용자 정보 등 한번 저장되면 장기적으로 XSS 공격코드가 웹 서비스에 남아 사용자에게 지속적으로 피해를 줄 수 있다. 보통 서비스 기능 상 장기적으로 데이터가 남는 프로필 저장, 게시글 작성, 댓글 작성등의 기능에서 나타나는 경우가 많다.</p>
<h2 id="3-dom-xss">{3} DOM XSS</h2>
<p>사용자 입력이 페이지에 직접 반영되는 형태가 아닌, JS 함수등으로 인해 DOM 내부에서 처리되는 중 스크립트가 실행 가능한 XSS를 의미한다.</p>
<h1 id="4-csrf-공격-기법">[4] CSRF 공격 기법</h1>
<h2 id="1-csrf란">{1} CSRF란?</h2>
<p>Cross-Site Request Forgery(CSRF, 사용자간 요청 위조)는 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격을 말한다.</p>
<h2 id="2-csrf-공격-과정">{2} CSRF 공격 과정</h2>
<p>CSRF 공격을 받는 상황이 몇가지 전제되어 있어야 한다.
일단 사용자가 보안이 취약한 서버로부터 이미 인증을 받은 상태여야 하고, 쿠키 기반으로 서버 세션 정보를 획득할 수 있어야 합니다. 그리고 공격자는 서버를 공격하기 위한 요청 방법에 대해 미리 파악하고 있어야 한다. 예상치 못한 파라미터가 있으면 불가능하다.</p>
<p>이러한 전제 조건이 있는 상황에서 사용자가 보안에 취약한 서버에 로그인을 하게 되면, 사용자 브라우저에 저장되어 있는 세션 아이디를 이용하여 사용자가 악성 스크립트 페이지를 누르도록 유도한다. 사용자가 악성 스크립트가 작성된 페이지 접근하게 되면  쿠키에 저장된 sessionID는 브라우저에 의해 자동적으로 함께 서버로 요청된다. 서버는 쿠키에 담긴 sessionID를 통해 해당 요청이 인증된 사용자로부터 온 것으로 판단하고 처리하게 되면서 악성 코드가 사용자의 pc에서 실행이 된다.</p>
<h1 id="5-di">[5] DI</h1>
<p>DI는 Dependency Injection의 줄임말로 의존관계 주입이라는 의미이다. 우리말로 풀어도 무슨 의미인지 잘 모르겠으니 단어 하나 하나 다 알아보도록 하자.</p>
<h2 id="1-dependency의존-관계">{1} Dependency(의존 관계)</h2>
<p>&#39;A가 B를 의존한다&#39; 라는 의미는 B에 따라 A에 영향을 미친다는  의미로 바꿔 말할 수도 있다. 예를 들어 요리사는 김치찌개 레시피를 보고 요리를 한다고 하면, 요리사는 김치찌개 레시피에 의존적이고, 레시피가 변하면 그에 맞춰 요리사도 요리를 달리하게 될 것이다. </p>
<p>이를 코드로 표현하면, </p>
<pre><code class="language-java">
public class Chef {

    private final KimchiSoupRecipe kimchiSoupRecipe;

    public Chef() {
        kimchiSoupRecipe = new KimchiSoupRecipe();
    }
}</code></pre>
<p>김치찌개 레시피가 변화할 때마다 Chef 객체가 생성될 때, 변화된 레시피가 초기화되기 때문에 의존적이라고 할 수 있다.</p>
<p>이렇게 서로 종속적인 관계를 맺는 형태는 객체지향적이지 못한 코드라고 할 수 있다. 어느 한 코드를 수정해야할 경우에 이에 의존적인 다른 모든 코드를 수정해야만 하는 상황이 오기 때문이다.</p>
<p>이를 OOP의 5가지 원칙 중 하나인 의존관계 역전 원칙을 통해 상위 모듈이 하위 모듈에 의존하지 않고, 상위 모듈, 하위 모듈 모두 추상화된 모듈에 의존하게끔 코드를 작성해야만 한다.</p>
<p>이해하기 어려우므로 예를 들어서 설명하면, 김치찌개 레시피가 아닌 한식 레시피라는 추상화된 인터페이스를 생성한 후, 인터페이스 내에는 각종 한식 요리의 레시피 메서드를 선언만 해놓은후, 각종 레시피는 인터페이스를 구현해 놓고 따로 정의를 해놓고 가져다 쓰게 되면, 레시피가 달라지더라도 레시피 클래스만 변경하면 되므로 관계가 느슨해지고 결합도가 낮아진다. </p>
<blockquote>
<p>참고 자료</p>
</blockquote>
<ul>
<li><a href="https://tecoble.techcourse.co.kr/post/2021-04-27-dependency-injection/">https://tecoble.techcourse.co.kr/post/2021-04-27-dependency-injection/</a></li>
<li><a href="https://tecoble.techcourse.co.kr/post/2021-06-25-aop-transaction/">https://tecoble.techcourse.co.kr/post/2021-06-25-aop-transaction/</a></li>
<li><a href="http://wiki.hash.kr/index.php/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98">http://wiki.hash.kr/index.php/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98</a></li>
<li><a href="https://www.hahwul.com/cullinan/xss/">https://www.hahwul.com/cullinan/xss/</a></li>
<li><a href="https://junhyunny.github.io/information/security/spring-boot/spring-security/cross-site-reqeust-forgery/">https://junhyunny.github.io/information/security/spring-boot/spring-security/cross-site-reqeust-forgery/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.11.24 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.11.24-TIL</link>
            <guid>https://velog.io/@k_siik/2022.11.24-TIL</guid>
            <pubDate>Thu, 24 Nov 2022 11:50:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/k_siik/post/f7900041-9c86-44ac-a399-2b43d1bd23fc/image.png" alt=""></p>
<p>오늘의 커밋</p>
<ul>
<li>jwtfilter 앞 단에 filter 하나 더 둬서 jwtException 처리</li>
</ul>
<p>스프링 시큐리티와 jwt만 생각하면 숙련주차 때 고생하던 생각이 난다. CRUD도 이제 막 배우기 시작했는데 그 어려운 보안 관련 프레임워크를 배우다니..! 어우.. 그 당시엔 열심히 삽질해도 콘크리트 바닥을 삽질하는 기분이였는데 요즘엔 그 단단하던 바닥에 작은 흠집 정도는 나는 정도의 이해도가 생기긴 했다. 여전히 많은 공부가 필요하지만 현재로서는 이 정도의 이해도를 가진 것만으로도 대견스럽다. TIL 시작.</p>
<h1 id="1-spring-security">[1] Spring Security</h1>
<p>스프링 시큐리티에 대해 다시 한 번 공부해 보고, 숙련주차에는 이해하지 못했던 스프링 시큐리티의 내부 구조에 대해 아주 조금만 더 깊이 들어가 본 후에 프로젝트에서 jwt 관련 이슈에 대해 이야기 해보는 순서로 진행해 보려고 한다.</p>
<h2 id="1-spring-security란">{1} Spring Security란?</h2>
<p>스프링 시큐리티는 인증, 인가와 같은 보안을 담당하는 스프링 하위 프레임워크이다. Spring Security는 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안관련 로직을 작성하지 않아도 된다는 장점이 있다. 스프링 시큐리티를 사용하지 않는다면 세션으로 자동적으로 체크해준다던가 로그인 후 리다이렉트해준다던가 하는 당연스러웠던 것들을 자체적으로 구현해야만 한다.</p>
<h2 id="2-spring-security-구조">{2} Spring Security 구조</h2>
<h3 id="1-스프링-영역">(1) 스프링 영역</h3>
<p><img src="https://velog.velcdn.com/images/k_siik/post/a564e2ec-308d-4e5f-b87e-30a267e5c2f2/image.png" alt=""></p>
<p>스프링 시큐리티 구조에 대해 이야기하기 앞서 스프링 MVC 앞 단의 구조에 대해 먼저 조금 알아보려고 한다. 위 그림을 살펴보면 스프링 영역의 맨 앞 단에 내가 완전 모르는 Dispatcher Servlet과 Interceptor가 있고, 그나마 조금 공부한 AOP, Controller 등이 있다. 이 이후에는 Service, Respository, DB가 차례로 있을 것이다. 스프링 영역에 대해 키워드 위주로만 공부하고 지나가고 다음에 제대로 다뤄보자.</p>
<blockquote>
<p>Dispatcher Servlet - Distpatch는 &#39;보내다&#39;라는 의미이고, Servlet은 &#39;클라이언트의 요청에 대해 동적으로 작동하는 웹 어플리케이션 컴포넌트&#39;이다. 디스패처 서블랫은 HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에게 위임해 주는 Front Controller 역할을 한다. </p>
</blockquote>
<blockquote>
<p>Interceptor - 인터셉터는 디스패처 서블릿이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공한다. 세부적으로 적용해야 하는 인증이나 인가와 같이 클라이언트 요청과 관련된 작업 등이 있다. 예를 들어 특정 그룹의 사용자는 어떤 기능을 사용하지 못하는 경우가 있는데, 이러한 작업들은 컨트롤러로 넘어가기 전에 검사해야 하므로 인터셉터가 처리하기에 적합하다.</p>
</blockquote>
<blockquote>
<p>Filter - 스프링 영역 바깥에 있는 영역으로 애플리케이션의 HTTP 요청 및 응답을 가로채는 데 사용되는 개체이다. doFilter라는 메서드가 필터체인으로 가로채 주는 역할을 한다. 인터셉터와 AOP도 비슷한 역할을 하지만 실행되는 시점이 다르므로 각 상황에 맞춰 구현해야만 한다.</p>
</blockquote>
<p>스프링 시큐리티는 디스패처 서블릿보다 앞 단인 Filter 기반으로 동작하기 때문에 Spring MVC와 분리되어 관리 및 동작한다. 인터셉터에서도 보안 관련 작업을 할 수 있지만 인터셉터 이전 단계인 필터에서 구현하는 것이 일반적이고 효율적이다.</p>
<h3 id="2-스프링-시큐리티-구조">(2) 스프링 시큐리티 구조</h3>
<p><img src="https://velog.velcdn.com/images/k_siik/post/7f08cb97-43f9-47cc-b2e8-308ab9f5075f/image.png" alt=""></p>
<p>위의 이미지와 함께 사용자를 인증하는 과정을 알아보면,</p>
<ol>
<li>사용자가 로그인을 요청한다.(HTTP Request)</li>
<li>HTTP 요청이 Authentication Filter를 제일 처음 거친다.</li>
<li>UsernamePasswordAuthenticationToken을 거쳐서 username와 password 데이터를 가져온 후 인증을 위한 토큰을 생성 후 인증을 다른 쪽에 위임한다.</li>
<li>AuthenticationManager, AuthenticationProvider, UserDetailService를 거쳐 DB에 접근한다.</li>
<li>DB에 존재하는 유저라면 USerDetails로 꺼내 session을 생성한다.</li>
<li>스프링 시큐리티의 인메모리 세션 저장소인 SercurityContextHolder에 세션을 저장한다.</li>
<li>사용자에게 Session ID와 함께 응답을 내려준다.</li>
<li>이후 요청에서는 요청 쿠키에서 SessionId를 보고 검증 후 유효하면 요청에 대한 응답을 한다.</li>
</ol>
<h2 id="3-프로젝트에-유효하지-않은-토큰-관련-예외-처리-적용">{3} 프로젝트에 유효하지 않은 토큰 관련 예외 처리 적용</h2>
<p>프로젝트에서 유효하지 않은 토큰으로 Http 요청을 받았을 때 500에러가 터졌었는데 이 이슈를 언제 해결하나 프로젝트 초반부터 고민하고 있었는데 갑자기 무슨 바람이 들었는지 하던 작업을 다 멈춰 버리고 어제, 오늘 예외 처리하는데 투자했다. 결과적으로는 500에러가 아닌 401 Unauthorized 에러와 함께 에러 메세지를 응답하도록 처리했다. 효과적으로 처리를 했는지는 아직도 잘은 모르겠지만 원하는 결과대로 했다는 것에 만족하기로 했다.</p>
<h3 id="1-첫번째-시도">(1) 첫번째 시도</h3>
<p>프로젝트에서는 전역적인 예외처리를 AOP를 사용하여 GlobalExceptionHandler가 모든 예외를 잡도록 설정했었어서 토큰 관련 예외도 여기서 다 잡아낼줄 알았다. </p>
<p>하지만 JwtFilter는 AOP보다 앞 단에 위치해 있다는 사실을 알게 되고 나서부터 Filter에 대한 공부를 시작했고 Filter chain을 커스텀해서 사용할 수 있으며 JWT 토큰 관련 예외를 잡기 위해서는 JwtFilter보다 앞 단에 위치해 있어야만 예외를 잡을 수 있다는걸 알게 되었다. </p>
<p>지금까지 전역적인 예외처리를 할 때에도 Service 단보다 AOP 단이 앞 단에 있기 때문에 예외를 잡을 수 있었던 것처럼 어찌보면 당연한 이치다.</p>
<h3 id="2-두번째-시도">(2) 두번째 시도</h3>
<pre><code class="language-java">@RequiredArgsConstructor
public class JwtSecurityConfiguration
        extends SecurityConfigurerAdapter&lt;DefaultSecurityFilterChain, HttpSecurity&gt; {

    private final TokenProvider tokenProvider;
    private final JwtExceptionFilter jwtExceptionFilter;

    @Override
    public void configure(HttpSecurity httpSecurity) {
        JwtFilter customJwtFilter = new JwtFilter(tokenProvider);
        httpSecurity.addFilterBefore(customJwtFilter, UsernamePasswordAuthenticationFilter.class);

        // JwtFilter 앞단에 JwtExceptionFilter 를 위치시키겠다는 설정
        httpSecurity.addFilterBefore(jwtExceptionFilter, JwtFilter.class);
    }
}</code></pre>
<p>먼저 Config 클래스에 JwtFilter보다 JwtExceptionFilter가 더 앞 단에 위치하도록 설정했다.</p>
<pre><code class="language-java">@Component
public class JwtExceptionFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        try {
            filterChain.doFilter(request, response);
        } catch (GlobalException e) {
            setErrorResponse(response, e);
        }
    }

    private void setErrorResponse(HttpServletResponse response, GlobalException e) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType(&quot;application/json; charset=UTF-8&quot;);

        ObjectMapper mapper = new ObjectMapper();

        response.getWriter().write(mapper.writeValueAsString(
                ResponseDto.fail(
                        e.getErrorCode().getHttpStatus(),
                        e.getErrorCode().getMessage(),
                        e.getErrorCode().getDetail()
                )));
    }
}</code></pre>
<p>JwtExceptionFilter에서는 doFilter가 진행이 되다가 GlobalException이 터질 경우 401 코드와 함께 에러메세지를 응답하도록 했다. </p>
<p>여기서 처음 알게 된 사실도 있고, 비효율적(?)으로 코드를 짰다고 느꼈던 포인트가 몇가지 있다.</p>
<p>이 코드를 작성하면서 HttpServletResponse의 객체를 통해 상태 코드를 설정해줄 수 있다는 것도 처음 알게 되었다.</p>
<p>그리고 프로젝트에서 지금껏 썼던 response 양식인 ResponseDto의 fail 메서드를 이용하고 싶었지만 어떻게 하는지 잘 몰라서 객체를 json 형태로 바꿔주는 ObjectMapper를 이용해서 응답을 했는데 이렇게 하면 안될 것 같은 느낌이 강하게 들었지만 현재로선 이게 최선이였다. 나중에 더 공부해서 리펙터링해야겠다.</p>
<pre><code class="language-java">    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException e) {
            log.info(&quot;잘못된 JWT 서명입니다.&quot;);
            throw new GlobalException(ErrorCode.WRONG_TOKEN);
        } catch (ExpiredJwtException e) {
            log.info(&quot;만료된 JWT 토큰입니다.&quot;);
            throw new GlobalException(ErrorCode.TOKEN_EXPIRED);
        } catch (UnsupportedJwtException e) {
            log.info(&quot;지원되지 않는 JWT 토큰입니다.&quot;);
            throw new GlobalException(ErrorCode.UNSUPPORTED_TOKEN);
        }
    }</code></pre>
<p>그리고 마지막으로 토큰 검증하는 메서드에 각 조건에 따라 GlobalException을 터뜨리도록 했다.</p>
<blockquote>
</blockquote>
<p>참고 자료</p>
<ul>
<li><a href="https://sungminhong.github.io/spring/security/">https://sungminhong.github.io/spring/security/</a></li>
<li><a href="https://velog.io/@hellonayeon/spring-boot-jwt-expire-exception">https://velog.io/@hellonayeon/spring-boot-jwt-expire-exception</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.11.23 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.11.23-TIL</link>
            <guid>https://velog.io/@k_siik/2022.11.23-TIL</guid>
            <pubDate>Wed, 23 Nov 2022 14:08:13 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/k_siik/post/d39818fe-e611-47ef-a5c9-3a5d3b5308f5/image.png" alt=""></p>
<h4 id="오늘의-커밋">오늘의 커밋</h4>
<ul>
<li>게시물 CRUD에 해시태그 적용</li>
<li>프로필 수정 시 db 업데이트 안되는 이슈 해결</li>
<li>프로필 수정 시 비밀번호 문자열 그대로 업데이트되는 이슈 해결</li>
</ul>
<h1 id="1-해시태그-기능">[1] 해시태그 기능</h1>
<p>프로젝트 기획 단계에서는 봉사활동을 카테고리 별로 나눈 후, 카테고리로 검색할 수 있는 기능을 만드는 수준에서 끝이 났었다. 하지만 게시물에 해시태그와 같은 기능을 추가하면 어떨까 라는 아이디어가 프로젝트 진행 중에 나오게 되었다. </p>
<h2 id="이슈-1-커스텀한-해시태그-vs-지정된-해시태그">이슈 1) 커스텀한 해시태그 VS 지정된 해시태그</h2>
<p>처음에는 인스타그램과 같이 해시태그를 커스텀해서 만드는 형태를 생각했었지만 이렇게 커스텀할 경우, 띄어쓰기, 오타, 다양한 단어 사용 등의 이유로 해시태그들이 통일성이 떨어질 것이 우려되었다. 그래서 결국 해시태그 여러개를 지정해 놓고 그 안에서 고르는 형태로 진행하게 되었다. </p>
<p>여러모로 이 방법이 좋다고 생각하는게 앞서 언급했듯이 통일성이 생기기 때문에 한 해시태그에 해당하는 게시글을 검색하기가 수월하고, db에도 지정된 해시태그들만 저장되어 복잡해지지 않을거라 생각했다.</p>
<h2 id="이슈-2-해시태그-저장-방식">이슈 2) 해시태그 저장 방식</h2>
<h3 id="방법-1---board-테이블-문자열-컬럼에-저장">방법 1 - board 테이블 문자열 컬럼에 저장</h3>
<pre><code class="language-java">String hashtag = &quot;#CHILD#CLEANING...&quot;</code></pre>
<p>board 테이블에 문자열 형태의 컬럼을 만들고 해시태그 여러개를 한 문자열에 이어 붙여서 저장하는 형태를 생각해 보았다. 이렇게 
하면 데이터를 경제적으로 사용할 수 있다는 점이 장점이지만 해시태그 별 검색을 하기에는 매우 어려운 구조이다.</p>
<h3 id="방법-2---board-테이블-list-컬럼에-저장">방법 2 - board 테이블 list 컬럼에 저장</h3>
<pre><code class="language-java">List&lt;String&gt; = [&quot;CHILD&quot;, &quot;CLEANING&quot;,...] </code></pre>
<p>이렇게 저장하는 방식은 문자열 형태로 저장하는 것보다는 검색하기에 수월하겠지만 그래도 여전히 어려운 구조이다. 그리고 한 컬럼 내에 여러 개의 데이터를 저장하는 방식이 좋은 방법은 아니라고 한다.</p>
<h3 id="방법-3---hashtag-테이블-새로-생성해서-board-pk값과-함께-저장">방법 3 - hashtag 테이블 새로 생성해서 board pk값과 함께 저장</h3>
<p>내 지식으로는 검색하기에 가장 수월한 방법이 테이블을 새로 만드는 방법밖에는 없다고 생각한다. 이렇게 테이블을 새로 만들면 게시물을 조회할 때마다 게시물 테이블과 해시태그 테이블을 모두 조회해와야 한다는 단점이 있긴 하지만 이 이슈는 해시태그의 최대 개수를 제한하는 방법을 통해 성능을 그나마 유지할 수 있을 것이다. 그 대신, 테이블을 새로 만듦으로써 장점은 게시물 별 해시태그 조회는 당연 가능하고, 해시태그별 검색도 할 수 있게 된다.
<img src="https://velog.velcdn.com/images/k_siik/post/7a696f00-690d-493e-9a4b-dc1b3a7564e0/image.png" alt=""></p>
<h1 id="2-프로필-수정">[2] 프로필 수정</h1>
<pre><code class="language-java">@Transactional
public ResponseDto&lt;MsgResponse&gt; updateMyProfile(Member member, CompanyUpdateRequest companyUpdateRequest) throws IOException {
    check.isAdmin(member);

    check.isPassword(companyUpdateRequest.getPassword(), companyUpdateRequest.getPasswordConfirm());

    String profileImage = s3Uploader.uploadFiles(companyUpdateRequest.getProfileImage(), &quot;company&quot;);
    member.update(companyUpdateRequest, profileImage);

    return ResponseDto.success(new MsgResponse(&quot;수정 완료!&quot;));
}</code></pre>
<h2 id="1-db에-update안되는-이슈">{1} db에 update안되는 이슈</h2>
<p>위 코드는 버그를 발견했을 당시의 코드이다. 코드 리뷰를 짧게 하자면, 매개변수로 받은 member 객체를 수정 요청한 데이터로 update를 한 후, flush를 통해 db에 저장하도록 구현을 했다. 이렇게 코드를 구현하니까 수정된 데이터가 db에 적용이 안되는 이슈가 발생했다.</p>
<p>여기서 문제는 매개변수로 받은 멤버 객체에서 치명적인 오류가 있었다. 매개변수로 받은 맴버 객체는 Spring Security에서 제공하는 @AuthenticationPrincipal을 적용한 UserdetailImpl로부터 불러온 멤버 객체이다. 단순히 추측일뿐이긴 하지만 JPA가 제공하는 멤버 객체가 아니라서 db에 반영이 안되는 거라고 생각해서 JpaRepository로부터 불러온 멤버 객체를 업데이트하니까 db에 잘 적용이 되었다. 이 이슈 관련해서는 더 공부해볼 필요가 있을 것 같다.</p>
<h2 id="2-비밀번호-문자열로-db에-저장되는-이슈">{2} 비밀번호 문자열로 db에 저장되는 이슈</h2>
<p>위와 같이 이슈를 해결하고 난 후, 수정한 비밀번호로 재로그인을 시도했는데 401 UnAuthorized 에러가 발생했다. 비밀번호가 틀렸다는 의미이다. 비밀번호가 잘 수정되었는지 테이블을 확인해보았는데 입력받은 문자열 그대로 저장이 되어있었다.</p>
<p>이 이슈는 매우 위험한 이슈이다. 개발적으로 위험한 수준이 아니라 철컹철컹할 수도(?) 있는 수준의 위험성이다. 현재는 개발단계이고 우리 웹서비스를 이용하는 사람도 없으니 문제될 것이 없지만 나중에 웹서비스를 배포하게 되고, 사용자가 생길 때 이런 이슈가 터지게 되면 보안 관련 법안에 저촉되는 행위라는 이야기를 들었다.</p>
<p>등골이 오싹해지면서 곧바로 코드를 수정하여 요청받은 비밀번호 문자열을 인코딩하여 db에 update시킴으로써 해결하였다.</p>
<p>프로필 수정 관련 이슈를 모두 해결한 후의 코드이다.</p>
<pre><code class="language-java">@Transactional
public ResponseDto&lt;MsgResponse&gt; updateMyProfile(Member member, CompanyUpdateRequest companyUpdateRequest) throws IOException {
    check.isAdmin(member);

    // 시큐리티에서 제공하는 member 객체에 db 로부터 불러온 member 객체 덮어 쓰기
    member = check.findMember(member.getMemberId());
    check.isPassword(companyUpdateRequest.getPassword(), companyUpdateRequest.getPasswordConfirm());

    String encodedPassword = passwordEncoder.encode(companyUpdateRequest.getPassword());

    String profileImage = s3Uploader.uploadFiles(companyUpdateRequest.getProfileImage(), &quot;company&quot;);
    member.update(companyUpdateRequest, profileImage, encodedPassword);

    return ResponseDto.success(new MsgResponse(&quot;수정 완료!&quot;));
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.11.22 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.11.22-TIL</link>
            <guid>https://velog.io/@k_siik/2022.11.22-TIL</guid>
            <pubDate>Tue, 22 Nov 2022 22:45:11 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/k_siik/post/beda9229-aa2e-4747-902f-d3c55cd210d3/image.png" alt=""></p>
<h4 id="오늘의-커밋">오늘의 커밋</h4>
<ul>
<li>컴퍼니 페이지에서 내 게시물들에 지원한 지원자 전체 조회</li>
<li>게시물, 프로필 update 시 리스펀스값 수정</li>
<li>A브랜치에서 작업 중 들어온 잡다한 긴급 수정 사항들</li>
<li>해시태그 기능 추가(해시태그 엔티티 생성, 게시물에 해시태그 crud 적용 중) -&gt; 내일 포스팅 예정</li>
</ul>
<p>TIL 시작.</p>
<h1 id="1-companypage">[1] companypage</h1>
<h2 id="1-기존-방식">{1} 기존 방식</h2>
<p>프로젝트에서 원래 내가 생각했던 유저 플로우는 컴퍼니 페이지에 접속하면 본인이 게시한 게시글 목록을 조회할 수 있고, 조회된 게시글을 클릭하면 그 게시글에 해당하는 지원자 목록을 볼 수 있도록 하는걸 생각했고, 그렇게 구현을 했었다. 그래서 api도 내 게시글 목록 조회와 게시글에 해당하는 지원자 목록 조회 이렇게 두개로 나눠져 있었다.</p>
<h2 id="2-새-방식">{2} 새 방식</h2>
<p>하지만 프런트엔드의 성호 님과의 회의를 통해 api를 하나로 합쳐
내 게시글 전체에 지원한 지원자 목록을 한 번에 보여주고, 게시글에 대한 정보까지 한꺼번에 리스펀스하기로 했다. </p>
<p><img src="https://velog.velcdn.com/images/k_siik/post/fa45c457-b932-4e03-b195-08d367ead6bb/image.png" alt=""></p>
<p>와이어 프레임을 예로 들어 설명하면, 각 카드에는 지원자 정보(username), 신청 정보(신청 pk값, 신청 승인 여부), 게시물 정보(title, contnent, area 등..)값이 포함되어 있는 형태이다. 따라서 하나 api 리스펀스 값에 세 개의 테이블 정보를 불러와야하는 형태인 것이다. </p>
<p>처음 이 아이디어에 대해 들은 순간, &#39;어? 이거 내가 항상 해왔던 방식이랑 다르게 엄청 복잡하겠다.&#39; 생각이 들었다. 근데 뭔가 재미있을 것 같다는 생각이 들어서 성호 님께 자신있게 할 수 있을 것 같다고 말씀 드렸지만 생각보다 복잡했다.</p>
<h2 id="3-코드-리뷰">{3} 코드 리뷰</h2>
<pre><code class="language-java">/**
 * 내 게시물 지원자 전체 조회
 */
public ResponseDto&lt;List&lt;EnrollDetailResponse&gt;&gt; getVolunteers(Member member, int page, int size) {
    // TODO : 페이징 어떻게 하지..?
    check.isAdmin(member);
    // Pageable pageable = PageRequest.of(page, size);
    List&lt;Board&gt; myBoards = boardRepository.findAllByMember(member);
    List&lt;EnrollDetailResponse&gt; enrollDetailResponses = new ArrayList&lt;&gt;();
    for (Board myBoard : myBoards) {
        List&lt;Enrollment&gt; enrollments = enrollRepository.findAllByBoard(myBoard);
        for (Enrollment enrollment : enrollments) {
            enrollDetailResponses.add(new EnrollDetailResponse(enrollment));
        }
    }
    return ResponseDto.success(enrollDetailResponses);
}    </code></pre>
<p>먼저 코드 리뷰를 먼저 하자면,
관리자인지 확인 후, 관리자가 맞다면 본인이 생성한 게시물 목록을 boardRepository에서 조회해 온다. 그 다음, for 문으로 각 게시물에 해당하는 지원(enrollmentr) 목록을 조회한다.
각 지원에 대한 데이터를 enrollDetailResponse에 담아 리스펀스를 한다. 이 api를 구현하기 전에는 이중 for 문을 사용하리라고는 생각도 못했다. 분명 이 방법보다 더 좋은 방향의 코드를 작성할 수 있겠지만 일단 내가 할 수 있는 최선은 이 코드였다.</p>
<h1 id="2-git-stash">[2] git stash</h1>
<h2 id="1-git-stash란">{1} git stash란?</h2>
<p>아직 마무리하지 않은 작업을 스택에 잠시 저장할 수 있도록 하는 명령어이다. 이를 통해 아직 완료하지 않은 일을 commit하지 않고 나중에 다시 꺼내와 마무리할 수 있다.</p>
<h2 id="2-언제-사용">{2} 언제 사용?</h2>
<blockquote>
<p>A브랜치에서 작업하다가 급하게 B브랜치에서 작업해야 할때</p>
</blockquote>
<blockquote>
<p>A브랜치에서 작업하다가 B브랜치에서 다른 작업을 작업 해야하는데 깜빡하고 A브랜치에서 작업하다가 나중에 깨달았을 때</p>
</blockquote>
<p>프로젝트 VONGLE에서는 api 별로 브랜치를 새로 파서 작업을 한다. 작업을 하다보면 클라이언트의 요구 사항이라던가 갑자기 할 작업이 떠오르는 등의 이유로 브랜치를 바꿔서 작업을 해야 하는 경우가 생긴다. 기존의 브랜치가 마무리가 되어가는 상황에서 브랜치를 바꾸는 경우에는 커밋을 하고 브랜치를 바꾸면 되지만 마무리가 되지 않았을 경우에는 커밋하기가 애매한데 이럴 때 git stash라는 명령어를 사용하면 좋다.</p>
<h2 id="3-git-stash-사용법">{3} git stash 사용법</h2>
<blockquote>
<p>git status - 현재 수정된 내용 확인</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/k_siik/post/429d20d4-7397-4027-b202-8405f78a0cfe/image.png" alt=""></p>
<p>현재 수정된 내용이 있지만 마무리가 안되어서 커밋하기엔 애매한 상황이라고 가정하자.</p>
<blockquote>
<p>git stash - 수정된 내용 임시 저장</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/k_siik/post/7676a3ed-bf68-47bd-abaf-09b961ca1adf/image.png" alt=""></p>
<p>git stash 명령어를 치면 working directory에 임시 저장이 되고, 수정 전 커밋 상황으로 돌아가고, git status에도 수정된 내용이 사라진다.</p>
<blockquote>
<p>git stash list - 임시 저장된 내역들 조회</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/k_siik/post/e0d931af-7002-4ed5-bab4-7dde3ca778c7/image.png" alt=""></p>
<p>임시 저장된 내역을 조회할 수 있다.(연습하느라 같은 수정 사항을 두 번 저장해 버렸다.)</p>
<blockquote>
<p>git switch -c 이동할 브랜치 - 다른 브랜치 파서 다른 작업
git commit -am &#39;커밋 내용&#39; - 다른 작업 커밋
git push origin 브랜치 - 커밋 내역 푸쉬</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/k_siik/post/c3ef3928-0ba9-4634-922f-73f5d7212270/image.png" alt=""></p>
<blockquote>
<p>git switch 기존브랜치 - 기존 브랜치로 다시 이동
git stash list - 여전히 임시 저장된 내역이 있는지 확인</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/k_siik/post/f7bbe5c8-c2e3-4dc3-a360-0cdb63d30d23/image.png" alt=""></p>
<p>다른 브랜치에서 작업을 하고 기존 브랜치로 돌아왔는데도 임시 저장된 내역이 있는 것을 확인할 수 있다.</p>
<blockquote>
<p>git stash apply - 최신 스태시 적용
git stash apply 내역이름 - 특정 스태시 적용</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/k_siik/post/c0066abe-309a-4b99-a575-43ca80ac5006/image.png" alt=""></p>
<p>맨 처음 수정 내역이 다시 적용된 것을 확인할 수 있다.</p>
<p>(기존 브랜치에는 다른 브랜치에서의 수정사항이 적용이 안되었기 때문에 pull을 해서 기존 브랜치에도 적용해야 한다.)</p>
<h2 id="4-잡다한-명령어">{4} 잡다한 명령어</h2>
<blockquote>
<p>git stash drop 스태시이름 - 특정 스태시 제거
git stash drop - 최근 스태시 제거</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/k_siik/post/6c1663d3-601f-4a54-b14d-f033d1e644d7/image.png" alt=""></p>
<p>아까 실수로 같은 스태시를 저장한 내역을 제거해 보았다.</p>
<blockquote>
<p>git stash pop - 스태시 제거하면서 적용(apply + drop)</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.11.21 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.11.21-TIL</link>
            <guid>https://velog.io/@k_siik/2022.11.21-TIL</guid>
            <pubDate>Mon, 21 Nov 2022 14:38:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/k_siik/post/2d021e6b-da60-4d10-96e3-09f8ed6a908b/image.png" alt=""></p>
<h4 id="오늘의-커밋">오늘의 커밋</h4>
<ul>
<li>봉사활동 승인 api 버그 수정</li>
<li>start line과 body의 status code 일치</li>
<li>게시물, 댓글 pagination</li>
</ul>
<p>TIL 시작.</p>
<h1 id="1-봉사-활동-승인-api-버그">[1] 봉사 활동 승인 api 버그</h1>
<p>일반 멤버가 봉사 활동을 신청하게 되면 관리자가 지원자를 승인할지 거부할지 결정할 수 있는데 이 때 사용되는 api가 봉사 활동 승인 api이다.</p>
<p>처음 구현할 당시에는 memberId로 신청 여부를 조회해온 다음 신청이 되어있으면 승인 or 거절을 선택할 수 있도록 구현했었다. </p>
<pre><code class="language-java">    // 멤버아이디로 봉사활동 지원자 조회
    Optional&lt;Enrollment&gt; findByMember(Member member);</code></pre>
<p>그런데 여기에는 치명적인 오류가 숨어있다. memberId로만 조회를 하게 되면 해당 memberId를 가지고 있는 유저가 다른 게시물에도 봉사 활동 신청을 했을 경우, memberId로 조회할 수 있는 것이 두개 이상이 되버리기 때문에 에러가 발생한다. (에러 메세지를 까먹어서 기록해두지 않은 것이 후회된다..)</p>
<p>sol) memberId, boardId 두개로 조회를 하려다가 그냥 봉사 활동 지원자 목록 조회 api에서 enrollId를 리스펀스하고, 리스펀스한 enrollId를 이용해서 신청 여부를 조회해 오는 방향으로 수정해서 버그를 해결할 수 있었다.</p>
<pre><code class="language-java">    /*
        신청 여부 확인
     */
    public Enrollment isEnrolled(Long enrollId) {
        Enrollment enrollment = enrollRepository.findById(enrollId).orElse(null);

        if (enrollment == null) {
            throw new GlobalException(ErrorCode.ENROLLMENT_NOT_FOUND);
        }

        return enrollment;
    }</code></pre>
<h1 id="2-status-code-이슈">[2] status code 이슈</h1>
<p>11월 19일에 처음 이 이슈에 대해 TIL을 작성하고 난 후, 계속 이 이슈가 머릿속을 떠나질 않았다. 그래서 나중에 좀 여유가 될때 해결하려고 했던 계획과는 달리, 오늘 해결해 버렸다.(할 일도 많은데..ㅠㅜ) body와 start line의 status code가 일치되니 기분은 아주 좋았다.</p>
<p>사실 아직도 start line에 접근해서 status code를 바꿔주는 방법을 자세히 알지는 못한다. 다만, 3주 쯤 전부터 관심을 가지고 공부했던 ResponseEntity를 통해 status code를 바꾸는 법에 대해서는 알고 있었다. 그래서 ResponseEntity를 통해 status code는 enum으로 관리했던 각각의 예외들에 해당하는 status code를 입력해주었고, 바디에는 기존에 쓰던 ResposneDto의 fail 메서드를 넣어줌으로써 해결했다.</p>
<pre><code class="language-java">@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(GlobalException.class)
    protected ResponseEntity&lt;?&gt; handleGlobalException(GlobalException e) {


        return ResponseEntity.status(e.getErrorCode().getHttpStatus())
                .body(ResponseDto.fail(
                        e.getErrorCode().getHttpStatus(),
                        e.getErrorCode().getMessage(),
                        e.getErrorCode().getDetail())
        );
    }
}</code></pre>
<p>약간 야메(?)같은 방법이지만 지금 당장 임시로 해결되었다는 것만으로도 기분이 좋고, 나중에 더 나은 방법을 찾을 계획이다. 그리고 이미 start line에 status code가 적용되었으니 바디에는 에러 메세지 정도만 리스펀스하는 방향으로 가야겠다.</p>
<p><img src="https://velog.velcdn.com/images/k_siik/post/68c86b71-2b67-4be3-b3be-8c84a3fb8c3c/image.png" alt=""></p>
<h1 id="3-pagination">[3] pagination</h1>
<p>지금까지 팀프로젝트를 하면서 페이징 작업을 다른 분들이 맡아서 해주셨기 때문에 이번 프로젝트에서만큼은 내가 해보고 싶다는 욕심을 냈고, 결국 내가 구현해 내었다. 사실 구현 자체는 그렇게 어렵지는 않았다.</p>
<p>pagination이란 특정 데이터를 조회해 올 때, 설정해둔 개수만큼 차례로 불러오는 방식을 뜻한다.</p>
<p><img src="https://velog.velcdn.com/images/k_siik/post/678483a9-d5fb-4243-af09-907365a3b1ef/image.png" alt=""></p>
<p>예시로 보면 바로 알 수 있듯이, 구글에 어떤 키워드를 검색했을 때 한 페이지별로 데이터를 볼 수 있는 형태를 pagination이라고 한다.</p>
<p>pagination이라는 것은 개발자가 직접 구현할 수도 있지만 JPA에서 제공하는 Pageable이라는 객체를 사용하면 훨씬 편하게 pagination 작업을 할 수 있다.</p>
<p>먼저, 페이지네이션이 필요한 데이터를 조회해 오기 위해 repository에 Pageable 객체를 매개변수로 하는 메서드를 선언한다.</p>
<pre><code class="language-java">public interface BoardRepository extends JpaRepository&lt;Board, Long&gt; {
    // 날짜별 봉사활동 목록 조회
    List&lt;Board&gt; findAllByDueDay(LocalDate dueDay, Pageable pageable);
}</code></pre>
<p>pageable 객체를 매개변수로 넣어주기만 해도 jpa가 알아서 설정된 양만큼의 데이터를 불러오도록 해준다.</p>
<p>그 다음엔 controller에서 몇 페이지에 몇 개의 데이터를 받아 볼지 requestParam의 형태로 요청을 받는다.</p>
<pre><code class="language-java">public class BoardController {
    /**
     * 게시물 날짜별 조회
     */
    @GetMapping(&quot;/date/{dueDay}&quot;)
    public ResponseDto&lt;List&lt;BoardResponse&gt;&gt; getBoardsByDueDay(@PathVariable LocalDate dueDay,
                                                              @RequestParam(name = &quot;page&quot;, defaultValue = &quot;1&quot;) int page,
                                                              @RequestParam(name = &quot;size&quot;, defaultValue = &quot;4&quot;) int size) {
        page = page - 1;

        return boardService.getBoardsByDueDay(dueDay, page, size);
    }
}</code></pre>
<p>프로젝트에서는 현재 page와 size만 Param으로 받지만 정렬형태도 param으로 설정해줄 수 있다. 그리고 page의 경우에는 사용자 입장에서는 1부터 시작이지만 자바는 0부터 시작하기 때문에 1을 빼줘야 한다.</p>
<pre><code class="language-java">    /**
     * 게시물 날짜별 조회
     */
    public ResponseDto&lt;List&lt;BoardResponse&gt;&gt; getBoardsByDueDay(LocalDate dueDay, int page, int size) {
        Pageable pageable = PageRequest.of(page,size);
        List&lt;Board&gt; boards = boardRepository.findAllByDueDay(dueDay, pageable);

        List&lt;BoardResponse&gt; boardResponses = new ArrayList&lt;&gt;();
        for (Board board : boards) {
            boardResponses.add(new BoardResponse(board));
        }

        return ResponseDto.success(boardResponses);
    }</code></pre>
<p>서비스 단에서는 컨트롤러에서 넘어온 page와 size로 pagebable 객체를 초기화 시킨 후, repository에 정의한 쿼리 메서드에 매개변수로 넣어줌으로써, jpa가 size, page에 맞는 데이터를 조회할 수 있도록 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.11.20 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.11.20-TIL</link>
            <guid>https://velog.io/@k_siik/2022.11.20-TIL</guid>
            <pubDate>Mon, 21 Nov 2022 01:30:09 GMT</pubDate>
            <description><![CDATA[<p>아.. 어제는 그제의 밀린 TIL을 작성하고 너무나도 귀찮아서 어제 TIL을 작성하지 않았었는데 친절하신 매니저님께서 슬랙에 친히 내 이름을 박제시켜주셔서 귀찮음을 무릅쓰고 어제의 TIL을 오늘 작성해보려고 한다. 오늘의 주제는 11월 17일 TIL에 ModelAttribute 관련된 이슈에서 해결 원인을 찾지 못했었는데 이 이슈에 대해 이야기해보려고 한다. TIL 시작.</p>
<h1 id="1-modelattribute">[1] @ModelAttribute</h1>
<p>프로젝트 VONGOLE에서 이미지를 업로드할 때, dto에 @ModelAttribute라는 어노테이션을 붙여서 요청을 들여온다. 이미지 업로드 부분은 성민님이 구현을 해주셨는데 나는 지금까지 RequestPart나 RequestParam을 이용해서 이미지를 받아왔는데 성민님 덕분에 ModelAttribute를 알게 되었고 나름 구글링을 하면서 공부를 해보았다. </p>
<h2 id="1-requestparam">{1} RequestParam</h2>
<pre><code class="language-java">@RestController
@RequiredArgsConstructor
public class BoardController {

    private final BoardService boardService;

    public ResponseDto&lt;BoardResponse&gt; createBoard(@AuthenticationPrincipal UserDetailsImpl userDetails,
                                                  @RequestParam String title,                                                                          
                                                  @RequestParam String content,
                                                  @RequestParam String author,
                                                  @RequestPart MultipartFile multipartFile) {
        return boardService.createBoard(userDetailImpl.getMember(), title, content, author, multipartFile)                                               
    }                                         
}</code></pre>
<p>예를 들어서 RequestParam을 사용해서 이미지를 받을 경우, 일일히 사용자의 요청을 받아오게 된다. 이 방법의 단점이라 하면 매개변수가 많아지면 많아질수록 코드가 복잡해지고 순서가 바뀌는 개발자 실수가 일어날 수 있다.</p>
<h2 id="2-modelattribute">{2} ModelAttribute</h2>
<p>반면 ModelAttribute의 경우에는 1대1 매핑을 해주는 RequestParam과는 달리 객체 매핑을 해주기 때문에 코드가 한결 깔끔해지고 순서가 바뀔 위험도 없다.</p>
<pre><code class="language-java">@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/boards&quot;)
public class BoardController {

    private final BoardService boardService;

    @PostMapping()
    public ResponseDto&lt;BoardCreateResponse&gt; createBoard(@AuthenticationPrincipal UserDetailsImpl userDetails,
                                                   @ModelAttribute @Valid BoardRequest boardRequest) throws IOException {
        return boardService.createBoard(userDetails.getMember(), boardRequest);
    }</code></pre>
<p>하지만 ModelAttribute를 사용 시 유의해야 할 점이 있다.</p>
<h2 id="3-modelattribute-사용시-주의할-사항">{3} ModelAttribute 사용시 주의할 사항</h2>
<p>ModelAttribute를 사용할 때 객체 매핑이 저절로 되는 것이 아니라 어떤 설정을 해줘야만 한다. 코드를 보며 예를 들어 설명해 보면,</p>
<pre><code class="language-java">@Getter
@AllArgsConstructor
public class BoardRequest {

    @NotEmpty(message = &quot;빈 칸을 채워 주세요.&quot;)
    private String title;

    @NotEmpty(message = &quot;빈 칸을 채워 주세요.&quot;)
    private String content;

    private MultipartFile boardImage;

   ...
}</code></pre>
<p>@ModelAttribute 설정을 해준 dto를 객체로 바인딩해주기 위해서는 두가지 방법이 있다. </p>
<ol>
<li><p>@AllArgsConstructor를 이용해서 각 필드를 초기화한 객체를 생성한다.</p>
</li>
<li><p>@NoargsConstructor과 @Setter로 각각의 필드 초기화
이 방법은 기본 생성자로 객체를 생성한 후, 각 필드들을 setter로 초기화하는 방법으로 객체를 바인딩하게 된다.</p>
</li>
</ol>
<pre><code class="language-java">@Getter
@NoArgsConstructor
@AllArgsConstructor
public BoardRequest {
    ...
}</code></pre>
<p>나는 지금까지 그냥 습관적으로 위의 코드처럼 NoArgsConstructor와 AllArgsConstructor를 같이 붙여왔던 것 같다. 이렇게 코드를 짰더니 NullpointerException이 뜨게 되었다. 그 당시에는 도저히 뭐가 문제인지 모르겠어서 진구 님께 도움을 요청했었는데 생성자 중에 NoArgsConstructor를 이용해서 객체를 생성하게 된 것 같아서 초기화가 안되어 있어 NullpointerException이 뜬 것 같다고 알려 주셨다.</p>
<p>NoArgsConstructor 어노테이션을 제거함으로써 에러를 잡을 수 있었다.
생각이 담기지 않은 코드가 이렇게 무섭다. 새삼 다시 한 번 코드 한 줄 한 줄을 생각하면서 작성해야겠다고 생각했던 이슈였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.11.19 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.11.19-TIL</link>
            <guid>https://velog.io/@k_siik/2022.11.19-TIL</guid>
            <pubDate>Sat, 19 Nov 2022 10:39:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/k_siik/post/22af60b6-0197-4398-9853-aee53ed94a7b/image.png" alt=""></p>
<p>갈수록 커밋 내역이 줄어들고 잔디밭에 잔디숱이 적어지는데 이러다 빵구날까봐 걱정이다. 사실 커밋할 것들은 많지만 뭔가 전체적으로 구현이 되어서 긴장도 풀어지고 체력도 떨어져서 핑계대느라 커밋을 안하고 있는거다...반성하자! 할 일이 많다! TIL 시작.</p>
<h1 id="1-status-code-issue">[1] status code issue</h1>
<p>프로젝트에서는 항해 대대로 유물처럼 전해져 내려오는 ResponseDto의 fail 메서드를 사용해서 예외 처리를 하고 있다. 이 코드를 지금껏 잘 차용해서 쓰면서도 눈치채지 못했던 궁금증이 이제야 생기게 되었다. fail 메서드를 통해 상태 코드, 에러 메세지 등을 바디에 담아 응답하는데 상태 코드를 바디에 담아서 응답하는 것이 어떤 의미가 있는지 잘 모르겠다.</p>
<pre><code class="language-java">public static &lt;T&gt; ResponseDto&lt;T&gt; fail(Integer httpStatus, String message, String detail) {
    return new ResponseDto&lt;&gt;(false, null, new Error(httpStatus, message, detail));
}</code></pre>
<pre><code class="language-java">@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(GlobalException.class)
    protected ResponseDto&lt;?&gt; handleGlobalException(GlobalException e) {
        return ResponseDto.fail(
                e.getErrorCode().getHttpStatus(),
                e.getErrorCode().getMessage(),
                e.getErrorCode().getDetail());
    }
}</code></pre>
<p>예를 들어 404 NOT FOUND 상태 코드를 응답할 때 실제 Strat line에는 200 코드가 뜨고, 바디에만 404를 담아 보낸다.
<img src="https://velog.velcdn.com/images/k_siik/post/f841478b-2a00-427b-bb47-bb2596d58526/image.png" alt=""></p>
<p>바디에만 상태 코드를 404를 날리고 실제 start line의 상태코드를 200으로 날리는건 문제가 있다고 생각했고, 지금 당장은 아니지만 프로젝트 진행이 어느정도 마무리가 되면 바디에는 에러 메세지 정도만 담아 보내고, start line에 상태코드를 변화를 주는 방향으로 리팩터링을 해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.11.18 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.11.18-TIL</link>
            <guid>https://velog.io/@k_siik/2022.11.18-TIL</guid>
            <pubDate>Fri, 18 Nov 2022 12:12:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/k_siik/post/243e6ab8-3a8c-44bf-a663-aec8749b9876/image.png" alt=""></p>
<p>오늘은 거의 대부분 api를 구현했기 때문에 커밋을 많이 하진 않았고, 깃 관련된 이슈를 해결하느라 하루를 다썼던 것 같다.(사실상 깃 이슈는 3시간 정도 썼고, 나머지는 방황하는데 썼다...)</p>
<h4 id="오늘의-커밋">오늘의 커밋</h4>
<ul>
<li>enum으로 관리하는 봉사 활동 지원자 승인 여부 String으로 변환</li>
</ul>
<p>TIL 시작.</p>
<h1 id="1-enum">[1] enum</h1>
<p>자바를 공부할 때에도 enum에 관해서는 깊이 공부하지를 않았고 코드를 짤 때에도 enum을 사용할 일이 별로 없었기 때문에 enum과의 어색한 관계에 있다가 실전 프로젝트에서 멤버 권한, 봉사 활동 승인 여부 등에 필요할 것 같아 이제야 친해지기 위해 노력하고 있다. 구글링으로 빠른 정보 훑기를 통해 학습한거라 매우 얕게 공부를 하게 되었지만 적어도 쓸 줄은 알게 되었다.</p>
<h2 id="1-enum-사용-이유">{1} enum 사용 이유</h2>
<p>enum이라는 것은 enumerated type의 약자로 서로 연관된 상수들의 집합이다. 대표적인 예로는 요일을 enum으로 정의할 수 있다.</p>
<pre><code class="language-java">public enum Week {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}</code></pre>
<p>이런 식으로 정의를 해놓으면 조건에 맞게 enum 값들을 불러다가 사용할 수 있다.</p>
<p>enum이라는 클래스가 없을 때엔 final 로 선언된 상수를 사용했었는데 예를 들면,</p>
<pre><code class="language-java">public class Week {
    private final int MONDAY = 1;
    private final int TUESDAY = 2;
    .
    .

}</code></pre>
<p>이런 식으로 int 값으로 정의한 후, 초기화된 숫자와 같은 경우 해당 요일로 정의를 해서 사용했다. 이런 방식은 가독성도 떨어지고, 무엇보다 IDE의 자동완성 지원을 받기가 힘들다.</p>
<h2 id="2-프로젝트에-enum-적용">{2} 프로젝트에 enum 적용</h2>
<pre><code class="language-java">public enum Approval {
    WAITING, PASS, FAIL;
}</code></pre>
<p>다음은 일반 멤버가 봉사 활동을 지원했을 때, 관리자 멤버가 승인 여부를 선택하기 위해 만들어진 enum 클래스이다.
먼저, 일반 멤버가 봉사 활동을 지원하면 WAITING으로 초기화가 되도록 설정해 놓았고, 관리자가 이 멤버를 승인하게 되면 PASS, 거절하게 되면 FAIL로 update를 했다.</p>
<p>여기서 몰랐던 사실은 enum 값에 아무것도 초기화를 하지 않은 상태로 사용하게 되면 정의한 순서대로 0,1,2...가 초기화되게 된다. 그래서 봉사 활동을 신청하게 되면 테이블에 WAITING 문자열이 뜨지 않고, 0이 떴다. 이를 해결하기 위해 각 enum 값에 초기화를 해주는 방법도 있지만 구글링을 통해 @Enumerated(EnumType.STRING) 를 알게 되었고, 이를 붙여줌으로써 enum 이름값 그대로 문자열로 저장할 수 있었다.</p>
<h1 id="2-깃-커밋-내역-관련-이슈">[2] 깃 커밋 내역 관련 이슈</h1>
<p><img src="https://velog.velcdn.com/images/k_siik/post/ba03215d-ae05-4201-8443-25e471ff0bd4/image.png" alt=""></p>
<p>실전 프로젝트에 와서는 api 별로 이슈 등록을 한 뒤, 각각 브랜치를 파서 작업을 한 후 머지를 하는 방식으로 진행하려고 노력 중이다. 하지만 브랜치에서 작업을 하다보면 갑작스럽게 다른 api의 코드를 수정할 일이 생기곤 하는데 그럴 때마다 브랜치를 바꾸는걸 깜빡한다. 위에 커밋내역은 깜빡해버린 결과이다.</p>
<p>어떤 브랜치에서 작업을 하다가 다른 api를 수정할 일이 생겼지만 기존의 브랜치에서 작업을 해버렸다. 나중에 깨닫고 그때서야 새로운 브랜치를 파서 그대로 커밋을 해버렸다. 근데 중요한건 이전의 브랜치에도 커밋이 존재했기 때문에 두 브랜치를 모두 메인에 머지하였다. 그 때부터 재앙이 시작되었다. 과거의 커밋 내역들이 rebase된 것마냥 좌르륵 붙어버렸다. 게다가 그 사실을 인지하지 못한 상태로 계속 코드 수정하고 커밋을 했고, 그 후로도 메인에 계속 머지하다가 나중에 깨닫게 되었다..</p>
<p>코드 상의 문제는 없었기에 천만다행이였지만 커밋 내역이 더럽혀졌다는 것이 용납이 안되었고, 팀원들에게도 미안한 마음이 들었다. 해결해 보기위해 두명의 매니저님과 동기 여러명에게 도움을 요청하기도 했지만 코드 상의 문제가 없다면 굳이 코드 누락의 위험을 감수하면서까지 뭔갈 시도하려고 하지 말고 그냥 진행하는 것도 방법이라고 해서 해결책은 찾지 못하게 되었다.</p>
<p>이 에피소드를 항상 기억하고 있다가 언젠간 이런 상황에서의 해결책을 찾아 보고 싶다.</p>
<h1 id="3-오늘-흘려보낸-잡다한-시간들">[3] 오늘 흘려보낸 잡다한 시간들..</h1>
<p>프로젝트가 벌써 마무리되는 듯한 기분은 엄청난 오해였다.. 생각보다 고려해야할 사항이 많았고, 세세하게 들어가면 에러가 나는 상황이 매우 많았다. 그냥 api 잘 구현했고, 겉으로 보기에 에러가 나지 않았기 때문에 어제까진 방심했었던 것 같은데 오늘 이런저런 생각을 하며 코드를 다시 읽어보는데 수정해야할 부분이 너무나 많았다. 게다가 검색 api도 구현해야 하는데 검색 기능을 구현하려면 동적쿼리, querydsl 등을 공부해야 할 것 같다.
오늘은 할게 많아서 오히려 정리가 안되고 뭘할지 몰라서 방황하다 시간이 훌쩍 흐른 것 같아서 아쉬운 하루였다. 체력도 점점 고갈되는게 체감이 되는데 할건 많고.. 오늘은 소주 한 잔 하고 잘란다.ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ</p>
<p>내일 또 뭐해야 할지 몰라서 방황하지 말고, 해야할 목록을 적고 TIL을 마무리해야겠다.</p>
<h1 id="4-프로젝트-이슈">[4] 프로젝트 이슈</h1>
<ul>
<li>검색 api(동적 쿼리, querydsl 공부)</li>
<li>봉사활동 지원자 수 관련 데이터 테이블 2개에 존재하기 때문에 데이터 정합성 관련 이슈 해결(Lock, Sync schedule 등 공부)</li>
<li>createdAt 타임존 다른 현상 </li>
<li>전역 예외 처리</li>
<li>헤더에 상태 코드 전송</li>
<li>자잘한 에러</li>
<li>자동 배포화</li>
<li>웹소켓 (후 순위)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022.11.17 TIL]]></title>
            <link>https://velog.io/@k_siik/2022.11.17-TIL</link>
            <guid>https://velog.io/@k_siik/2022.11.17-TIL</guid>
            <pubDate>Thu, 17 Nov 2022 14:24:55 GMT</pubDate>
            <description><![CDATA[<h4 id="오늘의-커밋">오늘의 커밋</h4>
<ul>
<li>현재 게시물 조회 기능이 날짜별 게시물 조회 기능밖에 없었는데 클라이언트의 요구 사항에 따라 최신순 게시물 전체 조회 기능을 추가</li>
<li>commentResponse에 commentId 추가</li>
<li>String으로 받는 날짜 데이터 LocalDate 타입으로 변환</li>
<li>Volunteer  엔티티에서 Enrollment 엔티티로 이사</li>
<li>MultiPartFile 요청 시 NullPointerException 에러 해결</li>
</ul>
<p>TIL 시작.</p>
<h1 id="1-string---localdate">[1] String -&gt; LocalDate</h1>
<p>지금까지는 날짜 타입에 대한 공부가 잘안되었기 때문에 날짜 데이터를 String으로 받아서 저장하곤 했었다. 하지만 구글링을 해본 결과, LocalDate, LocalDateTime, Timestamp 등 매우 많은 클래스가 정의되어 있었고, 이 중에서 일단 날짜만 나타내는 타입인 LocalDate 를 사용하기로 했다.</p>
<p>후론트 분들꼐서 javascript에서는 날짜 타입이 없다고 하시면서 String으로 날짜 데이터를 보내야 한다고 하셨다. 백엔드의 역할은 String 타입의 데이터를 LocalDate 타입으로 변환해서 저장해야만 하고, 그 역할을 내가 맡아서 했다.</p>
<p>사실, 생각보다 어려운 부분은 아니였다. 구글링을 통해 타입 변경 관련 문법을 쉽게 찾을 수 있었다. 날짜를 나타내는 방식은 매우 여러가지가 있었다.
<img src="https://velog.velcdn.com/images/k_siik/post/7d43aea6-174b-41b9-9552-5bd6c70e6f04/image.png" alt=""></p>
<p>이렇게 많은 종류의 데이터 양식이 enum 형태로 정의가 되어 있었고, 후론트 분들과의 회의 끝에 ISO_LOCAL_DATE 타입인 yyyy-MM-dd 형태를 사용하기로 했다.</p>
<p>후론트 분들이 &quot;yyyy-MM-dd&quot; 형태의 String 값으로 데이터를 요청하게 되면 이 데이터를 다음과 같이 LocalDate 타입으로 변환한다.</p>
<pre><code class="language-java">this.dueDay = LocalDate.parse(boardRequest.getDueDay(),DateTimeFormatter.ISO_LOCAL_DATE);
this.startDate = LocalDate.parse(boardRequest.getStartDate(),DateTimeFormatter.ISO_LOCAL_DATE);
this.endDate = LocalDate.parse(boardRequest.getEndDate(),DateTimeFormatter.ISO_LOCAL_DATE);</code></pre>
<p>boardRequest에서 꺼낸 값들이 &quot;yyyy-MM-dd&quot; String 값이고, 이를 타입변환하여 db에 저장하게 된다.</p>
<h1 id="2-multipartfile-이미지-요청-시-nullpointerexception">[2] MultiPartFile 이미지 요청 시 NullPointerException</h1>
<p>아.. 지긋지긋한 이미지 업로드 에러와 NullPointerException이 또 발생했다. 이 에러는 S3 이미지 업로드를 공부하기 시작한 순간부터 계속 따라왔던 에러이다. 여전히 원인이 뭔지 추측만 할뿐이고 해결된 이유도 잘은 모르지만 어쨋든 기록해야 다음에 까먹지 않을테니 기록하려고 한다.</p>
<p>먼저, 이미지를 업로드하는 방식은,</p>
<pre><code class="language-java">@ModelAttribute @Valid BoardRequest boardRequest</code></pre>
<p>컨트롤러에서 @ModelAttribute 어노테이션을 붙인 request요청을 매개변수로 받는다.</p>
<p>(ModelAttribute 에 관련해서는 내일 TIL 에서 다뤄보려고 한다.)</p>
<p>BoardRequest는 다음과 같다.</p>
<pre><code class="language-java">@Getter
@NoArgsConstructor
@AllArgsConstructor
public class BoardRequest {

    @NotEmpty(message = &quot;빈 칸을 채워 주세요.&quot;)
    private String title;

    @NotEmpty(message = &quot;빈 칸을 채워 주세요.&quot;)
    private String content;

    private MultipartFile boardImage;

    @NotEmpty(message = &quot;봉사 활동 날짜를 정해 주세요.&quot;)
    private String dueDay;

    @NotEmpty(message = &quot;지원 시작 날짜를 정해 주세요.&quot;)
    private String startDate;

    @NotEmpty(message = &quot;지원 마감 날짜를 정해 주세요.&quot;)
    private String endDate;

    @NotEmpty(message = &quot;봉사 활동 위치를 정해 주세요.&quot;)
    private String area;

    @NotEmpty(message = &quot;상세 주소를 작성해 주세요.&quot;)
    private String detailArea;
}</code></pre>
<p>이렇게 요청을 받았을 때 NullPointerException 이 떴다.</p>
<p>원인은 잘 모르겠지만 이것저것 무지성으로 코드를 수정하다가 문득 BoardRequest의 기본생성자가 쓰이지 않는다는 사실을 깨닫고 @NoArgsConstructor 를 제거해 보았다. 그런데.. 신기하게도 정상적으로 작동하였다. 이 에러를 진짜 많이 봤었고, 예전에는 심지어 포기하고 다른 방법으로 이미지 업로드 기능을 구현하기도 했었는데 이제야 원인도 모르고 해결 이유도 모르지만 해결책을 찾게 되었다.</p>
<p>해결된 이유에 대해 감히 추측을 해보건데 직렬화랑 관련이 되지 않았을까 하는 무모한 추측을 해본다..... 직렬화 언젠가 공부해서 TIL 작성하고 만다...!</p>
]]></description>
        </item>
    </channel>
</rss>