<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>boo_choo.log</title>
        <link>https://velog.io/</link>
        <description>부추가 좋아요</description>
        <lastBuildDate>Wed, 18 May 2022 06:01:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>boo_choo.log</title>
            <url>https://velog.velcdn.com/cloudflare/boo_choo/e7245fda-b6fb-4599-ab38-5e423a73d713/ef00b061ee5aefc1721e1830317ab473%20(1).jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. boo_choo.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/boo_choo" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[인텔리제이 빌드 오류]]></title>
            <link>https://velog.io/@boo_choo/%EC%9D%B8%ED%85%94%EB%A6%AC%EC%A0%9C%EC%9D%B4-%EB%B9%8C%EB%93%9C-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@boo_choo/%EC%9D%B8%ED%85%94%EB%A6%AC%EC%A0%9C%EC%9D%B4-%EB%B9%8C%EB%93%9C-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Wed, 18 May 2022 06:01:36 GMT</pubDate>
            <description><![CDATA[<p>java: package jdk.nashorn.internal.objects.annotations does not exist
java: package sun.security.util does not exist</p>
<p>SVN에서 체크아웃 받아서 빌드하는데 계속 오류가 나길래 다 지우고 다시 받고를 반복하기를 수십번..</p>
<p>구글링을 해봐도..아무리 생각해도 이건 컴파일 오류고 jdk설치가 안됐다는건데.. 설치도 제대로 해놓은 마당에 이런 오류가 뜨니까 어이가 없을뿐.. 어차피 이클립스로도 잘만되고 그냥 포기할까 하다가..</p>
<p><a href="https://programmerah.com/solved-error-3-31-java-package-jdk-nashorn-internal-ir-does-not-exist-37129/">이 글</a>발견</p>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/4984dfb5-8611-40fe-9748-d9e2d2c7c1c3/image.png" alt=""></p>
<p>SDK부분이 왜 인지는 모르겠지만 아마존 코레토 어쩌고로 설정돼있어서 이런 오류가 난거였다 하..아놔..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RealGrid]]></title>
            <link>https://velog.io/@boo_choo/RealGrid</link>
            <guid>https://velog.io/@boo_choo/RealGrid</guid>
            <pubDate>Wed, 11 May 2022 06:18:39 GMT</pubDate>
            <description><![CDATA[<div class="MuiPaper-root MuiCard-root jss1683 MuiPaper-elevation2 MuiPaper-rounded"><div class="MuiCardHeader-root"><div class="MuiCardHeader-content"><span class="MuiTypography-root MuiCardHeader-title MuiTypography-h5 MuiTypography-displayBlock"><h3 class="MuiTypography-root jss1458 MuiTypography-h3" style="margin: 0px; font-weight: 500;"><a class="anchor-link" id="getvalue-itemindex-field"></a>getValue<span class="jss1686">(</span>itemIndex<span class="jss1684">:</span><i>number</i><span class="jss1687">,</span>field<span class="jss1684">:</span><i>string </i>|<i> number</i><span class="jss1686">)</span><span class="jss1684">:</span><i>any</i></div><hr class="MuiDivider-root MuiDivider-light"><div class="MuiCardContent-root jss1690"><div><p>행의 순서와 데이터 필드 인덱스로 지정되는 데이터셀의 값을 반환한다.</p></div><div><h4>매개변수</h4><ul><li><strong>itemIndex: </strong> <i>number</i> <p>행의 순서</p></li><li><strong>field: </strong> <i>string </i>|<i> number</i> <p>필드 인덱스 또는 필드명</p></li></ul></div><div><h4>반환값</h4>타입: <i>any</i><p>데이터 필드의 값</p></div><div><h4>상세 설명</h4><p>지정한 행이 수정중인 경우 수정된 값을 반환한다. 편집이 완료되지 않은 셀의 값은 가져올수 없다.</p></div><div><h4>예제 코드</h4> <p></p><pre class=" language-js"><code class=" language-js">gridView<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token string">'UnitPrice'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p></p></div></div></div>

<p>HTML 렌더러
HTML 셀 렌더러는 셀에 표시되는 데이터를 HTML형태로 표현할 수 있습니다. 표시하고 싶은 데이터를 inline HTML의 형태로 return 하면 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day3]]></title>
            <link>https://velog.io/@boo_choo/Day3</link>
            <guid>https://velog.io/@boo_choo/Day3</guid>
            <pubDate>Wed, 11 May 2022 05:33:13 GMT</pubDate>
            <description><![CDATA[<p>콜백함수</p>
<blockquote>
<p><a href="https://bigtop.tistory.com/35">https://bigtop.tistory.com/35</a></p>
</blockquote>
<p>jsonp?(JSON with Padding)</p>
<blockquote>
<p><a href="https://blog.kingbbode.com/26">https://blog.kingbbode.com/26</a>
<a href="https://js2prince.tistory.com/entry/Ajax-JSONP%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-Cross-Domain-%ED%95%B4%EA%B2%B0">https://js2prince.tistory.com/entry/Ajax-JSONP%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-Cross-Domain-%ED%95%B4%EA%B2%B0</a></p>
</blockquote>
<p>자바 스크립트는 서로 다른 도메인에 대한 요청을 보안상 제한한다. 이 정책을 동일근원정책(Same-Origin Policy, SOP) 정책이라고 하며, 이러한 정책으로 인해 생기는 이슈를 cross-domain 문제라고 한다. 
데이터를 호출하는 도메인과 데이터를 반환하는 도메인의 주소가 다르면 Ajax를 이용할 수 없다.
이를 해결하기 위한 방법 중 하나가 jsonp</p>
<p>selectpicker(&#39;refresh&#39;);?</p>
<blockquote>
<p><a href="https://qiita.com/zinbe/items/45c41251d9b2644f3431">https://qiita.com/zinbe/items/45c41251d9b2644f3431</a></p>
</blockquote>
<p>JS로 속성 변경할 경우, 미반영될 가능성을 미연에 방지하기 위함</p>
<p>typeahed js &lt; 자동완성 플러그인</p>
<blockquote>
<p><a href="https://programmer93.tistory.com/59">https://programmer93.tistory.com/59</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[출근 둘째날]]></title>
            <link>https://velog.io/@boo_choo/%EC%B6%9C%EA%B7%BC-%EB%91%98%EC%A7%B8%EB%82%A0</link>
            <guid>https://velog.io/@boo_choo/%EC%B6%9C%EA%B7%BC-%EB%91%98%EC%A7%B8%EB%82%A0</guid>
            <pubDate>Tue, 10 May 2022 05:11:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://kaludin.tistory.com/39">https://kaludin.tistory.com/39</a></p>
</blockquote>
<p>validate() : 룰 정하기
valid() : 룰 실행하기</p>
<blockquote>
<p><a href="https://coder-question-ko.com/cq-ko-blog/611">https://coder-question-ko.com/cq-ko-blog/611</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[출근 첫째날]]></title>
            <link>https://velog.io/@boo_choo/%EA%B3%B5%EB%B6%80</link>
            <guid>https://velog.io/@boo_choo/%EA%B3%B5%EB%B6%80</guid>
            <pubDate>Mon, 09 May 2022 07:46:15 GMT</pubDate>
            <description><![CDATA[<ol>
<li>api 그룹 설정 : @Tag</li>
</ol>
<p>name : 태그의 이름
description : 태그에 대한 설명</p>
<p>Tag에 설정된 name이 같은 것 끼리 하나의 api 그룹으로 묶습니다.
주로 Controller Class 나 Controller Method 영역에 설정합니다.</p>
<pre><code>import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = &quot;user&quot;, description = &quot;사용자 API&quot;)
@RequestMapping(value = &quot;${demo.api}/users&quot;)
@RestController
public class UserController {
  ...
}</code></pre><p>UserController 에 @Tag를 설정해 보았습니다.</p>
<img src="https://user-images.githubusercontent.com/31076826/87741120-ad486b80-c81e-11ea-954c-6ccc7243273e.PNG"> 
@Tag 애너테이션 설정을 완료한 후 Swagger UI 화면입니다.
태그명과 설명이 각 태그에 설정되었습니다.

<ol start="3">
<li>api 상세 정보 설정 : @Operation</li>
</ol>
<p>summary : api에 대한 간략 설명
description : api에 대한 상세 설명
responses : api Response 리스트
parameters : api 파라미터 리스트</p>
<p>애너테이션으로 api 동작에 대한 명세를 작성하는 애너테이션으로, Controller method에 설정합니다.</p>
<p>Swagger UI가 fold상태일때도 간략히 확인할 수 있는 간략정보는 summary에 작성하고, 필요에 따라 상세 정보를 표기하고자 한다면 description에 설명을 추가하면 됩니다.
responses는 아래에서 설명할 @ApiResponse 리스트들 설정하는 요소입니다.
parameters는 path, query, header, cookie 등의 형태로 들어오는 파라미터에 대한 정보를 설정하는 요소입니다.</p>
<pre><code>@GetMapping(&quot;/{id}&quot;)
@Operation(summary = &quot;회원 조회&quot;, description = &quot;id를 이용하여 user 레코드를 조회합니다.&quot;)
public ResponseEntity&lt;? extends BasicResponse&gt; select(
    @Parameter(description = &quot;user 의 id&quot;) @PathVariable(&quot;id&quot;) long id) {
  ...
}
</code></pre><p>위의 코드는 summary와 description을 설정한 회원조회 api입니다.</p>
<img src="https://user-images.githubusercontent.com/31076826/87741124-ae799880-c81e-11ea-94de-eb6304ad5321.PNG">
@Operation 설정 후, Swagger UI에 메서드에 대한 설명글이 추가되었습니다.

<blockquote>
<p><a href="https://blog.jiniworld.me/91#a01">https://blog.jiniworld.me/91#a01</a></p>
</blockquote>
<blockquote>
<p><a href="https://intrepidgeeks.com/tutorial/simplify-spring-jdbc-templates">https://intrepidgeeks.com/tutorial/simplify-spring-jdbc-templates</a></p>
</blockquote>
<p>jsp  &gt; ajax &gt; controller &gt; dao ( jdbc templates &gt; listforquery 실행 &gt; sql 실행 결과 반환 ) &gt; controller &gt; ajax &gt; jsp</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바와 jsp로 캘린더 만들기 下]]></title>
            <link>https://velog.io/@boo_choo/%EC%9E%90%EB%B0%94%EC%99%80-jsp%EB%A1%9C-%EC%BA%98%EB%A6%B0%EB%8D%94-%EB%A7%8C%EB%93%A4%EA%B8%B0-%E4%B8%8B</link>
            <guid>https://velog.io/@boo_choo/%EC%9E%90%EB%B0%94%EC%99%80-jsp%EB%A1%9C-%EC%BA%98%EB%A6%B0%EB%8D%94-%EB%A7%8C%EB%93%A4%EA%B8%B0-%E4%B8%8B</guid>
            <pubDate>Sat, 16 Apr 2022 14:18:44 GMT</pubDate>
            <description><![CDATA[<p>전글에 이어서.. </p>
<pre><code>CalendarVO[][] contents_data_arr = new CalendarVO[32][4];
        if (contents_list.isEmpty() != true) {
            int j = 0;
            for (int i = 0; i &lt; contents_list.size(); i++) {

                int date = Integer.parseInt(String.valueOf(contents_list.get(i).getCustom_date()).substring(
                        String.valueOf(contents_list.get(i).getCustom_date()).length() - 2,
                        String.valueOf(contents_list.get(i).getCustom_date()).length()));

                // 저장되어 있는 총 컨텐츠 갯수가 1개가 넘을 경우
                if (i &gt; 0) {

                    // i번째 컨텐츠 등록 날짜와 i-1번째 컨텐츠 등록 날짜를 비교
                    int date_check = Integer.parseInt(String.valueOf(contents_list.get(i - 1).getCustom_date())
                            .substring(String.valueOf(contents_list.get(i - 1).getCustom_date()).length() - 2,
                                    String.valueOf(contents_list.get(i - 1).getCustom_date()).length()));
                    // i번째 컨텐츠 등록 날짜와 i-1번째 컨텐츠 등록 날짜가 같을 경우
                    // 즉, 같은 날에 등록된 컨텐츠 갯수가 1개 이상일 경우
                    if (date_check == date) {
                        j = j + 1;
                        contents_data_arr[date][j] = contents_list.get(i);
                        // 같은 날에 등록된 컨텐츠 갯수가 1개일 경우
                    } else {
                        j = 0;
                        contents_data_arr[date][j] = contents_list.get(i);
                    }
                    // // 저장되어 있는 총 컨텐츠 갯수가 1개일 경우
                } else {
                    contents_data_arr[date][j] = contents_list.get(i);
                }
            }
        }

        // 실질적인 달력 데이터 리스트에 데이터 삽입 시작
        // 일단 시작 인덱스까지 아무것도 없는 데이터 삽입
        for (int i = 1; i &lt; Integer.parseInt(String.valueOf(today_info.get(&quot;start&quot;))); i++) {
            calendarData = new DateUtil(null, null, null, null, null);
            dateList.add(calendarData);
        }

        // 날짜 삽입
        for (int i = Integer.parseInt(String.valueOf(today_info.get(&quot;startDay&quot;))); i &lt;= Integer
                .parseInt(String.valueOf(today_info.get(&quot;endDay&quot;))); i++) {
            CalendarVO[] contents_data_arr3 = new CalendarVO[4];
            contents_data_arr3 = contents_data_arr[i];

            // 특정 날짜가 오늘에 해당할 경우 value에 &#39;today&#39;라는 값을 넣어준다.
            if (i == Integer.parseInt(String.valueOf(today_info.get(&quot;today&quot;)))) {
                calendarData = new DateUtil(String.valueOf(dateData.getYear()), String.valueOf(dateData.getMonth()),
                        String.valueOf(i), &quot;today&quot;, contents_data_arr3);
            } else {
                calendarData = new DateUtil(String.valueOf(dateData.getYear()), String.valueOf(dateData.getMonth()),
                        String.valueOf(i), &quot;normal_date&quot;, contents_data_arr3);
            }
            dateList.add(calendarData);
        }

        // 달력 빈 곳 빈 데이터로 삽입
        int index = 7 - dateList.size() % 7;

        if (dateList.size() % 7 != 0) {

            for (int i = 0; i &lt; index; i++) {
                calendarData = new DateUtil(null, null, null, null, null);
                dateList.add(calendarData);
            }
        }</code></pre><p>적절히 주석을 달아놔서 크게 어려운 부분은 없을 텐데, 아마 [32][[4]의 2차원 배열을 생성하는 부분이 의아할 것이다.
사실 별 의미 없다 [32]는 한 달의 일(DATE)수를 의미하고 [4]는 같은 날짜에 등록할 수 있는 최대 컨텐츠 갯수이다. (4로 한건 별 의미 없다 코드 원작성자 분이 최대 등록 갯수를 4개로 지정하셨길래 그냥 나도 굳이 수정하지 않은것 뿐)</p>
<p>이걸 왜 이렇게 하냐. 순수하게 달력만을 그릴 때, 루프를 돌며 며칠인지, 무슨 요일인지, TODAY여부 등의 여러가지 정보를 담은 순수한 날짜 정보만 담겨있는 달력 배열을 만들게 된다. 그리고 나중에 컨텐츠 정보를 추가하게 되는건데 만약에 [32]라는 길이가 정해져 있는 배열이 아니라 List를 이용한 유동적 길이를 가지는 배열에 정보를 담고 같이 합치게 된다면 엄청난 오류가 발생한다.
해당 날짜에 상관 없이 배열의 길이상, 위치상 1에 해당하게 되니 이를 달력 배열과 합치게 된다면 무조건 1~4일에 컨텐츠가 들어가게 되는 것이다. 
따라서 길이를 [32]로 고정시켜 놓고 루프를 돌면서 해당 날짜(DATE)에 컨텐츠 정보가 같이 들어갈 수 있게 하는 것.. 말이 중구난방이라 이해가 될 지는 모르겠지만.. 하여튼 대충 그런 이유이다.</p>
<p>어쨌뜬 위의 과정을 통해 하루하루마다 날짜의 정보와 컨텐츠의 정보가 담겨있는 배열이 완성된다!
이제 이걸 뷰(jsp)에 뿌려주기만 하면 완성. 코드 원작성자 분이 너무나도 친절하시게 jsp의 코드까지 작성해주셔서 자세한 설명은 생략하고 컨텐츠 정보가 출력되는 부분만 언급하겠다.</p>
<pre><code>&lt;div style=&quot;display: flex; justify-content: center;&quot;&gt;
                                                    &lt;!-- 제일 처음 등록된 컨텐츠 하나만 보이도록 begin값과 end값 지정--&gt;
    &lt;c:forEach var=&quot;contents_list&quot; items=&quot;${dateList.contents_data_arr}&quot; varStatus=&quot;status&quot; begin=&quot;0&quot; end=&quot;0&quot;&gt;
        &lt;c:if test=&quot;${!empty contents_list.poster_path }&quot;&gt;
            &lt;a href=&quot;${pageContext.request.contextPath}/contents/detail.do?contents_type=${contents_list.contents_type }&amp;contents_num=${contents_list.contents_num}&quot;&gt;
                &lt;div style=&quot;width: 100px; position: relative;&quot;&gt;
                    &lt;!-- 포스터가 출력되는 부분 --&gt;
                    &lt;img src=&quot;${contents_list.poster_path }&quot; style=&quot;width: 100%; max-height: 140px;&quot;&gt;
                &lt;/div&gt;
            &lt;/a&gt;
        &lt;/c:if&gt;
    &lt;/c:forEach&gt;
    &lt;!-- + 아이콘을 만들기 위한 부분 --&gt;
                &lt;!-- 등록되어 있는 컨텐츠가 2개 이상일 경우, 최초 한번만 + 아이콘이 생성될 수 있도록 begin값과 end값 지정 --&gt;
    &lt;c:forEach var=&quot;contents_list&quot; items=&quot;${dateList.contents_data_arr}&quot; varStatus=&quot;status&quot; begin=&quot;1&quot; end=&quot;1&quot;&gt;
    &lt;!-- 길이가 4로 고정되어있는 배열이기 때문에 등록되어 있는 컨텐츠가 없어도 반복문을 돌기 때문에 공란이 출력되지 않도록 컨텐츠가 있는 경우에만 보여지도록 조건 설정 --&gt;
        &lt;c:if test=&quot;${!empty contents_list.poster_path }&quot;&gt;
                                            &lt;!-- 이미지 위에 +아이콘이 출력될 수 있도록 transform을 이용해 위치를 고정시켜준다 --&gt;
            &lt;div class=&quot;dropdown&quot; data-hover=&quot;dropdown&quot; style=&quot;position: absolute; transform: translate(125%, 370%);&quot;&gt;
                &lt;!-- + 아이콘을 누르면 드롭다운 토글이 실행된다 --&gt;
                &lt;a class=&quot;btn dropdown-toggle&quot; href=&quot;#&quot; role=&quot;button&quot; id=&quot;dropdownMenuLink&quot; data-bs-toggle=&quot;dropdown&quot; aria-expanded=&quot;false&quot; style=&quot;border-radius: 100%; background: white; padding: 3px 8px; opacity: .7;&quot;&gt;
                &lt;span style=&quot;font-size: 14px; color: #74747b;&quot;&gt;+&lt;/span&gt;&lt;/a&gt;
        &lt;/c:if&gt;
        &lt;!-- + 아이콘 마우스 오버 및 클릭시 보여지게 되는 부분 --&gt;
        &lt;ul class=&quot;dropdown-menu&quot; aria-labelledby=&quot;dropdownMenuLink&quot;&gt;
            &lt;c:forEach var=&quot;contents_list&quot; items=&quot;${dateList.contents_data_arr}&quot; varStatus=&quot;status&quot; begin=&quot;1&quot; end=&quot;3&quot;&gt;
                &lt;c:if test=&quot;${!empty contents_list.poster_path }&quot;&gt;
                    &lt;li&gt;&lt;a class=&quot;dropdown-item&quot; href=&quot;${pageContext.request.contextPath}/contents/detail.do?contents_type=${contents_list.contents_type }&amp;contents_num=${contents_list.contents_num}&quot;&gt;
                            &lt;div style=&quot;width: 100px; position: relative;&quot;&gt;
                                &lt;img src=&quot;${contents_list.poster_path }&quot; style=&quot;width: 100%; max-height: 140px;&quot;&gt;
                            &lt;/div&gt;
                        &lt;/a&gt;&lt;/li&gt;

                &lt;/c:if&gt;
            &lt;/c:forEach&gt;
    &lt;/c:forEach&gt;
    &lt;/ul&gt;
&lt;/div&gt;</code></pre><p>위의 과정을 통해서 짜잔 완성
<img src="https://velog.velcdn.com/images/boo_choo/post/87bddfe8-ed85-4b5f-8049-9360358517cd/image.gif" alt="">적절히 css를 수정해서 호버 및 클릭 효과를 줘봤다
역시 프론트엔드가 제일 어렵다.. 휴</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바와 jsp로 캘린더 만들기 上]]></title>
            <link>https://velog.io/@boo_choo/%E3%85%87%E3%84%BB</link>
            <guid>https://velog.io/@boo_choo/%E3%85%87%E3%84%BB</guid>
            <pubDate>Fri, 15 Apr 2022 13:55:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/boo_choo/post/728cf95a-51f9-43f6-8ca8-e02912dd8f58/image.gif" alt="">대충 이런 느낌의 기능이다.</p>
<p><a href="https://velog.io/@boo_choo/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%BA%98%EB%A6%B0%EB%8D%94-%EA%B8%B0%EB%8A%A5">전 글</a>에서 insert한 데이터들을 마이 프로필 - 캘린더 페이지에서 확인하는 기능이다.
짤에는 지난달 전달 밖에 안보여줬지만 더 예전, 더 과거의 달력도 열람할 수 있다. 해당 날짜에 등록된 컨텐츠의 포스터가 출력되며 해당 날짜에 2개 이상 컨텐츠가 등록된다면 포스터 오른쪽 하단에 + 아이콘이 생기며 마우스 오버 및 클릭시 드롭다운 형식으로 나머지 컨텐츠들의 포스터가 보이는 기능이다.</p>
<p>내가 맡았던 기능은 아니었는데 어쩌다보니 내가 구현하게 됐다. 원래 담당자였던 조원은 구글 캘린더나 풀 캘린더 등의 API를 사용해서 구현하려고 했었는데 내가 생각하기에는 아무리 생각해도 API를 사용하는 의미가 없고 오히려 우리가 구현하려는 기능을 구현하지 못할 것 같아서 정말 미안하지만 내가 API 사용을 포기하자고 했다. 지금생각해도 넘 미안하군.. 어쨌든 각설하고..</p>
<p>자바의 calendar 클래스와 jsp의 for:each 반복문을 이용해서 구현했다. <a href="https://blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=canghun13&logNo=221090906941">이글</a>을 참조해서 만들었는데 자바 부분만 이해하는데 반나절이 걸렸다. 그래도 덕분에 너무 간편하게 구현할 수 있었습니다.. 감사합니다..</p>
<p>원글 작성자 분은 순수히 날짜만 계산하는 DateUtil클래스를 별도로 두고, Controller와 뷰(jsp) 이렇게 세개를 적절히 활용해서 구현하셨는데 조원을 이해하기 위해 따로 주석을 많이 달아놨던 부분은 아래와 같다.</p>
<p>먼저 DateUtil 클래스의 일부</p>
<pre><code>public Map&lt;String, Object&gt; today_info(DateUtil dateData) {
        Map&lt;String, Object&gt; today_Data = new HashMap&lt;String, Object&gt;();

        // 날짜 함수를 이용하기 위한 Calendar 클래스의 인스턴스 호출
        Calendar cal = Calendar.getInstance();

        // getter setter를 이용해서 지정된 연도와 월의 값이 있을 경우 캘린더 값을 초기 설정해준다
        cal.set(Integer.parseInt(dateData.getYear()), Integer.parseInt(dateData.getMonth()), 1);

        int startDay = cal.getMinimum(java.util.Calendar.DATE);
        int endDay = cal.getActualMaximum(java.util.Calendar.DAY_OF_MONTH);
        int start = cal.get(java.util.Calendar.DAY_OF_WEEK);

        // 오늘 날짜에 해당하는 연도와 월 등의 정보를 알기 위한 호출
        Calendar todayCal = Calendar.getInstance();

        // 연도의 표시 형식 지정
        SimpleDateFormat ysdf = new SimpleDateFormat(&quot;yyyy&quot;);
        SimpleDateFormat msdf = new SimpleDateFormat(&quot;M&quot;);

        // 현재 연도를 위에 지정한 yyyy의 형식으로 int 타입 변수에 저장
        int today_year = Integer.parseInt(ysdf.format(todayCal.getTime()));
        int today_month = Integer.parseInt(msdf.format(todayCal.getTime()));

        // 지정한 연도와 달이 있을 경우
        int search_year = Integer.parseInt(dateData.getYear());
        int search_month = Integer.parseInt(dateData.getMonth()) + 1;

        int today = -1;
        // 지정한 연도와 달이 현재 연도와 달과 일치할 경우
        if (today_year == search_year &amp;&amp; today_month == search_month) {
            SimpleDateFormat dsdf = new SimpleDateFormat(&quot;dd&quot;);
            today = Integer.parseInt(dsdf.format(todayCal.getTime()));
        }

        search_month = search_month - 1;

        Map&lt;String, Integer&gt; before_after_calendar = before_after_calendar(search_year, search_month);

        today_Data.put(&quot;start&quot;, start);
        today_Data.put(&quot;startDay&quot;, startDay);
        today_Data.put(&quot;endDay&quot;, endDay);
        today_Data.put(&quot;today&quot;, today);
        today_Data.put(&quot;search_year&quot;, search_year);
        today_Data.put(&quot;search_month&quot;, search_month + 1);
        today_Data.put(&quot;before_year&quot;, before_after_calendar.get(&quot;before_year&quot;));
        today_Data.put(&quot;before_month&quot;, before_after_calendar.get(&quot;before_month&quot;));
        today_Data.put(&quot;after_year&quot;, before_after_calendar.get(&quot;after_year&quot;));
        today_Data.put(&quot;after_month&quot;, before_after_calendar.get(&quot;after_month&quot;));

        // sql문 조건 설정을 위한 시작 날짜와 끝 날짜 값 설정
        this.db_startDate = String.valueOf(search_year) + &quot;-&quot; + String.valueOf(search_month + 1) + &quot;-&quot;
                + String.valueOf(startDay);
        this.db_endDate = String.valueOf(search_year) + &quot;-&quot; + String.valueOf(search_month + 1) + &quot;-&quot;
                + String.valueOf(endDay);

        // 인자값으로 사용하기 위해 Map에 넣어준다
        today_Data.put(&quot;db_startDate&quot;, db_startDate);
        today_Data.put(&quot;db_endDate&quot;, db_endDate);

        return today_Data;
    }</code></pre><p>초반에 저 db_startDate와 db_endDate의 역할이 뭔가 했는데 매퍼사용을 위해 sql문을 작성하고 실행해보면서 알게됐다.
위의 두 값이 없으면 만약에 2022년 4월 16일에 컨텐츠를 등록했다고 하더라도 모든 연/월 16일에 컨텐츠가 계속해서 출력되는 치명적인 오류가 발생한다.
db_startDate와 db_endDate의 설정으로 연/월의 조건체크가 가능한 것.</p>
<p>위와 같이 작성한 DateUtil 클래스는 컨트롤러에서 이렇게 사용된다.</p>
<pre><code>public String calendar(Model model, HttpSession session, HttpServletRequest request, DateUtil dateData) {

        // 날짜 함수를 이용하기 위한 Calendar 클래스의 인스턴스 호출
        Calendar cal = Calendar.getInstance();

        DateUtil calendarData;

        // 따로 지정한 연도와 월이 없는경우 현재 연도와 월을 set
        if (dateData.getDate().equals(&quot;&quot;) &amp;&amp; dateData.getMonth().equals(&quot;&quot;)) {
            dateData = new DateUtil(String.valueOf(cal.get(Calendar.YEAR)), String.valueOf(cal.get(Calendar.MONTH)),
                    String.valueOf(cal.get(Calendar.DATE)), null, null);
        }

        Map&lt;String, Object&gt; today_info = dateData.today_info(dateData);
        List&lt;DateUtil&gt; dateList = new ArrayList&lt;DateUtil&gt;();

        String db_startDate = String.valueOf(today_info.get(&quot;db_startDate&quot;));
        String db_endDate = String.valueOf(today_info.get(&quot;db_endDate&quot;));

        // 본 컨텐츠 내역 불러와서 저장
        ArrayList&lt;CalendarVO&gt; contents_list = calenderService.selectList(mem_num, db_startDate, db_endDate, dateData);</code></pre><p>맨 마지막 문장이 mapper실행을 위한 service 호출이다.
아래가 mapper문장인데 어쩌다보니 많이 길어졌다. </p>
<pre><code>@Select(
&quot;SELECT 
    TO_CHAR(custom_date, &#39;YYYY-MM-DD&#39;) custom_date, 
    contents_num, 
    contents_type, 
    poster_path 
FROM dcontents_cal 
WHERE mem_num=#{mem_num} 
    AND custom_date BETWEEN #{db_startDate} AND #{db_endDate} &quot;
)
public ArrayList&lt;CalendarVO&gt; selectList(
@Param(&quot;mem_num&quot;) Integer mem_num, 
@Param(&quot;db_startDate&quot;) String db_startDate, 
@Param(&quot;db_endDate&quot;) String db_endDate, 
DateUtil dateData);</code></pre><p>@param의 역할은 여러개의 인자를 받을 경우에 각각을 연결시켜주는 역할</p>
<pre><code>1. 뷰에서 날짜 정보를 가져옴 
2-1. (특정 날짜의 달력을 열람하고 있을 경우)
해당 달력의 연/월/일의 값을 db_startDate와 db_endDate에 입력
2-2. (특정 날짜의 달력을 열람하고 있는게 아닐 경우)
오늘에 해당하는 연/월/일의 값을 db_startDate와 db_endDate에 입력
3. custom_date BETWEEN @@년 @@월 1일 AND @@년 @@월 말일 의 where절을 갖게 되는 것이다.</code></pre><p>위의 과정을 통해 특정 달의 컨텐츠 정보만 불러올 수 있는 것이다!
(아직 달력 그리는 코드는 보지도 못했다 다음글에 이어서..)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트를 이용한 캘린더 기능]]></title>
            <link>https://velog.io/@boo_choo/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%BA%98%EB%A6%B0%EB%8D%94-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@boo_choo/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%BA%98%EB%A6%B0%EB%8D%94-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Wed, 13 Apr 2022 16:38:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/boo_choo/post/00e99d6e-182c-41d9-b659-852af4804c3b/image.gif" alt="">
어떤 기능인지부터 간단하게 설명하자면, 컨텐츠 시청 날짜를 등록할 수 있는 기능이다. 보이는 바와 같이 모달창으로 달력을 띄워서 날짜를 선택 후, 등록버튼을 누르면 컨텐츠 및 날짜 정보가 DB에 저장되는 방식이다.</p>
<p>이 기능 구현과 관련해서도.. 우여곡절이 정말 많았는데.. 각설하고.. 먼저 모달을 위한 기본 설정부터 하도록 하겠다.</p>
<p>(모달 기능은 다른 조원이 구현한 기능을 긁어다 쓴거라서 대략적인 흐름밖에 모르겠다.)
모달로 구현하기 위해서는 먼저 모달창에 보여질 영역에 대해서만 작성한 별도의 jsp가 필요하(ㄴ것같)다. 
<img src="https://velog.velcdn.com/images/boo_choo/post/fa20e2b9-8950-4aee-8e91-bbdf8ecca811/image.png" alt="">이런식으로 별도의 jsp를 생성한 뒤,
<img src="https://velog.velcdn.com/images/boo_choo/post/49343439-57c5-477c-9ede-0c0dc5e6f80e/image.png" alt="">모달로 보여질 영역의 ①id를 지정해주고 jsp:include 기능을 통해서 아까 생성한 jsp파일을 불러와준다.
<img src="https://velog.velcdn.com/images/boo_choo/post/22638625-f46c-45cf-9d8a-abd608944630/image.png" alt="">불러올 페이지에서는 버튼 태그 안에 data-bs-target 속성에 ①의 id값을 부여해주고, data-bs-toggle의 속성으로는 modal을 지정해준다. 
해당 버튼을 누르면 사실은 존재하지만 JS와 CSS등으로 숨겨져있던 영역(①)이 모달창으로 보여지게 되는 것. 
자세한건 <a href="https://getbootstrap.kr/docs/5.0/components/modal/">여기서</a> 확인하면 되겠다.</p>
<p>모달 설정이 끝났다면 이제 자바스크립트로 달력을 그려보자.
<a href="https://im-developer.tistory.com/115">이 글</a>을 참고했다. 적게 일하고 많이 버세요..</p>
<p>이미 완벽하게 짜여져있는 코드라서 정말 일부분만 우리 프로젝트 방향성에 맞게 수정했다. 추가 및 수정된 기능은 아래와 같다.</p>
<blockquote>
<ol>
<li>캘린더 최초 등록</li>
<li>이미 등록되어있는 날짜가 있고, 그와 다른 날짜를 누르면 날짜가 변경된다.</li>
<li>이미 등록되어있는 날짜가 있고, 그와 같은 날짜를 누르면 날짜가 삭제된다.</li>
</ol>
</blockquote>
<p>2만 설명해보자면 먼저 contents controller에서 캘린더 등록 여부와 관련된 정보를 컨텐츠 디테일 페이지에 뿌려준다.</p>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/f40c8def-362d-485d-85fc-05b9b1c524d3/image.png" alt="">이를 JS로 확인하여</p>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/25f75b7c-cba9-42c1-b20d-8df30629a510/image.png" alt="">만약 값이 있을 경우 위에 보이는 것처럼 해당 날짜를 하늘색으로 색을 칠해준다.</p>
<p>간단히 말해서</p>
<ol>
<li>컨텐트 컨트롤러에서 컨텐츠 디테일에 뿌린 값을 </li>
<li>JS에서 연/월/일로 분리하여 별개의 변수에 저장 </li>
<li>열람 중인 달력의 연/월과 2에서 구한 연/월을 비교. </li>
<li>동일할 경우 날짜(date)를 그리는 반복문 안에 적절히 삽입. 
의 과정을 거친다.</li>
</ol>
<p>라고 간단히 말은 했지만.. 이 부분 또한 조원들에게 설명하는 것을 포기했다.
<img src="https://velog.velcdn.com/images/boo_choo/post/9dd88c94-965c-440b-91d5-f24ec0377232/image.png" alt="">뭐 어쨌든 해당 날짜에 dayMark라는 css 클래스를 추가하게 됨으로써 위와 같이 색이 바뀌게 되는 것.</p>
<p>이 상태에서 다른 날짜를 클릭하게 되면 아래와 같이 변하는데, <img src="https://velog.velcdn.com/images/boo_choo/post/02996127-99c6-43a9-89a9-4b0c04ddd926/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/604fffa5-2731-4a7f-b1fd-7ca6dfaa4fd8/image.png" alt="">
위와 같은 클릭 이벤트 액션을 만들어 주었기 때문이다. &#39;day-active&#39;가 새로 선택한 날짜, dayMark가 기존에 등록되어있는 날짜이다.</p>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/524bd250-0596-46c7-911c-9c5b4c9adec4/image.png" alt="">다시 백엔드쪽으로 넘어와서, 새로운 날짜를 선택한 뒤 등록 버튼을 누르면 ajax를 통해 캘린더 컨트롤러로 데이터를 넘기게 된다. 
선택되어 있는 날짜의 연/월/일 값들을 각각 뽑아내서 custom_date라는 변수에 저장한 뒤,</p>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/5b7ab01b-e33b-4ee2-b4c8-01cc3f0b7695/image.png" alt="">
다음과 같은 조건 체크를 통해 데이터를 넘기면 된다. insert와 delete도 같은 로직이라 이 둘은 설명을 생략하도록 한다. </p>
<p>이 기능 관련해서 정말 많은 고생을 했는데 역시 결과물은 너무나도 심플하다. 가슴아프게..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[별점 + 코멘트 기능]]></title>
            <link>https://velog.io/@boo_choo/c</link>
            <guid>https://velog.io/@boo_choo/c</guid>
            <pubDate>Wed, 13 Apr 2022 13:07:38 GMT</pubDate>
            <description><![CDATA[<p>왓챠피디아를 이용해봤다면 알 수 있을 것이다. 코멘트만 남길 수도, 별점만 남길 수도, 코멘트도 별점도 남길 수도 있다는 것을.. 다양한 경우의 수가 존재한다.</p>
<p>처음에 테이블 설계 때 막연하게 별점 테이블의 PK를 코멘트 테이블에서 FK로 받아서 둘을 연결시켜주면 되겠다고 생각만 했다. 
<img src="https://velog.velcdn.com/images/boo_choo/post/473c5cef-f8dd-4075-a2d6-bcf848898701/image.png" alt="">프로젝트 초반에 설계했던 ERD</p>
<p>공교롭게 별점, 코멘트 기능이 내가 담당하던 기능이 아니었어서 신경을 못썼었다. 일단 각자 insert는 되고 있었기에 잘 되고 있구나 생각하다가 내가 코멘트 목록 출력 부분을 구현하게 되면서 이상한 점을 깨닫게 됐다.</p>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/8c15bf0b-55ae-464f-830d-56c5ba81dd99/image.png" alt="">보이는 바와 특정 컨텐츠에 대한 각 유저마다의 코멘트 내용과 출력이 같이 출력되어야 하는데, 이 때 </p>
<pre><code>SELECT a.*, s.star FROM (
SELECT d.name, d.photo_name, c.* FROM dmember_detail d JOIN dcomment c ON d.mem_num=c.mem_num ) a 
LEFT OUTER JOIN dcontents_star s ON a.star_num = s.star_num 
WHERE a.contents_type=#{contents_type} and a.contents_num=#{contents_num} 
ORDER BY a.comment_num DESC</code></pre><p>위의 sql문을 사용해서 리스트를 불러오는데 분명 별점이 존재하는데도 불구하고 별점이 불러와지지 않는 것이다. 
sql문을 다시 살펴보고 sql developer에서 실행해보고 결과 값도 확인한 뒤에야 알 수 있었다.</p>
<p>기존에 설계했던 대로 코멘트 테이블과 별점 테이블을 FK로 엮어줘야 하는데 코멘트 테이블의 star_num 컬럼은 그대로 계속 null인 상태로 새로운 row가 생성되고 별점 테이블에서는 또 별도의 row가 생성되고 있으니 둘이 연결이 될 수 있을리가..
너무나도 당연한 이야기긴한데 그때 당시에는 눈에 잘 안보였다 ㅎ;</p>
<blockquote>
<ol>
<li>별점만 주는 경우 </li>
<li>코멘트만 작성하는 경우</li>
<li>별점을 먼저 준 뒤 코멘트를 작성하는 경우</li>
<li>코멘트를 먼저 작성한 뒤 별점을 주는 경우</li>
</ol>
</blockquote>
<p>1,2 는 문제가 없고 결국 3,4가 문제인 건데.. 조건체크와 insert 문장을 어떻게 작성해야 하나 고민 꽤나 했었다.
star_num의 존재 여부를 먼저 확인한 뒤, 없으면 새롭게 ①star_num을 생성. 그리고 나중에 생성되는 row에 기존에 생성된 ①의 star_num을 입력해주는 걸로 해결했다. 텍스트로만 보면 감이 안오니까 같이 봐보도록 하자.</p>
<h3 id="1-먼저-컨트롤러에서-컨텐츠-디테일-페이지로-데이터를-뿌릴-때-해당-컨텐츠에-유저가-평가한-별점-정보star_num-등를-같이-뿌려준다">1. 먼저 컨트롤러에서 컨텐츠 디테일 페이지로 데이터를 뿌릴 때, 해당 컨텐츠에 유저가 평가한 별점 정보(star_num 등)를 같이 뿌려준다.</h3>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/89f8e89d-4af5-4636-ab07-714dfa443ed7/image.png" alt="">해당 jsp에서는 hidden타입으로 어딘가에 저장해놨었다.</p>
<h3 id="2-그리고-코멘트-등록-이벤트-발생-시-ajax로-코멘트-컨트롤러로-star_num을-전달한다">2. 그리고 코멘트 등록 이벤트 발생 시, ajax로 코멘트 컨트롤러로 star_num을 전달한다.</h3>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/8cff1934-9b57-4d72-9d03-d1c556fe5eef/image.png" alt="">이 때 만약 star_num이 존재하지 않는다면 임의로 star_num 값을 0으로 명시해준다. (단순한 조건체크 용 값 때문에 어떤 값이든 상관없음)</p>
<h3 id="3-코멘트-컨트롤러로-이동해서-넘겨받은-데이터들을-매퍼로-또-전달한다">3. 코멘트 컨트롤러로 이동해서, 넘겨받은 데이터들을 매퍼로 또 전달한다.</h3>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/0fa35258-2381-4028-bb1e-1a6403a73010/image.png" alt="">xml 타입의 mapper를 사용할 것이고 이것저것 데이터를 넘길거라 map 객체에 넘겨받은 데이터를 저장해서 mapper로 전달했다.
약간 여기서부터 조원들에게 설명하기를 포기했다. </p>
<h3 id="4-insert문을-실행시켜보자">4. insert문을 실행시켜보자.</h3>
<pre><code>&lt;insert id=&quot;insertComment&quot; parameterType=&quot;map&quot; &gt;
        INSERT INTO dcomment 
                (
                    comment_num,
                    contents_num,
                    contents_type,
                    content,
                    mem_num,
                    reg_date,
                    star_num)
        VALUES 
                (
                    dcomment_seq.nextval,
                    #{commentVO.contents_num},
                    #{commentVO.contents_type},
                    #{commentVO.content},
                    #{commentVO.mem_num},
                    SYSDATE,
                &lt;if test=&quot;star_num == 0 &quot;&gt; &lt;!-- 별점이 없으면 일단 dcomment 테이블에서 임의의 star_num을 생성 → 나중에 별점을 추가할 경우 이 star_num과 연결할 용도 --&gt;
                    dcontents_star_seq.nextval
                &lt;/if&gt;
                &lt;if test=&quot;star_num &gt; 0 &quot;&gt; &lt;!-- 이미 등록된 별점이 있으면 dcontents_star 테이블에서 가져온 star_num을 입력 --&gt;
                    #{star_num}
                &lt;/if&gt;
                )
    &lt;/insert&gt;</code></pre><p>일반적인 insert문과 크게 다를 건 없고 중간에 if로 조건문이 추가적으로 있을 뿐이다. 앞서 ajax에서 설정했듯이 star_num이 0이라면 (기존에 평가한 별점이 없다면) 그동안은 별점 테이블에서만 사용되었던 별점 시퀀스를 이용해 (일단)새롭게 star_num을 생성해서 insert를 한다. 반면 star_num 값이 존재한다면 (기존에 평가한 별점이 있다면) 별점 테이블에 존재하는, 3의 과정에서 뷰단에 뿌려졌던 star_num값을 가져와서 똑같이 입력한 뒤 insert해주면 된다.</p>
<p>즉, 간단히 말해서 </p>
<ol>
<li>star_num이 존재하든 안하든 일단 contents controller에서 contents detail page로 값을 뿌려준다.</li>
<li>ajax를 통해 contents detail page에 뿌려졌던 star_num을 comment controller로 전달</li>
<li>controller에서 다시 mapper로 전달</li>
<li>if문을 통해 insert되는 value를 다르게 설정
의 과정을 반복하는 것.</li>
</ol>
<p>별점이 없었을 경우 : 코멘트 테이블에새로운 star_num이 생성됨 (나중에 별점을 줄 경우에 사용되어야 하기 때문에 임의로 일단 생성해준다)
별점이 있었을 경우 : 별점 테이블에 존재하는 star_num을 끌고와서 코멘트 테이블에 저장</p>
<p><img src="https://velog.velcdn.com/images/boo_choo/post/0146741b-1983-416c-8aad-ecd06c4fc03f/image.png" alt="">또한 제약조건도 변경해야했다. 코멘트를 먼저 작성하는 경우 star_num을 FK로 한다면 FK 무결성 제약조건에 위반돼서 어쩔 수 없이 FK 제약 조건을 삭제했다. 별개의 테이블이 되어버린 둘..</p>
<p>코멘트 작성의 케이스로 설명을 했는데 반대의 경우도 똑같이 진행하면 된다. 분명 좀더 깔끔한 코드가 있을 것 같은데.. 내 머리로는 이게 한계..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TMDB API로 정보 불러오기 下]]></title>
            <link>https://velog.io/@boo_choo/TMDB-API%EB%A1%9C-%EC%A0%95%EB%B3%B4-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0-%E4%B8%8B</link>
            <guid>https://velog.io/@boo_choo/TMDB-API%EB%A1%9C-%EC%A0%95%EB%B3%B4-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0-%E4%B8%8B</guid>
            <pubDate>Mon, 11 Apr 2022 15:23:53 GMT</pubDate>
            <description><![CDATA[<p align="center">
<img src="https://velog.velcdn.com/images/boo_choo/post/5a22d23c-9539-44ae-8d42-b7d4944b79ea/image.png">
<img src="https://velog.velcdn.com/images/boo_choo/post/e4db7d62-8249-4447-80d6-dad067c7dad0/image.png">
위와 같이 컨텐츠 타입별로 출력 결과를 다르게 하기 위한 작업
</p>

<pre><code>&lt;button    onclick=&quot;location.href=&#39;${pageContext.request.contextPath}/main/main.do?type=movie&#39;&quot;&gt;영화&lt;/button&gt;
&lt;button onclick=&quot;location.href=&#39;${pageContext.request.contextPath}/main/main.do?type=tv&#39;&quot;&gt;TV&lt;/button&gt;</code></pre><p>jsp에서 url을 통해 parameter 값을 컨트롤러로 전달할 수 있도록 한다.</p>
<h2 id="3-컨트롤러에서-데이터-처리하기">3. 컨트롤러에서 데이터 처리하기</h2>
<pre><code>@Controller
public class MainController {
    @RequestMapping(&quot;/main/main.do&quot;)
                                            // jsp에서 전달받은 컨텐츠 타입
    public ModelAndView main(@RequestParam(value = &quot;type&quot;, defaultValue = &quot;movie&quot;) String contents_type) {

        // 영화 정보를 불러오는 클래스 (이전 글에서 설명한 데이터 파싱 전용 클래스)
        GetInfoUtil util = new GetInfoUtil();

        List&lt;ContentsVO&gt; release_date = null;
        release_date = util.getInfoList(contents_type);

        // List에 담긴 ContentsVO를 날짜 내림차순으로 정렬
        Collections.sort(release_date, new SortByDate());

        ModelAndView mav = new ModelAndView();
        mav.setViewName(&quot;main&quot;);

        mav.addObject(&quot;release_date&quot;, release_date); // 최신 공개 순

        return mav;
    }
}</code></pre><br>

<h3 id="vo의-특정-값으로-list-정렬하기">VO의 특정 값으로 List 정렬하기</h3>
<p>또한 VO의 특정 값으로 List를 정렬하기 위해(평점 순, 공개 순) Comparator 인터페이스를 사용해서 클래스를 생성해줬다. → <a href="https://mine-it-record.tistory.com/279">참조</a> <br></p>
<pre><code>public class SortByDate implements Comparator&lt;ContentsVO&gt; {
    @Override
    public int compare(ContentsVO o1, ContentsVO o2) {
        Date first = o1.getRelease_date();
        Date second = o2.getRelease_date();
        return second.compareTo(first);
    }
}</code></pre><h2 id="4-뷰jsp에서-데이터-출력하기">4. 뷰(jsp)에서 데이터 출력하기</h2>
<p>그리고 jsp에서는 아래와 같이 c:forEach 를 사용해서 데이터를 반복해서 출력해주면 된다!</p>
<pre><code>&lt;ul&gt;
    &lt;li&gt;
        &lt;c:forEach var=&quot;release_date&quot; begin=&quot;0&quot; end=&quot;19&quot; step=&quot;1&quot; items=&quot;${release_date}&quot;&gt; // 한 row에 최대 20개까지만 출력되도록
            &lt;a href=&quot;${pageContext.request.contextPath}/contents/detail.do?contents_type=${release_date.contents_type }&amp;contents_num=${release_date.contents_num}&quot;&gt;
                &lt;div&gt;
                    &lt;div&gt;
                        &lt;img src=&quot;${release_date.poster_path }&quot;&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
                &lt;div&gt;
                    &lt;div&gt;${release_date.title }&lt;/div&gt;
                    &lt;div&gt;
                        &lt;fmt:formatDate value=&quot;${release_date.release_date }&quot; pattern=&quot;yyyy-MM-dd&quot; /&gt;
                    &lt;/div&gt;
                    &lt;div&gt;
                        &lt;span&gt;평균&lt;/span&gt;
                        &lt;span&gt;
                            ${Math.ceil((release_date.vote_average)/2*10)/10} &lt;/span&gt;
                    &lt;/div&gt;
                    &lt;div&gt;인기도 :
                        ${release_date.popularity }&lt;/div&gt;
                &lt;/div&gt;
            &lt;/a&gt;
        &lt;/c:forEach&gt;
    &lt;/li&gt;
&lt;/ul&gt;</code></pre><p align="center">
<img src="https://velog.velcdn.com/images/boo_choo/post/14ca2b21-6999-47c1-bc1b-7b963bd1a68f/image.gif">
결과물!
</p>]]></description>
        </item>
        <item>
            <title><![CDATA[TMDB API로 정보 불러오기 上]]></title>
            <link>https://velog.io/@boo_choo/TMDB-API%EB%A1%9C-%EC%A0%95%EB%B3%B4-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@boo_choo/TMDB-API%EB%A1%9C-%EC%A0%95%EB%B3%B4-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Mon, 11 Apr 2022 04:57:31 GMT</pubDate>
            <description><![CDATA[<p>디즈니 플러스에서 제공하는 컨텐츠 데이터를 불러오기 위해서 <a href="https://www.themoviedb.org/?language=ko">TMDB</a>에서 제공하는 오픈 API를 사용했다. 로컬 DB에 데이터를 저장하지 않고 데이터를 불러와서 바로 출력하는 방식은 처음이라 조금 헤맸었다.</p>
<h2 id="1-tmdb-api-사용법">1. TMDB API 사용법</h2>
<p>가입과 API KEY발급 설명은 <a href="https://www.sagein.net/703">이 글</a>을 참조해주세요.
가입과 KEY발급까지 끝났다면 <a href="https://developers.themoviedb.org/3/">https://developers.themoviedb.org/3/</a> &lt; 여기로 이동해줍니다.
TMDB API는 URL을 통해서 데이터를 얻는 방식으로 사용이 가능한데, 친절하게도 사용자가 얻고자 하는 데이터에 따라서 탭을 분할해서 자세히 설명해주고 있다. </p>
<p align="center">
<img src="https://velog.velcdn.com/images/boo_choo/post/fb706e72-0181-4f45-b95b-46287ab5c1f5/image.png">
내가 만약 특정 회사에서 제공하는 컨텐츠(우리 프로젝트에 있어서는 디즈니 플러스가 되겠다)의 목록을 알고 싶다면 DISCOVER탭을 선택해서 진행할 수 있다.</p>

<p align="center">
<img src="https://velog.velcdn.com/images/boo_choo/post/d959d968-3e66-48d1-bc63-cb03abfca3b8/image.png">
해당 페이지로 이동해서 하단을 보면 이런 화면이 보인다. 원하는 데이터를 불러올 때 필요하고, 사용할 수 있는 parameter들을 확인할 수 있다.</p>

<p align="center">
<img src="https://velog.velcdn.com/images/boo_choo/post/68a781a6-090e-45d4-a478-3bd62142767e/image.png">
사용할 조건들의 value값을 오른편의 입력칸에 입력해주면 정말 친절히도 TMDB측에서 알아서 쿼리 스트링으로 엮어서 url을 생성해준다.</p>

<p align="center">
<img src="https://velog.velcdn.com/images/boo_choo/post/852b7a4f-cc43-4907-a737-e427f88791e2/image.png">
이 때 적절한 parameter의 value값을 모르겠다면 헤더의 OAS버튼을 눌러보자.</p>
<p align="center">
<img src="https://velog.velcdn.com/images/boo_choo/post/677f7118-b3a7-477c-8e31-615f3c7cc841/image.png">
json 형식으로 API의 스펙이 명시되어 있다. Ctrl+F로 검색해서 필요한 정보를 찾아내면 된다. 내가 사용할 정보는 디즈니 플러스에 해당하는 provider_id의 value값! 337이라고 명시되어있는 것을 알 수 있다.</p>


<h2 id="2-자바로-json-타입-데이터-파싱하기">2. 자바로 json 타입 데이터 파싱하기</h2>
<p align="center">
위와 같은 방법을 통해 url을 완성시켜서 아무 웹 브라우저에서 실행시켜보자.
<br>(예를 들어 디즈니 플러스에서 제공하는 컨텐츠 중 한국에서 이용할 수 있는 영화 목록을 불러오기 위한 url은 다음과 같다 : https://api.themoviedb.org/3/discover/movie?api_key=본인API KEY값&with_watch_providers=337&watch_region=KR&language=ko&page=)
<img src="https://velog.velcdn.com/images/boo_choo/post/af78964d-a094-47a5-9f4d-c453628a3aad/image.png"></p>

<p>위의 url을 실행하면 위와 같은 보자마자 정신이 아득해지는 결과물을 얻을 수 있다. <br>
맨 첫줄부터 해석해보자면 &quot;page&quot; : 1의 의미는 총 검색 결과 중 첫 페이지를 출력하고 있으며, &quot;results&quot; : 의 의미는 해당 페이지의 검색 결과는 다음과 같다라는 의미다.<br> 
또한 대괄호로 크게 묶고 그 안에 또 다시 중괄호와 쉼표로 나열돼있는데 { }로 묶여져있는 텍스트들이 하나의 컨텐츠 정보가 담긴 하나의 객체라고 생각한다면 쉽다.</p>
<p align="center">
<img src="https://velog.velcdn.com/images/boo_choo/post/d410b268-47a7-499f-b788-ec58761830c3/image.png" >
이해를 돕기 위한 이미지.. 이걸 조원들한테 이해시키느라 정말 힘들었다.<br>
이제 또 하나의 컨텐츠 객체 안에서 내가 필요한 정보들만 적절히 뽑아내서 사용하면 된다!</p>

<p>프로젝트 시작 당시에는 파이썬을 배우지 않았어서 자바를 이용한 데이터 파싱 방식을 사용했다.
<a href="https://velog.io/@garam0410/Java-OPEN-API-%ED%8C%8C%EC%8B%B1%ED%95%98%EA%B8%B0-JSON">이 글</a>을 참조해서 코드를 작성했다. 너무너무 감사하고 적게 일하고 많이 버시길..</p>
<pre><code>public List&lt;ContentsVO&gt; getInfoList(String type) {
        int pages = getPages(type);

        DateFormat dateFormat = new SimpleDateFormat(&quot;yyyy-MM-dd&quot;);
        String date = &quot;0001-01-01&quot;;

        List&lt;ContentsVO&gt; infoList = null;
        List&lt;Integer&gt; genreList = null;

        try {

            infoList = new ArrayList&lt;ContentsVO&gt;();

            // 페이지 마다 루프를 돌며 값 추출 및 저장
            for (int i = 1; i &lt;= pages; i++) {
                String apiURL = &quot;https://api.themoviedb.org/3/discover/&quot; + type + &quot;?api_key=&quot; + KEY
                        + &quot;&amp;with_watch_providers=337&amp;watch_region=KR&amp;language=ko&amp;page=&quot; + i;
                URL url = new URL(apiURL);

                BufferedReader bf;

                bf = new BufferedReader(new InputStreamReader(url.openStream(), &quot;UTF-8&quot;));

                result = bf.readLine();

                JSONParser jsonParser = new JSONParser();
                JSONObject jsonObject = (JSONObject) jsonParser.parse(result);
                JSONArray list = (JSONArray) jsonObject.get(&quot;results&quot;);

                for (int j = 0; j &lt; list.size(); j++) {
                    ContentsVO vo = new ContentsVO();
                    JSONObject contents = (JSONObject) list.get(j);

                    vo.setContents_num(Integer.parseInt(String.valueOf(contents.get(&quot;id&quot;))));
                    vo.setContents_type(type);
                    vo.setOverview(contents.get(&quot;overview&quot;).toString());
                    vo.setVote_average(Float.parseFloat(String.valueOf(contents.get(&quot;vote_average&quot;))));
                    vo.setPopularity(Float.parseFloat(String.valueOf(contents.get(&quot;popularity&quot;))));

                    // 컨텐츠 타입(영화/시리즈)에 따라서 파싱 방법 다르게 설정
                    if (type.equals(&quot;movie&quot;)) {

                        // 시리즈일 경우 release_date를 key로 데이터 파싱
                        if (contents.get(&quot;release_date&quot;) == null || contents.get(&quot;release_date&quot;).equals(&quot;&quot;)) {
                            vo.setRelease_date(dateFormat.parse(date));
                        } else {
                            Date release_date = dateFormat.parse((String) contents.get(&quot;release_date&quot;));
                            vo.setRelease_date(release_date);
                        }
                        vo.setTitle(contents.get(&quot;title&quot;).toString());
                    } else if (type.equals(&quot;tv&quot;)) {

                        // 시리즈일 경우 first_air_date를 key로 데이터 파싱
                        if (contents.get(&quot;first_air_date&quot;) == null || contents.get(&quot;first_air_date&quot;).equals(&quot;&quot;)) {
                            vo.setRelease_date(dateFormat.parse(date));
                        } else {
                            Date first_air_date = dateFormat.parse((String) contents.get(&quot;first_air_date&quot;));
                            vo.setRelease_date(first_air_date);
                        }

                        // 시리즈일 경우 title이 아닌 name을 key로 데이터 파싱
                        vo.setTitle(contents.get(&quot;name&quot;).toString());
                    }
                    if (contents.get(&quot;poster_path&quot;) == null || contents.get(&quot;poster_path&quot;).toString().equals(&quot;&quot;)) {
                        vo.setPoster_path(&quot;&quot;);
                    } else {
                        vo.setPoster_path(contents.get(&quot;poster_path&quot;).toString());
                    }

                    // 장르 id를 List&lt;integer&gt; 형태로 저장 → 장르 비교를 위한 작업
                    JSONArray genre_list = (JSONArray) contents.get(&quot;genre_ids&quot;);
                    genreList = new ArrayList&lt;Integer&gt;();
                    for (int k = 0; k &lt; genre_list.size(); k++) {
                        genreList.add(Integer.parseInt(String.valueOf(genre_list.get(k))));
                    }
                    vo.setGenres(genreList);
                    infoList.add(vo);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return infoList;
    }</code></pre><p>배열 형태로 저장돼있는 각각의 객체들에서 해당 key 값에 해당하는 value 값을 추출해서 VO에 저장한다고 생각하면 간단하다!</p>
<p>...이어서</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[왓챠 피디아 클론 코딩 : 디즈니 피디아]]></title>
            <link>https://velog.io/@boo_choo/%ED%8C%8C%EC%9D%B4%EB%84%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%94%94%EC%A6%88%EB%8B%88-%ED%94%BC%EB%94%94%EC%95%84</link>
            <guid>https://velog.io/@boo_choo/%ED%8C%8C%EC%9D%B4%EB%84%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%94%94%EC%A6%88%EB%8B%88-%ED%94%BC%EB%94%94%EC%95%84</guid>
            <pubDate>Sun, 10 Apr 2022 12:24:53 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/boo_choo/post/52b6ceec-7205-4dd0-a065-c84252826b5d/image.png" width=60% style="margin:auto;">
<p>
  <a href="https://pedia.watcha.com/ko-KR">왓챠피디아</a>를 레퍼런스로 하여 클론 코딩하기. 간단히 말해서 컨텐츠의 범위를 디즈니 플러스에서 이용할 수 있는 컨텐츠로만 축소한 버전이라고 생각하면 되겠다.
</p><br>
<img src="https://velog.velcdn.com/images/boo_choo/post/65e9e43d-a4cd-464b-aa42-765ab9553ed2/image.gif" width=80% style="margin:10px auto;">
<p >
  실제 구현 페이지 >> <a href="http://www.disney-pedia.shop">디즈니 피디아</a><br>(현재 회원가입은 막아놓은 상태. ID/PW : boochoo로 로그인하여 조회만 가능)<br>
  깃허브 >> <a href="https://github.com/sujinee0505/Disney_Pedia_Sujin">깃허브</a>
</p>  
<hr>

<h2 id="프로젝트-개요">프로젝트 개요</h2>
<h3 id="🛠️-개발-환경">🛠️ 개발 환경</h3>
<ul dir="auto">
  <li>Tool : eGov Spring Framework</li>
  <li>DBMS : Oracle 11g</li>
  <li>Server : Apache Tomcat v9.0</li>
  <li>Language : JAVA / SQL / JSP / jQuery / JavaScript / HTML / CSS</li>
</ul>
</br>

<h3 id="🔥-프로젝트-진행">🔥 프로젝트 진행</h3>
<ul dir="auto">
  <li>진행 기간 : 2022/03/15 ~ 2022/04/11</li>
  <li>2022/03/15 ~ 2022/03/18 : 주제 선정 및 테이블 설계, Git Hub 연동</li>
  <li>2022/03/19 ~ 2022/04/03 : 기능 및 화면 구현</li>
  <li>2022/04/04 ~ 2022/04/10 : 오류 수정, UI 정리, 발표 준비</li>
  <li>2022/04/11 : 발표</li>
</ul>
</br>


<h3 id="🎥-왓챠피디아-클론-기능">🎥 왓챠피디아 클론 기능</h3>
<ul dir="auto">
  <li>'디즈니 플러스'에서 제공하는 컨텐츠의 정보를 확인할 수 있습니다.</li>
  <li>해당 컨텐츠에 평점과 코멘트를 작성할 수 있습니다.</li>
  <li>다른 사람들이 작성한 평점과 코멘트를 열람할 수 있습니다.</li>
  <li>다른 사람들이 작성한 코멘트에 좋아요를 할 수 있습니다.</li>
  <li>보고싶어요와 평점, 코멘트 기능을 통해 내가 아직 시청하지 못한 컨텐츠와 이미 시청한 컨텐츠를 구분하여 확인할 수 있습니다.</li>
  <li>프로필에서 내 정보 및 타유저 정보를 확인할 수 있습니다.</li>
</ul>
</br>

<h3 id="🆕-새롭게-추가된-기능">🆕 새롭게 추가된 기능</h3>
<ul dir="auto">
  <li>캘린더 기능을 통해 언제 해당 컨텐츠를 시청했는지 확인할 수 있습니다.</li>
  <li>채팅 기능을 통해 디즈니 플러스를 같이 이용할 사람들을 모집할 수 있습니다.</li>
  <li>내가 작성한 코멘트 뿐만 아니라 타유저가 작성한 코멘트 및 타유저가 좋아한 코멘트를 열람할 수 있습니다.</li>
</ul>
<hr>

<h2 id="🌿-내가-구현한-기능">🌿 내가 구현한 기능</h2>
<h3 id="프론트-엔드">프론트 엔드</h3>
<p> 
  거의 모든 페이지 프론트 엔드 제작 및 수정에 참여했기 때문에.. 처음부터 끝까지 담당했던 페이지는 아래와 같다.<br>
  ✔️ 헤더, 푸터, 메인, 검색, 컨텐츠 디테일 페이지<br>
</p>

<h3 id="로그인-로그아웃">로그인, 로그아웃</h3>
<p> 
  ✔️ 로그인 시 해당 페이지에 머물 수 있도록 설정<br>
  ✔️ 로그아웃 시 페이지별로 리다이렉트 페이지 별도 설정
</p>

<h3 id="메인-페이지">메인 페이지</h3>
<p> 
  ✔️ TMDB API를 활용하여 컨텐츠 정보 출력<br>
  ✔️ 컨텐츠 타입, 평균 평점, 코멘트 수(자체 DB 활용) 등의 기준으로 데이터 필터링<br>
  ✔️ 버튼으로 조작하는 횡스크롤<br>
</p>

<h3 id="검색-페이지">검색 페이지</h3>
<p> 
  ✔️ 데이터 필터링<br>
  ✔️ 검색 카테고리 별로 다른 화면 출력<br>
</p>

<h3 id="컨텐츠-상세-페이지">컨텐츠 상세 페이지</h3>
<p> 
  ✔️ 컨텐츠 정보 출력<br>
  ✔️ 보고싶어요 기능<br>
  ✔️ 캘린더 기능<br>
  ✔️ light box 플러그인을 활용한 이미지 캐러셀 팝업기능<br>
  ✔️ 코멘트 좋아요 기능<br>
</p>

<h3 id="코멘트-상세-페이지">코멘트 상세 페이지</h3>
<p> 
  ✔️ 컨텐츠 정보 및 코멘트 정보 출력<br>
  ✔️ 코멘트 좋아요 및 댓글 기능<br>
</p>

<h3 id="채팅-페이지">채팅 페이지</h3>
<p> 
  ✔️ ajax로 구현한 채팅 메시지 입출력 기능<br>
  ✔️ ajax로 구현한 모집현황 변경 기능<br>
</p>

<h3 id="마이-페이지">마이 페이지</h3>
<p> 
  ✔️ 내 프로필 및 타유저 프로필 열람 기능<br>
  ✔️ 캘린더 기능<br>
  ✔️ 마이 컨텐츠 조회 기능(보고싶어요 및 평가)<br>
  ✔️ 내가 작성한 코멘트 조회 및 수정,삭제 기능<br>
  ✔️ 내가 좋아한 코멘트 조회 및 좋아요 취소 기능<br>
</p>

<p>이 외에도 전반적으로 자잘한 기능 구현 및 오류 수정 담당.. </p>
<hr>

<h2 id="💪-후기-및-소감">💪 후기 및 소감</h2>
<ul dir="auto">
   <li>소통, 소통, 소통..</li>
    <span>
      프로젝트 초반 제일 힘들었던건 파이널때 갑자기 변경된 팀이었다.  두번의 프로젝트 동안 맞춰온 팀워크가 있었는데 이제와서 새롭게 합을 맞추려니 조금 어려움이 있었다.<br>
      각자가 그동안 배우고 해왔던 프로젝트의 방향과 방법이 다르다보니 처음에 이를 서로 공유하고 의견을 합치하는 데에도 꽤나 시간이 걸렸다. <br>
      큰 틀이든 세세한 부분이든 뭐든 확실히 정하고 진행해야 하는 나와, 일단 진행 먼저 하고 세세한 부분은 담당자가 알아서 하자는 다른 조원들간의 의견 불합치.. 다행히 팀원들이 내 의견을 따라줘서 적어도 '나'는 만족스럽게 진행할 수 있었다.
    </span><br>

<p><br><li>빠른 상황 판단과 과감한 포기의 중요성</li>
    <span>
      내가 얼마나 이 기능을 구현하기 위해 많은 시간과 노력을 쏟았는지와는 별개로 프로젝트의 진행과 완성을 위해서 과감히 포기할 줄도 알아야 한다는 점을 다시한번 느꼈다. 이를 위해서는 제대로 된 상황 판단이 받쳐줘야 가능하겠지. 본인이 판단이 서지 않는다면 다른 사람에게 도움을 청해서라도 판단을 내려야 할 필요가 있다.
    </span><br></p>
<p><br><li>기능 구현 관련</li>
    <span>
          <em>개념조차 모호했던 &#39;데이터 분석&#39;, &#39;데이터베이스 모델링&#39;의 중요성</em><br>
      API 활용 가능 여부도 불분명했고 마음이 급해서 초반에 제대로 데이터 분석을 못했던 것이 아쉽다. <br>
      좀 더 마음의 여유를 갖고 제대로 분석한 뒤 진행했다면 계속해서 테이블을 수정할 필요도 없었을 터.. 다음 프로젝트(가 있다면) 때는 좀 더 여유를 갖도록 하자.<br><br>
      <em>배움에는 끝이 없고 의미없는 실수와 과정은 없다.</em><br>
      지난 세미 프로젝트때 엄청난 고생을 했던 &#39;정렬&#39;기능.. 비록 내가 담당했던 기능은 아니지만 해당 조원에게 도움을 줄 수 있어서 너무 뿌듯했다. 이래서 여러번의 프로젝트 경험이 중요한 거구나 다시한번 느꼈다. 계속해서 쌓여가는 지식들. 까먹지 않도록 잘 정리해놔야지<br><br>
      <em>처음 해보는 코드 리뷰</em><br>
      지난 두번의 프로젝트를 같이 해왔던 팀은 다들 개인적인 성향이 강해서 본인이 맡은 담당한 부분만 구현하고 담당하지 않은 부분의 코드분석은 개인적으로 알아서 진행해왔었다.
     물론 이해가 어려운 부분이 있다면 담당자에게 개인적으로 물어보긴 했었지만.. <br>
      이번 팀은 그런 지난 팀 프로젝트 경험에서 얻어가는게 없었다는 의견이 있어서 각자 새롭게 구현한 기능이 생길 때 마다 코드리뷰를 하며 설명하는 시간을 갖자고 했다. 비록 그 코드리뷰는 거의 내 담당 발표시간이 되어버리긴 했지만 ㅎ..<br>
      <img src="https://velog.velcdn.com/images/boo_choo/post/cca04e30-5bd6-4dac-a31b-b6606c96d161/image.png" style="width : 70%; margin:10px auto;" alt="코드 리뷰를 위한 이미지" >
      코드 리뷰는 처음 해보는거라 초반에는 어떤식으로 해야할지도 막막하고 청자도 이해하기 힘들어 했었는데 역시나 해봐야 는다고 점점 실력이 좋아지는게 스스로 느껴졌다.<br>
      코드 리뷰를 준비하면서 왜? 라는 질문을 스스로 끊임없이 했다. 팀원들의 쉬운 이해를 위해 텍스트도 정리해보고 그림도 그려보고.. 솔직히 당시에는 이게 웬 고생이냐 싶었지만 결론적으로는 내가 더 얻어가는게 많았던 시간이었다. 너무너무 좋은 경험이되었다. 
    </span></p>
</ul>]]></description>
        </item>
    </channel>
</rss>