<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hoit_98.log</title>
        <link>https://velog.io/</link>
        <description>반가워_! 세상아!</description>
        <lastBuildDate>Thu, 09 Sep 2021 17:06:28 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hoit_98.log</title>
            <url>https://images.velog.io/images/hoit_98/profile/b44386b7-4d2b-4732-9f94-80e3cc7fa92a/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hoit_98.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hoit_98" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[코딩테스트] 메뉴 리뉴얼(JAVA)]]></title>
            <link>https://velog.io/@hoit_98/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%A9%94%EB%89%B4-%EB%A6%AC%EB%89%B4%EC%96%BCJAVA</link>
            <guid>https://velog.io/@hoit_98/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%A9%94%EB%89%B4-%EB%A6%AC%EB%89%B4%EC%96%BCJAVA</guid>
            <pubDate>Thu, 09 Sep 2021 17:06:28 GMT</pubDate>
            <description><![CDATA[<h4 id="문제링크--2021-kakao-blind-recruitment----메뉴리뉴얼">문제링크 : <a href="https://programmers.co.kr/learn/courses/30/lessons/72411">2021 KAKAO BLIND RECRUITMENT -  메뉴리뉴얼</a></h4>
<h4 id="아이디어">아이디어</h4>
<p>i번 손님이 주문한 단품메뉴조합(orders[i])에서 2개 메뉴로 구성할 수 있는 가짓수는 nC2개 라고 할 수 있다.
각 손님의 nC2개 경우의 수를 모두 구하고, 가장 자주 나온 경우를 구하면 가장 많이 함께 주문한 단품메뉴들을 구할 수 있다.</p>
<ol>
<li>R = 각 course, 메뉴 구성 개수이다. </li>
<li>각 orders(String이다.) 에서 R개를 뽑아 새로운 subString을 만든다.</li>
<li>모든 orders에 대해 subString을 구했을 때, 가장 많이 나온 subString이 답이 된다.</li>
<li>각 R에 대해 모두 진행한다.</li>
</ol>
<h4 id="풀이코드">풀이코드</h4>
<pre><code class="language-java">import java.util.*;
class Solution {
    static int maxVal ;
    static Map&lt;String,Integer&gt; map = new HashMap&lt;&gt;();

    public String[] solution(String[] orders, int[] course) {

         ArrayList&lt;String&gt; result = new ArrayList&lt;&gt;();

    //1. 전체 orders 알파벳 오름차순으로 정렬
        for(int i=0;i&lt;orders.length;i++){
            char[] chars = orders[i].toCharArray();
            Arrays.sort(chars);
            orders[i] = String.valueOf(chars);
        }
    // 2. 전체 course(R번)
        for(int j=0;j&lt;course.length;j++){
            maxVal=1;
            map.clear();
            // 각 orders 에서 R개 뽑은 경우의 수 찾기
            for(int i=0;i&lt;orders.length;i++){
                char[] chars = orders[i].toCharArray();
                boolean[] visit = new boolean[chars.length];
                comb1(chars,visit,0,chars.length,course[j]);
            }
            //주문이 2번이상인 경우에만
            if(maxVal&gt;1){
                for(Map.Entry&lt;String,Integer&gt; entry : map.entrySet()){
               //최댓값을 결과 list에 저장
                    if(entry.getValue() == maxVal){
                        result.add(entry.getKey());
                    }
                }
            }

        }


        String[] answer = new String[result.size()];
        for(int k=0;k&lt;result.size();k++){
            answer[k] = result.get(k);
        }
        Arrays.sort(answer); //결과 list sortting
        return answer;
    }


//nCr 구하기
    void comb1(char[] arr, boolean[] visited, int start, int n, int r) {
        if(r == 0) {
            //arr에서 현재 visit에 해당하는 부분을 조합.
            makeStr(arr,visited);
            return;
        } else {
            for(int i = start; i &lt; n; i++) {
                visited[i] = true;
                comb1(arr, visited, i + 1, n, r - 1);
                visited[i] = false;
            }
        }
    }

     void makeStr(char[] arr, boolean[] visit){
        String newStr=&quot;&quot;;
        for(int i=0;i&lt;arr.length;i++){
            if(visit[i]==true){
                newStr+=arr[i];
            }
        }
    //새로 만든 조합이 이미 map에 있을경우
        if(map.containsKey(newStr)){
            int current = map.get(newStr);
            map.put(newStr,current+1);
            if(current+1&gt;maxVal) //maxVal 저장하기.
                maxVal= current+1;
        }else{
            map.put(newStr,1);
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[SOP와 CORS ]]></title>
            <link>https://velog.io/@hoit_98/SOP%EC%99%80-CORS</link>
            <guid>https://velog.io/@hoit_98/SOP%EC%99%80-CORS</guid>
            <pubDate>Mon, 06 Sep 2021 09:45:34 GMT</pubDate>
            <description><![CDATA[<p>cors 에러는 웹개발에서 계속 발생하고, 곤란하게만든다. 무슨이유로 에러가 발생하는가. 어떻게 해결할 것인가?</p>
<hr>
<h3 id="sop란">SOP란</h3>
<p>CORS 에러는 SOP라는 브라우저의 원칙으로 인해 발생한다.
SOP는 <strong>Same Origin Policy</strong>로, 동일 출처가 아닌 경우 발생한다.
여기서 동일 출처는 프로토콜,호스트,포트가 동일한 경우이다.</p>
<h3 id="sop가-필요한-이유">SOP가 필요한 이유</h3>
<p>그렇다면 동일 출처가 아닌 경우 브라우저가 접근을 차단하는 이유는 뭘까?</p>
<p><img src="https://images.velog.io/images/hoit_98/post/aa955fd5-e428-4bca-941d-2b59e53c5752/image.png" alt="">
사용자가 웹사이트에 접근할 때는 브라우저의 쿠키에 로그인 세션 토큰을 남기게 된다.
해당 토큰을 사용해 사용자는 매번 로그인할 필요없이 서비스를 이용할 수 있다.
웹사이트는 토큰을 받아 파싱, 해석하여 로그인되었음을 인지한다.</p>
<p><img src="https://images.velog.io/images/hoit_98/post/8211c3fb-15b0-49d8-932d-0c86e52d994c/image.png" alt="">
그러나 로그인 토큰이 남아있는 브라우저에서, 
사용자가 악성사이트에 접속한 경우, 악성사이트는 사용자 브라우저의 토큰에 접근할 수 있다.
악성사이트가 이 토큰으로 네이버와 같은 웹사이트에 로그인을 요청할 경우, 사용자는 악의적인 상황에 놓일 수 있다.(탈탈,, 털려)</p>
<p><img src="https://images.velog.io/images/hoit_98/post/35951ca9-74f6-4fd7-9bb5-6b6b5b843943/image.png" alt=""></p>
<p>이런 상황에서 브라우저는 SOP에 따라 이런 악의적 상황을 막습니다.
브라우저는 악성 사이트의 요청서버와, 요청받고 응답을 주는 응답서버의 출처가 다른걸 보고 CORS에러를 내립니다.</p>
<p>웹사이트 서버에서 악성사이트를 허가해주지 않는 이상, 악성사이트는 CORS에러로 정상적으로 로그인할 수 없습니다.</p>
<p>따라서 사용자가 보는 피해를 방지할 수 있습니다.</p>
<ul>
<li>간단 정리<pre><code>👿 악의적인 상황</code></pre></li>
</ul>
<ol>
<li>내가 A 사이트에 로그인하면 브라우저에 ID등 세션정보(토큰)를 쿠키에 저장함</li>
<li>그 상황에서 우연히 특정 악성 사이트에 접근하게된다.</li>
<li>악성사이트는 브라우저에게 시켜 내 쿠키를 읽어 토큰을 갈취함</li>
<li>악성사이트에서 A 사이트에 내 세션 토큰으로 요청해 로그인하고 나쁜짓함 </li>
</ol>
<p>😇 브라우저의 SOP에 의한 해결</p>
<ol>
<li>악성사이트에 대한 A사이트의 응답을 분석한다.</li>
<li>두 사이트의 출처가 다르다면 악성사이트에게 CORS 에러 내림</li>
<li>악성사이트는 나쁜짓 못한다.<pre><code></code></pre></li>
</ol>
<h3 id="cors란">CORS란</h3>
<p>CORS란 Cross origin resource sharing으로,
이런 sop로 발생하는 에러를 해결해주는 일종의 방법입니다.</p>
<p>sop로 사용자를 보호할 수 있지만,
서버는 점차 확장되고, 때로는 다른 회사의 서버부를 이용해야(API 사용 등등) 하는 상황이 자주 발생합니다.
따라서 조건에 따라 이런 다른 출처를 허가해줘야 합니다.</p>
<h3 id="cors의-흐름">CORS의 흐름</h3>
<h4 id="1-단순요청">1. 단순요청</h4>
<p>요청만으로 서버에 영향을 주지 않는 단순요청의 경우,</p>
<ol>
<li>서버에게 본요청을 전달한다.</li>
<li>서버가 OK랑 Access-Control-Allow-Origin를 보낸 경우 응답을 전달한다.</li>
<li>Access-Control-Allow-Origin이 안오면 본요청이 fail되고 브라우저는 CORS에러를 발생시킨다.
<img src="https://images.velog.io/images/hoit_98/post/0bdf0ad6-e83c-4a9d-b8b1-2f957644b625/image.png" alt=""></li>
</ol>
<h4 id="2-예비요청이-있는-경우">2. 예비요청이 있는 경우</h4>
<p>단순요청이 아닌경우, 예비요청을 통해 허가를 받은 후, 본 요청을 할 수 있습니다.</p>
<ol>
<li>OPTIONS 메소드로 Origin(호스트 uri)을 실어서 보낸다. (예비 요청)</li>
<li>서버가 요청을 허가할 경우 OK랑 Access-Control-Allow-Origin을 보낸다.</li>
<li>브라우저는 서버의 허가를 확인 후 본 요청을 보내게 된다.
<img src="https://images.velog.io/images/hoit_98/post/0cb3f38f-fdc8-44f8-b15d-4076dca2b06a/image.png" alt=""></li>
</ol>
<h3 id="cors의-해결">CORS의 해결</h3>
<p>CORS는 크게 두 부분으로 해결할 수 있습니다.</p>
<ol>
<li>프론트엔드의 웹서버에서 요청을 받아서 백엔드서버로 요청을 보내는 것</li>
<li>백엔드 서버에서 자체적으로 요청을 허가해주는 것</li>
</ol>
<h4 id="1-프론트엔드에서-프록시-이용">1. 프론트엔드에서 프록시 이용</h4>
<p>우선 cors에러는 브라우저를 통과하며 발생합니다.
따라서 온전한 서버끼리의 통신에서는 발생하지 않습니다.
프론트엔드 단의 웹서버에서 요청을 프록시 형태로 감싸 백엔드 서버로 바로 요청할 경우, 에러가 발생하지 않습니다.</p>
<h4 id="2-백엔드-서버에서-허가">2. 백엔드 서버에서 허가</h4>
<p>백엔드 서버에서 Access-Control-Allow-Origin 응답을 설정하게 하여, 임의로 허가를 내려줄 수 있습니다.</p>
<ul>
<li>스프링 서버 전역적으로 CORS 설정</li>
</ul>
<pre><code class="language-java">static class AppConfig implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping(&quot;/api/**&quot;)
                    .allowedMethods(&quot;GET&quot;, &quot;POST&quot;) //메소드 지정 가능
                    .allowedOrigins(&quot;*&quot;); //와일드 카드: 전체 다 허락
        }
    }   
</code></pre>
<ul>
<li>특정 컨트롤러나 메소드에 개별적 CORS 설정</li>
</ul>
<pre><code class="language-java">@Controller
//특정 컨트롤러에만 CORS 적용하고 싶을때.
@CrossOrigin(origins = &quot;*&quot;, methods = RequestMethod.GET) 
public class customController {

    @GetMapping(&quot;/url&quot;)  
    //특정 메소드에만 CORS 적용 가능
    @CrossOrigin(origins = &quot;*&quot;, methods = RequestMethod.GET) 
    @ResponseBody
    public List&lt; &gt; findAll(){
        return service.getAll();
    }

}</code></pre>
<h3 id="참고자료">참고자료</h3>
<ol>
<li><p><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/CORS">CORS 흐름 관련 자료</a></p>
</li>
<li><p><a href="https://evan-moon.github.io/2020/05/21/about-cors/">더 자세한 CORS 해결 방법</a></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210902]]></title>
            <link>https://velog.io/@hoit_98/TIL</link>
            <guid>https://velog.io/@hoit_98/TIL</guid>
            <pubDate>Fri, 03 Sep 2021 06:46:11 GMT</pubDate>
            <description><![CDATA[<h3 id="spasingle-page-application">SPA(Single Page Application)</h3>
<p>화면에 그리는 내용을 서버가 아닌 브라우저에서 처리하는 방식.</p>
<ul>
<li>SPA의 진행방식
사용자의 클릭 
-&gt; 화면에 그려져야할 html 을 한번만 로딩
-&gt; ajax를 이용해서 back단에서 서버에 http 요청을 보냄
-&gt; 서버가 json으로 응답
-&gt; 클라가 받아서 처리(DOM 조작을 통해 렌더링)
서버는 확실히 프론트와 더 분리가 가능해짐.</li>
</ul>
<hr>
<p>HTTP (stateless 때문에)
사용자 정보를 알리기 위해 쿠키에 sessionId를 함께 실어 보냄.
세션은 서버에서 관리하고,  쿠키로 세션 정보를 주고 받음</p>
<ul>
<li>SPA<ul>
<li>세션에 메뉴정보나, 설정 정보를 담으면 세션이 점점 커진다.<ul>
<li>이런 정보(사용자 데이터 모델의 일부)는 브라우저의 local storage에 담는다.</li>
<li>그래서 정보 변경등 필요할때 데이터를 요청이 가능하다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>서버사이드 라우팅</th>
<th>클라이언트 라우팅</th>
</tr>
</thead>
<tbody><tr>
<td>요청받는 URL에 따라 리소스 반환</td>
<td>URL의 변화에 따라 화면상태를 변경</td>
</tr>
<tr>
<td>URL 받아 웹 페이지를 반환</td>
<td></td>
</tr>
<tr>
<td>리다이렉트시 서버요청 있음</td>
<td>서버요청 없음</td>
</tr>
</tbody></table>
<h3 id="corscross-origin-resource-sharing">CORS(Cross Origin Resource Sharing)</h3>
<p><strong>1. 상황</strong></p>
<pre><code>하나의 web application은 여러개의 호스트와 다양한 리소스에 접근하게됨.
사용자를 악의적인 행위에서 보호하기 위해 
브라우저는 동일 출처에서만 리소스 접근을 허용하도록 함.
따라서 다양한 리소스에 접근하게 해주는게 CORS</code></pre><pre><code>👿 악의적인 상황이란?

1. 내가 A 사이트에 로그인하면 브라우저에 ID등 세션정보(토큰)를 쿠키에 저장함
2. 그 상황에서 우연히 특정 악성 사이트에 접근하게된다.
3. 악성사이트는 브라우저에게 시켜 내 쿠키를 읽어 토큰을 갈취함
4. 악성사이트에서 A 사이트에 내 세션 토큰으로 요청해 로그인하고 나쁜짓함 </code></pre><hr>
<p><strong>2. SOP와 CORS</strong></p>
<ul>
<li><p>Same Origin(동일한 출처): <strong>프로토콜, 호스트,포트가 동일</strong>한 경우.</p>
</li>
<li><p>동일출처가 아닐 때 에러발생
-&gt; SOP(same Origin Policy)가 막음.
-&gt; CORS가 이를 해결</p>
</li>
<li><p>CORS: http 헤더를 이용해서 application의 다른 출처 리소스에 접근하게 하는 메커니즘.
-&gt; 어떤 기준을 충족시키면 리소스 공유가 되도록 함.</p>
</li>
</ul>
<hr>
<p><strong>3.CORS 흐름</strong></p>
<ul>
<li><p>예비요청(preflight)을 보내냐에 따라 CORS 흐름이 달라짐.</p>
</li>
<li><p>단순요청이 아닐때는 예비요청을 보냄</p>
</li>
<li><p>단순요청의 경우: <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/CORS#%EB%8B%A8%EC%88%9C_%EC%9A%94%EC%B2%ADsimple_requests">단순요청의 기준들</a>을 모두 만족하는 경우</p>
</li>
<li><p>예비요청이 있는 경우</p>
<ul>
<li>OPTION 메소드로 보내고<ul>
<li>Origin(호스트 uri)을 실어서 보냄</li>
<li>서버가 Origin의 요청을 허가할 경우 답변으로 OK랑 Access-Control-Allow-Origin: (어쩌구)를 보낸다</li>
<li>허용을 확인후 본 요청을 보내게 된다.</li>
</ul>
</li>
</ul>
</li>
<li><p>예비요청이 없는 경우</p>
<ul>
<li>단순요청(본요청)을 보낸다.<ul>
<li>서버가 OK랑 Access-Control-Allow-Origin를 보낸다</li>
<li>Access-Control-Allow-Origin이 안오면 본요청이 fail된다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<p><strong>4. CORS 지원방식</strong></p>
<p><strong>CORS 방식 1. 프론트엔드의 프록시 이용</strong></p>
<ul>
<li>url에 대한 요청을 프론트엔드 웹서버에 보내고</li>
<li>웹서버가 요청을 정제해 back단에서 백엔드 스프링서버에 http 요청을 보내고 받아 처리한다.</li>
</ul>
<p><strong>CORS 방식 2. 스프링 서버 전역적으로 CORS 설정</strong></p>
<pre><code class="language-java">static class AppConfig implements WebMvcConfigurer, ApplicationContextAware {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping(&quot;/api/**&quot;)
                    .allowedMethods(&quot;GET&quot;, &quot;POST&quot;) //메소드 지정 가능
                    .allowedOrigins(&quot;*&quot;); //전체 다 허락
        }
    }   
</code></pre>
<p><strong>CORS 방식 3. 특정 컨트롤러나 메소드에 개별적 CORS 설정</strong></p>
<pre><code class="language-java">
@Controller
//특정 컨트롤러에만 CORS 적용하고 싶을때.
@CrossOrigin(origins = &quot;*&quot;, methods = RequestMethod.GET) 
public class customController {

    @GetMapping(&quot;/url&quot;)  
    //특정 메소드에만 CORS 적용 가능
    @CrossOrigin(origins = &quot;*&quot;, methods = RequestMethod.GET) 
    @ResponseBody
    public List&lt; &gt; findAll(){
        return service.getAll();
    }

}</code></pre>
<p>** 도메인과 DTO</p>
<ul>
<li>http 리퀘스트가 안받아진다고 도메인을 건들지 말것.</li>
<li>도메인은 도메인의 룰을 유지 해줘야함.</li>
<li>request를 처리할 DTO를 만들어서 처리해주자.</li>
<li>비즈니스 도메인의 룰(validation 등)은 모두 도메인 클래스에 넣을것</li>
<li>DTO의 validation 룰은 컨트롤러 요청에 맞춰 정한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210901]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210901</link>
            <guid>https://velog.io/@hoit_98/TIL-20210901</guid>
            <pubDate>Thu, 02 Sep 2021 02:20:13 GMT</pubDate>
            <description><![CDATA[<h3 id="1spring-mvc---form">1.Spring MVC - Form</h3>
<ol>
<li><p>form 담긴 html 파일</p>
<pre><code class="language-html">&lt;-- 1. POST로 해당 url에 요청 --&gt;
&lt;form th:action=&quot;@{/customers/new}&quot; method=&quot;post&quot;&gt; 
     &lt;div class=&quot;mb-3&quot;&gt;  
         &lt;label for=&quot;exampleInputEmail1&quot; class=&quot;form-label&quot;&gt;Email address&lt;/label&gt;
       &lt;-- 2. input name을 설정해줄 것. --&gt;
         &lt;input type=&quot;email&quot; name=&quot;email&quot; class=&quot;form-control&quot; id=&quot;exampleInputEmail1&quot; aria-describedby=&quot;emailHelp&quot;&gt;
     &lt;/div&gt;
     &lt;div class=&quot;mb-3&quot;&gt;
         &lt;label for=&quot;exampleInputPassword1&quot; class=&quot;form-label&quot;&gt;Name&lt;/label&gt;
         &lt;input type=&quot;text&quot; name=&quot;name&quot; class=&quot;form-control&quot; id=&quot;exampleInputPassword1&quot;&gt;
     &lt;/div&gt;
     &lt;div class=&quot;mb-3 form-check&quot;&gt;
         &lt;input type=&quot;checkbox&quot; class=&quot;form-check-input&quot; id=&quot;exampleCheck1&quot;&gt;
         &lt;label class=&quot;form-check-label&quot; for=&quot;exampleCheck1&quot;&gt;Check me out&lt;/label&gt;
     &lt;/div&gt;
     &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;Submit&lt;/button&gt;
 &lt;/form&gt;</code></pre>
</li>
<li><p>Controller에 POST 핸들러 작성해주기</p>
<pre><code class="language-java"> @PostMapping(&quot;/customers/new&quot;)
 public String addNewCustomer(CreateCustomerRequest createCustomerRequest){
     customerService.createCustomer(createCustomerRequest.email(), createCustomerRequest.name());
     return &quot;redirect:/customers&quot;;
 }</code></pre>
</li>
</ol>
<p>CreateCustomerRequest에서는 1번의 폼에서 전달해준 input의 name에 해당하는 요소를 받아 처리한다.    </p>
<pre><code class="language-java">public record CreateCustomerRequest(String email, String name) {
}</code></pre>
<ul>
<li><p>파라미터를 url로 전달했을 때</p>
<ul>
<li><p>@PathVariable(&quot;custoemrId&quot;) : path에 있는 변수 찾아 매핑해줌. </p>
<pre><code class="language-java">@GetMapping(&quot;/customers/{customerId}&quot;)
public String findCustomer(@PathVariable(&quot;custoemrId&quot;) UUID customerId, Model model){
  var maybeCustomer = customerService.getCustomer(customerId);
  if(maybeCustomer.isPresent()){
      model.addAttribute(&quot;customer&quot;,maybeCustomer.get());
      return &quot;views/customer-details&quot;;
  }else{
      return &quot;views/404&quot;;
  }

}</code></pre>
</li>
</ul>
</li>
<li><p>정리</p>
<ul>
<li>컨트롤러에서 DTO(CreateCustomerRequest 같은)를 통해 데이터를 받아온다.</li>
<li>validation,http 확인 등의 작업을 수행한다.</li>
<li>그 후 받아온 데이터를 실제 로직은 서비스와 entity에서 하도록 넘겨준다.</li>
</ul>
</li>
</ul>
<h3 id="2-webapplicationcontext">2. WebApplicationContext</h3>
<ol>
<li><p>여러 서블릿이 공유가 가능한 내용을 Servlet Context에 저장
모든 서블릿들이 접근 가능한 루트 서블릿이 서블릿
WebApplicationContext: ApplicationContext를 상속받음. 거기에 Servlet Context접근하는 기능이 추가 되어 있음.</p>
<p>Servlet Context: 여러 서블릿이 공유가 가능한 정보를 담고 있음.</p>
<ul>
<li>여러 dispatcher servlet에서도 접근이 가능함.</li>
<li>여러 dispatcher servlet이 만들어지면 여러 WebApplicationContext이 만들어짐.</li>
<li>각 Context는 어떤 관계를 가지는가? 모든 context에 접근 가능한 Bean이 없을까?</li>
<li><blockquote>
<p>모든 ApplicationContext들이 접근 가능한 root ApplicationContext이 필요함.</p>
</blockquote>
</li>
</ul>
<p>root ApplicationContext</p>
<ul>
<li>Servlet Context가 만들어질때 root ApplicationContext가 생김.</li>
<li>Servlet Context에 attribute로 들어가있음.</li>
<li>그 후 생겨난 servlet들은 Servlet Context에 접근해 root ApplicationContext를 사용하게됨.</li>
</ul>
</li>
</ol>
<ul>
<li><p>로더, 리스너 내용</p>
</li>
<li><p>서블릿 어플리케이션 구조부분</p>
</li>
</ul>
<ol start="2">
<li>실습</li>
</ol>
<ul>
<li><p>처음에 Dispatcher Servelet의 WebApplicationContext의 parent는 없다.</p>
</li>
<li><p>모든 빈들이 WebApplicationContext 컨테이너에 다 등록되어 있다.</p>
</li>
<li><blockquote>
<p>root ApplicationContext을 만들고, 거기에 서비스랑 데이터 access 관련된 빈들을 등록하고 관리할것임.</p>
</blockquote>
</li>
<li><blockquote>
<p>dispatcher servelet 의 WebApplicationContext에는 mvc 관련된 빈만 등록하도록 하고 루트랑 부모 자식으로 연결되도록.</p>
</blockquote>
</li>
</ul>
<pre><code class="language-java">
@EnableWebMvc
    @Configuration

    //컨트롤러에 해당하는 빈만 스캔하겠다.
    @ComponentScan(basePackages = &quot;org.prgrms.kdt.customer&quot;,
            includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CustomerController.class),
            useDefaultFilters = false

    )
    static class AppConfig implements WebMvcConfigurer, ApplicationContextAware {
          //스프링의 app context
        ApplicationContext applicationContext;
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            registry.jsp().viewNames(&quot;jsp/*&quot;);          
        }

        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler(&quot;/resources/**&quot;)
                    .addResourceLocations(&quot;/resources/&quot;);
        }
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }


    //컨트롤러 제외하고 다른 빈들(서비스, 데이터관련)만 스캔하겠다.
    @Configuration
    @ComponentScan(basePackages = &quot;org.prgrms.kdt.customer&quot;,
            excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CustomerController.class)
    )
    @EnableTransactionManagement
    static class RootConfig{
        @Bean
        public DataSource dataSource(){
          return  DataSourceBuilder.create().build();
        }
        //그 외 트랜잭션 및 jdbcTemplate 관련 Bean 설정
    }

     @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //1.RootConfig로 root컨텍스트 만들어주기
        var rootApplicationContext = new AnnotationConfigWebApplicationContext();
        rootApplicationContext.register(RootConfig.class);
        //2. 로드 리스너 설정해주기 -&gt; 부모 자식 연결해줌
        var loaderListener =new ContextLoaderListener(rootApplicationContext);
        servletContext.addListener(loaderListener);

    //3. DispatcherServlet은 AppConfig로 만들어주기
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(AppConfig.class);
        var dispatcherServlet= new DispatcherServlet(applicationContext);

        logger.info(&quot;Starting Server...&quot;);
        ServletRegistration.Dynamic servletRegistration = servletContext.addServlet(&quot;test&quot;, dispatcherServlet);
        servletRegistration.addMapping(&quot;/&quot;);
        servletRegistration.setLoadOnStartup(1);
    }</code></pre>
<ol start="3">
<li>setLoadOnStartup</li>
</ol>
<table>
<thead>
<tr>
<th>CRUD 명</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>0 이상</td>
<td>ServletContext 초기화 될때 </br>ServletContextListener 호출해서 하위 인스턴스 다 생성</td>
</tr>
<tr>
<td>-1</td>
<td>ServletContext 초기화, </br> 이후에 사용될때 각 서블릿 인스턴스 생성하고 초기화 진행</td>
</tr>
</tbody></table>
<p>디폴트 값이 -1로 정의되어 있음.</p>
<h3 id="3-rest-api">3. REST API</h3>
<ul>
<li><p>REST(Representational Transfer): 웹 상의 자료를 HTTP위에서 SOAP이나 쿠키를 통한 세션 트랙킹 같은
별도의 전송 계층 없이 전송하기 위한 아주 간단한 인터페이스</p>
</li>
<li><p>API(application programming interface) : </p>
</li>
</ul>
<p>REST API : REST 아키텍쳐 스타일을 따르는 API</p>
<p>REST 아키텍쳐 스타일
균일한 인터페이스 : URI로 지정한 리소스에 대한 조작을 통일되고 한정적인 인터페이스로 수행하는 아키텍처 스타일</p>
<p>SOAP : 단 하나의 url에 요청을 xml에 기술하고 전달</p>
<p>LV 1: 하나의 리소스에 대해 다양한 형태로 표현이 가능하다
LV 2: http 메서드(get,post,put,delete)를 도입하는 것
LV 3: HATEOAS(Hypermedia as the Engine of Application State)</p>
<ul>
<li>리소스와 함께 link에 해당 리소스로 할 수 있는 행위들을 제공</li>
</ul>
<p>API 설계</p>
<ul>
<li>URI는 자원을 표현, 명사를 사용</li>
<li>자원을 가지고 할 행위는 http 메서드를 사용</li>
<li>슬래시는 계층관계를 나타냄</li>
<li>URI 마지막에 슬래시를 포함하지 않기</li>
<li>하이픈으로 URI 가독성을 높이기</li>
</ul>
<hr>
<p>실습
스프링에서 레스트api 개발을 위해 annotation 제공</p>
<ol>
<li>RequestBody 전달받은 요청 메시지를 원하는 형태로 변환.</li>
<li>ResponseBody 우리가 결과낸 모델클래스가 response로 변환.</li>
<li>RestController Controller + ResponseBody
RestController를 적용하면 하위 모든 메서드에 respnse Body가 생긴다.</li>
</ol>
<p>Dispatch Servlet의 요청을 받아
핸들러매핑으로 핸들러 찾고, 핸들러 어댑터가 argument Resolver를 사용해서 적절히 변형해 핸들러에 전달
@RequestBody,@ResponseBody, HttpEntity를 리턴할경우, HTTP 메시지 컨버터가 동작해 결과 모델을 HTTP 메시지로 변환한다.</p>
<ol>
<li>설정하지 않으면 디폴트 메시지 컨버터가 작동해 JSON으로 변환해 출력</li>
<li>initializer에서 디폴트 메시지 컨버터를 아예 변경<pre><code class="language-java">public void configureMessageConverters(List&lt;HttpMessageConverter&lt;?&gt;&gt; converters) {
         //디폴트 메시지 컨버터가 싹 바뀐다.
         var messageConverter = new MarshallingHttpMessageConverter();
         var xStreamMarshaller = new XStreamMarshaller();
         messageConverter.setMarshaller(xStreamMarshaller);
         messageConverter.setUnmarshaller(xStreamMarshaller);
         converters.add(messageConverter);
     }</code></pre>
</li>
<li>initializer에서 디폴트 메시지 컨버터를 경우에 따라 다르도록 확장</li>
</ol>
<pre><code class="language-java">    @Override
        public void extendMessageConverters(List&lt;HttpMessageConverter&lt;?&gt;&gt; converters) {
            //디폴트 메시지 컨버터에서 확장하는 방식으로 사용가능

            //1.xml 방식으로 컨버팅하도록 추가
            var messageConverter = new MarshallingHttpMessageConverter();
            var xStreamMarshaller = new XStreamMarshaller();
            messageConverter.setMarshaller(xStreamMarshaller);
            messageConverter.setUnmarshaller(xStreamMarshaller);
            converters.add(0, messageConverter);


            //2. 날짜의 경우 변환해주는 다른 메시지 컨버터를 추가
            var javaTimeModule = new JavaTimeModule();
            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ISO_DATE_TIME));
            var modules = Jackson2ObjectMapperBuilder.json().modules(javaTimeModule);
            converters.add(1, new MappingJackson2HttpMessageConverter(modules.build()));

        }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210831]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210831-sggffi8b</link>
            <guid>https://velog.io/@hoit_98/TIL-20210831-sggffi8b</guid>
            <pubDate>Wed, 01 Sep 2021 04:55:07 GMT</pubDate>
            <description><![CDATA[<p>8월의 마지막 TIL</p>
<hr>
<h3 id="1-springmvc">1. SpringMVC</h3>
<h4 id="1-dispatch-servlet">1. Dispatch Servlet</h4>
<ul>
<li>하나의 서블렛(front controller)이 모든 요청을 받아서 다른 컨트롤러를 호출시키는 형태<ul>
<li>개발자는 컨트롤러를 작성하고, 스프링 Dispatch Servlet이 작성한 컨트롤러를 호출함.</li>
</ul>
</li>
<li>Dispatcher Servlet은 단하나의 서블릿이 된다.</li>
<li>어떤 컨트롤러를 처리할지, 예외 어떻게 처리할 지 결정하게 된다.<h4 id="2-스프링-mvc-요청-처리과정">2. 스프링 MVC 요청 처리과정</h4>
</li>
</ul>
<ol>
<li>사용자 요청이 들어온다.</li>
<li>Dispatcher Servlet이 요청에 맞는 컨트롤러를 찾아 요청을 위임</li>
<li>해당 컨트롤러는 서비스를 호출하여, 화면에 전달할 모델을 만든다.</li>
<li>컨트롤러는 만든 모델과 뷰를 Dispatcher Servlet에 전달한다.</li>
<li>Dispatcher Servlet은 받은 모델과 뷰로 그에 적합한 컨텐츠를 제작</li>
<li>제작한 컨텐츠를 HTTP 응답으로 사용자에게 보낸다</li>
</ol>
<p>구현방법-----</p>
<ol>
<li>웹 application initializer 할때 Dispatcher Servlet을 등록하여 사용</li>
</ol>
<h4 id="3-핸들러매핑과-viewresolver">3. 핸들러매핑과 ViewResolver</h4>
<ul>
<li><p>핸들러 매핑
Handler Mapping: 요청의 URL 기준으로 어떤 핸들러에게 위임할지 결정</p>
</li>
<li><p>핸들러 호출 시 파라미터 매핑
Handler adapter: 핸들러의 메서드 파라미터에 맞는 정보로 변환해준다.
① Dispatcher Servlet이 모든 HTTP서블릿 리퀘스트 오브젝트를 받고
② 어댑터로 변환해서 적절한 파라미터를 핸들러에게 전달.</p>
</li>
<li><p>ViewResolver
뷰는 어떻게 처리되는가</p>
</li>
</ul>
<p><a href="https://terasolunaorg.github.io/guideline/public_review/Overview/SpringMVCOverview.html"><img src="https://i.loli.net/2019/06/07/5cfa26b5972fb76643.jpg" alt="참고 자료 그림"></a></p>
<ol>
<li>Dispatcher Servlet이 컨트롤러에 GET요청 위임</li>
<li>컨트롤러가 뷰(뷰이름) - 모델을 반환</li>
<li>ViewResolver에서 name에 해당하는 view를 찾아 반환<ul>
<li>InternalResource ViewResolver : 최하단의 ViewResolver, JSP를 처리</li>
</ul>
</li>
</ol>
<hr>
<ul>
<li>전체적인 스프링 MVC 흐름
<a href="https://terasolunaorg.github.io/guideline/public_review/_images/RequestLifecycle.png"><img src="https://terasolunaorg.github.io/guideline/public_review/_images/RequestLifecycle.png" alt="참고 자료 그림"></a></li>
</ul>
<ol>
<li>요청이 오면 Dispatcher Servlet이 받는다</li>
<li>핸들러 매핑에 의해 핸들러(컨트롤러) 선택</li>
<li>핸들러 어댑터가 핸들러에 맞게 파라미터 변환</li>
<li>핸들러가 파라미터 받아 서비스 수행, 결과로 모델과 뷰네임 생성.</li>
<li>view name이 Dispatcher Servlet에 전달된다.</li>
<li>Dispatcher Servlet이 ViewResolver에 뷰네임, 모델을 전달<ul>
<li>ViewResolver가 특정한 View를 찾아줌(JSP 등등)</li>
</ul>
</li>
</ol>
<p>4번을 제외한 과정을 스프링부트가 대신 해준다.</p>
<h3 id="2-springmvc-실습">2. SpringMVC 실습</h3>
<p><del>JSP 거의 쓰지 않는다..! 백엔드 공부할때 JSP에 너무 시간을 쏟지 않기</del></p>
<h4 id="1dispatcherservlet-만들고-설정">1.dispatcherServlet 만들고 설정</h4>
<p>@EnableWebMvc : 스프링 mvc가 필요한 빈들이 자동으로 등록됨 </p>
<pre><code class="language-java">
    @EnableWebMvc
    @Configuration
    @ComponentScan(basePackages = &quot;org.prgrms.kdt.customer&quot;)
    @EnableTransactionManagement
    static class AppConfig implements WebMvcConfigurer {
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            registry.jsp(); //리퀘스트.jsp 로 연결!
        }
}
@Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(AppConfig.class);
        var dispatcherServlet= new DispatcherServlet(applicationContext);


        logger.info(&quot;Starting Server...&quot;);
        ServletRegistration.Dynamic servletRegistration = servletContext.addServlet(&quot;test&quot;, dispatcherServlet);
        servletRegistration.addMapping(&quot;/*&quot;);
        servletRegistration.setLoadOnStartup(1);
    }</code></pre>
<h4 id="2-jsp-파일-설정하기">2. jsp 파일 설정하기</h4>
<pre><code class="language-html">&lt;c:forEach var=&quot;i&quot; begin=&quot;1&quot; end=&quot;10&quot; step=&quot;1&quot;&gt;${i}&lt;br&gt;&lt;/c:forEach&gt;


&lt;c:forEach var=&quot;customer&quot; items=&quot;${customers}&quot;&gt;${customer.customerId}&lt;br&gt;&lt;/c:forEach&gt;


 &lt;table class=&quot;table&quot;&gt;
        &lt;thead&gt;
        &lt;tr&gt;
            &lt;th scope=&quot;col&quot;&gt;ID&lt;/th&gt;
            &lt;th scope=&quot;col&quot;&gt;Name&lt;/th&gt;
            &lt;th scope=&quot;col&quot;&gt;Email&lt;/th&gt;
            &lt;th scope=&quot;col&quot;&gt;CreatedAt&lt;/th&gt;
            &lt;th scope=&quot;col&quot;&gt;LastLoginAt&lt;/th&gt;
        &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;
    &lt;c:forEach var=&quot;customer&quot; items=&quot;${customers}&quot;&gt;${customer.customerId}
        &lt;tr&gt;
            &lt;th scope=&quot;row&quot;&gt;${customer.customerId}&lt;/th&gt;
            &lt;td&gt;${customer.name}&lt;/td&gt;
            &lt;td&gt;${customer.email}&lt;/td&gt;
            &lt;td&gt;${customer.createdAt}&lt;/td&gt;
            &lt;td&gt;${customer.lastLoginAt}&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/c:forEach&gt;
        &lt;/tbody&gt;
    &lt;/table&gt;</code></pre>
<h4 id="3-정적리소스-처리">3. 정적리소스 처리</h4>
<p>톰캣의 web.xml에 있는디폴트 서블릿을 통해 리소스를 가져오게 된다.</p>
<pre><code class="language-java"> static class AppConfig implements WebMvcConfigurer {
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //   &#39; /resources/** &#39; 에 관한 리소스에 대한 요청이 오면 
            registry.addResourceHandler(&quot;/resources/**&quot;)

                    .addResourceLocations(&quot;/resources/&quot;);
                     //   &#39; /resources/ &#39; 의 위치에서 리소스를 찾는다.
                    .setCachePeriod(60) //캐시설정
                    .resourceChain(true) //리소스 체인 걸기
                    .addResolver(new EncodedResourceResolver());//gzip으로 압축된 리소스 먼저 찾음
        }
 }</code></pre>
<ul>
<li>요청부분 예시<pre><code class="language-jsp">&lt;img src=&quot;&lt;c:url value=&quot;/resources/bg.jpg&quot;/&gt;&quot; class=&quot;img-fluid&quot;&gt;</code></pre>
<h4 id="4-thymeleaf-활용">4. Thymeleaf 활용</h4>
자바 템플릿 엔진이다. HTML로 작성된 파일을 파싱해서 뷰를 제공.</li>
</ul>
<ol>
<li><p>타임 리프추가</p>
<pre><code class="language-xml">&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<pre><code class="language-java">java</code></pre>
</li>
<li><p>사용하기 전에</p>
</li>
<li><p>문법</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210831]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210831</link>
            <guid>https://velog.io/@hoit_98/TIL-20210831</guid>
            <pubDate>Tue, 31 Aug 2021 04:57:05 GMT</pubDate>
            <description><![CDATA[<h3 id="1-웹-기술">1. 웹 기술</h3>
<h4 id="1-웹-구성요소들">1) 웹 구성요소들</h4>
<pre><code> **URI: 웹의 많은 리소스를 식별하기 위한 식별자.**

 상대경로: 내 위치 기준이동 (앞에 슬래시 없음.)
 절대경로: 
 -&gt; 배포될때 리소스 위치가 달라지면서 변경될 수 있다.(찾을수 없을 수 있다.)
 -&gt; URI에 아스키만 사용가능, 인코딩-디코딩 신경써야함.




 https://velog.io/@hoit_98/TIL-20210827

 URI 스케마 : https
 호스트명 : velog.io
 패스: @hoit_98/TIL-20210827</code></pre><hr>
<p><strong>HTTP: 리소스를 접근하고 수정하게 해준다. URI로 조작대상을 지정해서 
Hyper Text Transfer Protocol : 하이퍼 텍스트 문서를 교환하기 위해 만들어진 통신 규약</strong></p>
<p>--  (요청/request) -- &gt;<br>클라이언트    웹 브라우저         OS         호스트서버
&lt; -- (응답/response) --</p>
<ul>
<li><p>주요 특징</p>
<ul>
<li>TCP/IP 기반</li>
<li>요청/응답형 프로토콜</li>
<li>동기형 프로토콜 : 요청이 있으면 반드시 응답이 있는</li>
<li>스테이트리스 : 요청을 보내고 나면 그 요청에 대한 상태는 날아갑니다. 매번 요청을 보낼때마다 상태 정보를 같이 실어서 보내줘야한다.
(예 토큰/ 쿠키 전달)</li>
</ul>
</li>
<li><p>8개의 메서드 가짐
get, post, put, delete, head, options, trace, connect</p>
</li>
</ul>
<p>*
| CRUD 명 | 의미 | 메서드   |
| ------- | ---- | -------- |
| create  | 작성 | post/put |
| read    | 읽기 | get      |
| update  | 갱신 | put      |
| delete  | 삭제 | delete   |</p>
<hr>
<p><strong>HTML(h) : 연결성을 가진 마크업 언어</strong>
hyper Text : 텍스트 안에 링크가 있어 다른정보와 연결성이 있는거.</p>
<p>URI -&gt; 프로토콜 : </p>
<p>웹 특징</p>
<ul>
<li>하이퍼미디어 시스템</li>
<li>분산 시스템 : 복수의 컴퓨터가 처리함. 전세계의 브라우저가 다양한 각서버에 접속하여 처리하는 방식.</li>
</ul>
<h4 id="2-웹-아키텍쳐">2) 웹 아키텍쳐</h4>
<pre><code>아키텍쳐 : 시스템 구성과, 동작원리, 시스템 구성환경을 설명하는 설계도
웹 어플리케이션 아키텍쳐 : 웹 어플을 구성하는 시스템들간의 상호작용과 동작원리들.

백엔드에서 리소스, DB를 읽어 프론트엔드에 reponse 전달
프론트엔드에서 렌더링 시킨 웹브라우저를 사용자에게 제공.</code></pre><p><img src="https://cdn-clekk.nitrocdn.com/tkvYXMZryjYrSVhxKeFTeXElceKUYHeV/assets/static/optimized/rev-ffaf87e/wp-content/uploads/2021/04/Web_Application_Architecture_Diagram__diagram_-1024x569.png" alt=""></p>
<pre><code>   1. DNS: 브라우저에서 들어온 URI 요청에 해당하는 ip를 찾아 제공

   2. Load Balancer: 서버에 들어온 요청을 분산시켜줌(scale out)
   -&gt; 여러대의 컴퓨터, 어떻게 분배시켜줄지(알아서 인스턴스에 분배?)

   3. 웹서버: 자바, 파이썬 등으로 코드 실행, 여러 개의 서버로 분산가능 -&gt; OLTP

   4. DB: jdbc 등을 활용, 데이터베이스의 데이터 제공

   5. 캐싱: 똑같은 정보요청이 빈번할때, 비효율적인 db 접근을 줄이고 캐싱한 정보를 가져오게함.
   -&gt;redis(가벼운 db를 캐싱 서버로 사용)

   6a. Job Queue: 오래걸리는 작업을 넣어둔다.

   6b. Job Server: job queue에서 할일을 전문으로 하는 서버에 압축해놓고 완료되면 웹서버에게 알린다.

   7. 텍스트 서치에 많은 리소스가 쓰이므로 서치용 서비스 분리 가능

   8. 분산된 서버에 해당하는 여러개의 서비스 존재할 수 있음

   9a.Data firehorse: 클라우드나 Data wareHouse의 데이터를 불러오고 저장
   -&gt;kafka: 데이터를 받아서 분배
   9b.Data wareHouse: 분석용이나 따로 사용하려 데이터웨어하우스에 저장

   10.클라우드 저장소 : S3등의 클라우드에 이미지 등을 CDN을 통해 고객이 빠르게 접근할 수 있게 해줌.

   11. CDN : 각 전세계에 캐시서버등을 분산해놓고, 근접한 요청을 처리하도록함.
   -&gt;클라우드 프론트</code></pre><h3 id="2-servlet">2. Servlet</h3>
<ol>
<li><p>웹서버</p>
<p>HTTP 웹서버 : HTTP 프로토콜을 지원하는 서버/ 정적컨텐츠만제공
웹 애플리케이션 서버와 차이 : 동적 컨텐츠를 지원하느냐</p>
<p>WAS</p>
<ul>
<li>db 조회, 비즈니스 로직처리를 하게됨. </li>
<li>자바 코드를 실행해 비즈니스 로직을 처리해 동적 컨텐츠를 처리.</li>
<li>jsp나 서블릿을 구동할수 있다.(동적 처리)</li>
<li>일반적인 웹서버 역할도 수행가능(정적 처리)
만든 웹어플리케이션(.war) -&gt; WAS(톰캣, 제티 등)에 deploy</li>
</ul>
<p>nginx -&gt; 웹서버, 로드밸런싱(돌려막기)
웹서버를 분리해서 기능을 앞단에 해서 클라이언트와 소통,
웹서버 뒤 방화벽 뒤에 WAS 구동시켜 동적인 컨텐츠(인증, 인가 등)를 가져온다.
DB에는 WAS에서만 접근되도록한다
<a href="https://velog.io/@change/WEB%EC%84%9C%EB%B2%84-WAS-%EB%B6%84%EB%A6%AC-%EC%9D%B4%EC%9C%A0">https://velog.io/@change/WEB%EC%84%9C%EB%B2%84-WAS-%EB%B6%84%EB%A6%AC-%EC%9D%B4%EC%9C%A0</a></p>
<hr>
<p>서블릿: 클라이언트의 요청을 받아 서비스단에 필요한 것을 요청.
서버 역할(요청수행, 응답)을 하는 컴포넌트.
서블릿은 스펙에 맞게 웹 애플리케이션을 개발 -&gt; WAS에 돌려 구동</p>
<p>WAS JSP를 사용하여 랜터링 -&gt; 정적컨텐츠 생성</p>
<hr>
<p>MVC 패턴
관심을 모델, 뷰, 컨트롤러로 분리
모델: 데이터처리(POJO로 데이터만 담고 있는다) / JAVA Bean(자바 객체)
뷰: 화면(보여지는 것 처리) / JSP
컨트롤러: 로직(모델-뷰를 연결해 사용자 요청처리) / Filter나 Servlet</p>
</li>
</ol>
<h3 id="3-servlet-lifecycle">3. Servlet LifeCycle</h3>
<p>웹서버 요청 -&gt; WAS에 요청
웹 컨테이너에서 실행하며 여러 쓰레드 생성 -&gt; 서블릿의 서비스를 요청</p>
<p>서블릿 : init 된 후 종료될때 까지 사용
init() -&gt; 각 쓰레드에서 요청된 서블릿의 서비스(do get/ do Post)를 수행 -&gt; destroy</p>
<p>각 리퀘스트에서 
매번 새 서블릿 인스턴스를 생성하는 것(X)
매번 새 쓰레드에서 한 서블릿의 서비스가 요청되는것(O)
-&gt; 따라서 서블릿 안에서 어떤 필드값을 수정, 저장 하면 다른 쓰레드에서도 그 값을 참조할 수 있게됨.
-&gt; 다른요청과 기존 요청과 뒤섞이는 일이 발생한다.
=&gt; !!! 메서드 안에서 변수를 만들고 처리해야 함 !!!</p>
<h3 id="4-servlet-was-실습">4. Servlet, WAS 실습</h3>
<p>*<em>구현한 테스트 서블렛 *</em></p>
<pre><code class="language-java">public class TestServlet extends HttpServlet {
    private static final Logger logger = LoggerFactory.getLogger(TestServlet.class);

    @Override
    public void init() throws ServletException {
        super.init();
        logger.info(&quot;Init Servlet&quot;);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestURI = req.getRequestURI();
        logger.info(&quot;Got request from {}&quot;,requestURI);

        var writer=resp.getWriter();
        writer.println(&quot;hello servlet!!&quot;);
    }
}</code></pre>
<ol>
<li>web.xml 을 이용</li>
</ol>
<pre><code class="language-xml">&lt;web-app xmlns=&quot;http://xmlns.jcp.org/xml/ns/javaee&quot;
         xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
         xsi:schemaLocation=&quot;http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd&quot;
         version=&quot;3.1&quot;&gt;
    &lt;servlet&gt;
        &lt;servlet-name&gt;test&lt;/servlet-name&gt;
        &lt;servlet-class&gt;org.prgrms.kdt.servlet.TestServlet&lt;/servlet-class&gt;
    &lt;/servlet&gt;
    &lt;servlet-mapping&gt;
        &lt;servlet-name&gt;test&lt;/servlet-name&gt;
        &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
    &lt;/servlet-mapping&gt;
&lt;/web-app&gt;</code></pre>
<ol start="2">
<li>annotation을 이용<ul>
<li>구현한 test 서블릿 위에 @WebServlet을 달아준다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">@WebServlet(value = &quot;/*&quot;, loadOnStartup = 1)
public class TestServlet extends HttpServlet {
    private static final Logger logger = LoggerFactory.getLogger(TestServlet.class);
}</code></pre>
<ol start="3">
<li>스프링에서 제공해주는 인터페이스를 구현 <ul>
<li>WebApplicationInitializer</li>
</ul>
</li>
</ol>
<pre><code class="language-java">public class KdtWebApplicationInitializer implements WebApplicationInitializer {
    private static final Logger logger = LoggerFactory.getLogger(KdtWebApplicationInitializer.class);

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //WAS 실행 선행 후행으로 작업해줘야할때 사용할 수 있음.
        logger.info(&quot;Starting Server...&quot;);
        ServletRegistration.Dynamic servletRegistration = servletContext.addServlet(&quot;test&quot;, new TestServlet());
        servletRegistration.addMapping(&quot;/*&quot;);
        servletRegistration.setLoadOnStartup(1);
    }
}</code></pre>
<p>*<em>세 방법 모두 톰캣인 WAS 에 war를 배포하는 방식 *</em></p>
<p><strong>-&gt; 오래된 방식이라 요즘은 내장 톰켓을 사용해 jar로 패키징.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210827]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210827</link>
            <guid>https://velog.io/@hoit_98/TIL-20210827</guid>
            <pubDate>Sat, 28 Aug 2021 15:19:31 GMT</pubDate>
            <description><![CDATA[<h1 id="2w-d4">2W D4</h1>
<h3 id="1-spring의-트랜젝션관리">1. Spring의 트랜젝션관리</h3>
<p>AOP : <strong>CrossCuttingConcern</strong>을 해결해주는 방법.
부가기능 : 로깅, 보안 등 각 레이어에 걸쳐서 적용할 부가적 기능
AOP 
-&gt; 레이어의 핵심기능(관심사)과 부가기능을 분리하게 해줌</p>
<p>각 비즈니스 로직 앞-뒤에 끼어들어갈 부가기능을 어떻게 추가할 수 있을까.</p>
<ul>
<li><p>컴파일시점
AspectJ 같은 AOP 프레임워크가 소스코드를 컴파일 전에 부가기능을 소스에 삽입하는방식</p>
</li>
<li><p>클래스 로딩시점
클래스 로딩할때 바이트 코드에다가 부가기능을 삽입한다.</p>
</li>
<li><p>** 런타임 시점
스프링에서 제공하는 AOP 방식.
사용할 객체의 프록시를 이용해서 프록시가 본 비지니스전에 부가기능을 호출해서 사용하는 방식.**</p>
</li>
</ul>
<p>스프링 AOP 프록시 두가지 방식
-JDK 다이내믹 프록시(인터페이스 기반) :인터페이스를 구현한 객체가 프록시의 타겟이 된다.
-CGLib 프록시(클래스 기반)</p>
<h3 id="2-spring-proxies">2. Spring Proxies</h3>
<p>프록시는 일을 대신 해주는 패턴이었다.</p>
<p>프록시는 클라이언트의 요청을 받아서 적정한 선,후 처리후 타겟을 반환해준다.</p>
<p>즉. 프록시는 타겟을 감싸서 타겟의 요청을 대신 받아준다.</p>
<p><a href="https://sa1341.github.io/2019/05/25/%EC%8A%A4%ED%94%84%EB%A7%81-AOP-%EA%B0%9C%EB%85%90-%EB%B0%8F-Proxy%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EA%B5%AC%EB%8F%99%EC%9B%90%EB%A6%AC/">참고 블로그 - 그림참고</a></p>
<pre><code class="language-java">//JDK 프록시 사용예시 

//타겟 클래스 (실제)
class CalculatorImpl implements Calculator{

    @Override
    public int add(int a, int b) {
        return a+b;
    }
}

interface Calculator{
    int add(int a, int b);
}

//핸들러, 프록시가 할일 정의
class LoggingInvocationHandler implements InvocationHandler {

    private static final Logger log = LoggerFactory.getLogger(LoggingInvocationHandler.class);
    private final Object target;

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

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        //부가 기능(로그) 먼저 실행
        log.info(&quot;{} executed&quot;,method.getName());
        //주요 기능 , 즉 진짜 일을하는 객체를 반환
        return method.invoke(target,objects);
    }
}



public class JdkProxyTest {
       public static void main(String[] args) {

        var calculator = new CalculatorImpl();
        var proxyInstance = (Calculator) Proxy.newProxyInstance(
                LoggingInvocationHandler.class.getClassLoader(),
                new Class[]{Calculator.class},
                new LoggingInvocationHandler(calculator));

        var add = proxyInstance.add(1,2);

    }
}</code></pre>
<h4 id="스프링-aop-사용하기">스프링 aop 사용하기</h4>
<ol>
<li>AOP 디펜던시 추가</li>
</ol>
<pre><code class="language-xml">&lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-aop&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<ol start="2">
<li>aspectJ 어노테이션을 이용해 aop기능을 사용</li>
</ol>
<pre><code>어노테이션을 읽고, 실제 스프링 AOP 방식으로 aspect(뒤에나옴)를 적용하게 해준다.
프록시로 구현되어 런타임에 적용되고
xml은 오래된 방식이라 aspectJ의 어노테이션 방식이 권장됨.</code></pre><p>[추가 조사]</p>
<ul>
<li><p>위빙(뒤에 나옴) 과정에서 프록시객체가 생성된다. </p>
<p>이 프록시 객체가 생성됨으로써 특정 조인포인트에 어드바이스가 적용된다.</p>
</li>
<li><p><a href="https://n1tjrgns.tistory.com/261">참고 블로그</a></p>
</li>
</ul>
<h3 id="3-spring-aop-주요용어">3. Spring AoP 주요용어</h3>
<p>타겟 (핵심기능을 가지고있는 모듈, 부가기능이 적용될 대상)</p>
<p>조인포인트 (부가기능이 적용될 수 있는 수많은 포인트(<strong>메서드</strong>)들 )</p>
<p>포인트 컷(어느 조인포인트를 선택할지 선별하는 정규표현식 : 기준)</p>
<p>에스펙트 : 어드바이스 + 포인트 컷, 부가기능의 set.
-&gt; 부가기능(로깅기능)을 포함한 타겟.
-&gt; 어떤 내용을 어디에 적용할지.
=&gt; 스프링에서는 이 aspect를 빈으로 등록해서 사용</p>
<p>어드바이스(적용될 부가기능의 내용)
:Before, After,Around,After Returning, After Throwing
-&gt;어떤 조인포인트에서, 이 작업을 Before에 할지 언제할지 결정</p>
<p>위빙(부가기능이 적용되는 과정) == 타겟의 조인 포인트에 어드바이즈를 적용하는 과정.
-&gt; 타겟의 특정메소드를 선정해서, 어떠한 방식을 통해, 어드바이스를 적용하는게 위빙이다.</p>
<h3 id="4-spring-aop-실습">4. Spring AoP <strong>실습</strong></h3>
<pre><code class="language-java"> //지정자: 타겟의 여러 조인트 포인트중에서 어드바이스를 어떻게 적용시킬지 AOP에게 알려주는 키워드들
    //@Around(&quot;execution()&quot;) -&gt; 메서드를 실행하는 시점에 내용을 적용하겠다.

    //public 메소드에 접근할때, 리턴 타입이 뭐든(*)
    //org.prgrms.kdt..* 해당 위치 뭐든
    //(..) 파라미터 뭐든
    //@Around(&quot;org.prgrms.kdt.aop.CommonPointCut.repositoryInsertMethodPointCut()&quot;)
    @Around(&quot;@annotation(org.prgrms.kdt.aop.TrackTime)&quot;) // 해당 에노테이션에서 부가기능을 실행하겠다.
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info(&quot;Before method called. {}&quot;, joinPoint.getSignature().toString());
        var startTime = System.nanoTime();
        var result = joinPoint.proceed();
        var endTime = System.nanoTime() - startTime;
        log.info(&quot;After method call with return result -&gt; {} and time taken {} nanoseconds&quot;, result,endTime);
        return result;
    }</code></pre>
<pre><code>어드바이스들 
1. execute
2. within : 특정 클래스를 쓸 수 있음

어드바이스들을 미리 지정하고 메소드명으로 소환이 가능한 @PointCut을 쓸 수 있음.</code></pre><h3 id="5spring-트랜잭션-실습">5.Spring 트랜잭션 실습</h3>
<pre><code>트랜잭션 -&gt; ACID을 유지
중간에 망가지면 -&gt; 롤백.
성공적으로 마무리되면 중간 과정을 커밋.
</code></pre><h3 id="6spring-트랜잭션-매니저-실습">6.Spring 트랜잭션 매니저 실습</h3>
<pre><code>트랜잭션 매니저 : 
-&gt;
트랜잭션 템플릿</code></pre><h3 id="7transactional">7.Transactional</h3>
<p>지금까지직접 트랜잭션 매니저, 템플릿 가지고 우리가 호출해가면서 씀.
(programatic 한 트랜잭션 매니징)</p>
<p>-&gt; 스프링에서는 @Tranactional을 통해 선언적 트랜잭션 관리가 가능</p>
<p>//에러가 나면 롤백한다 -&gt; 이런건 사실상 부가기능임. 주요로직이 아니다. 
@Transactional이 트랜잭션관리
-&gt; 이는 사실 aop 기술이 적용되고 그 안엔 프록시가 작동한다.
<strong>여러 db sql 을 실행하는 서비스 단에 적용해서 뭔가 잘못되면 전체 롤백이 가능하도록함.</strong></p>
<p>@Trasactional 어노테이션을 달면? (아직 몰라도될듯)
1.@Trasactional을 적용한 메서드가 있는 클래스 타입의 프록시가 생성됨
2. 위처럼 스프링 AOP에 의해서 자동으로 생성된 프록시에
3. 트랜잭션 관련 기능들이(부가기능) 앞뒤로 붙게된다.</p>
<p>@EnableTransactionManagement 
-&gt; 실행하는 스프링프로젝트에 달아줘야 트랜잭션 관리 가능</p>
<h3 id="8트랜잭션-전파와-격리">8.트랜잭션 전파와 격리</h3>
<h4 id="1-트랜잭션-전파">1. 트랜잭션 전파</h4>
<p>트랜잭션 전파 : 트랜잭션이 처리되는 과정 중에, 또다른 트랜잭션이 진행되는 것.
롤백이 이어지는 상황이 있을 수 있다.
<strong>@Trasactional(propagation= )</strong></p>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>REQUIRED</td>
<td>바깥에 트랜잭션 있으면 사용하고 없으면 안에서 만들어 사용</td>
</tr>
<tr>
<td>REQUIRED_NEW</td>
<td>바깥에 트랜잭션 있던없던 안에서는 만들어 사용 (바깥 트랜잭션의 커밋 롤백에 영향을 주지 않는다.)</td>
</tr>
<tr>
<td>SUPPORTS</td>
<td>바깥에 트랜잭션이 있으면 사용하고 없으면 안만들고 안씀</td>
</tr>
<tr>
<td>NOT_SUPPORTED</td>
<td>바깥에 트랜잭션이 있어도 그 트랜잭션 잠시 중단시키고, 메소드 실행종료되면 다시 실행되게 함.  안에서는 안만든다.</td>
</tr>
<tr>
<td>MANDATORY</td>
<td>실행할때 반드시 이전에 만들어진 트랜잭션이 존재해야함</td>
</tr>
<tr>
<td>NEVER</td>
<td>트랜잭션 진행 상황에서 실행 될 수 없다. 만약 이미 진행 중인 트랜잭션이 존재하 면 예외 발생</td>
</tr>
<tr>
<td>NESTED</td>
<td>이미 진행 중인 트랜잭션이 존재하면 중첩된 트랜잭션에서 실행되어야 함(이미 진행 중인 트랜잭션이 존재하면 중첩된 트랜잭션에서 실행되어야 함)</td>
</tr>
</tbody></table>
<h4 id="2-트랜젝션-격리">2. 트랜젝션 격리</h4>
<p><strong>@Trasactional(isolation =  )</strong>
트랜잭션이 이뤄지면서 다른 트랜잭션도 이뤄질 수 있다. 
따라서 이런 트랜잭션 사이가 지독하게 엮이는 일이 생길 수 있다.<br>-&gt; 그러면 아직 커밋도 안된 값이 다른데서 읽힌다거나.. 제법 복잡해진다.</p>
<p>그래서 이런 트랜잭션이 얼마나 다른 트랜잭션에 독립적인지. 
독립성의 단계를 나눈게 Trasaction isolation 레벨이다.</p>
<table>
<thead>
<tr>
<th align="left"><strong>isolation 레벨</strong>  / 발생문제</th>
<th align="center"><strong>dirty read</strong></th>
<th align="center"><strong>non-repeatable read</strong></th>
<th align="center"><strong>phantom reads</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>READ_UNCOMMITTED</strong></td>
<td align="center">Yes</td>
<td align="center">Yes</td>
<td align="center">Yes</td>
</tr>
<tr>
<td align="left"><strong>READ_COMMITTED</strong></td>
<td align="center">No</td>
<td align="center">Yes</td>
<td align="center">Yes</td>
</tr>
<tr>
<td align="left"><strong>REPEATABLE_READ</strong></td>
<td align="center">No</td>
<td align="center">No</td>
<td align="center">Yes</td>
</tr>
<tr>
<td align="left"><strong>SERIALIZABLE</strong></td>
<td align="center">No</td>
<td align="center">No</td>
<td align="center">No</td>
</tr>
</tbody></table>
<hr>
<p>READ_UNCOMMITTED : 커밋 안된것도 읽음
READ_COMMITTED : 커밋된 내용만 읽음
REPEATABLE_READ : 한 트랜잭션에서 먼저 읽어진 데이터의 경우에 다른 트랜잭션에서 변경,삭제되는 걸 방지한다.
SERIALIZABLE: 한 트랜잭션에서 먼저 읽어진 데이터의 경우에 다른 트랜잭션에서 새로 insert되는 것을 방지한다/ 막는다.</p>
<hr>
<p>dirty read : (수정중인 데이터를 읽을 수 있는가?) 커밋되지 않은 것도 읽으므로, 롤백되어도 이미 읽어버린 후라서 돌이킬수없음. 지저분하게 읽게됨</p>
<p>non-repeatable read : 한트랜잭션에서 앞-뒤로 같은 쿼리값을 읽는데 중간에 다른 트랜잭션에서 값을 바꾸면 앞-뒤 쿼리의 결과가 다름. 
일관성 없는 읽기.</p>
<p>phantom reads : 한트랜잭션에서 앞-뒤로 같은 쿼리값을 읽는데 중간에 다른 트랜잭션에서 값을 insert하면 앞-뒤 쿼리의 결과가 다름. 갑자기 뭐가 새로 생김.
없었는데? 있었습니다. </p>
<hr>
<p>@Trasactional(isolation =  )로 수준을 설정해줄 수 있다.</p>
<ul>
<li>default: 사용하는 DB에서 설정한 아이솔레이션 레벨을 쓰겠다. 
(mysql : SELECT @@SESSION.transaction_isolation 쿼리로 확인이 가능)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[리눅스 디렉토리별 용량 확인하기]]></title>
            <link>https://velog.io/@hoit_98/%EB%A6%AC%EB%88%85%EC%8A%A4-%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC%EB%B3%84-%EC%9A%A9%EB%9F%89-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hoit_98/%EB%A6%AC%EB%88%85%EC%8A%A4-%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC%EB%B3%84-%EC%9A%A9%EB%9F%89-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 28 Aug 2021 15:15:18 GMT</pubDate>
            <description><![CDATA[<h5 id="본-방식은-ec2의-아마존-리눅스centos에서-진행했다">본 방식은 EC2의 아마존 리눅스(CentOS)에서 진행했다.</h5>
<p>이상하게도 자꾸 리눅스 용량이 부족한 현상이 있었다. 
어떤 부분이 용량을 차지하는지 궁금해 찾게되었다.</p>
<hr>
<ol>
<li>root 로 이동해준다.<pre><code class="language-shell">[root@(아이피) ~]# cd /</code></pre>
</li>
<li>아래 명령어로 하위 디렉토리 path를 입력하여 크기를 확인한다.<blockquote>
<p>du -sh /폴더경로/*</p>
</blockquote>
```
[root@(아이피) /]# du -sh ./*</li>
<li>1M    ./bin
26M     ./boot</li>
<li>0K    ./cgroup
140K    ./dev
38M     ./etc</li>
<li>9G    ./home
179M    ./lib
19M     ./lib64</li>
<li>8G    ./logs</li>
<li>0K    ./srv
0       ./sys
132K    ./tmp</li>
<li>0K    ./tmpt</li>
<li>6G    ./usr
27G     ./var</li>
</ol>
<p>```
<del>(var 파일이 굉장히 큰것을 확인 후, 이상 로그 발생부를 찾았다)</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL]20210826]]></title>
            <link>https://velog.io/@hoit_98/TIL20210826</link>
            <guid>https://velog.io/@hoit_98/TIL20210826</guid>
            <pubDate>Thu, 26 Aug 2021 13:29:56 GMT</pubDate>
            <description><![CDATA[<h3 id="1-embedded-database">1. Embedded DataBase</h3>
<h4 id="왜-임베디드-db-쓰는가">왜 임베디드 DB 쓰는가</h4>
<pre><code> DB등의 외부환경이 테스트 성공 실패에 영향을 주면 테스트에 대한 자동화 불가능.
 -&gt; 개발중에 고정적으로 사용할수있는 내부 DB가 필요함.
 -&gt; 임베디드 DB 사용</code></pre><h4 id="임베디드-db의-사용">임베디드 DB의 사용</h4>
<p>EmbeddedDatabase 자체가 datasource를 extend한다. 
따라서 DB연결 후 datasource 형태로 반환가능.</p>
<pre><code class="language-java">//H2 사용할때
EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2) //  H2 Db를 통해 
.setScriptEncoding(&quot;UTF-8&quot;)
.ignoreFailedDrops(true) 
.addScript(&quot;schema.sql&quot;)//add script: 스크립트 읽어서 초기설정
.build()</code></pre>
<h4 id="오픈소스-임베디드-db의-사용">오픈소스 임베디드 DB의 사용</h4>
<p>근데, SQL 자체가 벤더사(MYSQL, POSTQL 등)에 따라 쿼리에 쓰이는 function이 다르기 때문에(ex. UUID_TO_BIN) mysql에 맞춘 쿼리에는 H2 DB 가 동작 안할수도있다.</p>
<p>-&gt; 벤더사에 맞는 임베디드 DB(embedded-mysql 등) 사용이 필요할 수 있다. (대체로 오픈소스로 사용할수있다.)</p>
<p>=&gt; 따라서 그런 function 필요없도록 쿼리 작성하거나, 안된다면 적절한 임베디드 db 오픈소스를 활용해서 동작시킬 수 있다.</p>
<pre><code class="language-java">
//1.Config 등의 메소드에 사용할 데이터소스를 반환
var dataSource = DataSourceBuilder.create()
                    .url(&quot;jdbc:mysql://localhost:2215/test-order_mgmt&quot;)
                    .username(&quot;test&quot;)
                    .password(&quot;test1234!&quot;)
                    .type(HikariDataSource.class)
                    .build();

@BeforeAll
    void setUp(){
        //1. 임베디드 mysql에 관해 설정
        var  mysqldConfig= aMysqldConfig(v8_0_11)
                .withCharset(UTF8)
                .withPort(2215)
                .withUser(&quot;test&quot;, &quot;test1234!&quot;)
                .withTimeZone(&quot;Asia/Seoul&quot;)
                .build();
        EmbeddedMysql embeddedMysql = anEmbeddedMysql(mysqldConfig)
                .addSchema(&quot;test-order_mgmt&quot;,classPathScript(&quot;schema.sql&quot;))
                .start();
    }</code></pre>
<p><a href="https://github.com/wix/wix-embedded-mysql">mysql 임베디드 DB 오픈소스 링크</a></p>
<h3 id="2namedparameterjdbctemplate">2.NamedParameterJdbcTemplate</h3>
<h4 id="namedparameterjdbctemplate란">NamedParameterJdbcTemplate란?</h4>
<p>스프링이 NamedParameterJdbcTemplate를 제공 
-&gt; 템플릿의 ?와 숫자 인덱스 기반(일반 JdbcTemplate) 아닌
-&gt; 이름 기반의 파라미터를 설정하게 해주는 템플릿</p>
<p>JdbcTemplate을 가지고 있고, 그 파라미터 부분을 조정해서 제공.</p>
<ul>
<li>해시맵을 제공하고, 그 해시맵의 키값을 사용</li>
</ul>
<h4 id="exception-처리">Exception 처리</h4>
<p>잘못된 sql 접근 
-&gt; sqlException에서 얻는 코드값이 벤더사마다 다름.
-&gt; 스프링이 <strong>DataAccessException</strong>을 제공, 다양한 데이터접근 에러를 포함(DuplicateKeyException 등)하고 있음.
-&gt; 타입화된 에러를 활용할 수 있음</p>
<pre><code class="language-java">    try {
            customerNamedJdbcRepository.insert(newCustomer);
        }catch (BadSqlGrammarException e){
            //원래는 각 에러코드마다 처리해야함.
            // BadSqlGrammarException처럼 타입화된 에러를 활용해 이를 쉽게 처리가능
            logger.error(&quot;got BadSqlGrammarException errorcode -&gt;{}&quot;,e.getSQLException().getErrorCode());
        }</code></pre>
<h4 id="트랜잭션">트랜잭션</h4>
<p>트랜잭션 -&gt; ACID을 유지
중간에 망가지면 -&gt; 롤백.
성공적으로 마무리되면 중간 과정을 커밋.</p>
<pre><code class="language-java">connection.setAutoCommit(false);
connection.setAutoCommit(true);
connection.rollback();</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210826]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210826</link>
            <guid>https://velog.io/@hoit_98/TIL-20210826</guid>
            <pubDate>Wed, 25 Aug 2021 17:15:10 GMT</pubDate>
            <description><![CDATA[<h3 id="1-datasource">1. DataSource</h3>
<pre><code> 매번 커넥션 생성, 소멸시 리소스가 많이 소모됩니다. 
 -&gt; 커넥션 풀이 등장, datasource를 통해서 커넥션을 받고 반납할 수 있음!

 커넥션 미리 만들어 놓고 풀에 저장해뒀다가 
 필요할때마다 커넥션을 가져와서 쓰고 다시 반환

 커넥션 풀을 데이터소스에 의해서 가져와지고 반납된다.
 따라서 커넥션 클로즈를 하는거는 사실 데이터 소스에 커넥션을 반환하는거당

 *여러 데이터 소스 구현 라이브러리 있음
 1. 스프링제공 : simpleDriverDataSource -&gt; 사실 커넥션풀 개념은 아니고 그냥 열고 닫음
 2. 아파치의 DBCP
 3. HikariCP</code></pre><h3 id="2datasource---hikaricp">2.DataSource - HikariCP</h3>
<pre><code>빠르고 안정적인 DBCP
spring-boot-starter-jdbc 디펜던시 설정시 함께 들어온다.
</code></pre><h3 id="3datasource---customerrepository1-2">3.DataSource - CustomerRepository(1, 2)</h3>
<pre><code class="language-java">//DataSorce를 HikariDataSource 타입을 제작해서 주입받아 사용하기!
@Configuration
    @ComponentScan(basePackages = {&quot;org.prgrms.kdt.customer&quot;})
    static class config{
        @Bean
        public DataSource dataSource(){
            var dataSource = DataSourceBuilder.create()
                    .url(&quot;jdbc:mysql://localhost/database이름&quot;)
                    .username(&quot;사용자이름&quot;)
                    .password(&quot;사용 비밀번호&quot;)
                    .type(HikariDataSource.class)
                    .build();


            return dataSource;
        }
    }

    @Autowired
    CustomerJdbcRepository customerJdbcRepository;

    @Autowired
    DataSource dataSource;</code></pre>
<h3 id="3-testinstance-lifecycle">3. TestInstance Lifecycle</h3>
<p>원래 @BeforeAll은 static 메소드.
따라서 그 안에서 인스턴스 변수에 접근이 안된다.
-&gt; @TestInstance(TestInstance.Lifecycle.PER_CLASS) // 클래스 단위로 인스턴스 하나 생성.
-&gt; 따라서 @BeforeAll이 static 아니여도 되고, 내부 필드들을 가지고 작업도 가능하다</p>
<p><img src="https://www.arhohuttunen.com/junit-5-test-lifecycle/junit-5-per-method-lifecycle.svg" alt=""></p>
<p>[추가 조사]
JUnit 에서는 @Test 가 붙은 테스트 메서드를 실행시킬 때, 각각 클래스를 새로 로딩 한다.
왜냐면 -&gt; @TestInstance(TestInstance.Lifecycle.PER_METHOD)가 디폴트라서 메소드 별로 로딩한다.
그래서 BeforeAll이 스태틱이여야 한다. 
BeforeAll이 전체 테스트 이전에 한번만 실행되므로
각 테스트 메소드의 인스턴스가 생성되지 않았더라도, 호출되어야 하기 때문.</p>
<p>참고: <a href="https://alkhwa-113.tistory.com/entry/JUnit-%EC%9D%98-TestInstance">링크</a></p>
<hr>
<h4 id="순서대로-테스트-진행">순서대로 테스트 진행</h4>
<p>클래스 단위로 실행할 테스트에서 순서를 정해 진행하지 않으면 오류가 발생할 수 있다.
이때 테스트 클래스에 
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)를 달아서 순서대로 실행하게 할 수 있다.</p>
<table>
<thead>
<tr>
<th>MethodOrderer.{?}.class</th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>OrderAnnotation</td>
<td>@order(숫자) 어노테이션 순서대로</td>
</tr>
<tr>
<td>DisplayName</td>
<td>@DisplayName(&quot;&quot;) 알파벳순</td>
</tr>
<tr>
<td>MethodName</td>
<td>메소드 명 알파벳순</td>
</tr>
<tr>
<td>Random</td>
<td>랜덤 순서대로</td>
</tr>
</tbody></table>
<pre><code>@order(숫자) : 숫자 순서대로(오름차순) 테스트 실행</code></pre><h3 id="4-jdbc-template">4. JDBC Template</h3>
<pre><code>커넥션 맺는 부분과 예외처리 하는부분이 계속 반복된다.
이 반복되는 부분과 변경되는 부분을 템플릿 콜백 패턴을 이용해서 
JDBC 템플릿 패턴을 제공.

JDBC 템플릿: 변경이 필요한 제공해서 간결한 코드를 쓰게 해준다.</code></pre><pre><code class="language-java">jdbcTemplate.query(&quot;select * from customers&quot;, customerRowMapper);</code></pre>
<ol>
<li><p>.query() 가 실행되는 방식</p>
</li>
<li><p>.update() 가 실행되는 방식</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210819]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210819</link>
            <guid>https://velog.io/@hoit_98/TIL-20210819</guid>
            <pubDate>Thu, 19 Aug 2021 07:13:37 GMT</pubDate>
            <description><![CDATA[<ol>
<li><p>DI
circular dependency를 만들면 스프링 어플리케이션 생성이 안된다.</p>
</li>
<li><p>Component
컴포넌트들(@Component) : 용도에 맞게 분류시켜줌
각각의 어노테이션을 사용해서 <del>하는 클래스다 라고 스프링에 알려준다! 아</del> 쉽당!
Bean중에 컴포넌트로 정의하면 컴포넌트로 인식하고,
@Service
달면 서비스 클래스를 따로 생성및 등록 안해도 찾아서 사용할 수 있게 된다.</p>
</li>
<li><p>Component
@ComponentScan() 
()안에 패키지명이나 클래스명을 넣어서 원하는 것만 스캔할수도 있당! 
쓸모없는건 제낄수 있다 내가     원하는거만 스캔된다 아이 편해~
특정 컴포넌트만 필터링하는 exclude도 가능하당 ㅎ
@Autowired -&gt; 헷갈령,,,
자동으로 주입시켜준당 레포는 레포에 서비스는 서비스에
원래는 생성자에 주입할때도 달아줬는데 스프링 버전업되면서 생성자 주입은 자동으로 되도록 바뀐거야아아아아
그래서 생성자가 두 개라면 스프링이몰라서 못찾는겨</p>
</li>
</ol>
<p>생성자 주입이 좋다! 
-&gt; 초기화때 모든 의존관계가 생기니까 안전해!
-&gt; 엄청 엮인 애는 파라미터가 엄청 많을테니까, 눈을 의심해서 두번 확인할 수 있음</p>
<p>Autowired(자동으로 주입되는게) 2개면 스프링이 몰라요
-&gt; @Primary 를 붙여줘서 우선순위 주거나 
-&gt; @Qaulifier 해가지고 사용하는 쪽에서 쓸 컴포넌트 정할수 있당</p>
<p>4.BeanScope : 빈이 어떤 범위로 만들어질까
싱글톤이 기본 - &gt; 자동으로 같은 빈 = 같은 객체로 생성.
프로토타입으로 -&gt; 같은 빈이 다른 객체로 생성됨.
-&gt; 싱글톤으로 쓰는게 좋다. 왱?</p>
<ol start="4">
<li>Life Cycle 객체의 생명주기
생명 생성이 되면 -&gt; postConstruct, afterPropertySet,initmethod(Bean에 임의로 구현한) 순으로 콜백을 거칠 수 있음
소멸되면 -&gt; PreDestroy ,destroy, 
순으로 콜백 거칠 수 있슴</li>
</ol>
<h3 id="오늘의-한마디">오늘의 한마디</h3>
<p>오늘 강의 재밌당! 주말에 한번 더 보는게 좋겠엉
학부때 암것도 모른채로 들었던 내용인데 관심생기고 보는 느낌은 다르다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210816]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210816</link>
            <guid>https://velog.io/@hoit_98/TIL-20210816</guid>
            <pubDate>Mon, 16 Aug 2021 10:29:42 GMT</pubDate>
            <description><![CDATA[<h3 id="20210816">20210816</h3>
<h5 id="이게-뭐지">이게 뭐지</h5>
<ol>
<li><p>Record 키워드(java 14)</p>
</li>
<li><p>reduce function</p>
</li>
<li><p>orElseThrow</p>
</li>
<li><p>Transitive Dependencies</p>
<h5 id="배우고-느낀것">배우고 느낀것</h5>
</li>
<li><p>느슨한 결합도를 위해서 -&gt; 런타임 의존성 고려</p>
</li>
<li><p>지금 내가 생각하는 소프트웨어의 Build</p>
<pre><code>- 내가 필요한 라이브러리 받아서 쓰게 해줌
- 컴파일해서 돌려볼 수 있게 만들어줌
- 아 맞다 릴리즈할때는 버전도 관리하게 해주는거 같았는데
- 아직은 이정도밖에 모르겠다. 근데 그래도 없으면 안된다 나혼자 라이브러리 설치하는 일은 겪고 싶지 않아.</code></pre><ol start="3">
<li>EJB 가 뭔디?
Auto Configuration 뭔디 ;;</li>
</ol>
</li>
</ol>
<p> 내가 이해한 IoC(Inversion of control)</p>
<pre><code> - 생성된 객체에 따라서 프레임워크가 그 객체가 사용할 객체나 메소드들을 결정한다.
 - 나는 그냥 원하는 주문을 만들었을 뿐인데, 그에 해당하는 방식대로 할인이 된다.
</code></pre><h4 id="일기장">일기장</h4>
<p>😥 코딩하는건 재밌는뎅 알아야될게 넘넘 많아서 약간 막막허다
책읽는 건 좋은데 30층 짜리 책장으로 둘러싸여가지고 당황스럽다고 해야되나...?
근데 뭐 사실 할 수 있는거는 하나씩 꺼내읽는거 밖엔 없다 아직 ㅎ
그냥 주어진대로 내가 재미있는대로 내가 필요한 대로 한달은 그렇게 해볼란당</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210813]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210813</link>
            <guid>https://velog.io/@hoit_98/TIL-20210813</guid>
            <pubDate>Mon, 16 Aug 2021 06:28:12 GMT</pubDate>
            <description><![CDATA[<ol>
<li>Index는 select, delete, join을 빠르게/ update, insert를 느리게 만들엉..</li>
</ol>
<h3 id="내가-느낀거-배운거">내가 느낀거 배운거</h3>
<ol>
<li><p>예전에 암것도 모르고 트랜잭션으로 데이터 insert 시간 줄인적이 있는데, 트랜잭션이 이런거고나... 좀 더 알아볼필요가 있을덧</p>
</li>
<li><p>SQL 키트 풀면서 0~23 까지 가상테이블을 만들일이 있었당. 이거를 그렇게 Recursive 키워드로 푸는게 좋은지? 아니면 프로시저를 쓰면 어떤징 궁금하다아</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210812]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210812</link>
            <guid>https://velog.io/@hoit_98/TIL-20210812</guid>
            <pubDate>Thu, 12 Aug 2021 07:17:29 GMT</pubDate>
            <description><![CDATA[<p>SQL에 문자열을 쓸 때 큰따옴표를 써야할까? 작은따옴표 써야할까?
문자열 안에 인용문이 들어가면 오류가 날 수 있어서 큰따옴표가 나은,,듯?
-&gt; <a href="https://bskyvision.com/952">https://bskyvision.com/952</a></p>
<p>근데 또 날짜함수안에는 작은따옴표 안에 써줘야해서 헷갈린다.
PR 날릴때 같이 질문하고 다시 정리해야지</p>
<h3 id="오늘-배운거-느낀거">오늘 배운거, 느낀거</h3>
<p>강의에서 자기 그릇을 한정짓지 말라고 하셨당
너무 내가 자주 생각하는 일 같아서 찔렸다..ㅎ 
뭔지 모르게 힘이 되서 눈물이 찔금날뻔..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210811]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210811</link>
            <guid>https://velog.io/@hoit_98/TIL-20210811</guid>
            <pubDate>Thu, 12 Aug 2021 02:15:58 GMT</pubDate>
            <description><![CDATA[<h3 id="새로-배운-것들">새로 배운 것들</h3>
<p>1) select문</p>
<pre><code>    1) count(NULL) -&gt; 더하기 수행 안함

        2)
            CASE WHEN(조건) THEN 값
            ELSE 값

        3) DISTINC는 뒤에 따라오는 모든 필드에 적용이 된대~!</code></pre><h3 id="오늘-깨달은-거-생각한-거">오늘 깨달은 거, 생각한 거</h3>
<ol>
<li>ORDER BY 1, GROUP BY 1 하면 SELECT 문의 첫번째꺼 기준으로 된다.</li>
</ol>
<p>-&gt; 두 번 반복해 쓸 필요가 없어서 편리하다.
-&gt; 뭐가 더 좋을까? 직관적으로는 두번쓰는게 낫나?</p>
<p>열심히 사는 사람들을 보면서 감명도 받고 불안감도 같이 느끼기도 한다. 그래도 답은 일단 하는 거 같다. 월말엔 많이 변해있으면 좋겠당..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[디자인 패턴] Interpreter 패턴]]></title>
            <link>https://velog.io/@hoit_98/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Interpreter-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@hoit_98/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Interpreter-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Tue, 10 Aug 2021 14:32:43 GMT</pubDate>
            <description><![CDATA[<h3 id="interpreter-패턴">Interpreter 패턴</h3>
<p>​    문법적 규칙을 클래스화 하여, 일련의 규칙을 통해 언어/문법을 해석하는 패턴이다.</p>
<h3 id="😊-사용되는-곳">😊 사용되는 곳</h3>
<p>​    SQL 문은 SELECT (어쩌구)  FROM (저쩌구) WHERE (이러쿵저러쿵) 과 같이</p>
<p>​    특정 문법을 지키면 MYSQL에서 알아서 쿼리결과를 짠 하고 내줍니다.</p>
<p>​    덕분에 개발자들은 손쉽게 DB에서 결과를 얻게 됩니다. </p>
<p>​    이렇듯 간단한 <strong>문법적 규칙(대체로 문자열을 해석하는)을 클래스화해서 사용자가 원하는 답을 얻게 해주는 패턴</strong>이 Interpreter 패턴입니다.</p>
<p>​    주로 SQL 해석, shell 명령어 해석기 등에 사용하는 패턴입니다.</p>
<h3 id="🤔-구현을-어떻게-할까-구현-예시">🤔 구현을 어떻게 할까 (구현 예시)</h3>
<p>​    Interpreter 패턴으로 <a href="https://ko.wikipedia.org/wiki/%EC%97%AD%ED%8F%B4%EB%9E%80%EB%93%9C_%ED%91%9C%EA%B8%B0%EB%B2%95">후위 표기법</a> 을 구현해보겠습니다.</p>
<p>​    후위 표기법에서는 1+2 를 1 2 + 로 표현합니다.</p>
<p>​    (3 + 5) * (4 + 2)  =&gt; 3 5 + 4 2 + *</p>
<p>​    우리가 &quot;a b +&quot; 를 요청해도, &quot;w x z - +&quot; 를 요청해도 </p>
<p>​    문법 해석기가 알아서 해석해서 결과를 줄 수 있어야 합니다.</p>
<p><img src="https://images.velog.io/images/hoit_98/post/d00ed04e-25ef-4640-b55d-6724f88ac4c7/image.png" alt=""></p>
<ol>
<li>우리가 &quot;w x z - +&quot;라는 문장을 Evaluator라는 문법해석기에 넣을 겁니다.</li>
<li>Evaluator는 문장을 해석해 저 문장대로 계산이 가능한 계산기를 출력해줍니다.</li>
<li>그 계산기에 사용자가 원하는대로 문자=10(w=10) 과 같이 입력을 넣어주면</li>
<li>우리는 답 37을 얻을 수 있습니다.</li>
</ol>
<h3 id="📜-코드로-살펴볼까요">📜 코드로 살펴볼까요!</h3>
<ol>
<li>연산자들</li>
</ol>
<ul>
<li>연산자 interface </li>
</ul>
<pre><code class="language-java">import java.util.Map;

//우리가 만들 연산자들
public interface Expression {
    //어떻게 해석할지 구현
    public int interpret(Map&lt;String, Expression&gt; variables);
}</code></pre>
<ul>
<li><p>Plus 연산자 구현</p>
<pre><code class="language-java">//Plus 연산자 구현
public class Plus implements Expression{
 //+의 왼쪽에 올 표현(숫자일 수도 있고 이미 연산된 결과일 수 있음)
 Expression leftOperand;
  //+의 오른쪽에 올 표현
 Expression rightOperand;

 public Plus(Expression leftOperand, Expression rightOperand) {
     this.leftOperand = leftOperand;
     this.rightOperand = rightOperand;
 }

 @Override
 public int interpret(Map variables) {
     //왼쪽과 오른쪽의 결과를 더해서 반환
     return leftOperand.interpret(variables)+rightOperand.interpret(variables);
 }
}</code></pre>
<ul>
<li><p>Minus 연산자 구현</p>
<pre><code class="language-java">//Minus 연산자 구현
public class Minus implements Expression{
//-의 왼쪽에 올 표현(숫자일 수도 있고 이미 연산된 결과일 수 있음)
Expression leftOperand;
//-의 왼쪽에 올 표현(숫자일 수도 있고 이미 연산된 결과일 수 있음)
Expression rightOperand;

public Minus(Expression leftOperand, Expression rightOperand) {
   this.leftOperand = leftOperand;
   this.rightOperand = rightOperand;
}

@Override
public int interpret(Map variables) {
    //왼쪽에서 오른쪽의 결과를 빼서 반환
   return leftOperand.interpret(variables)-rightOperand.interpret(variables);
}
}</code></pre>
</li>
<li><p>Variable(알파벳에 해당) 연산자 구현</p>
<pre><code class="language-java">public class Variable implements Expression{
private String alphabet;

public Variable(String alphabet) {
   this.alphabet = alphabet;
}

@Override
public int interpret(Map&lt;String, Expression&gt; variables) {
   // 사용자가 입력한 Map에 alphabet이 있으면 그 알파벳에 해당하는 숫자 반환
   // 없으면 0 반환 
   if(variables.get(alphabet) == null)
       return 0;
   return variables.get(alphabet).interpret(variables);
}
}</code></pre>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>Number(사용자가 입력한 숫자) 연산자 구현</p>
<pre><code class="language-java">public class Number implements Expression{
  private int num;

  public Number(int num) {
      this.num = num;
  }

  @Override
  public int interpret(Map variables) {
      return num; //숫자를 반환
  }
}</code></pre>
<ol start="2">
<li>문법에 따라 연산 제조</li>
</ol>
<ul>
<li>Evaluator(해석기) 구현</li>
<li>후위 연산법은 스택을 통해 구현합니다.(참고 : <a href="https://ko.wikipedia.org/wiki/%EC%97%AD%ED%8F%B4%EB%9E%80%EB%93%9C_%ED%91%9C%EA%B8%B0%EB%B2%95">후위 표기법</a>)</li>
</ul>
</li>
</ul>
<pre><code class="language-java">    import java.util.Map;
    import java.util.Stack;

    public class Evaluator implements Expression{
        private Expression syntax; //만들 최종 해석

        public Evaluator(String expression){
            //expression을 입력받음
            Stack&lt;Expression&gt; stack = new Stack&lt;Expression&gt;();

            for(String strToken : expression.split(&quot; &quot;)){
                if(strToken.equals(&quot;+&quot;)){
                    Expression expressionPlus = new Plus(stack.pop(),stack.pop());
                    stack.push(expressionPlus);
                }else if(strToken.equals(&quot;-&quot;)){
                    Expression expressionMinus = new Minus(stack.pop(),stack.pop());
                    stack.push(expressionMinus);
                }else {
                    stack.push(new Variable(strToken));
                }
            }
            //해석 결과로 나온 계산기 
            syntax = stack.pop();
        }

        //사용자가 입력한 숫자로 계산기에 계산돌리기
        @Override
        public int interpret(Map&lt;String, Expression&gt; variables) {
            return syntax.interpret(variables);
        }
    }</code></pre>
<ol start="3">
<li>메인</li>
</ol>
<pre><code class="language-java">    import java.util.HashMap;
    import java.util.Map;

    public class Main {
        public static void main(String[] args) {
            //해석을 원하는 문장
            String expression =&quot;w x z - +&quot;;

            //문장에 따른 해석기를 제작
            Evaluator sentence = new Evaluator(expression);

            //사용자가 w = 5, x = 10, z =42 를 입력
            Map&lt;String, Expression&gt; variables = new HashMap&lt;String, Expression&gt; ();
            variables.put(&quot;w&quot;,new Number(5));
            variables.put(&quot;x&quot;,new Number(10));
            variables.put(&quot;z&quot;,new Number(42));

            //해석기에 입력을 넣고 결과 얻기
            int result = sentence.interpret(variables);
            System.out.println(result);
        }

    }</code></pre>
<p>💻 결과</p>
<p> &quot;w x z - +&quot; </p>
<p>(z-x) + w == (42-10) + 5</p>
<pre><code>37</code></pre><hr>
<h3 id="💕-참고한-링크들">💕 참고한 링크들</h3>
<ol>
<li><p>기본적인 Interpreter 패턴이해</p>
<p><a href="https://beomseok95.tistory.com/288">https://beomseok95.tistory.com/288</a></p>
</li>
<li><p>구체적인 Interpreter 패턴 구현(<strong>후위 표기법</strong> 구현)</p>
<p><a href="https://hamait.tistory.com/199">https://hamait.tistory.com/199</a></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[디자인 패턴] Flyweight 패턴]]></title>
            <link>https://velog.io/@hoit_98/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Flyweight-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@hoit_98/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Flyweight-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Tue, 10 Aug 2021 14:09:15 GMT</pubDate>
            <description><![CDATA[<h3 id="flyweight플라이웨이트-패턴">Flyweight(플라이웨이트) 패턴</h3>
<p>​    인스턴스를 가능한 한 공유해서 사용함으로써 메모리를 절약하는 패턴</p>
<h3 id="👿-문제상황">👿 문제상황</h3>
<p>​    마인크래프트 게임에 나무를 설치하고 싶습니다. </p>
<p>​    나무는 색(color)을 정할 수 있고, 특정 위치(x, y)에 세울 수 있습니다.</p>
<p>​    만약 내가 마크에 미쳐서 나무 10000개를 맵에 심는다고 할 때, </p>
<p>​    나무 한 그루에 들어가는 데이터는</p>
<p>​    color : 4Byte , x: 4byte, y:4byte 라고 합시다.</p>
<p>​    모든 나무를 심을 때마다 객체를 생성한다면, 나무 10,000개에는 120,000byte 가 듭니다.
<img src="https://images.velog.io/images/hoit_98/post/80015d98-c611-4252-8cde-0f4601769d85/image.png" alt=""></p>
<p>​    더 마크에 열광하는 사람들은 더 많은 나무를 세울텐데,, 나<del>~</del>중에는 메모리가 터질수도 있습니다.</p>
<h3 id="😇-어떻게-해결할까">😇 어떻게 해결할까</h3>
<p>​    모든 나무의 x, y는 다르지만 나무 색은 항상 바뀔 필요는 없죠? 거의 같고 가끔 색 바꾸게 됩니다.</p>
<p>​    그러면 나무를 계속 새로 만들지 말고,, 저장해놨다가 가져다 쓰는 식으로 한다면??</p>
<p><img src="https://images.velog.io/images/hoit_98/post/953ebe1d-262c-4983-9d5e-408335881658/image.png" alt=""></p>
<p>​    그러면 새로운 색상의 나무가 추가될 때만 새 객체를 생성한다면</p>
<p>​    나무가 <strong>10000개 여도 12 * (새로운 색상을 추가한 횟수) byte</strong> 이렇게 변하겠네요!</p>
<p>​    내가 만약 색을 2개만 쓴다면 24byte 이니까 이전보다 119976byte 가량 절약할 수 있습니다!</p>
<p>​    나무 수가 많을 수록 더 절약할 수 있겠네요!</p>
<p>​    이렇게 인스턴스를 가능한대로 공유시켜서 쓸데없는 new를 통한 메모리 낭비를 줄이는것이 Flyweight 패턴입니다!</p>
<h3 id="🤔-구현을-어떻게-할까">🤔 구현을 어떻게 할까</h3>
<p>​    플라이웨이트 패턴을 구현하기 위해 아래 3가지가 필요합니다.</p>
<ol>
<li><p>실제 공유될 객체 (나무)</p>
</li>
<li><p>객체의 인스턴스를 생성하고 공유해주는 팩토리(Factory) </p>
<p>-&gt; 같은 색의 나무가 없다면 새로 생성하고, 있다면 그 색의 나무를 반환해줍니다.</p>
</li>
<li><p>패턴을 사용할 고객, 클라이언트 </p>
<p>-&gt; 우리는 그냥 2번에게 OO색 나무를 요청하고, 받아서 X,Y를 설정하고 설치하면 됩니다.</p>
</li>
</ol>
<h3 id="📜-코드로-살펴볼까요">📜 코드로 살펴볼까요!</h3>
<ol>
<li><p>공유할 Tree 객체</p>
<pre><code class="language-java">public class Tree {

 // 나무는 아래와 같이 3개 정보를 가지고 있습니다.
 private String color;
 private int x;
 private int y;

 //색상으로만 생성자를 만들어줄게요.
 public Tree(String color) {
     this.color = color;
 }

 public void setX(int x) {
     this.x = x;
 }

 public void setY(int y) {
     this.y = y;
 }

 //나무를 심을 때
 public void install(){
     System.out.println(&quot;x:&quot;+x+&quot; y:&quot;+y+&quot; 위치에 &quot;+color+&quot;색 나무를 설치했습니다!&quot;);
 }
}</code></pre>
</li>
</ol>
<ol start="2">
<li>Tree를 제공해줄  TreeFactory<pre><code class="language-java">public class TreeFactory {
//HashMap 자료구조를 활용해서 만들어진 나무들을 관리해볼게요
public static final HashMap&lt;String, Tree&gt; treeMap = new HashMap&lt;&gt;();

</code></pre>
</li>
</ol>
<pre><code>public static Tree getTree(String treeColor){
    //Map에 입력받은 색상의 나무가 있는지 찾습니다. 있으면 그 객체를 제공합니다.
    Tree tree = (Tree)treeMap.get(treeColor); 

   //만약 아직 같은 색상의 나무가 Map에 없다면 새로 객체를 생성해 제공합니다.
    if(tree == null){
        tree = new Tree(treeColor);
        treeMap.put(treeColor, tree);
        System.out.println(&quot;새 객체 생성&quot;);
    }

    return tree;
}</code></pre><p>}</p>
<pre><code>


 3. 사용할 고객
```java
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.println(&quot;원하는 색을 입력해주세요 :)&quot;);
        for(int i=0;i&lt;10;i++){
            //나무 색 입력받기
            String input = scanner.nextLine();
            //팩토리에서 나무 하나 공급받기
            Tree tree = (Tree)TreeFactory.getTree(input);
            //나무 x,y 설정하고
            tree.setX((int) (Math.random()*100));
            tree.setY((int) (Math.random()*100));
            //나무 설치하기
            tree.install();
        }

    }
}</code></pre><p> 💻 결과</p>
<p><strong>다른 색상이 들어올 때만 새로운 객체를 생성합니다!</strong></p>
<pre><code class="language-shell">원하는 색을 입력해주세요 :)
초록
새로운 객체 생성
x:47 y:55 위치에 초록색 나무를 설치했습니다!
연두
새로운 객체 생성
x:86 y:33 위치에 연두색 나무를 설치했습니다!
초록
x:62 y:30 위치에 초록색 나무를 설치했습니다!
초록
x:83 y:21 위치에 초록색 나무를 설치했습니다!
초록
x:47 y:46 위치에 초록색 나무를 설치했습니다!
연두
x:49 y:59 위치에 연두색 나무를 설치했습니다!
연두
x:10 y:38 위치에 연두색 나무를 설치했습니다!
카키
새로운 객체 생성
x:78 y:39 위치에 카키색 나무를 설치했습니다!
카키
x:21 y:41 위치에 카키색 나무를 설치했습니다!
카키
x:58 y:26 위치에 카키색 나무를 설치했습니다!</code></pre>
<h3 id="❓-싱글톤-패턴과-다른게-뭐죠">❓ 싱글톤 패턴과 다른게 뭐죠?</h3>
<ul>
<li><p>싱글톤 역시 객체를 하나만 생성하고 활용하는 패턴입니다.
둘의 다른 점은 뭘까요?
우리의 나무 예시를 생각해볼까요?</p>
<p>우리가 제작한 <strong>Flyweight 패턴</strong>에서,
나무는 색깔이 바뀔 때 새로운 객체를 생성합니다. 
색상 별로 하나씩, 결과적으로는 여러개의 나무가 생깁니다.
또한 만들어진 객체의 색상은 바꿀 수 없는 일입니다.
따라서 <strong>하나씩 여러종류</strong>를 가질 수 있습니다.</p>
<p>그러나 <strong>싱글톤 패턴</strong>이라면,
나무 클래스에 단 한개의 나무만 만들수 있습니다.
따라서 싱글톤 패턴을 사용한다면,
만들어진 단 하나의 객체(나무)의 색깔을 바꿔야만합니다. 
싱글톤은 이렇듯 하나의 클래스에 단 하나의 인스턴스를 생성하고, 대신 변수를 필요시 변경해가며 쓸 수 있다는 차이가 있습니다.
따라서 싱글톤 패턴은 <strong>종류 상관없이 단 하나만</strong> 가질 수 있습니다.</p>
</li>
</ul>
<h3 id="😎-멋진-사실">😎 멋진 사실</h3>
<p>사실 플라이웨이트 패턴은 Java의 String Constant Pool에 적용됩니다...!</p>
<p>자바의 String은 만들어질때 String Constant Pool에 저장되어 </p>
<p>같은 문자열이 pool에 있다면 이를 불러오는 방식으로 되어있습니다!</p>
<p>(관련 내용을 정리했던 제 블로그 글 : <a href="https://velog.io/@hoit_98/Assignment-StringBuffer-and-String-Builder">링크</a> ) </p>
<p>따라서 String class는 Flyweight 패턴을 적용하여 메모리를 절약할 수 있습니다!!</p>
<hr>
<h3 id="💕-참고한-링크들">💕 참고한 링크들</h3>
<ol>
<li><p>기본적인 패턴 설명 + 용량 절약에 대한 내용(리팩토링 그루)</p>
<p><a href="https://refactoring.guru/design-patterns/flyweight">https://refactoring.guru/design-patterns/flyweight</a></p>
</li>
<li><p>플라이웨이트 패턴의 자바 구현 예제</p>
<p><a href="https://lee1535.tistory.com/106">https://lee1535.tistory.com/106</a></p>
</li>
<li><p>플라이웨이트 패턴과 String Constant Pool에 대한 내용</p>
<p><a href="https://it-mesung.tistory.com/183">https://it-mesung.tistory.com/183</a></p>
</li>
<li><p>나무 예제에 대한 자세한 내용</p>
<p><a href="https://m.blog.naver.com/2feelus/220669069127">https://m.blog.naver.com/2feelus/220669069127</a></p>
</li>
<li><p>싱글톤과의 비교
 <a href="https://medium.com/remember/flyweight-pattern-f02c771a72fb">https://medium.com/remember/flyweight-pattern-f02c771a72fb</a></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[디자인 패턴] Mediator 패턴]]></title>
            <link>https://velog.io/@hoit_98/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Mediator-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@hoit_98/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Mediator-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Tue, 10 Aug 2021 08:40:29 GMT</pubDate>
            <description><![CDATA[<h3 id="mediator중재자-패턴">Mediator(중재자) 패턴</h3>
<p>​    객체간의 상호작용을 캡슐화하여 하나의 중재자 클래스가 이를 처리하고 분산시켜주는 패턴이다.</p>
<h3 id="👿-문제상황">👿 문제상황</h3>
<pre><code> 회원가입을 기능을 만드려고 합니다. 아래 두 가지 기능을 넣고 싶습니다.    </code></pre><pre><code>1. 빈칸이 있는 채로 버튼을 누르면 채워달라고 경고문을 텍스트로 출력하고 싶습니다.
2. 빈칸이 없는 채로 버튼을 누르면 회원 가입이 완료 됐다는 이미지를 띄우고 싶습니다.</code></pre><p><img src="https://images.velog.io/images/hoit_98/post/8a0202a8-969e-4a6d-a6d3-7a6179f28a6c/image.png" alt=""></p>
<hr>
<p><img src="https://images.velog.io/images/hoit_98/post/780de460-6d7f-4cb2-8b78-945a43702333/image.png" alt=""></p>
<p>문제 1) 
우리는 버튼기능에 빈칸이 있는 경우, 없는 경우 각자 다르게 구현해야 합니다.
그 안에 텍스트박스에 대한 내용, 이미지 로딩에 대한 내용을 모두 연관지어야 합니다.</p>
<p>문제 2) 해당 버튼의 로직은 다른 페이지에서는 재활용하기 어렵습니다. 왜냐면 위 두 기능과 연관되기 때문입니다.</p>
<h3 id="😇-어떻게-해결할까">😇 어떻게 해결할까</h3>
<p>​    그렇다면 버튼 클릭이라는 이벤트를 전달받아 경고문 텍스트박스와 이미지 로더에 따로 그 이벤트를 전달하면 어떨까요?</p>
<p><img src="https://images.velog.io/images/hoit_98/post/9c5dabdd-d4b5-4053-812a-48d7fb46ccd0/image.png" alt=""></p>
<p>버튼은 버튼 클릭만, 텍스트박스는 텍스트만, 이미지로더는 이미지로딩 기능만 담당하면 됩니다! </p>
<p>그래서 다른 페이지에서도 버튼, 경고문, 이미지로더를 각자 사용할 수 있습니다.</p>
<p>이렇게 버튼, 텍스트박스, 이미지 로더가 각자 자기 역할만 수행할 수 있도록, 그 사이의 상호작용을 전달하고 조절해주도록 중재자를 설정해주는 것이 Mediator 패턴입니다!</p>
<p>​    </p>
<h3 id="🤔-구현을-어떻게-할까">🤔 구현을 어떻게 할까</h3>
<ol>
<li><p>컴포넌트들 구현</p>
<ul>
<li>버튼 - 클릭 이벤트 발생시키고 메디에이터에 노티</li>
<li>텍스트 - 텍스트 띄움 </li>
<li>이미지- 이미지 로딩하고 띄움)</li>
</ul>
</li>
<li><p>Mediator (조정해주기)</p>
<ul>
<li><p>클릭 이벤트 받음</p>
</li>
<li><p>클릭 이벤트 발생시 경우 처리 -&gt; 텍스트나 이미지에 일 시키기</p>
</li>
</ul>
</li>
</ol>
<h3 id="📜-코드로-살펴볼까요">📜 코드로 살펴볼까요!</h3>
<ol>
<li>Mediator <pre><code class="language-java">//Mediator 인터페이스 선언 
//Mediator 인터페이스 선언 
// - 모든 Mediator는 전달 받는 기능인 notify가 필요하다고 구현
public interface Mediator {
 void notify(Component sender, String event);
}
</code></pre>
</li>
</ol>
<p>//회원가입에서 사용될 Mediator
public class ConcreteMediator implements Mediator{
    private Button button;
    private TextBox textBox;
    private ImageLoader imageLoader;</p>
<pre><code>//Mediator에서 사용할 컴포넌트를 연결, 컴포넌트들에 Mediator를 연결
public ConcreteMediator(Button button, TextBox textBox, ImageLoader imageLoader) {
    this.button = button;
    this.button.setMediator(this);

    this.textBox = textBox;
    this.textBox.setMediator(this);

    this.imageLoader = imageLoader;
    this.imageLoader.setMediator(this);
}

//각 컴포넌트들에게 noti가 왔을 때 처리해줄 내용
@Override
public void notify(Component sender, String event) {
    if(sender== button &amp;&amp; event == &quot;click&quot;){ //버튼 클릭이벤트 발생시
        if(textBox.getText()==&quot;&quot;)){//텍스트 비어있는지 확인
            textBox.warning();//비어있으면 경고
        }else{
            imageLoader.ImageLoad();//있으면 이미지 로드
        }
    }
}</code></pre><p>}</p>
<pre><code>
2. 컴포넌트들
```java
//컴포넌트 부모 클래스
public class Component {
    protected Mediator mediator;

    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }
}


//버튼 -&gt; 클릭 이벤트 발생
public class Button extends Component {
    @Override
    public void setMediator(Mediator mediator) {
        super.setMediator(mediator);
    }

    public void click(){
        System.out.println(&quot;[버튼 클릭] \n&quot;);
        super.mediator.notify(this,&quot;click&quot;);
    }
}

//이미지 로더 -&gt; 이미지 로드 후 이미지 띄움
public class ImageLoader extends Component{

    @Override
    public void setMediator(Mediator mediator) {
        super.setMediator(mediator);
    }

    public void ImageLoad(){
        System.out.println(&quot;이미지 로딩 \n&quot;);
        System.out.println(&quot;=========img========&quot;);
        System.out.println(&quot;\n&quot;);
        System.out.println(&quot;회원 가입을 축하드립니다!&quot;);
        System.out.println(&quot;\n&quot;);
        System.out.println(&quot;====================&quot;);
    }
}

//텍스트 박스 -&gt; 인풋 또는 경고문만 출력
public class TextBox extends Component{
    String text;
    @Override
    public void setMediator(Mediator mediator) {
        super.setMediator(mediator);
    }

    public String getText() {
        return text;
    }

    public void warning(){
        System.out.println(&quot;=========TEXT========&quot;);
        System.out.println(&quot;공란을 채워주세요.&quot;);
        System.out.println(&quot;=====================\n&quot;);
    }

    public void input(String input){
        text = input;
        System.out.println(&quot;=========TEXT========&quot;);
        System.out.println(input);
        System.out.println(&quot;====================\n&quot;);
    }
}</code></pre><pre><code class="language-java">   //컴포넌트 부모 클래스
   public class Component {
       protected Mediator mediator;

       public void setMediator(Mediator mediator) {
           this.mediator = mediator;
       }
   }


   //버튼 -&gt; 클릭 이벤트 발생
   public class Button extends Component {
       @Override
       public void setMediator(Mediator mediator) {
           super.setMediator(mediator);
       }

       public void click(){
           System.out.println(&quot;[버튼 클릭] \n&quot;);
           super.mediator.notify(this,&quot;click&quot;);
       }
   }

   //이미지 로더 -&gt; 이미지 로드 후 이미지 띄움
   public class ImageLoader extends Component{

       @Override
       public void setMediator(Mediator mediator) {
           super.setMediator(mediator);
       }

       public void ImageLoad(){
           System.out.println(&quot;이미지 로딩 \n&quot;);
           System.out.println(&quot;=========img========&quot;);
           System.out.println(&quot;\n&quot;);
           System.out.println(&quot;회원 가입을 축하드립니다!&quot;);
           System.out.println(&quot;\n&quot;);
           System.out.println(&quot;====================&quot;);
       }
   }

   //텍스트 박스 -&gt; 인풋 또는 경고문만 출력
   public class TextBox extends Component{
       String text;
       @Override
       public void setMediator(Mediator mediator) {
           super.setMediator(mediator);
       }

       public String getText() {
           return text;
       }

       public void warning(){
           System.out.println(&quot;=========TEXT========&quot;);
           System.out.println(&quot;공란을 채워주세요.&quot;);
           System.out.println(&quot;=====================\n&quot;);
       }

       public void input(String input){
           text = input;
           System.out.println(&quot;=========TEXT========&quot;);
           System.out.println(input);
           System.out.println(&quot;====================\n&quot;);
       }
   }</code></pre>
<ol start="3">
<li>Main<pre><code class="language-java">import java.util.Scanner;
</code></pre>
</li>
</ol>
<p>public class Main {
    public static void main(String[] args) {</p>
<pre><code>    Scanner scanner = new Scanner(System.in);
    Button button = new Button();
    TextBox textBox = new TextBox();
    ImageLoader imageLoader = new ImageLoader();
    Mediator mediator = new ConcreteMediator(button,textBox,imageLoader);

    //정보 입력받기
    System.out.println(&quot;=====회원가입========&quot;);
    System.out.println(&quot;아이디 입력 : &quot;);
    String input = scanner.nextLine();
    //텍스트 박스에 입력
    textBox.input(input);
    //버튼 클릭하기
    button.click();
}</code></pre><p>}</p>
<pre><code>


### 💻 결과


 1. 입력 없을 때
```shell
=====회원가입========
아이디 입력 : 

=========TEXT========

====================

[버튼 클릭]

=========TEXT========
공란을 채워주세요.
==================== </code></pre><ol start="2">
<li>입력 있을 때<pre><code class="language-shell">=====회원가입========
아이디 입력 : 
Loopy
=========TEXT========
Loopy
====================
</code></pre>
</li>
</ol>
<p>[버튼 클릭]</p>
<p>이미지 로딩 </p>
<p>=========img========</p>
<p>회원 가입을 축하드립니다!</p>
<p>====================</p>
<pre><code>


------

### 💕 참고한 링크들

1. 기본적인 패턴 설명 (리팩토링 그루)
https://refactoring.guru/design-patterns/mediator

2. 리팩토링 그루 해석글, 컴포넌트 예시

   https://joycestudios.tistory.com/44

3. 더 상세한 컴포넌트 예시(체크박스, 버튼, 텍스트박스로 로그인 dialog 구현)

   https://ocwokocw.tistory.com/m/111

4. 디자인 패턴 설명 영상(얄코)

   https://www.youtube.com/watch?v=q3_WXP9pPUQ</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210810]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210810</link>
            <guid>https://velog.io/@hoit_98/TIL-20210810</guid>
            <pubDate>Tue, 10 Aug 2021 07:51:52 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-today-i-learn">🤔 Today I Learn!</h2>
<ol>
<li><p>MYSQL의 Scale-Out</p>
<ul>
<li>Master(읽,쓰,수정 다 가능)- Slave(읽기만 가능) 구성</li>
<li>Slave를 늘려 Scale-Out</li>
</ul>
</li>
<li><p>Docker</p>
<ul>
<li><p>프로그램이랑 필요한 기타 소프트웨어를 하나의 <strong>패키지</strong>로 만드는 플랫폼</p>
</li>
<li><p>Docket Image : <strong>패키지</strong>를 소프트웨어 파일 시스템 형태로 만든거
Docker Container :  docker image를 실행시킨것</p>
</li>
<li><p>docker 사용
  docker 설치 
  -&gt; docker Registry(docker hub)에서 image 다운로드 
  -&gt; 실행 -&gt; docker 컨테이너 생성, 사용</p>
</li>
<li><p><strong>내 OS에 영향 안주고 소프트웨어 동작하게 해줌</strong></p>
</li>
</ul>
</li>
</ol>
<p>Q. 궁금한 점  </p>
<ul>
<li>데이터의 정합성
  데이터가 모순없이 일관되게 일치해야한다.(데이터들의 값이 서로 일치해야 한다.)
  -&gt; 다른 테이블에 같은 값이 들어가는 경우, foregin key 등을 사용해서 둘을 명확히 일치 시켜줘야 함으로 이해</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20210809]]></title>
            <link>https://velog.io/@hoit_98/TIL-20210809</link>
            <guid>https://velog.io/@hoit_98/TIL-20210809</guid>
            <pubDate>Mon, 09 Aug 2021 16:48:41 GMT</pubDate>
            <description><![CDATA[<p>🤔 Today I Learn</p>
<ul>
<li><p>Persistence Layer</p>
</li>
<li><p>Star schema: 데이터를 논리적 단위로 나눠 저장하고 필요시 조인하는 방식</p>
</li>
<li><p>Denormalized schema: 데이터를 나누지 않고 그냥 원하는 데이터들을 모아서 때려 넣고 이를 반복하는 방식 -&gt;스토리지 낭비는 좀 있지만 조인이 필요없음</p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>