<?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>Thu, 23 May 2024 13:13:03 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/f-exuan21/profile/eb69394a-3308-4606-bbb4-b5ffb3857942/image.gif</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 에라 모르겠다.. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/f-exuan21" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[99클럽 코테 스터디 4일차 TIL + 스택]]></title>
            <link>https://velog.io/@f-exuan21/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%9D%BC%EC%B0%A8-TIL-%EC%8A%A4%ED%83%9D</link>
            <guid>https://velog.io/@f-exuan21/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%9D%BC%EC%B0%A8-TIL-%EC%8A%A4%ED%83%9D</guid>
            <pubDate>Thu, 23 May 2024 13:13:03 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p><a href="https://leetcode.com/problems/valid-parentheses/">https://leetcode.com/problems/valid-parentheses/</a></p>
<h2 id="풀이">풀이</h2>
<h3 id="1안">1안</h3>
<pre><code class="language-java">class Solution {
    public boolean isValid(String s) {

        int length = s.length();

        if(length % 2 != 0) return false;

        HashMap&lt;Character, Character&gt; map = new HashMap&lt;&gt;();
        map.put(&#39;)&#39;, &#39;(&#39;);
        map.put(&#39;]&#39;, &#39;[&#39;);
        map.put(&#39;}&#39;, &#39;{&#39;);
        Stack&lt;Character&gt; stack = new Stack&lt;&gt;();

        for(Character c : s.toCharArray()) {
            if(c == &#39;(&#39; || c == &#39;[&#39; || c == &#39;{&#39;) {
                stack.push(c);
            } else {
                if(stack.isEmpty() || stack.pop() != map.get(c)) {
                    return false;
                }
            }
        }

        return stack.isEmpty();

    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/e26d066f-c442-49bf-bf53-bfe4d203cd89/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/1f1a3925-eb31-49d6-b022-8106e783c16d/image.png" alt="">
속도면에서는 빠르지만, HashMap을 사용해서 메모리를 많이 차지했다.</p>
<h3 id="2안">2안</h3>
<pre><code class="language-java">class Solution {
    public boolean isValid(String s) {

        Stack&lt;Character&gt; stack = new Stack&lt;&gt;();

        for(Character c : s.toCharArray()) {
            if(c == &#39;(&#39; || c == &#39;{&#39; || c == &#39;[&#39;) {
                stack.push(c);
            } else {

                if(stack.isEmpty()) return false;

                if(c == &#39;)&#39; &amp;&amp; stack.pop() != &#39;(&#39;) {
                    return false;
                } else if(c == &#39;}&#39; &amp;&amp; stack.pop() != &#39;{&#39;) {
                    return false;
                } else if(c == &#39;]&#39; &amp;&amp; stack.pop() != &#39;[&#39;) {
                    return false;
                } 
            }
        }

        return stack.isEmpty();

    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/7ca2cb23-2de6-4e6d-9803-8dd6596363ee/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/4c58b761-276f-4292-856d-fada7bacc25a/image.png" alt=""></p>
<p>메모리는 적게 사용했지만, 속도가 이전보다 살짝 느려졌다.</p>
<h3 id="3안">3안</h3>
<pre><code class="language-java">class Solution {
    public boolean isValid(String s) {

        Stack&lt;Character&gt; stack = new Stack&lt;&gt;();

        for(Character c : s.toCharArray()) {
            if(c == &#39;(&#39;) {
                stack.push(&#39;)&#39;);
            } else if(c == &#39;{&#39;) {
                stack.push(&#39;}&#39;);
            } else if(c == &#39;[&#39;) {
                stack.push(&#39;]&#39;);
            } else {
                if(stack.isEmpty()) return false;
                if(stack.pop() != c) return false;
            }
        }

        return stack.isEmpty();

    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/b4a49149-8ceb-4e6a-accf-1d6c115a0d10/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/ece7a460-29e9-4d3a-8d7b-0260092b9357/image.png" alt="">
속도는 1안 과 같지만, 메모리는 HashMap을 쓰지 않아 덜 차지하였다.</p>
<p><del>근데 사실, 그래프로 봐서 그렇지 별 차이는 안나는 것 같.ㅇ...</del></p>
<h2 id="스택">스택</h2>
<p>스택(Stack)은 &quot;쌓다&quot;라는 의미로 하나씩 쌓아올리고, 뺄 때는 위에서부터 빼는 구조이다. LIFO(Last In First Out) 구조라고도 한다. 반대로는 큐(Queue) 구조가 있는데 먼저 들어간 것이 먼저 나오는 구조이다. FIFO(First In First Out) 구조라고도 한다.</p>
<p>당장 우리가 하는 코딩에도 스택구조가 떡하니 있다.
메소드 안에서 메소드를 호출하고, 또 메소드를 호출했다고 생각해보자.</p>
<pre><code>A() { 
    B()
}

B() {
    C()
}

C() {
    ...
}</code></pre><p>A를 호출하고 B를 호출하고 C를 호출하고, C가 끝나면 종료되고, B가 종료되고, A가 종료된다. 바로 이게 스택구조이다.</p>
<p>또한, 우리가 사용하는 웹 브라우저에서 &#39;뒤로가기&#39; 기능도 스택이 될 것이다. </p>
<p>스택에는 pop, push가 있다.
push는 삽입연산이다. 스택에 데이터를 삽입한다. 
pop은 삭제연산이다. 스택에서 데이터를 뺀다. 이때, 가장 마지막에 들어갔던 데이터가 빠질 것이다.</p>
<p>자바에서 사용하는 메소드는 아래와 같다.</p>
<h4 id="public-e-pushe-item">public E push(E item)</h4>
<p>Pushes an item onto the top of this stack. This has exactly the same effect as:
 addElement(item)</p>
<h4 id="public-e-pop">public E pop()</h4>
<p>Removes the object at the top of this stack and returns that object as the value of this function.</p>
<h4 id="public-e-peek">public E peek()</h4>
<p>Looks at the object at the top of this stack without removing it from the stack.</p>
<p>-&gt; <code>pop()</code>은 제일 위에 있는 요소를 추출하면서 리턴도 해주는데, <code>peek()</code>은 리턴만 해주고 추출은 하지 않는다. 즉 그대로 스택에 남아있다.</p>
<h4 id="public-boolean-empty">public boolean empty()</h4>
<p>Tests if this stack is empty.</p>
<hr>
<p>[참고] <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Stack.html">https://docs.oracle.com/javase/8/docs/api/java/util/Stack.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 2일차 TIL + 해시]]></title>
            <link>https://velog.io/@f-exuan21/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%9D%BC%EC%B0%A8-TIL-%ED%95%B4%EC%8B%9C</link>
            <guid>https://velog.io/@f-exuan21/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%9D%BC%EC%B0%A8-TIL-%ED%95%B4%EC%8B%9C</guid>
            <pubDate>Tue, 21 May 2024 12:33:08 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42576?language=java">https://school.programmers.co.kr/learn/courses/30/lessons/42576?language=java</a></p>
<h2 id="풀이">풀이</h2>
<pre><code class="language-java">import java.util.HashMap;

class Solution {
    public String solution(String[] participant, String[] completion) {

        HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;();

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

        for(String p : participant) {
            Integer count = map.getOrDefault(p, 0);

            if(count == 0) {
                return p;
            } else {
                map.put(p, count - 1);
            }
        }
        return &quot;&quot;;
    }
}</code></pre>
<hr>
<h2 id="interface-mapkv---내-기준-알아두면-좋을-것-같은-메소드">Interface Map&lt;K,V&gt; - 내 기준 알아두면 좋을 것 같은 메소드</h2>
<h3 id="default-v-getordefaultobject-key-v-defaultvalue">default V getOrDefault(Object key, V defaultValue)</h3>
<p>Returns the value to which the specified key is mapped, or defaultValue if this map contains no mapping for the key.</p>
<p>key 값으로 매핑되는 Value 가 있으면, Value 를 리턴. 없으면 defaultValue 를 리턴.</p>
<h3 id="default-v-putifabsentk-key-v-value">default V putIfAbsent(K key, V value)</h3>
<p>If the specified key is not already associated with a value (or is mapped to null) associates it with the given value and returns null, else returns the current value.</p>
<p>key 값으로 매핑되는 값이 없으면 value 를 넣고 null 을 리턴. 있으면 현재 value 를 리턴.</p>
<h3 id="default-v-computek-key-bifunction-super-k-super-v-extends-v-remappingfunction">default V compute(K key, BiFunction&lt;? super K,? super V,? extends V&gt; remappingFunction)</h3>
<p>Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping).</p>
<p>지정된 key와 매핑된 값을 계산해서 새로운 값을 넣어준다. (매핑된 것이 없을 경우엔 null) </p>
<pre><code class="language-java">Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.compute(&quot;addOne&quot;, (key, oldValue) -&gt;  oldValue == null ? 0 : oldValue + 1);
System.out.println(map.get(&quot;addOne&quot;)); // 0

map.compute(&quot;addOne&quot;, (key, oldValue) -&gt;  oldValue == null ? 0 : oldValue + 1);
System.out.println(map.get(&quot;addOne&quot;)); // 1
</code></pre>
<h3 id="default-v-computeifabsentk-key-function-super-k-extends-v-mappingfunction">default V computeIfAbsent(K key, Function&lt;? super K,? extends V&gt; mappingFunction)</h3>
<p>If the specified key is not already associated with a value (or is mapped to null), attempts to compute its value using the given mapping function and enters it into this map unless null.</p>
<p>지정된 key 가 value와 관계가 없으면(혹은 null이라면), function 으로 매핑한 값으로 계산을 하고 map에 넣음.</p>
<pre><code class="language-java">Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
String value = map.computeIfAbsent(&quot;apple&quot;, key -&gt; &quot;red &quot; + key);
System.out.println(value); // red apple

value = map.computeIfAbsent(&quot;apple&quot;, key -&gt; &quot;green &quot; + key);
System.out.println(value); // red apple</code></pre>
<h3 id="default-v-computeifpresentk-key-bifunction-super-k-super-v-extends-v-remappingfunction">default V computeIfPresent(K key, BiFunction&lt;? super K,? super V,? extends V&gt; remappingFunction)</h3>
<p>If the value for the specified key is present and non-null, attempts to compute a new mapping given the key and its current mapped value.</p>
<p>지정된 key 의 value 가 존재하고 non-null 일 경우, 주어진 key 와 현재의 value로 새로운 매핑을 계산함.</p>
<pre><code class="language-java">Map&lt;String, String&gt; map = new HashMap&lt;&gt;();
map.put(&quot;Apple&quot;, &quot;Red&quot;);


String str = map.computeIfPresent(&quot;Apple&quot;, (key, oldValue) -&gt; key + oldValue + &quot;Good&quot;);
System.out.println(str);    // AppleRedGood


String str_null = map.computeIfPresent(&quot;none&quot;, (key, oldValue) -&gt; key + oldValue + &quot;Hello&quot;);
System.out.println(str_null);     // null
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 1일차 TIL + 해시]]></title>
            <link>https://velog.io/@f-exuan21/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%9D%BC%EC%B0%A8-TIL-%ED%95%B4%EC%8B%9C</link>
            <guid>https://velog.io/@f-exuan21/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%9D%BC%EC%B0%A8-TIL-%ED%95%B4%EC%8B%9C</guid>
            <pubDate>Mon, 20 May 2024 13:55:42 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/1845?language=java">https://school.programmers.co.kr/learn/courses/30/lessons/1845?language=java</a></p>
<h2 id="풀이">풀이</h2>
<pre><code class="language-java">import java.util.HashMap;

class Solution {
    public int solution(int[] nums) {

        int length = nums.length;
        int maxCount = length / 2;

        HashMap&lt;Integer, Integer&gt; pocketmons = new HashMap&lt;&gt;();

        for(int i = 0; i &lt; length; i++) {
            pocketmons.put(nums[i], 1);
        }

        int answer = pocketmons.size() &gt; maxCount ? maxCount : pocketmons.size();

        return answer;
    }
}</code></pre>
<p>HashMap을 이용하면, key값이 중복되지 않기 때문에 이를 이용하였다.
nums[i]를 키 값으로 하여 HashMap에 넣고, 마지막에는 그 size()를 구하여 maxCount 보다 클 경우에는 maxCount를 최대포켓몬 수로 정하고, 반대일 경우에는 size() 값을 최대포켓몬 수로 정하였다.</p>
<hr>
<h2 id="해시">해시</h2>
<h3 id="hashcode">hashCode</h3>
<p><code>HashMap</code>은 <code>hashCode</code>를 이용하기 때문에 대용량 데이터를 조회하는데 유용하다.
여기서 <code>hashCode</code>란 <strong>해시 알고리즘에 의해 생성된 정수 값</strong>이다.</p>
<p><code>HashMap</code>은 <code>key</code>값을 통해 <code>hashCode</code>(정수)를 생성하고, 생성된 <code>hashCode</code>를 <code>bucket</code>에 접근할 수 <code>index</code>로 변환해 배열에 값을 저장한다. 즉, 하나하나 확인을 하는 것이 아니라, <code>key</code>값을 <code>hashCode</code>로 변환하고 해당 값을 <code>index</code>로 변환해 바로 탐색하는 것이다. 그렇기 때문에 대용량 데이터에서 조회가 빠르다.</p>
<p>하지만, <code>hashCode</code>를 만드는 알고리즘이 좋지 않으면, 만든 <code>hashCode</code>가 같아질 수 밖에 없기 때문에 배열의 한 방에 많은 데이터가 들어가게 된다. 이런 현상을 충돌현상이라고 하는데, 이것을 피하기 위해 <code>Hash Algorithm</code>을 잘 만들어야 한다.</p>
<p>만약 아래처럼 구현한다면 이 클래스로 생성된 모든 객체들은 다 똑같은 해시코드를 갖는 것이기 때문에 충돌현상이 생겨 문제가 될 것이다.</p>
<pre><code class="language-java">    @Override
    public int hashCode() {
        return 1; // 모든 객체들이 해시코드를 1을 갖게 됩니다.
    }</code></pre>
<p>원래는 배열의 인덱스를 조회하는 것이기 때문에 시간복잡도가 <code>O(1)</code>이었겠지만, 이런식으로 충돌이 일어나면 탐색하는데 <code>O(n)</code>까지 걸릴 수가 있다.</p>
<p>위 코드는 극단적인 예시이지만, <code>hashCode</code>는 다른 <code>Key</code>값으로 동일한 <code>hashCode</code>를 만들어 내기도 하는데, 그 이유는 <code>key</code> 값은 문자열이고 그 가짓수가 무한한데 비해 <code>hashCode</code>는 정수이기 때문에 알고리즘이 아무리 좋아도 어떤 <code>Key</code>들은 중복되는 <code>hashCode</code>를 만들어낼 수 밖에 없기 때문이다.</p>
<p>또는, 다른 <code>hashCode</code>를 받았지만 <code>Index</code>로 변환하는 과정에서 동일한 <code>Index</code>를 받는 경우에도, 한 <code>Index</code>에 값이 몰리기 때문에 충돌이 일어나서 <code>O(n)</code>까지 걸릴 수가 있다.</p>
<h3 id="bucket">bucket</h3>
<p><code>HashMap</code>에서 데이터를 담는 공간을 <code>bucket</code>이라고 칭한다. 자바에서 해당 <code>bucket</code>의 크기는 동적으로 변경된다. </p>
<p><code>HashMap</code>의 기본 생성자에서 <code>capacity</code>는 <code>16</code>, <code>load factor</code>는 <code>0.75</code>로 생성되는데, <code>threshold = capacity * load factor</code> 가 된다. 여기서 <code>threshold</code>는 <strong>버킷의 크기를 변경할 때의 임계점</strong>이라고 생각하면 된다.</p>
<p>즉, <code>threshold</code>는 <code>16 * 0.75 = 12</code>가 되는데, 이 말은 버킷안에 저장된 데이터가 <code>12개</code>가 넘어가면 <code>bucket</code>의 크기를 늘린다는 이야기이다. <code>HashMap</code>은 버킷의 크기를 늘릴 때 <code>2배</code>를 늘린다. 즉, 원래사이즈 16의 2배인 <code>32개</code>가 되는 것이다. 따라서 데이터가 많아지면 메모리를 많이 사용하게 된다.</p>
<p>사이즈가 변경되는 과정을 <code>rehashing</code> 이라고 하는데, <strong>해당 과정에서 사이즈만 변경되는 것이 아니라 해시코드를 다시 계산하여 해당 키-값들의 버킷 위치를 다시 정하게 된다.</strong> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GraphQL 맛보기]]></title>
            <link>https://velog.io/@f-exuan21/GraphQL-%EB%A7%9B%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@f-exuan21/GraphQL-%EB%A7%9B%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 25 Dec 2023 05:11:55 GMT</pubDate>
            <description><![CDATA[<p>동기와 함께 작은 프로젝트를 시작하는데 (내가 볼 땐) 엄청난 문제가 생겼다.
API 를 만들었는데, READ 부분에서 너무 한 번에 많은 데이터를 가져오는 것 같았다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/ebecc1da-7827-4b02-804f-64dc56675a88/image.png" alt="">
위 데이터를 기준으로 모바일 어플리케이션에서 리스트를 뿌려주려고 했는데 저 변수 하나하나를 입력하기가 너무 귀찮았다.(내 기준 정말 엄청난 문제임)
<img src="https://velog.velcdn.com/images/f-exuan21/post/b2dce980-e8f3-49c2-aa21-d9ecb9938281/image.png" alt="">
쓰다가 포기한 상태 ... 
그리고 일단 무엇보다 필요 없는 데이터들이 너무 많았다. 예를 들어 creationDate 같은 값은 모바일에서 전혀 쓰일 일이 없다. 그렇다고 백엔드에서 메소드를 하나 더 짜기에는  그것도 너무 귀찮았...
그래서 찾은 방법이!
<img src="https://velog.velcdn.com/images/f-exuan21/post/3561c4a2-0fd3-476b-94b9-cb2c876c051c/image.png" alt="">
GraphQL!!!! 
예전에 다른 동기가 프로젝트에서 사용하는데 옆에서 힐끗힐끗 구경했던 기억이 있는데, 그 때 나한테 프론트엔드(리액트) 부분에 대해서 이것저것 물어보길래 저도 같이 GraphQL이 뭐냐고 역으로 꼬치꼬치 캐물었다. 어렴풋이 듣기로는 백엔드에서 리턴해준 모든 값을 읽는게 아니라 내가 원하는 값만 받아낼 수 있다고 했던 것 같아 한 번 찾아봤고, 이거다! 싶었다. 그래서 바로 세팅 ㄱㄱ</p>
<p>개발의 세팅은 반이라고 ... 세팅하면서 엄청난 오류들을 마주할 마음의 준비를 하고 세팅에 들어갔다. </p>
<h3 id="1-setting">1. Setting</h3>
<p>먼저 build.gradle 에 아래와 같은 dependency 를 추가해준다.</p>
<pre><code class="language-gradle">implementation &#39;org.springframework.boot:spring-boot-starter-graphql&#39;</code></pre>
<p>그리고 application.properties 에 아래와 같이 설정을 넣어준다.</p>
<pre><code>#/graphql/schema HTTP를 통해 스키마 조회 가능 여부
spring.graphql.schema.printer.enabled=true
#graphiql 페이지 사용 여부
spring.graphql.graphiql.enabled=true
#graphiql 페이지 경로(기본이 /graphiql임)
spring.graphql.graphiql.path=/graphiql</code></pre><p>여기서 graphiql 페이지가 무엇이냐면!
<img src="https://velog.velcdn.com/images/f-exuan21/post/4fb32663-4805-4a71-ad73-f5481fa6a968/image.png" alt="">
이렇게 데이터 통신을 테스트해볼 수 있는 페이지이다.
그래서 위처럼 세팅을 해두면 <a href="http://localhost/grqphiql">http://localhost/grqphiql</a> 로 접속하면 저 페이지로 들어갈 수 있다.</p>
<h3 id="2-scheme">2. Scheme</h3>
<p>생각보다 세팅은 너무 허무하게 끝났고, 이제 Scheme을 등록해주면 된다.
일단 맨 처음 보여줬던 사진들처럼 많은 변수들을 넣어야 하지만, graphql은 Date 형 같은 Variable을 제공하지 않아서 이 부분은 나중에 추가하기로 하고, 정말 테스트를 위해 간단한 값들만 넣어봤다.
<code>resources/graphql/schema.graphqls</code> 경로에 넣으면 된다. (없으면 생성)</p>
<pre><code class="language-graphql">type Query {
    findCultureNearby: [Culture]
}

type Culture {
    id: ID
    seq: Int
    typeCode: String
    title: String
    thumbnail: String
}</code></pre>
<p>그리고 Controller를 생성해준다.</p>
<pre><code class="language-java">@Controller
public class CultureGraphqlTestController {

    @Autowired
    private CultureService cultureService;

    @QueryMapping
    public List&lt;CultureVO&gt; findCultureNearby() {
        return cultureService.findAll();
    }

}</code></pre>
<p>graphql은 메소드명과 Query안의 명칭을 매핑해서 해당 메소드를 호출해주기 때문에 명칭을 제대로 맞췄는지 확인해주어야 한다.(위 코드에서는 <code>findCultureNearBy</code> 메소드명을 사용하였다.)</p>
<h3 id="3-test">3. Test</h3>
<p>아직 뭐가 뭔지 모를 수도 있다. 하지만 <a href="http://localhost:8080/graphiql?path=/graphql">http://localhost:8080/graphiql?path=/graphql</a> 여기를 들어가서 이제 내가 불러오고 싶은 쿼리를 작성해보면 대충 감이 올 것이다.</p>
<pre><code class="language-graphql">query {
  findCultureNearby {
    id
    title
  }
}</code></pre>
<p><code>findCultureNearBy</code> 쿼리를 호출할 것인데 <code>id</code> 랑 <code>title</code> 값만 필요하다는 의미이다.
위처럼 호출하하면 아래와 같이 정말 필요한 값만 보여준다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/d1052514-88b6-408d-8c3f-5def0e19baff/image.png" alt=""></p>
<p>만약에 쿼리를 아래처럼 바꾸면 돌려주는 값도 당연히 달라진다.(API를 추가하지 않았지만 말이다!!)</p>
<pre><code class="language-graphql">query {
  findCultureNearby {
    title
    thumbnail
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/2c554208-832b-432d-bce9-204cbf86a94a/image.png" alt=""></p>
<p>분명 단점도 존재한다.
앱 개발할 때 편하려고 백엔드 개발할 때 한 번 더 고생해야된다는 점? (앱에서 변수 입력하기 싫어서 한 짓인데 결국 백엔드에서 한 번 더 입력했음...ㅋㅋㅋㅋㅋㅋ 알아서 Model 읽어다 세팅해주면 안되겠니..? ㅠㅠ) 
그리고 graphqls 에서 작성할 때 사용할 수 있는 데이터형이 너무 한정적이라는 점</p>
<ul>
<li>Int: 부호가 있는 32비트 정수.</li>
<li>Float: 부호가 있는 부동소수점 값.</li>
<li>String: UTF-8 문자열.</li>
<li>Boolean: true 또는 false.</li>
<li>ID: ID 스칼라 타입은 객체를 다시 요청하거나 캐시의 키로써 자주 사용되는 고유 식별자를 나타냅니다. ID 타입은 String 과 같은 방법으로 직렬화되지만, ID 로 정의하는 것은 사람이 읽을 수 있도록 하는 의도가 아니라는 것을 의미합니다.</li>
</ul>
<p>이게 전부임... 그래서 새로운 데이터를 사용하려면 생성해줘야함(아까 말한 Date 형 같은 경우가 이에 해당)
아마 이 때문에 첨부파일 형태도 지원이 안되지 않을까 싶은데 ...</p>
<p>일단 테스트 용으로 해본 것이기 때문에 효율은 어떨지는 써봐야 알 것 같다. 기회가 된다면 알아서 graphql이 생성되는 방식으로 개발을 한 번 해보고 싶다. </p>
<p>끝 -*</p>
<p>[참고사이트]
<a href="https://graphql-kr.github.io/learn/schema/">https://graphql-kr.github.io/learn/schema/</a>
<a href="https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/">https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C/C++] 2차원 배열과 메모리]]></title>
            <link>https://velog.io/@f-exuan21/llyrek96</link>
            <guid>https://velog.io/@f-exuan21/llyrek96</guid>
            <pubDate>Sun, 26 Nov 2023 06:05:41 GMT</pubDate>
            <description><![CDATA[<p>오랜만에 C++을 사용하면서 코딩테스트 공부를 하다보니 2차원 배열과 포인터가 혼동이 되기 시작했다.
예전에 C공부를 할 때, 포인터 주소를 하나하나 다 찍어가보면서 어떻게든 이해하려고 노력했던 기억이 나는데도 불구하고 오랜만에 잡아보려니까 정말 하나도 모르겠더라. (1차원에선 괜찮았는데, 2차원 넘어가니까 궁금한게 너무 생기는...)</p>
<p>그래서 다양하게 디버그 콘솔에 찍어 보면서 내 나름대로 이해하게 된 것들을 정리하고자 한다.
(잘못된 부분은 댓글로 남겨주면 수정하도록 하겠습니다!)</p>
<p>내가 의문을 가졌던 코드는 아래와 같다.</p>
<pre><code class="language-cpp">#include &lt;bits/stdc++.h&gt;

using namespace std;

int a[10];
int b[10][10];

int main() {

    fill(&amp;a[0], &amp;a[10], 100);

    for(int i = 0; i &lt; 10; i++) {
        cout &lt;&lt; a[i] &lt;&lt; &quot; &quot;;
    }

    cout &lt;&lt; &#39;\n&#39;;

    fill(&amp;b[0][0], &amp;b[0][0] + 10 * 10, 2); //여기서 의문을 갖게 되었다.
    for(int i = 0; i &lt; 10; i++) {
        for(int j = 0; j &lt; 10; j++) {
            cout &lt;&lt; b[i][j] &lt;&lt; &#39; &#39;;
        }
        cout &lt;&lt; &#39;\n&#39;;
    }

    return 0;

}</code></pre>
<p>1차원 배열로 생각하면 &amp;a[1] 은 a + 1 과 같다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/7cc07c26-ff12-45a8-afc4-eb49d910b352/image.png" alt="">
그러면 2차원 배열로 생각하면 <strong>b[0][1]과 b[1][0]은 포인터로 어떻게 표현될까?</strong> 가 나의 의문이었다.
단순하게 나는 b[0][1] 이 b + 1 로 표현될 것이라고 착각했고, 값이 다르다는 것을 확인할 수 있었다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/2ac7b20b-1633-474f-93ba-289d53225133/image.png" alt="">
왜 다를까? 나는 10 * 10 배열이더라도, 메모리에 저장되는 순서는 차례대로 이기 때문에 b + 1 이 &amp;b[0][1]과 같을 것이라고 생각했다.
디버그 콘솔로 찍어보니 메모리에 +4를 증가시키면서 순차적으로 저장되는 것은 맞았지만, 그렇다고 해서 b + 1 이 &amp;b[0][1] 이 지칭하는 것은 아니었다. 왜그럴까?
b[10][10] 은 아래 그림과 같이 생각하면 될 것 같다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/f653d27b-66bf-4e9b-b545-5870c009ccb5/image.png" style="height:350px"/>
때문에 b + 1 은  b[0][[1] 과 같은 것이 아니라 b[1][0] 와 같은 것이 맞는 답이다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/11016941-b890-44ea-9520-ec86fb1a5265/image.png" alt="">
그러면 여기서 &amp;b[0][1] 은 포인터로 어떻게 표현하는 것일까?
위 그림에서 보듯, b + 0 안에서 또 1을 증가시켜야 한다. 
<img src="https://velog.velcdn.com/images/f-exuan21/post/0071c608-cfc4-4d59-9a43-c889f8ecb6b9/image.png" alt="">
숫자 배열이기 때문에 포인터를 1 증가시킬 때마다 <code>4</code>가 증가한다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/fa7977e6-67fa-49cd-bd94-856dc94bfbfb/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/0da15392-bfee-4236-860f-d68186cdba7a/image.png" alt="">
b는 포인터를 1 증가시킬 때마다 <code>4 * 10(배열의 크기) = 40</code> 이 증가한다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/0f4cc1cc-7637-45ae-b2a9-1b9234afef83/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/7cdd6734-0b39-4280-a792-582fb98a1dc4/image.png" alt="">
뒤 숫자만 비교했을 때 <code>58 - 30 = 28(16진수)</code>을 10진수로 바꿔보면, <code>40</code>이 나오는 것을 알 수 있다. 
<img src="https://velog.velcdn.com/images/f-exuan21/post/accacf8d-f471-472a-af1c-d44addf5d802/image.png" style="height:350px"/></p>
<p>위의 그림들은 다 이해를 쉽게 하기 위해서 그린 그림이고, 실제로 메모리엔 차례대로 저장된다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/f18ef3ed-c06e-429d-9dbf-0ab2e55fbe61/image.png" alt=""></p>
<p>추가적으로,
&amp;b[0][1] 은 &amp;b[0][0] + 1 으로
&amp;b[1] 은 &amp;b[0] + 1로 표현할 수 있다는 것도 알아두자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해 플러스 코육대 참가 회고]]></title>
            <link>https://velog.io/@f-exuan21/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EC%BD%94%EC%9C%A1%EB%8C%80-%EC%B0%B8%EA%B0%80-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@f-exuan21/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EC%BD%94%EC%9C%A1%EB%8C%80-%EC%B0%B8%EA%B0%80-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 03 Oct 2023 03:22:05 GMT</pubDate>
            <description><![CDATA[<p>긴 추석 연휴동안 무엇을 해볼까 고민하던 중,
우연히도 &quot;항해 플러스 코육대&quot;라는 게시글을 보게 되었다.</p>
<p><a href="https://hanghaeplus-coyukdae.oopy.io/">https://hanghaeplus-coyukdae.oopy.io/</a></p>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/d5727f0a-b666-494b-9974-decfc78e489c/image.png" alt="">
그림과 같이 다양한 종복이 존재했고, 처음엔 테트리스에 도전해보려고 하다가 산소도 가야하고 전도 부쳐야하고 바쁜 일정들 사이에서 잠깐 잠깐 하기에는 행맨 게임이 제일 간단해보여 행맨 게임으로 도전해 보게 되었다.</p>
<p>리액트로 시작하려하다가, 평가하시는 분들이 편하게 평가하려면 그냥 html 만 열어도 바로 평가 되게끔 html, js 만 이용해서 간단하게 만들기로 했다.</p>
<p>예전에 어쩌다가 html의 canvas를 알게 되었는데, 생각보다 쉽고 간편해서 이걸로 한 번 도전해 보기로 했다. 그런데 canvas를 쓰니까 뭔가 이쁜 맛이 없어서 후회 중이다....</p>
<p align="center"><img src="https://velog.velcdn.com/images/f-exuan21/post/b956d80d-cd34-4989-b0bd-d23f819b93ad/image.png" width="50%"/></p>

<p>대충 화면은 이렇다... 초간단 초심플... 
디자인적 감각이 없는 사람인지라 어쩔 수가 없다... </p>
<p align="center"><img src="https://velog.velcdn.com/images/f-exuan21/post/15360f51-2e7e-4584-8d9e-179d7e1f95de/image.png
" width="50%"></p>

<p>여러번 실패하면 이렇게 그림이 하나하나 생기면서 행맨이 그려진다. 이 부분에서 canvas를 사용했다. </p>
<pre><code class="language-javascript">var hangman = {

    canvas : document.getElementById(&quot;canvas&quot;),
    x : 150,
    y : 50,
    r : 30,

    setBase : function() {

        ctx = canvas.getContext(&quot;2d&quot;);
        ctx.strokeStyle = &#39;black&#39;;
        ctx.lineWidth = 3;

        ctx.beginPath();

        ctx.moveTo(this.x, this.y);
        ctx.lineTo(this.x, this.y + 250);

        ctx.moveTo(this.x, this.y);
        ctx.lineTo(this.x + 100, this.y);

        ctx.moveTo(this.x - 20, this.y + 250);
        ctx.lineTo(this.x + 50, this.y + 250);

        ctx.stroke();

    },

    setRope : function() {

        ctx = canvas.getContext(&quot;2d&quot;);

        ctx.lineWidth = 3;

        ctx.beginPath();

        ctx.moveTo(this.x + 100, this.y);
        ctx.lineTo(this.x + 100, this.y + 50);

        ctx.moveTo(this.x + 50, this.y);
        ctx.lineTo(this.x, this.y + 50);

        ctx.stroke();

    },

    setHead : function() {

        ctx = canvas.getContext(&quot;2d&quot;);

        ctx.lineWidth = 5;

        ctx.beginPath();

        ctx.arc(this.x + 100, this.y + 50 + this.r, this.r, 0, 360);

        ctx.stroke();

    },

    setBody : function() {

        ctx = canvas.getContext(&quot;2d&quot;);

        ctx.lineWidth = 5;

        ctx.beginPath();

        ctx.moveTo(this.x + 100, this.y + 50 + this.r * 2);
        ctx.lineTo(this.x + 100, this.y + 50 + this.r * 2 + 50);

        ctx.stroke();

    },

    setArms : function() {

        ctx = canvas.getContext(&quot;2d&quot;);

        ctx.lineWidth = 5;

        ctx.beginPath();

        ctx.moveTo(this.x + 100, this.y + 50 + this.r * 2 + 10);
        ctx.lineTo(this.x + 50, this.y + 50 + this.r * 2 - 10);

        ctx.moveTo(this.x + 100, this.y + 50 + this.r * 2 + 10);
        ctx.lineTo(this.x + 150, this.y + 50 + this.r * 2 - 10);

        ctx.stroke();

    },

    setLegs : function() {

        ctx = canvas.getContext(&quot;2d&quot;);

        ctx.lineWidth = 5;

        ctx.beginPath();

        ctx.moveTo(this.x + 100, this.y + 50 + this.r * 2 + 50);
        ctx.lineTo(this.x + 50, this.y + 50 + this.r * 2 + 95);

        ctx.moveTo(this.x + 100, this.y + 50 + this.r * 2 + 50);
        ctx.lineTo(this.x + 150, this.y + 50 + this.r * 2 + 95);

        ctx.stroke();

    },

    setHands : function() {

        ctx = canvas.getContext(&quot;2d&quot;);

        ctx.lineWidth = 5;

        ctx.beginPath();
        ctx.arc(this.x + 50, this.y + 50 + this.r * 2 - 10, 5, 0, 360);
        ctx.stroke();

        ctx.beginPath();
        ctx.arc(this.x + 150, this.y + 50 + this.r * 2 - 10, 5, 0, 360);
        ctx.stroke();

    },

    setFeet : function() {

        ctx = canvas.getContext(&quot;2d&quot;);

        ctx.lineWidth = 5;

        ctx.beginPath();

        ctx.moveTo(this.x + 50 + 10, this.y + 50 + this.r * 2 + 95 + 5);
        ctx.lineTo(this.x + 50 - 10, this.y + 50 + this.r * 2 + 95 - 10);

        ctx.moveTo(this.x + 150 - 10, this.y + 50 + this.r * 2 + 95 + 5);
        ctx.lineTo(this.x + 150 + 10, this.y + 50 + this.r * 2 + 95 - 10);

        ctx.stroke();

    }

}</code></pre>
<p>위치를 쉽게 바꿀 수 있게 하기 위해서 시작 좌표 x, y 를 제일 처음 지정해주고,
각각 부위마다 함수를 따로 구현해 줬다.
그래서 실패할 때마다 아래와 같이 특정 부위 함수를 호출한다.</p>
<pre><code class="language-javascript">switch(counts) {
  case 1:
    hangman.setBase();
    break;
  case 2:
    hangman.setRope();
    break;
  case 3:
    hangman.setHead();
    break;
  case 4:
    hangman.setBody();
    break;
  case 5:
    hangman.setArms();
    break;
  case 6:
    hangman.setHands();
    break;
  case 7:
    hangman.setLegs();
    break;
  case 8:
    hangman.setFeet();
    game.failGame();                  
}</code></pre>
<p>성공하거나 실패했을 시, 알파벳 카드를 사용하지 못하게 막거나
Restart 를 눌렀을 시 다시 카드를 사용할 수 있게 원상태로 돌려놔야 하기 때문에 아래와 같은 함수를 사용했다.</p>
<pre><code class="language-javascript">reverseAllCards : function(isBack) {
  blanksDivs = document.getElementsByClassName(&quot;cards&quot;);

  for(var i = 0; i &lt; blanksDivs.length; i++) {
    blanksDivs[i].disabled = isBack;
  }
}</code></pre>
<p>isBack이 true일 경우 뒤집어 졌다는 의미로 클릭이 불가능하고,
false일 경우 뒤집어 지지 않았다는 의미로 클릭이 가능하다.</p>
<p>단어 같은 경우, 내가 만든 것에는 따로 갯수 제한이 없지만 항해 플러스 문제에서는 10자 이하의 단어만 허용했었다. 
words 라는 변수에 단어를 추가로 넣어주기만 하면 랜덤으로 뽑아서 제출해준다.</p>
<pre><code class="language-javascript">const words = [
    &quot;apple&quot;,
    &quot;banana&quot;,
    &quot;umbrella&quot;,
    &quot;triangle&quot;,
    &quot;cave&quot;,
    &quot;sunglass&quot;,
    &quot;chair&quot;,
    &quot;legal&quot;,
    &quot;illegal&quot;,
    &quot;valuable&quot;,
    &quot;chemical&quot;,
    &quot;delicious&quot;,
    &quot;active&quot;,
    &quot;creative&quot;,
    &quot;actual&quot;,
    &quot;wonderful&quot;,
    &quot;slim&quot;,
    &quot;fat&quot;,
    &quot;stupid&quot;,
    &quot;actual&quot;,
    &quot;double&quot;,
    &quot;creative&quot;,
    &quot;show&quot;,
    &quot;eternal&quot;,
    &quot;exalt&quot;,
    &quot;exert&quot;,
    &quot;exploit&quot;,
    &quot;explicit&quot;,
    &quot;revolve&quot;,
    &quot;ethereal&quot;,
    &quot;give&quot;,
    &quot;get&quot;,
    &quot;grain&quot;,
    &quot;great&quot;,
    &quot;bad&quot;,
    &quot;goverment&quot;,
    &quot;trim&quot;
];</code></pre>
<pre><code class="language-javascript">// 랜덤으로 단어를 하나 추출
word = words[Math.floor(Math.random() * words.length)]</code></pre>
<p>특정 알파벳 카드를 선택했을 때, 문제 제출된 단어에 포함된 단어인지 확인하는 로직은 아래와 같다.</p>
<pre><code class="language-javascript">onClickCard : function(btn) {

          // _ _ _ _ _ 와 같이 알파벳이 들어나는 elements
        blanksDivs = document.getElementsByClassName(&quot;blanks_cards&quot;);
        isCorrect = false;

          // 클릭한 알파벳은 사용하지 못하게 막아준다.
        btn.disabled = true;

          // 문제 단어 글자수 만큼 for 문을 돌면서 알파벳을 비교한다.
        for(var i = 0; i &lt; word.length; i++) {
            if(word[i].toUpperCase() == btn.innerText.toUpperCase()) {
                blanksDivs[i].innerText = word.charAt(i).toUpperCase();
                isCorrect = true;
                  // correctCounts가 문제 단어 글자수와 같아지면 성공으로 처리하기 위해, 증가시켜준다.
                correctCounts++;
            }
        }

        if(!isCorrect) {
            counts++;
            switch(counts) {
                case 1:
                    hangman.setBase();
                    break;
                case 2:
                    hangman.setRope();
                    break;
                case 3:
                    hangman.setHead();
                    break;
                case 4:
                    hangman.setBody();
                    break;
                case 5:
                    hangman.setArms();
                    break;
                case 6:
                    hangman.setHands();
                    break;
                case 7:
                    hangman.setLegs();
                    break;
                case 8:
                    hangman.setFeet();
                    game.failGame();                  
            }
        }else {
            if(correctCounts == word.length) {
                game.successGame();
            }
        }

    }</code></pre>
<p>처음에는 이미지를 내가 그려놓고 하나씩 보여줄까 했는데, 그림 그리는게 귀찮았다...
그래서 그냥 타이핑으로 모든 것을 해결하겠다는, 정말 무의미한 의지로 ㅋㅋㅋ canvas를 이용해보았다.
완성하고 보니 너무 허접해 보여서 후회중.. 그냥 그림그릴걸... 
그래도 추석동안 뭔가 하나 해봤다는게 나에게 큰 의미로 다가온 것 같다.</p>
<p>이 시기를 기점으로 앞으로 뭔가 작은 것 하나라도 해서 velog나 git에 꾸준히 올려야겠다는 의지를 다잡으려고 한다. (사실 저번주부터 올리고 있었는데, 깃 계정을 잘못 등록해서 이상한데에 잔디가 심어졌다...ㅠㅠ) </p>
<p>끝-*</p>
<p>깃허브 : <a href="https://github.com/f-exuan21/hangman">https://github.com/f-exuan21/hangman</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OSI 7 Layer 및  L2]]></title>
            <link>https://velog.io/@f-exuan21/Network</link>
            <guid>https://velog.io/@f-exuan21/Network</guid>
            <pubDate>Sat, 09 Sep 2023 04:59:23 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/f-exuan21/post/a409c133-3c3f-4b9e-a18e-cc7f788e11e9/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/b83dffee-8777-4fe7-935b-535726351743/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/9486c112-9217-4101-8eec-0d03ccb79949/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/16cfc506-337a-4e7a-8092-00a0da583231/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/454b2dff-772f-4130-9e56-579d038b2323/image.png" alt=""></p>
<p>출처 : 외워서 끝내는 네트워크 핵심이론 - 기초 (인프런)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TableView 에서 cell 의 너비 수정하기 ]]></title>
            <link>https://velog.io/@f-exuan21/TableView-%EC%97%90%EC%84%9C-cell-%EC%9D%98-%EB%84%88%EB%B9%84-%EC%88%98%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@f-exuan21/TableView-%EC%97%90%EC%84%9C-cell-%EC%9D%98-%EB%84%88%EB%B9%84-%EC%88%98%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 23 Jun 2023 11:18:33 GMT</pubDate>
            <description><![CDATA[<p>UITableView를 사용하는데, 처음엔 Style을 <strong>Inset Grouped</strong> 를 줘서 모양을 잡으려고 했다. <strong>Inset Grouped</strong> 를 사용하면, 아래 사진처럼 알아서 섹션에 corner round를 넣어주고, padding 도 알아서 잡아준다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/936939b2-93b6-4fc7-916b-b5cecf89ba8c/image.png" alt=""></p>
<p>그런데 회사에 출근하고 iOS 12에서 실행시켜봤더니... 
<img src="https://velog.velcdn.com/images/f-exuan21/post/d9d4f122-0f6c-4c5a-be85-0ff742fe3e3f/image.png" alt="">
이 따위로 뜨더라... 아마 <strong>Inset Grouped</strong>은 버전이 좀 올라간 뒤에 생긴 것 같다..
회사 특성상 최저 버전이 11이기 때문에, 디자인을 맞춰줘야한다는 왕고집이 발동되어 <strong>Inset Grouped</strong> 를 포기하고 <strong>Grouped</strong>로 갈아탔다.
( Grouped 를 하면 아래 사진 처럼 된다. )</p>
<p>그래서 필요한 작업들을 다 하고 나니까, 원했던 padding 이 죽어도 안잡히더라... 양 옆에 그냥 잉꼬부부마냥 아주 딱 붙어있다.</p>
<p>그래서 또 구글링에 이은 구글링을 하다가 해결했다..! </p>
<pre><code class="language-swift">class PastMeetingTableViewCell: UITableViewCell {

    @IBOutlet weak var subject: UILabel!

    @IBOutlet weak var dateAndTime: UILabel!


    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

    // cell 너비/패딩 조절
    override var frame: CGRect {
        get {
            return super.frame
        }
        set {
            var frame = newValue
            frame.origin.x += 20
            frame.size.width -= 2 * 20

            super.frame = frame
        }
    }

}</code></pre>
<p>cell을 구성하는 컨트롤러에서 frame을 override 해서 값을 바꿔주었더니 되었다.
width를 줄이고 origin의 x값도 변경시켜주었다!
<img src="https://velog.velcdn.com/images/f-exuan21/post/8102c38b-db5b-4917-a8d1-6597c809a2df/image.png" alt=""></p>
<p>(검은 블록은 회사 관련된 정보가 써있어서 가리기...)
사진을 보면 왼쪽 오른쪽 아주 이쁘게, 내가 원한만큼(각각 20) 마치 패딩이 잡힌 것처럼 보이는 것을 확인할 수 있다! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[frame.size.height 및 width 가 0일 때]]></title>
            <link>https://velog.io/@f-exuan21/frame.size.height-%EB%B0%8F-width-%EA%B0%80-0%EC%9D%BC-%EB%95%8C</link>
            <guid>https://velog.io/@f-exuan21/frame.size.height-%EB%B0%8F-width-%EA%B0%80-0%EC%9D%BC-%EB%95%8C</guid>
            <pubDate>Fri, 23 Jun 2023 11:05:07 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-swift">
header.insertSubview(card, at: 0)
card.leadingAnchor.constraint(equalTo: header.leadingAnchor, constant: 20.0).isActive = true
card.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -20.0).isActive = true
card.heightAnchor.constraint(equalToConstant: 213.0).isActive = true
card.topAnchor.constraint(equalTo: header.topAnchor, constant: 15.0).isActive = true
// card.layoutIfNeeded()
card.setGradient(color1: UIColor.black, color2: UIColor.blue)
</code></pre>
<pre><code class="language-swift">extension UIView {

    func setGradient(color1: UIColor, color2: UIColor) {

        let gradient: CAGradientLayer = CAGradientLayer()
        gradient.frame.size = CGSize(width: 300, height: 300)
        gradient.colors = [color1.cgColor, color2.cgColor]
//        gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
//        gradient.endPoint = CGPoint(x: 1.0, y: 1.0)
        print(self.bounds.size.height)
        gradient.frame = bounds
        layer.masksToBounds = true
        layer.insertSublayer(gradient, at: 0)
//        layer.addSublayer(gradient)
    }

}</code></pre>
<p>위처럼 header 라는 UIView 안에 card 라는 UIView 를 넣고, setGradient() 라는 함수를 만들어서 gradient를 주려고 했는데, 아무리 해도 안먹히더라...</p>
<p>대체 왜그럴까 하고, card.layout.size.height 랑 card.layout.size.width를 출력해보니 둘 다 0, 0 이 나왔다... </p>
<p>검색을 해보니 <code>card.layoutIfNeeded()</code> 를 추가해주니 잘 되었다. (주석처리 되어 있는 것)</p>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/32b1fb82-e5e0-4d57-b97a-6f1be261c0d0/image.png" alt=""></p>
<p>그런데 <code>layoutIfNeeded()</code> 를 사용하는 것을 피하라는 말이 있어서, frame에 대해서 조금 더 공부를 했다.</p>
<p><a href="https://babbab2.tistory.com/44">https://babbab2.tistory.com/44</a> (공부한 사이트) </p>
<p>하지만 대체 왜 size  의 모든 값들이 0이 나오는지 모르겠어서, 찾아보니</p>
<p><code>When you set up your views in viewDidLoad() the actual sizes have not yet been calculated. Try measuring the width in viewDidAppear(). By that time everything should be set up.</code></p>
<p><strong>viewDidLoad()</strong> 일 때는 아직 실제 사이즈가 계산되어지지 않고, <strong>viewDidAppear()</strong> 일 때 측정하면 값이 나온다고 한다. 아마 UITableViewDelegate의 <code>func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -&gt; UIView?</code> 같은 경우도 아직 <strong>viewDidAppear()</strong>가 호출된 상태가 아니기 때문에, 즉 뷰에 띄워진 상태가 아니기 때문에 무수한 0을 뱉어낸 것 같다.</p>
<p>그래서 혹여나! 싶어서 <strong>viewDidLoad()</strong> 에다가 <strong>setGradient()</strong>를 다시 호출해보았다.</p>
<pre><code class="language-swift">    override func viewDidAppear(_ animated: Bool) {

        super.viewDidAppear(animated)

        card.setGradient(color1: UIColor.black, color2: UIColor.blue)

    }</code></pre>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/7c5a7d42-7fcc-439a-9cff-ccf3908ac804/image.png" alt="">
완전잘됨...!!</p>
<p>UIViewController의 LifeCycle을 찾아보고 한 번 테스트 해봤다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/a94e2504-45fd-45c0-8e73-1b2f6dc7379f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/12933f7e-e729-4c6a-bc19-7771331ad059/image.png" alt=""></p>
<p>(213.0 이라고 출력된 것은 무시해도 된다.)
viewDidAppear() 이전까지 frame.size 출력 값이 다 (0.0, 0.0)인 것을 확인할 수 있었다! 그리고 viewDidAppear() 이전에 tableView와 관련된 함수가 호출되는 것까지 확인했다. 그러다보니 frame값이 0이 나온 것 같다.</p>
<p>역시 사람은 서칭을 해야해...b </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot Configuration Annotation Processor not configured]]></title>
            <link>https://velog.io/@f-exuan21/Spring-Boot-Configuration-Annotation-Processor-not-configured</link>
            <guid>https://velog.io/@f-exuan21/Spring-Boot-Configuration-Annotation-Processor-not-configured</guid>
            <pubDate>Mon, 29 May 2023 01:00:55 GMT</pubDate>
            <description><![CDATA[<p>@ConfigurationProperties 사용 중 이런 오류가 발생하였다.</p>
<p><code>Spring Boot Configuration Annotation Processor not configured</code></p>
<p>인터넷에 검색해보니 build.gradle에 아래와 같이 추가를 하면 된다고 한다.</p>
<pre><code>dependencies {
...
    annotationProcessor &quot;org.springframework.boot:spring-boot-configuration-processor&quot; // Use ConfigurationProperties
}</code></pre><p>그런데 이걸 추가하고 또 확인해보면 이번엔 새로운 문구가 뜬다.</p>
<pre><code>re-run spring boot configuration annotation processor to update generated metadata</code></pre><p>인텔리제이에서는 그냥 Invalidate Caches... 해주면 해결된다!
<img src="https://velog.velcdn.com/images/f-exuan21/post/66d7b25f-e661-4e3f-b4f4-9f219f930666/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Beanstalk 으로 스프링 배포하기!]]></title>
            <link>https://velog.io/@f-exuan21/Beanstalk-%EC%9C%BC%EB%A1%9C-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@f-exuan21/Beanstalk-%EC%9C%BC%EB%A1%9C-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 24 May 2023 12:41:22 GMT</pubDate>
            <description><![CDATA[<p>오늘은 Elastic Beanstalk 에 서버를 배포해보려고 한다.
EC2에는 몇 번 배포해봤지만 Elastic Beanstalk에 배포해보는 건 처음이다!</p>
<p>일단 제일 처음 aws 의 Beanstalk에 들어간다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/72e06822-6364-46bf-aa87-792eaf33794f/image.png" alt="">
간단하게 &#39;시작하기&#39;클릭!
<img src="https://velog.velcdn.com/images/f-exuan21/post/93a4102c-babc-4ee2-8ec1-998fd7a0e4b8/image.png" alt="">
환경 설정을 해준다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/b05cd554-d334-47fc-acf2-4f547fadcd2e/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/03b08a46-51f9-47d1-a42c-ce59aee7c36e/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/ba7dbcbd-ab45-4b15-871f-1275c8280b89/image.png" alt="">
이후로 나는 어차피 테스트하고 지울거기에 다음,다음,다음만 눌러주었다.</p>
<p>그리고 파일을 업로드 하기 위해서 작성했던 코드를 빌드를 진행해준다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/7428b393-bc41-4389-be0e-e8ef41829924/image.png" alt=""></p>
<p>빌드할 때 특징은 application.properties 파일을 따로 만들었다는 것이다.
application-proc.properties로 하나 만들고, 빌드할 때 VM Options 에 <code>-Dspring.profiles.active=proc</code> 를 추가해주었다. 만약 proc이 아닌 dev 나 그런것들을 쓰고싶다면, 저렇게 사용하면 된다.
또! 중요한 것은 Beanstalk은 기본적으로 <code>5000포트</code> 라고 한다. 그래서 application-proc.properties에 server.port=5000 또한 추가해주고 aws의 RDS를 생성해서 데이터베이스도 따로 세팅해주었다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/da2f3b08-8dba-45d1-8047-e8fdcc88f2e7/image.png" alt="">
쨘 성공!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 게시판 API 만들기 6]]></title>
            <link>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-6</link>
            <guid>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-6</guid>
            <pubDate>Sun, 21 May 2023 03:18:06 GMT</pubDate>
            <description><![CDATA[<p>API 명시서를 한 번 작성해보려고 했는데, 옛날에 Swagger를 딱 한 번 썼던 기억이 있어서 Swagger로 간편하게! 알아서 작성되게! 해보려고 한다.</p>
<p>Spring boot 3 미만에서는 springfox-swagger2 와 springfox-swagger-ui 를 의존성 축라해서 사용해야 하지만, 3 이상부터는 그냥 하나의 의존성만 추가해주면 된다.</p>
<pre><code>implementation &#39;org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2&#39;</code></pre><p>보면 이름이 다른 것을 알 수 있다. 구글링해서 찾아봤는데, 사람마다 하는 이야기가 달라서 좀 헷갈리긴 한다. 일단 swagger는 2020을 마지막으로 업데이트가 되지 않고 있다. 그 사이에 나온 게 SpringDoc(2019년)인데, 비교적 최근까지 업데이트가 되었다.</p>
<p>Springdoc은 Webflux라는 논 블록킹 비동기 방식의 웹 개발을 지원하도록 되어 있으며, springfox보다 더 사용하기 쉽다고 한다.(둘 다 심도있게 써보진 않아서 나는 비교하지 못하겠다.)</p>
<p>일단 한번 사용을 해보자.</p>
<hr>
<p>application.properties 에 아래와 같이 등록해 줄 수 있다.</p>
<pre><code class="language-properties"># spring doc
# springdoc.swagger-ui.path=/swagger-ui.html
springdoc.api-docs.path=/api-docs/json
springdoc.packagesToScan=com.hyeon.demo.controller
# springdoc.pathsToMatch=/boards</code></pre>
<p>주석 처리되어 있는 것은 내가 추후에 쓰지 않으려고 한 것이다. </p>
<ul>
<li><code>springdoc.swagger-ui.path=/swagger-ui.html</code> : HTML 페이지를 보여줄 url을 </li>
<li><code>springdoc.api-docs.path=/api-docs/json</code> : json 페이지를 보여줄 url을 정한다.</li>
<li><code>springdoc.packagesToScan=com.hyeon.demo.controller</code>: API 문서를 작성할 패키지명을 넣어준다. 여러개 넣으려면 그냥 ,(쉼표) 써서 연결해 작성하면 된다.</li>
<li><code>pringdoc.pathsToMatch=/boards</code> : API 문서를 작성할 API 범위를 지정한다. <code>/boards</code>와 같이 쓰면 <code>/boards</code> 인 API 만 작성되어지고, <code>/boards/**</code> 와 같이 작성하면 <code>/boards/</code> 하위 API 들이 다 작성되어진다.</li>
</ul>
<p><a href="https://springdoc.org/properties.html">여기</a> 에서 더 다양한 값들을 찾아볼 수 있다.</p>
<p>그 후 나는 SwaggerConfig.java 를 생성하였다.</p>
<pre><code class="language-java">@Configuration
public class SwaggerConfig {

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .group(&quot;board-public&quot;)
                .pathsToMatch(&quot;/boards/**&quot;)
                .build();
    }

    @Bean
    public GroupedOpenApi adminApi() {
        return GroupedOpenApi.builder()
                .group(&quot;board-admin&quot;)
                .pathsToMatch(&quot;/**&quot;)
                .addOpenApiMethodFilter(method -&gt; method.isAnnotationPresent(Admin.class)) //@Admin이 붙어 있는 Method만
                .build();
    }

    @Bean
    public OpenAPI springShopOpenAPI() {
        return new OpenAPI()
                .info(new Info().title(&quot;Board API&quot;)
                .description(&quot;Simple Board API&quot;)
                .version(&quot;v0.0.1&quot;));
    }

}</code></pre>
<p>GroupedOpenApi 같은 경우는 그룹을 생성해준다.
저렇게 생성해주면, 페이지를 열었을 때 아래와 같이 셀렉트 박스를 통해 선택할 수 있게 된다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/cd2ec100-dfdf-4415-b8c9-eb8c95c6cf20/image.png" alt=""></p>
<p>이 후 서버를 실행해보면, 아래와 같은 페이지가 나타난다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/2d2c320e-1285-4e32-9a7d-35def3e60b1b/image.png" alt=""></p>
<p>추가적으로, <code>.addOpenApiMethodFilter(method -&gt; method.isAnnotationPresent(Admin.class))</code> 에 있는 Admin.class 는 @interface 즉, 어노테이션이며 아래와 같이 선언만 해주었다.</p>
<pre><code class="language-java">public @interface Admin {

}</code></pre>
<p>이 어노테이션이 달린 메서드만 추려내서 board-admin 그룹에 보이게 된다.</p>
<p>끝 -*</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 게시판 API 만들기 5]]></title>
            <link>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-5</link>
            <guid>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-5</guid>
            <pubDate>Sun, 21 May 2023 03:02:35 GMT</pubDate>
            <description><![CDATA[<p>유효성 검사를 추가해보려고 한다.
조건은 아래와 같다.</p>
<ul>
<li><code>내용</code>은 1글자 이상 1000글자 이하여야 한다.</li>
<li><code>제목</code>은 공백으로만 이루어질 수는 없다.</li>
<li>게시글의 <code>id</code>(PK, primary key)로 특정 게시글을 조회했을 때, 존재하지 않는 게시글일 경우 에러 메시지로 응답하기</li>
<li>게시글을 수정할 때, 게시글 작성할 때의 유효성 검사 조건과 동일하게 가져가야 한다.</li>
<li>게시글의 <code>id</code>(PK, primary key)로 특정 게시글을 삭제하기 위해 조회했을 때, 존재하지 않는 게시글일 경우 에러 메시지로 응답하기</li>
<li><code>검색 키워드</code>는 공백을 제외한 1글자 이상이어야 한다.</li>
</ul>
<p>마지막 검색 키워드 관련된 것은 나중에 보면 알겠지만 내가 조건을 좀 달리했다. 어차피 공부하려고 하는 거니까 이것저것 시도해보는 것은 좋은 것일 거다...(아마...)!</p>
<hr>
<p>build.gradle에 아래와 같이 validation체크를 위해 의존성을 추가해준다.</p>
<pre><code>implementation &#39;org.springframework.boot:spring-boot-starter-validation&#39;</code></pre><p>준비는 다 됬으니, BoardRequest.java에서 요구사항에 맞게 어노테이션을 사용해서 유효성 검사를 해보자.</p>
<hr>
<h3 id="제목은-공백으로만-이루어질-수는-없다">제목은 공백으로만 이루어질 수는 없다.</h3>
<pre><code class="language-java">    @NotBlank(message = &quot;제목은 NULL이 아니어야 합니다.&quot;)
    private String title;</code></pre>
<p><code>@NotNull</code> : 모든 타입에 대해 NULL을 허용하지 않는다.
<code>@NotEmpty</code> : 아래 타입들에 한해서만 지원하며, NULL과 &quot;&quot;를 허용하지 않는다. 
    - CharSequence (length of character sequence is evaluated)
    - Collection (collection size is evaluated)
    - Map (map size is evaluated)
    - Array (array length is evaluated)
<code>@NotBlank</code> : NULL과 &quot;&quot;, &quot; &quot;(whitespace)를 허용하지 않는다.</p>
<hr>
<h3 id="내용은-1글자-이상-1000글자-이하여야-한다">내용은 1글자 이상 1000글자 이하여야 한다.</h3>
<pre><code class="language-java">    @Size(min = 1, max = 1000)
    private String content;</code></pre>
<p><code>@Size</code> : 문자열이나 배열ㅇ릐 크기를 검증하는데 사용된다.</p>
<hr>
<h3 id="게시글의-idpk-primary-key로-특정-게시글을-조회했을-때-존재하지-않는-게시글일-경우-에러-메시지로-응답하기">게시글의 id(PK, primary key)로 특정 게시글을 조회했을 때, 존재하지 않는 게시글일 경우 에러 메시지로 응답하기</h3>
<p>먼저 BoardServiceImpl을 수정해주었다.</p>
<pre><code class="language-java">    @Override
    @Transactional
    public Board update(int id, BoardRequest boardRequest) {
        Optional&lt;Board&gt; board = repository.findById(id);
        if(board.isPresent()) {
            board.get().update(boardRequest.getTitle(), boardRequest.getContent());
            return board.get();
        } else {
            return null;
        }
    }</code></pre>
<p>해당 id값을 가진 게시글이 존재하면 수정을 거친 뒤 true를 반환해주고,
id값을 가진 게시글이 존재하지 않으면 바로 false를 반환해준다.</p>
<p>그 후, BoardController 에서 아래와 같이 반환해준다.</p>
<pre><code class="language-java">    @PutMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;?&gt; updateBoard(@PathVariable(&quot;id&quot;) int id, @Validated @RequestBody BoardRequest boardRequest) {
        Board board = boardService.update(id, boardRequest);
        if(board == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(&quot;Invalid Board Id.&quot;);
        }
        return ResponseEntity.status(HttpStatus.OK)
                .body(board);
    }</code></pre>
<hr>
<h3 id="게시글의-idpk-primary-key로-특정-게시글을-삭제하기-위해-조회했을-때-존재하지-않는-게시글일-경우-에러-메시지로-응답하기">게시글의 id(PK, primary key)로 특정 게시글을 삭제하기 위해 조회했을 때, 존재하지 않는 게시글일 경우 에러 메시지로 응답하기</h3>
<p>수정과 마찬가지로 BoardService와 BoardSerivceImpl 로직들을 수정해주었다.</p>
<pre><code class="language-java">    @Override
    public boolean delete(int id) {
        Optional&lt;Board&gt; board = repository.findById(id);
        if(board.isPresent()) {
            repository.deleteById(id);
            return true;
        } else {
            return false;
        }
    }</code></pre>
<p>id를 통해 조회했을 때 값이 있으면 해당 게시글을 삭제하고 true를 반환해준다.
id를 통해 조회했을 때 값이 없으면 바로 false를 반환해준다.</p>
<p>그러면 BoardController.java에서는 아래와 같이 리턴 값을 이용해 유효성 검사를 해준다.</p>
<pre><code class="language-java">    @DeleteMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;?&gt; deleteBoard(@PathVariable int id) {
        boolean isDeleted = boardService.delete(id);
        if(!isDeleted) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(&quot;Invalid Board Id.&quot;);
        }
        return ResponseEntity.status(HttpStatus.OK)
                .build();
    }</code></pre>
<hr>
<h3 id="게시글을-수정할-때-게시글-작성할-때의-유효성-검사-조건과-동일하게-가져가야-한다">게시글을 수정할 때, 게시글 작성할 때의 유효성 검사 조건과 동일하게 가져가야 한다.</h3>
<p>이는 같은 BoardRequest 를 사용하기 때문에 따로 수정하지 않아도 똑같이 적용된다!
<img src="https://velog.velcdn.com/images/f-exuan21/post/871e96b1-157b-49e7-bd51-0d83d1011f67/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/05a84ae1-a0b3-427b-86f7-71e2c566b834/image.png" alt=""></p>
<hr>
<h3 id="검색-키워드는-공백을-제외한-1글자-이상이어야-한다">검색 키워드는 공백을 제외한 1글자 이상이어야 한다.</h3>
<p>드디어 내가 좀 생각을 바꿔서 추가했다는 기능에 도착했다.
나는 <strong><em>공백이 글자의 좌,우에 존재하면 지워주고 공백이어도 검색이 가능하도록</em></strong> 바꾸어보았다.</p>
<p>그 이유는, 어차피 게시판에서 빈 칸인 상태에서 검색하는 케이스도 있다고 생각했기 때문이다.
그래서 나는 아래와 같이 그냥 title에 trim()메소드를 이용해서 whitespace를 지워주었다.</p>
<pre><code class="language-java">    @GetMapping()
    public ResponseEntity&lt;?&gt; findAllBoards(@RequestParam(&quot;title&quot;) String title, Pageable pageable) {
        List&lt;Board&gt; boards = boardService.findAll(title.trim(), pageable); //title.trim()
        return ResponseEntity.status(HttpStatus.OK)
                .body(boards);
    }</code></pre>
<p>끝-*</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 게시판 API 만들기 4]]></title>
            <link>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-4</link>
            <guid>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-4</guid>
            <pubDate>Sun, 21 May 2023 01:48:57 GMT</pubDate>
            <description><![CDATA[<p>이번엔 Pageable을 수정을 좀 해보았다.
원래는 SpringDoc을 통해서 API 명시서 페이지를 만들려했는데, 만들고 보니 파라미터에 order가 있는게 아닌가! (저번에 Pageable에서 order를 찾다가 포기했었다.) 
그래서 다시 TRY 해보았는데, 역시나 있었다.</p>
<p>먼저, Repository interface 를 수정해주었다.</p>
<pre><code class="language-java">    List&lt;Board&gt; findByTitleContainingOrderByCreatedAtDesc(String title, Pageable pageable);

    List&lt;Board&gt; findByTitleContainingOrderByCreatedAtAsc(String title, Pageable pageable);</code></pre>
<p>원래는 이렇게 있던 것을 아래와 같이 수정하였다.</p>
<pre><code class="language-java">     List&lt;Board&gt; findByTitleContaining(String title, Pageable pageable);</code></pre>
<p>Service 도 마찬가지로 수정해주었다.
원래는 직접 order 파라미터를 받아서 DESC일 경우와 ASC일 경우를 분기처리해서 각각 다른 Repository 메소드를 타게 했는데 이제 하나로 통일하였다.</p>
<pre><code class="language-java">    public List&lt;Board&gt; findAll(String title, String order, Pageable pageable) {
        if(order.equalsIgnoreCase(&quot;DESC&quot;)) {
            return repository.findByTitleContainingOrderByCreatedAtDesc(title, pageable);
        } else {
            return repository.findByTitleContainingOrderByCreatedAtAsc(title, pageable);
        }
    }</code></pre>
<p>이렇게 되어 있던 것을 아래와 같이 수정하였다.</p>
<pre><code class="language-java">    public List&lt;Board&gt; findAll(String title, Pageable pageable) {
        return repository.findByTitleContaining(title, pageable);
    }</code></pre>
<p>그리고 이제는 더이상 필요없는 order 파라미터를 컨트롤러와 서비스 쪽에서 삭제해고, 파라미터를 보내서 테스트를 해보니 매우 잘 작동하였다!</p>
<h4 id="request">Request</h4>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/4d182c46-ea01-4faa-af6a-a8ae8727f79c/image.png" alt=""></p>
<h4 id="response">Response</h4>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/ece7cc78-b039-4023-aaf0-b7a9b5c5386c/image.png" alt=""></p>
<p>sort 파라미터 안에다가 배열로 우선 순위대로 나열할 파라미터를 나열하면, 그 순서대로 정렬해준다. 그리고  파라미터 옆에 &quot;,DESC&quot;, &quot;,ASC&quot;를 붙이면 내림차순 오름차순이 설정이 된다. 너무 유용하니 꼭 기억해 두어야 겠다.</p>
<p>끝-*</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Optional?]]></title>
            <link>https://velog.io/@f-exuan21/Optional</link>
            <guid>https://velog.io/@f-exuan21/Optional</guid>
            <pubDate>Sat, 20 May 2023 13:40:19 GMT</pubDate>
            <description><![CDATA[<h3 id="optional">Optional</h3>
<blockquote>
<p>Optional<T> 클래스는 Integer나 Double 클래스처럼 &#39;T&#39;타입의 객체를 포장해 주는 래퍼 클래스(Wrapper class)입니다.
따라서 Optional 인스턴스는 모든 타입의 참조 변수를 저장할 수 있습니다.
<a href="http://www.tcpschool.com/java/java_stream_optional">출처 : 코딩의 시작, TCP School</a></p>
</blockquote>
<p> 우리는 코딩을 하다가 <strong>NullPointException(이하, NPE)</strong>를 많이 접하게 된다.
   항상 코드를 짜다 보면, 나같은 경우도 가장 많이 걱정하는게 NPE이다. 그러다보니 사실 많이 번거롭고 코드도 길어지고... 나름 스트레스다...ㅠㅠ 
  특히 생각하지 못한 곳에서 NPE가 터지면.... 😵‍💫</p>
<p>  NULL을 개발한 사람도 나중에는 자신이 <strong>&quot;10억불 짜리 큰 실수&quot;</strong>를 했으며, NULL을 만든 것을  후회한다고 하였다.</p>
<p>  NPE를 피하기 위해 우리는 아래와 같이 항상 null체크를 해주어야만 했다.</p>
<pre><code class="language-java">
      Board board = getBoard(); //Board 객체를 가져오는 어떤 함수라고 가정
      if(board != null) {
          User user = board.getUser();
          if(user != null) {
              // 하고 싶은 일 작성
          }
      }
</code></pre>
<p>  이렇게 객체안의 객체도 계속 null체크를 해주어야 NPE로부터 마음이 편해질 수 있었다.
  그런데 <strong>Optional</strong>이 등장하면서 아주 편리해졌다.</p>
<pre><code class="language-java">      Optional&lt;Board&gt; board = Optional.ofNullable(getBoard());
      Optional&lt;User&gt; user = board.map(Board::getUser);

      board.</code></pre>
<p>  나같은 경우는 iOS 개발을 시작하면서 Swift를 공부했었는데, 거기에서 Optional이 있어서 아주 편했던 기억이 난다.(문법 자체는 그동안 공부했던 java나 c와 같은 언어와 달라서 당황했지만...;;)
  아무튼 오늘은 이 Optional에 대해 한 번 공부해보려고 한다.</p>
<h3 id="optional-선언">Optional 선언</h3>
<h4 id="1-static-t-optionalt-empty">1. static <T> Optional<T> empty()</h4>
<p>  비어있는 객체를 리턴해준다.
  Null 값이 올 수도 있는 변수의 자료형을 제너릭 타입으로 선언한다.</p>
<pre><code class="language-java">      Optional&lt;T&gt; opt = Optional.empty(); //Null이 올 수도 있음</code></pre>
<h4 id="2-static-t-optionalt-oft-value">2. static <T> Optional<T> of(T value)</h4>
<p>  Null이 아닌 명시된 값을 가지고 있는 Optional 객체를 반환한다.
  Null인 경우 NPE를 던진다.</p>
<pre><code class="language-java">      Optional&lt;String&gt; opt = Optional.of(&quot;Apple&quot;);</code></pre>
<h4 id="3-static-t-optionalt-ofnullablet-value">3. static <T> Optional<T> ofNullable(T value)</h4>
<p>  명시된 값이 Null이 아니면, 명시된 값을 가지는 Optional 객체를 반환한다.
  명시된 값이 Null이면, 비어있는 Optional 객체를 반환한다.</p>
<pre><code class="language-java">      Optional&lt;String&gt; opt = Optional.ofNullable(null);</code></pre>
<h3 id="optional-메소드">Optional 메소드</h3>
<h4 id="1-t-get">1. T get()</h4>
<p>  Optional 객체에 저장된 값을 반환한다.</p>
<h4 id="2-t-ispresent">2. T isPresent()</h4>
<p>  저장된 값이 존재하면 true를 반환하고, 존재하지 않으면 false를 반환한다.</p>
<h4 id="3-t-orelset-other">3. T orElse(T other)</h4>
<p>  저장된 값이 존재하면 그 값을 반환하고, 존재하지 않으면 인수로 전달된 값을 반환한다.</p>
<h4 id="4-t-orelsegetsupplier-extends-t-other">4. T orElseGet(Supplier&lt;? extends T&gt; other)</h4>
<p>  저장된 값이 존재하면 그 값을 반환하고, 존재하지 않으면 인수로 전달된 람다 표현식의 결과값을 반환한다.</p>
<h4 id="5-x-extends-throwable-t-orelsethrowsupplier-extends-x-exceptionsupplier">5. <X extends Throwable> T orElseThrow(Supplier&lt;? extends X&gt; exceptionSupplier)</h4>
<p>  저장된 값이 존재하면 그 값을 반환하고, 존재하지 않으면 인수로 전달된 예외를 발생시킨다.</p>
<h4 id="6-optionalt-orsupplier-extends-optional-extends-t-supplier---java-9">6. Optional<T> or(Supplier&lt;? extends Optional&lt;? extends T&gt;&gt; supplier) - java 9</h4>
<p>  중간처리 메서드로 기본값을 제공할 수 있는 공급자 함수를 정의할 수 있다. 기존에 제공중인 .orElseGet()과 유사하지만, 중간에 체이닝을 통해 우선순위를 결정할 수 있다. 물론 <strong><em>.or() 연산 중에 비어있는 옵셔널이 된다면 다음 .or() 메서드로 진행하게 된다.</em></strong></p>
<pre><code class="language-java">  String result = Optional.ofNullable(&quot;test&quot;)
          .filter(value -&gt; &quot;filter&quot;.equals(value))
          .or(Optional::empty)
          .or(() -&gt; Optional.of(&quot;second&quot;))
          .orElse(&quot;final&quot;);
  System.out.println(result); // print &#39;second&#39;</code></pre>
<h4 id="7-void-ifpresentorelseconsumer-super-t-action-runnable-emptyaction---java-9">7. void ifPresentOrElse(Consumer&lt;? super T&gt; action, Runnable emptyAction) - java 9</h4>
<p>  첫번째 매개변수인 action은 유효한 객체를 받을 경우 실행하고, 두번째 매개변수인 emptyAction은 유효한 객체를 받지 못한 경우 실행한다.</p>
<pre><code class="language-java">  Optional.ofNullable(null)
      .ifPresentOrElse(value -&gt; System.out.println(value), () -&gt; System.out.println(&quot;null&quot;)); // print &#39;null&#39;</code></pre>
<h4 id="8-streamt-stream---java-9">8. Stream<T> stream() - java 9</h4>
<p>  Optional 객체를 Stream 객체로 전환한다.</p>
<pre><code class="language-java">  List&lt;String&gt; result = List.of(1, 2, 3, 4)
    .stream()
    .map(val -&gt; val % 2 == 0 ? Optional.of(val) : Optional.empty())
    .flatMap(Optional::stream)
    .map(String::valueOf)
    .collect(Collectors.toList());
  System.out.println(result); // print &#39;[2, 4]&#39;</code></pre>
<blockquote>
<p>Optional&lt;List<T>&gt; 와 같이 사용하면 Optional도 null체크를 하고 List도 null체크를 해야하기 때문에 가독성이 떨어진다. 항상 List를 사용할 때에는 List를 채워주는 것이 좋다.</p>
</blockquote>
<pre><code class="language-java">  List data = Optional.ofNullable(somethingList).orElse(Collections.emptyList());
  if (!data.isEmpty()) {
      // do something...
  }</code></pre>
<p>끝-*</p>
<hr>
<p>참고
<a href="https://jdm.kr/blog/234">자바 옵셔널</a>
<a href="https://esoongan.tistory.com/95">[JAVA] Optional이란?</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 게시판 API 만들기 3]]></title>
            <link>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-3</link>
            <guid>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-3</guid>
            <pubDate>Mon, 15 May 2023 14:13:32 GMT</pubDate>
            <description><![CDATA[<h3 id="새로운-기능을-추가해보자">새로운 기능을 추가해보자</h3>
<p>데이터를 가져올 때 검색, 정렬, 갯수제한 기능을 추가해 보려고한다.
갯수제한을 그냥 limit로 보는게 아니라 Pagination느낌으로 해보려고 한다.</p>
<p>Spring Data JPA가 제공하는 PageNation이 있는데, Pageable과 PageRequest이다.
Pageable은 페이지네이션 정보를 담기 위한 인터페이스, PageRequest은 구현체이다.</p>
<p>Pageable 인터페이스를 확인해보면,
<img src="https://velog.velcdn.com/images/f-exuan21/post/12dd837b-8a18-40c8-9520-13d6006b97e7/image.png" alt="">
pageNumber, pageSize, offset과 같은 페이징 구현에 필요한 값들을 편하게 구할 수 있는 메소들을 추상화 시켜놓은 것을 확인할 수 있다. </p>
<p>이제 이것을 이용해서 한 번 구현해보자!</p>
<p>먼저 Repository를 구현해보자.</p>
<pre><code class="language-java">    List&lt;Board&gt; findByTitleContainingOrderByCreatedAtDesc(String title, Pageable pageable);</code></pre>
<p>위의 구문은 title 단어가 들어 있는 제목을 탐색하고(findByTitleContaining), created_at을 기준으로 내림차순(desc)을 하겠다는(OrderByCreated) 의미이다.</p>
<p>이것을 Service 계층에서 사용해보자.
이전에 만들어 놓았던 findAll() 메소드를 수정해서 사용할 것이다.</p>
<pre><code class="language-java">    @Override
    public List&lt;Board&gt; findAll(String title, String order, Pageable pageable) {
        if(order.equalsIgnoreCase(&quot;DESC&quot;)) {
            return repository.findByTitleContainingOrderByCreatedAtDesc(title, pageable);
        } else {
            return repository.findByTitleContainingOrderByCreatedAtAsc(title, pageable);
        }
    }</code></pre>
<p>나는 내림차순, 오름차순을 구분해 주고 싶어서 Repository에 메소드를 하나 더 생성했다.</p>
<p>아무튼, findAll() 메소드가 받는 매개변수도 달라졌기 때문에 Controller 계층도 수정을 해줘야한다.</p>
<pre><code class="language-java">    @GetMapping()
    public ResponseEntity&lt;List&lt;Board&gt;&gt; findAllBoards(@RequestParam String title, @RequestParam String order, Pageable pageable) {
        List&lt;Board&gt; boards = boardService.findAll(title, order, pageable);
        return ResponseEntity.status(HttpStatus.OK)
                .body(boards);
    }</code></pre>
<p>이제 모든 준비는 끝났다! 
포스트맨으로 API를 호출해 보면,</p>
<pre><code>    [GET] http://localhost:8080/boards?page=0&amp;size=5&amp;title=영화&amp;order=asc</code></pre><pre><code class="language-json">[
    {
        &quot;id&quot;: 5,
        &quot;title&quot;: &quot;어린왕자 만화영화 봤니?&quot;,
        &quot;content&quot;: &quot;재밌더라~&quot;,
        &quot;createdAt&quot;: &quot;2023-05-14T15:33:31.000+00:00&quot;,
        &quot;updatedAt&quot;: &quot;2023-05-14T15:33:31.000+00:00&quot;,
        &quot;isDeleted&quot;: null
    },
    {
        &quot;id&quot;: 6,
        &quot;title&quot;: &quot;어린왕자 만화영화 봤니?&quot;,
        &quot;content&quot;: &quot;재밌더라~&quot;,
        &quot;createdAt&quot;: &quot;2023-05-14T15:34:26.000+00:00&quot;,
        &quot;updatedAt&quot;: &quot;2023-05-14T15:34:26.000+00:00&quot;,
        &quot;isDeleted&quot;: null
    },
    {
        &quot;id&quot;: 7,
        &quot;title&quot;: &quot;어린왕자 만화영화 봤니?&quot;,
        &quot;content&quot;: &quot;재밌더라~&quot;,
        &quot;createdAt&quot;: &quot;2023-05-14T15:34:30.000+00:00&quot;,
        &quot;updatedAt&quot;: &quot;2023-05-14T15:34:30.000+00:00&quot;,
        &quot;isDeleted&quot;: null
    },
    {
        &quot;id&quot;: 8,
        &quot;title&quot;: &quot;어린왕자 만화영화 봤니?&quot;,
        &quot;content&quot;: &quot;재밌더라~&quot;,
        &quot;createdAt&quot;: &quot;2023-05-14T15:36:10.000+00:00&quot;,
        &quot;updatedAt&quot;: &quot;2023-05-14T15:36:10.000+00:00&quot;,
        &quot;isDeleted&quot;: null
    },
    {
        &quot;id&quot;: 9,
        &quot;title&quot;: &quot;어린왕자 만화영화 봤니?&quot;,
        &quot;content&quot;: &quot;재밌더라~&quot;,
        &quot;createdAt&quot;: &quot;2023-05-14T15:37:51.000+00:00&quot;,
        &quot;updatedAt&quot;: &quot;2023-05-14T15:37:51.000+00:00&quot;,
        &quot;isDeleted&quot;: &quot;N&quot;
    }
]</code></pre>
<p>이런 결과가 나오는 것을 확인해 볼 수 있다.
데이터들은 내가 계속 같은 값만 넣어서 똑같은 값을 보여주는 것일 뿐이다...
잘보면 createdAt 시간은 다르다... ㅎㅎ </p>
<p>page를 1로 바꾸면 이 데이터들 다음 데이터부터 5개가 출력된다.</p>
<p>끝 -*</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ORM 이란?]]></title>
            <link>https://velog.io/@f-exuan21/ORM-%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@f-exuan21/ORM-%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Mon, 15 May 2023 11:02:01 GMT</pubDate>
            <description><![CDATA[<h3 id="1-orm">1. ORM</h3>
<p>ORM(Object-Relational Mapping)은 대체 무엇일까?
이것은 자바의 객체와 데이터베이스(이하, 디비)를 연결하는 프로그래밍 기법이다.
예를 들어, 자바의 객체로 Board라는 객체가 있고 title, content 필드가 있다고 가정해보자. 이것을 디비로부터 값을 받아와서 넣어주려면 SQL 쿼리를 이용해야 한다. 그러면 우리는 또 SQL을 공부하는 번거로움을 갖게 된다.
(그런데 나는 사실 SQL을 아주 좋아한다...)</p>
<p>아무튼! 
ORM은 이런 번거로움을 없애주고 디비의 값을 마치 객체처럼 사용할 수 있게 해준다.
즉, SQL 공부를 하지 않아도 자바 언어로 디비 값을 조회 및 조작할 수 있게 되는 것이다.
이걸 해줄 수 있게 해주는 도구가 바로 <strong>ORM</strong>이다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/3129de17-ffd4-4640-8b5e-5c6bdc2dc125/image.png" alt=""></p>
<p>그런데 어렵고 복잡한 쿼리문 같은 경우는 자바 코드로 치는데 한계가 있을 것이다. 이것은 어쩔 수 없다고 생각한다... (편리함 대신 얻는 대가랄까...)</p>
<h3 id="2-jpa와-hibernate">2. JPA와 Hibernate</h3>
<p>스프링을 돌리다보면 JPA, Hibernate 단어를 많이 접하게 된다.
나같은 경우도 JPA, JPA 거리기만 했지 정말 JPA가 무엇이고, Hibernate가 무엇인지 잘 몰랐다.
오늘의 공부를 통해 확실히 알아가도록 하자.</p>
<p><strong>JPA</strong>는 <strong>자바에서 RDBMS를 사용하는 방식을 정의한 인터페이스</strong>이다. 인터페이스이기 때문에 이를 <strong>실제로 사용하기 위한 구현체</strong>를 추가로 갖춰야하는데 그 중 하나가 <strong>Hibernate</strong>되겠다.</p>
<p>즉, *<em>Hibernate = JPA 인터페이스를 구현한 구현체이자 자바용 ORM 프레임워크 *</em>이다.</p>
<h3 id="3-엔티티-매니저--영속성-컨텍스트">3. 엔티티 매니저 + 영속성 컨텍스트</h3>
<p>엔티티 매니저와 영속성 컨텍스트는 JPA의 중요한 컨셉 중 하나이다.</p>
<h4 id="엔티티">엔티티</h4>
<p>엔티티는 디비의 테이블과 매핑되는 객체를 의미한다.
디비의 테이블과 직접 연결된다는 특징이 있어서 자바 객체와 다르게 엔티티라고 따로 지칭한다.</p>
<h4 id="엔티티-매니저">엔티티 매니저</h4>
<p>엔티티 매니저는 엔티티를 관리해 디비와 어플리케이션 사이에서 객체를 생성, 수정, 삭제하는 등의 역할을 한다. 이런 엔티티 매니저를 만드는 곳이 엔티티 매니저 팩토리이다.
만약에 회원 두 명이 동시에 책을 빌리면, 회원 1의 요청에 대응해줄 엔티티 매니저와, 회원 2의 요청에 대응해줄 엔티티 매니저를 엔티티 매니저 팩토리에서 각각 생성해주는 것이다.
그리고 이렇게 생성된 엔티티 매니저들은 필요한 시점에 디비와 연결한 뒤에 쿼리를 실행한다.</p>
<p>BUT, 스프링 부트에서는 직접 엔티티 매니저 팩토리를 만들어서 관리하지 않고, 스프링 부트 내부에서 엔티티 매니저 팩토리를 하나만 생성해서 관리하고 <strong>@PersistenceContext</strong> 또는 <strong>@Autowired</strong>
어노테이션을 통해 엔티티 매니저를 사용한다.</p>
<p>그리고 스프링 부트는 기본적으로 빈은 하나만 생성해서 공유하므로 동시성 문제가 발생할 수 있다. 그래서 실제로는 엔티티 매니저가 아닌 실제 엔티티 매니저와 연결하는 프록시(가짜)엔티티 매니저를 사용한다. 필요할 때 디비 트랜잭션과 관련된 실제 엔티티 매니저를 호출하는 것이다.</p>
<blockquote>
<p>쉽게 말해 엔티티 매니저는 Spring Data JPA 에서 관리하므로 우리가 직접 생성하거나 관리할 필요가 없다.</p>
</blockquote>
<p>그리고 추가적으로 엔티티 매니저는 기본적으로 UPDATE를 제공하지 않는다. JPA는 트랜잭션의 범위에서 엔티티 객체의 상태가 변경되면 이를 트랜잭션 커밋 시점에 반영한다.</p>
<h4 id="영속성-컨텍스트">영속성 컨텍스트</h4>
<p>영속성 컨텍스트는 JPA의 중요한 특징 중 하나로, 엔티티를 관리하는 가상의 공간이다.
영속성 컨텍스트는 아래와 같은 특징을 가지고 있다.</p>
<ol>
<li>1차 캐시
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있다. 이 때 키는 @Id 어노테이션이 달린 필드 값이 되겠다. 엔티티를 조회하면 1차 캐시에서 먼저 조회해 값을 반환한다. 1차 캐시에 값이 없으면 디비에서 조회해 1차 캐시에 저장하고 반환한다. </li>
<li>쓰기 지연
쓰기 지연은 트랜잭션을 커밋하기 전까지는 실제로 디비에 질의문을 보내지 않고 쿼리를 모아두었다가 트랜잭션을 커밋할 때 한 번에 실행하는 것을 말한다. 이를 통해 디비 시스템의 위험 부담을 줄일 수 있다.</li>
<li>변경 감지
트랜잭션을 커밋하면 1차 캐시에 저장되어 있는 엔티티의 값과 현재 엔티티의 값을 비교해서 변경된 값이 있다면 변경사항을 감지해 변경된 값을 디비에 자동으로 반영한다.</li>
<li>지연 로딩
지연로딩은 쿼리로 요청한 데이터를 어플리케이션에 바로 로딩하는 것이 아니라 필요할 때 쿼리를 날려 데이터를 조회하는 것을 말한다. 반대로 조회할 때 쿼리를 보내 연관된 모든 데이터를 가져오는 즉시 로딩도 있다.</li>
</ol>
<h4 id="엔티티의-생명주기">엔티티의 생명주기</h4>
<p><img src="blob:https://velog.io/701c6878-53e4-458b-8eee-414bd2f581fd" alt="업로드중.."></p>
<pre><code class="language-java">public class EntityManagerTest {

    @Autowired
    EntityManager em;

    public void example() {
        // 엔티티 매니저가 엔티티를 관리하지 않는 상태 = 비영속 상태
        Member member = new Member(1L, &quot;홍길동&quot;);

        // 엔티티가 관리 상태가 됨 = 관리 상태
        em.persist(member);

        // 엔티티 객체가 분리된 상태가 됨 = 분리 상태
        em.detach(member);

        // 엔티티 객체가 삭제된 상태가 됨 = 삭제 상태
        em.remove(member);
    }

}</code></pre>
<ul>
<li><p>비영속 상태
엔티티 객체를 생성했지만 아직 영속성 컨텍스트에 저장하지 않은 상태</p>
</li>
<li><p>영속 상태
엔티티 매니저를 통해서 엔티티를 영속성 컨텍스트에 저장한 상태를 말하며 엔티티 매니저에 의해 관리된다.</p>
</li>
<li><p>준영속 상태
영속 상태의 엔티티를 더이상 관리하지 않는 상태이다.
1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.
식별자 값을 가지고 있다.</p>
</li>
<li><p>삭제
엔티티를 영속성 컨텍스트와 디비에서 삭제한다.</p>
</li>
</ul>
<h3 id="4-스프링-데이터-jpa">4. 스프링 데이터 JPA</h3>
<p>위와 같은 코드들을 개발자가 계속 작성해가면서 관리하면 매우 번거로울 것이다.
스프링 데이터는 비즈니스 로직에 더 집중할 수 있게 디비 사용 기능을 클래스 레벨에서 추상화하였다.</p>
<p>이전까지는 아래와 같이 메서드 호출로 엔티티 상태를 바꿔주었다.</p>
<pre><code class="language-java">@PersistenceContext
EntityManager em;

public void join() {
    Member member = new Member(1L, &quot;홍길동&quot;);
    em.persist(member);
}
</code></pre>
<p>이제 스프링 데이터 JPA를 사용하면 아래와 같이 하면 된다.</p>
<pre><code class="language-java">public interface MemberRepository extends JPARepository&lt;Member, Log&gt; {
}</code></pre>
<p>끝 -*</p>
<br>
<br>

<h4 id="참고자료">참고자료</h4>
<p><a href="https://dev-troh.tistory.com/151">엔티티와 엔티티매니저</a>
<a href="https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/">JPA, Hibernate, 그리고 Spring Data JPA의 차이점</a>
<a href="https://javabydeveloper.com/orm-object-relational-mapping/">What is ORM, how does it work?</a>
<a href="https://velog.io/@neptunes032/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%9E%80">JPA 영속성 컨텍스트란?</a>
[도서] 스프링 부터 3 백엔드 개발자 되기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 게시판 API 만들기 2]]></title>
            <link>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-2</link>
            <guid>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-2</guid>
            <pubDate>Mon, 15 May 2023 10:22:13 GMT</pubDate>
            <description><![CDATA[<h3 id="1-특정-게시판-조회-api-만들기">1. 특정 게시판 조회 API 만들기</h3>
<p>특정 한 게시판을 조회하는 API를 만들어 보려고 한다.</p>
<p>먼저 Service 코드부터 작성해보자.</p>
<pre><code class="language-java">    @Override
    public Board findOne(int id) {
        return repository.findById(id)
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;not found : &quot; + id));
    }
</code></pre>
<p><strong><em>findById</em></strong> 는 CrudRepository 에 있는 메소드이다.
id 를 기반으로 Board를 탐색한다.
서비스 코드를 작성했으니 컨트롤러를 작성해보자.</p>
<pre><code class="language-java">    @GetMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;Board&gt; findOneBoard(@PathVariable int id) {
        Board board = boardService.findOne(id);
        return ResponseEntity.status(HttpStatus.OK)
                .body(board);
    }</code></pre>
<p>이러면 벌써 게시글 하나 조회하는 API는 작성이 완료되었다. JPA를 이용하니 더욱더 간단하다.</p>
<h3 id="2-게시판-수정-api-만들기">2. 게시판 수정 API 만들기</h3>
<p>게시판 수정 API도 위와 마찬가지로 하면 되지만, 한 가지 단계를 더 추가해야한다.
이전에 만들었던 Board DTO  객체에 update 메소드를 추가해준다.</p>
<pre><code class="language-java">    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }</code></pre>
<p>그 후 이전에 했던 방법과 마찬가지로 각각 서비스와 컨트롤러 부분 코드를 작성해준다.</p>
<pre><code class="language-java">    @Override
    @Transactional
    public Board update(int id, BoardRequest boardRequest) {
        Board board = repository.findById(id)
                        .orElseThrow(() -&gt; new IllegalArgumentException(&quot;not found : &quot; + id));
        board.update(boardRequest.getTitle(), boardRequest.getContent());
        return board;
    }</code></pre>
<p>그런데 업데이트 메소드를 보면 위에 <strong><em>@Transactional</em></strong> 이 붙은 것을 확인할 수가 있다.
이 어노테이션은 매칭한 메서드를 하나의 트랜잭션으로 묶어준다. 
예를 들어 내가 A의 계좌에 돈을 송금하면 A의 계좌엔 돈이 입금 되어야 하는데, 돈이 송금되고 나서 시스템이 멈춰버리면 돈은 감쪽같이 사라지게 된다. 그렇기 때문에 이 두가지 작업을 하나로 묶어서, 즉, 트랜잭션으로 묶어서 한 단위로 실행하게끔 만드는 것이다.
여기서는 Board를 읽어오는 단위와 수정해주는 단위를 하나로 묶어서 작업한 것이다.</p>
<pre><code class="language-java">    @PutMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;Board&gt; updateBoard(@PathVariable int id, @RequestBody BoardRequest boardRequest) {
        Board board = boardService.update(id, boardRequest);
        return ResponseEntity.status(HttpStatus.OK)
                .body(board);
    }</code></pre>
<p>컨트롤러는 이전과 다를바가 없다.</p>
<h3 id="3-게시판-삭제-api-만들기">3. 게시판 삭제 API 만들기</h3>
<p>삭제도 마찬가지로 별 다를게 없다.
각 서비스 코드와 컨트롤러 코드를 작성해 주자.</p>
<pre><code class="language-java">    @Override
    public void delete(int id) {
        repository.deleteById(id);
    }</code></pre>
<pre><code class="language-java">    @DeleteMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;Void&gt; deleteBoard(@PathVariable int id) {
        boardService.delete(id);
        return ResponseEntity.status(HttpStatus.OK)
                .build();
    }</code></pre>
<p>삭제를 마지막으로 CRUD API를 다 만들었다! 
끝 -*</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 게시판 API 만들기]]></title>
            <link>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@f-exuan21/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B2%8C%EC%8B%9C%ED%8C%90-API-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 14 May 2023 15:38:51 GMT</pubDate>
            <description><![CDATA[<h3 id="1-엔티티-구성하기">1. 엔티티 구성하기</h3>
<p>가장 먼저 엔티티를 구성해보겠다.</p>
<table>
<thead>
<tr>
<th>컬럼명</th>
<th>자료형</th>
<th>null 허용</th>
<th>키</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>id</td>
<td>int</td>
<td>X</td>
<td>PK</td>
<td>게시글의 아이디</td>
</tr>
<tr>
<td>title</td>
<td>varchar(100)</td>
<td>X</td>
<td></td>
<td>게시글의 제목</td>
</tr>
<tr>
<td>content</td>
<td>varchar(1000)</td>
<td>X</td>
<td></td>
<td>게시글의 내용</td>
</tr>
<tr>
<td>created_at</td>
<td>date</td>
<td>X</td>
<td></td>
<td>게시글 작성 날짜</td>
</tr>
<tr>
<td>updated_at</td>
<td>date</td>
<td>O</td>
<td></td>
<td>게시글 수정 날짜</td>
</tr>
</tbody></table>
<p>나는 일단 제목과 내용을 우선적으로 넣을 것이기 때문에 이정도로 간단하게만 구상해보았다.</p>
<p>이제는 코드로 엔티티 클래스를 하나 만들어 보자.</p>
<pre><code class="language-java">@Entity //Entity로 지정
@Getter
public class Board {

    @Id // PK로 지정
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키 자동으로 1씩 증가
    @Column(name = &quot;id&quot;, nullable = false)
    private int id;

    @Column(name = &quot;title&quot;, nullable = false)
    private String title;

    @Column(name = &quot;content&quot;, nullable = false)
    private String content;

    @CreationTimestamp
    @Column(name = &quot;created_at&quot;)
    private Date createdAt;

    @UpdateTimestamp
    @Column(name = &quot;updated_at&quot;)
    private Date updatedAt;

    @Column(name = &quot;is_deleted&quot;)
    private String isDeleted = &quot;N&quot;;

    @Builder
    public Board(String title, String content) {
        this.title = title;
        this.content = content;
    }

    protected Board() {

    }
}</code></pre>
<p><strong>@Entity</strong>가 붙은 클래스는 JPA가 관리해주며, JPA를 사용해서 DB테이블과 매핑할 클래서는 @Entity를 꼭 붙여야만 매핑이 가능하다.</p>
<p><strong>@Getter</strong>와 같은 경우는 getXXX 메소드를 자동으로 생성해준다.
예를 들어 title같은 경우는 getTitle()을 통해 그 값을 읽을 수 있다.</p>
<p><strong>@Builder</strong>와 같은 경우는 생성자 위에 입력하면 빌더 패턴 방식으로 객체를 생성할 수 있게 된다. 빌더 패턴을 사용하면 어떤 필드에 어떤 값이 들어가는지 명시적으로 파악할 수 있다.</p>
<p>예를 들어 기본 생성자와 같은 경우는</p>
<pre><code class="language-java">new Board(&quot;잭과 콩나무 읽어보셨나요?&quot;, &quot;어린아이가 읽기 너무 좋아요.&quot;);</code></pre>
<p>와 같이 되겠지만, 빌더 패턴을 사용하면</p>
<pre><code class="language-java">Board.builder()
    .title(&quot;잭과 콩나무 읽어보셨나요?&quot;)
    .content(&quot;어린아이가 읽기 너무 좋아요.&quot;)
    .build();</code></pre>
<p>이렇게 사용할 수 있다. 
딱 보면 title에 어떤 값이 들어갔고, content에 어떤 값이 들어갔는지 쉽게 유추할 수가 있다.</p>
<h3 id="2-레포지토리-만들기">2. 레포지토리 만들기</h3>
<p>repository 패키지를 새로 만들고 Repository 인터페이스를 한 번 생성해 보자. </p>
<pre><code class="language-java">public interface BoardRepository extends JpaRepository&lt;Board, Long&gt; {

}</code></pre>
<p>BoardRepository는 JpaRepository를 상속받는다. JpaRepository는 CrudRepository를 상속받는데 이 클래스에는 기본적인 CRUD 메소드가 포함되어 있다. </p>
<h3 id="3-서비스-코드-작성하기">3. 서비스 코드 작성하기</h3>
<pre><code class="language-java">@Service
public class BoardServiceImpl implements BoardService {

    BoardRepository repository;

    @Autowired
    public BoardServiceImpl(BoardRepository repository) {
        this.repository = repository;
    }

    // 게시글 리스트 불러오는 메소드
    @Override
    public List&lt;Board&gt; findAll() {
        return repository.findAll();
    }

    // 게시글 저장 메소드
    @Override
    public Board save(BoardRequest boardRequest) {
        return repository.save(boardRequest.toEntity());
    }

}
</code></pre>
<h3 id="4-컨트롤러-코드-작성하기">4. 컨트롤러 코드 작성하기</h3>
<pre><code class="language-java">
@RestController
@RequestMapping(&quot;/board&quot;)
public class BoardController {

    BoardService boardService;

    @Autowired
    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }

    @GetMapping()
    public ResponseEntity&lt;List&lt;Board&gt;&gt; findAllBoards() {
        List&lt;Board&gt; boards = boardService.findAll();
        return ResponseEntity.status(HttpStatus.OK)
                .body(boards);
    }

    @PostMapping()
    public ResponseEntity&lt;Board&gt; addBoard(@RequestBody BoardRequest boardRequest) {
        Board board = boardService.save(boardRequest);
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(board);
    }

}
</code></pre>
<p>@RestController 어노테이션을 붙이면 HTTP 응답으로 객체 데이터를 JSON 형식으로 반환한다.</p>
<p>return 객체로는 ResponseEntity를 지정했는데, 아래와 같은 응답코드와 함께 객체를 반환해준다.</p>
<ul>
<li>200 OK : 요청이 성공적으로 수행됨</li>
<li>201 Created : 요청이 성공적으로 수행되었고, 새로운 리소스가 생성되었음</li>
<li>400 Bad Request : 요청 값이 잘못되어 요청에 실패했음</li>
<li>403 Forbidden : 권한이 없어 요청에 실패했음</li>
<li>404 Not Found : 요청 값으로 찾은 리소스가 없어 요청에 실패했음</li>
<li>405 Method Not Allowed : 지정된 메소드가 허용되지 않음 </li>
<li>500 Internal Server Error : 서버 상에 문제가 있어 요청에 실패했음</li>
</ul>
<p>이 외에도 응답코드가 여럿 있으니 필요하면 찾아보자.</p>
<p>이제 이 상태에서 서버를 구동하고 Postman으로 테스트를 해보자.</p>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/68d20cac-c430-4e51-ad0b-5ef6855896da/image.png" alt="">
Get 방식의 /board API에 요청을 했더니 </p>
<p><img src="https://velog.velcdn.com/images/f-exuan21/post/bdef721e-22f8-4bf6-ba9e-38ccbc9d4827/image.png" alt="">
이렇게 미리 넣어둔 데이터들이 잘 나오는 것을 확인해볼 수 있다.</p>
<p>Post 방식의 /board API도 마찬가지다.
<img src="https://velog.velcdn.com/images/f-exuan21/post/585a9e46-2a22-482b-8405-80ed23f678b9/image.png" alt="">
<img src="https://velog.velcdn.com/images/f-exuan21/post/b4f8eaad-6d7d-4261-af7b-c523de8f4ade/image.png" alt=""></p>
<p>이렇게 API 두개를 만들어보고 테스트까지 완료했다. 이와 마찬가지 방식으로 삭제와 수정, 그리고 한 개의 게시글 조회 API까지 만들어 보려고 한다.</p>
<p>끝-*</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[REST API?]]></title>
            <link>https://velog.io/@f-exuan21/REST-API</link>
            <guid>https://velog.io/@f-exuan21/REST-API</guid>
            <pubDate>Sun, 14 May 2023 11:46:59 GMT</pubDate>
            <description><![CDATA[<p>글제목, 내용 정도만 있는 간단한 게시판 API를 만들어 보려고 한다.
스프링을 만진지 어언 1년이 다 되어 스프링이 하나도 기억나지 않아 복습 차원에서 열심히 스터디 중이다! 😂</p>
<p>일단 가장 먼저 Rest API란 무엇일까?
<strong>Rest API</strong>란 <strong>URL의 설계 방식</strong>을 의미한다.
주소와 메서드만 보고 요청의 내용을 파악할 수 있다는 강력한 장점이 있어 많은 개발자들이 사용한다고 한다.</p>
<p>그러면 Rest API는 어떻게 사용할까?</p>
<h4 id="1-url에는-동사를-쓰지-말고-자원을-표시해야-한다">1. URL에는 동사를 쓰지 말고, 자원을 표시해야 한다.</h4>
<p>예를 들어서 게시판의 내용을 리턴해주는 API를 만든다고 해보자.
/getBoard?Id=1
/board?id=1
물론, 둘 다 사용해도 되지만, 어떤 개발자는 getBoard가 아닌 showBoard와 같이 사용한다면 API는 엉망이 될 것이다. 그래서 Restful API를 설계할 때는 이런 동사를 사용하지 않는다.</p>
<h4 id="2-동사는-http-메서드로">2. 동사는 HTTP 메서드로</h4>
<p>HTTP 메서드란 서버에 요청하는 방법을 나눈 것이다. 주로 사용하는 것들로는 GET, POST, PUT, DELETE 방식이 있다. 각각 POST-<strong>C</strong>REATE, GET-<strong>R</strong>EAD, PUT-<strong>U</strong>PDATE, DELETE-<strong>D</strong>ELETE의 역할을 하는데 이것을 <strong>CRUD</strong> 라고 부른다.</p>
<p>특정 게시글을 가져오려면 
GET /board/1</p>
<p>특정 게시글을 생성하려면
POST /board/1</p>
<p>특정 게시글을 수정하려면
PUT /board/1</p>
<p>특정 게시글을 삭제하려면
DELETE /board/1</p>
<p>과 같이 사용하면 된다.</p>
<p>위 규칙들을 지켜가며 게시판 API를 앞으로 개발해 나가 보겠다.</p>
]]></description>
        </item>
    </channel>
</rss>