<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev_h_o.log</title>
        <link>https://velog.io/</link>
        <description>사람의 마음에 차 있는 너르고 크고 올바른 기운</description>
        <lastBuildDate>Sun, 15 Oct 2023 06:35:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev_h_o.log</title>
            <url>https://velog.velcdn.com/images/dev_h_o/profile/f1853e8a-9467-46c1-848f-2e05249fcc98/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev_h_o.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_h_o" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[서브라임(Sublime) InsertNums과 Increment Selection 자동으로 숫자 넣기]]></title>
            <link>https://velog.io/@dev_h_o/%EC%84%9C%EB%B8%8C%EB%9D%BC%EC%9E%84Sublime-InsertNums%EA%B3%BC-Increment-Selection-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EC%88%AB%EC%9E%90-%EB%84%A3%EA%B8%B0</link>
            <guid>https://velog.io/@dev_h_o/%EC%84%9C%EB%B8%8C%EB%9D%BC%EC%9E%84Sublime-InsertNums%EA%B3%BC-Increment-Selection-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EC%88%AB%EC%9E%90-%EB%84%A3%EA%B8%B0</guid>
            <pubDate>Sun, 15 Oct 2023 06:35:50 GMT</pubDate>
            <description><![CDATA[<p><code>InsertNums</code>과 <code>IncrementSelection</code>는 서브라임 텍스트 에디터를 사용할 때 숫자를 유용하게 다룰 수 있는 패키지</p>
<h1 id="✔️-insertnums">✔️ InsertNums</h1>
<p>깃헙 주소 :  <a href="https://github.com/jbrooksuk/InsertNums">github</a></p>
<p>숫자를 자동으로 삽입해주는 패키지로 주로 반복되는 행 드래스해서 숫자 일렬로 쫙 넣을 때 자주 사용하는 패키지</p>
<p>1부터 1씩 증가해서 넣을 수도 있고 1부터 원하는 숫자만큼 증가시킬 수도 있다.</p>
<h4 id="단축키">단축키</h4>
<ul>
<li>윈도우 와 리눅스 : <code>Ctrl+Alt+N</code></li>
<li>OSX : <code>⌘+⎇+N</code></li>
</ul>
<h4 id="사용-방법">사용 방법</h4>
<ol>
<li><p>서브라임에서 <code>Package Control:install Package</code> 접속
단축키 : <code>Ctrl+Shift+P</code> /<code>⌘+⇧+P</code>
<img src="https://velog.velcdn.com/images/dev_h_o/post/204551d5-d8cf-4bb3-a997-ed54b0cccb61/image.png" alt=""></p>
</li>
<li><p><code>InsertNums</code> 검색 후 다운로드<img src="https://velog.velcdn.com/images/dev_h_o/post/604088e4-52d4-4c0f-a554-d5f13fb76061/image.png" alt=""></p>
</li>
<li><p>입력을 원하는 줄 커서를 활성화 시킨 뒤 단축키로 <code>InsertNums</code> 호출</p>
</li>
</ol>
<ul>
<li>1부터 증가
<img src="https://velog.velcdn.com/images/dev_h_o/post/b48b2f91-4e4e-4294-9142-e1dda3eec371/image.png" alt=""></li>
<li>3부터 2씩 증가
<img src="https://velog.velcdn.com/images/dev_h_o/post/0c9db8d6-ced1-457b-a1bd-9ebb5dd3ee01/image.png" alt=""></li>
</ul>
<p>이런식으로 숫자에 규칙을 부여해서 원하는 줄에 원하는대로 숫자를 입력할 수 있는 게 편해서 자주 사용한다.</p>
<hr>

<h1 id="✔️-increment-selection">✔️ Increment Selection</h1>
<p>깃헙 주소 :  <a href="https://github.com/yulanggong/IncrementSelection">github</a></p>
<p><code>InsertNums</code>과 비슷하지만 이 녀석은 좀 더 단순하다.
활용 방법이 더 있을 수도 있지만.. 나는 그냥 1부터 숫자 증가시킬 때만 사용하는 편</p>
<h4 id="단축키-1">단축키</h4>
<ul>
<li>윈도우 와 리눅스 : <code>Ctrl+Alt+I</code></li>
<li>OSX : <code>⌘+⌃+I</code></li>
</ul>
<h4 id="사용-방법-1">사용 방법</h4>
<ol>
<li><code>Package Control:install Package</code>에 접속 후 <code>Increment Selection</code> 다운로드
<img src="https://velog.velcdn.com/images/dev_h_o/post/51ea755e-8774-4c10-90eb-cee3e1f183e7/image.png" alt=""></li>
</ol>
<ol start="2">
<li>입력을 원하는 줄 커서를 활성화 시킨 뒤 단축키로 <code>Increment Selection</code> 호출</li>
</ol>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/93a3248f-490e-458f-a911-677907db050e/image.png" alt=""></p>
<p><code>1부터 10까지</code> 단축키 한번으로 입력되었다.</p>
<h4 id="📅-date">📅 DATE</h4>
<p><code>2023.10.15 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[파이썬] 엑셀 파일형식 가져와서 적용하기(openpyxl 패키지 활용)]]></title>
            <link>https://velog.io/@dev_h_o/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%97%91%EC%85%80-%ED%8C%8C%EC%9D%BC%ED%98%95%EC%8B%9D-%EA%B0%80%EC%A0%B8%EC%99%80%EC%84%9C-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0openpyxl-%ED%8C%A8%ED%82%A4%EC%A7%80-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@dev_h_o/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%97%91%EC%85%80-%ED%8C%8C%EC%9D%BC%ED%98%95%EC%8B%9D-%EA%B0%80%EC%A0%B8%EC%99%80%EC%84%9C-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0openpyxl-%ED%8C%A8%ED%82%A4%EC%A7%80-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Mon, 18 Sep 2023 12:51:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev_h_o/post/898fbf3f-4e0c-4d8b-8cb6-d6399f203be5/image.png" alt=""></p>
<h2 id="1-문제">1. 문제</h2>
<p><code>pandas</code>를 활용해서 엑셀의 숫자 데이터를 읽어 데이터베이스에 업로드하는데 표시형식이 적용 안되는 문제가 발생했다.
함수를 적용하거나 셀 서식에서 소수점을 적용한 게 반영이 안 돼서 <code>openpyxl</code>로 패키지를 바꿔서 적용하였다.</p>
<h2 id="2-해결">2. 해결</h2>
<blockquote>
</blockquote>
<ol>
<li><p>openpyxl 라이브러리를 활용해서 엑셀파일-시트 읽기</p>
</li>
<li><p>표시형식을 적용하여 리스트에 저장한다. (for문)</p>
<ul>
<li>연번이 있는 행만 가져온다.</li>
<li>표시형식과 값 가져온다.</li>
<li>표시형식 적용해서 리스트에 넣는다.</li>
</ul>
<pre><code class="language-python">workbook = load_workbook(filename=path, data_only=True) #data_only=True 셀 값 가져오기
sheet = workbook[xl.sheet_names[sheet_i]]
</code></pre>
</li>
</ol>
<h1 id="11행부터-데이터와-표시-형식-가져오기">11행부터 데이터와 표시 형식 가져오기</h1>
<h1 id="행row_index-열col_index-데이터cell_value-표시형식cell_format">행:row_index, 열:col_index, 데이터:cell_value, 표시형식:cell_format</h1>
<h1 id="11행부터-데이터와-표시-형식-가져오기-1">11행부터 데이터와 표시 형식 가져오기</h1>
<p>res_cnt = 0
y = 0
for row_index, row in enumerate(sheet.iter_rows(min_row=11, values_only=True), start=11):
    xls_raw_data = []
    if row[0] is not None:  # 데이터가 있는 경우에만 가져오기
        for col_index, cell_value in enumerate(row, start=1):
            if 4 &lt;= col_index &lt;= 13:
                res_cnt += 1
                cell = sheet.cell(row=row_index, column=col_index)
                cell_format = cell.number_format</p>
<pre><code>            #표시형식 조건
            if &#39;0.0_&#39; in cell_format :
                cell_value = format(cell_value, &quot;.1f&quot;)

            if &#39;0.00&#39; in cell_format :
                cell_value = format(cell_value, &quot;.2f&quot;)

            xls_raw_data.append(cell_value)

            # 가져온 데이터와 표시 형식 출력
            print(f&quot;행 {row_index}, 열 {col_index}: 데이터: {cell_value}, 표시 형식: {cell_format}&quot;)


    xls_data = &quot;||&quot;.join(map(str,xls_raw_data)) </code></pre><pre><code>
데이터를 제대로 출력하는지 확인하기 위해 표시형식 조건을 if문으로 설정하였다. 현재 코드에서는 소수점 1자리~2자리까지만 캐치하는데 이후 수정본에서는 모든 표시형식을 다 반영하게끔 수정하였다.

```python
cell = sheet.cell(row=row_index, column=col_index)
cell_format = cell.number_format

# print(cell_format)

#표시형식 전부 적용
if (cell_format!=&quot;General&quot;):
    tmp1 = cell_format.split(&#39;)&#39;)
    tmp2 = tmp1[0].split(&#39;.&#39;)

    if (len(tmp2)&gt;1):
        tmp3 = tmp2[1]
        cell_value = format(cell_value, &quot;.&quot; + str(len(tmp3) -1) + &quot;f&quot;)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 마이페이지 결제내역 불러오기 #ajax]]></title>
            <link>https://velog.io/@dev_h_o/Spring-%EB%A7%88%EC%9D%B4%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B2%B0%EC%A0%9C%EB%82%B4%EC%97%AD-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0-ajax</link>
            <guid>https://velog.io/@dev_h_o/Spring-%EB%A7%88%EC%9D%B4%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B2%B0%EC%A0%9C%EB%82%B4%EC%97%AD-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0-ajax</guid>
            <pubDate>Mon, 14 Aug 2023 16:38:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev_h_o/post/917e2f8c-0ae2-4af4-a921-4fa9ee3fb38d/image.png" alt=""></p>
<h2 id="🫥-마이페이지에서-결제내역-불러오기">🫥 마이페이지에서 결제내역 불러오기</h2>
<ol>
<li>사용자가 결제를 하면 결제 정보가 데이터베이스<code>mpay</code>테이블에 저장된다.</li>
<li>사용자가 마이페이지-결제내역 페이지에 들어가면 세션을 활용하여 사용자의 회원번호로 데이터를 가져온다. (내역이 없을 경우 데이터가 없다는 내용 출력)</li>
<li>전체, 회원권, 일일권, PT 메뉴 선택에 따라 다른 결과를 보여준다.</li>
<li>결제내역이 많아질 경우 페이지가 넘어가도록 페이징 처리</li>
</ol>
<p>마이페이지의 결제내역 섹션은 위와 같은 프로세스로 작동하도록 설계하였다.</p>
<h2 id="💡-1-페이지-세팅">💡 1. 페이지 세팅</h2>
<h4 id="결제내역-메뉴-선택">결제내역 메뉴 선택</h4>
<pre><code class="language-html">&lt;select id=&quot;sel&quot;&gt;
  &lt;th:block th:if=&quot;${payType == null}&quot;&gt;
    &lt;option value=&quot;all&quot; selected&gt;전체&lt;/option&gt;
    &lt;option value=&quot;membership&quot;&gt;회원권&lt;/option&gt;
    &lt;option value=&quot;daily&quot;&gt;일일권&lt;/option&gt;
    &lt;option value=&quot;pt&quot;&gt;PT&lt;/option&gt;
  &lt;/th:block&gt;
  &lt;th:block th:unless=&quot;${payType == null}&quot;&gt;
    &lt;th:block th:switch=&quot;${payType}&quot;&gt;
      &lt;th:block th:case=&quot;gpay&quot;&gt;
        &lt;option value=&quot;all&quot;&gt;전체&lt;/option&gt;
        &lt;option value=&quot;membership&quot; selected&gt;회원권&lt;/option&gt;
        &lt;option value=&quot;daily&quot;&gt;일일권&lt;/option&gt;
        &lt;option value=&quot;pt&quot;&gt;PT&lt;/option&gt;
      &lt;/th:block&gt;
      &lt;th:block th:case=&quot;dgpay&quot;&gt;
        &lt;option value=&quot;all&quot;&gt;전체&lt;/option&gt;
        &lt;option value=&quot;membership&quot;&gt;회원권&lt;/option&gt;
        &lt;option value=&quot;daily&quot; selected&gt;일일권&lt;/option&gt;
        &lt;option value=&quot;pt&quot;&gt;PT&lt;/option&gt;
      &lt;/th:block&gt;
      &lt;th:block th:case=&quot;tpay&quot;&gt;
        &lt;option value=&quot;all&quot;&gt;전체&lt;/option&gt;
        &lt;option value=&quot;membership&quot;&gt;회원권&lt;/option&gt;
        &lt;option value=&quot;daily&quot;&gt;일일권&lt;/option&gt;
        &lt;option value=&quot;pt&quot; selected&gt;PT&lt;/option&gt;
      &lt;/th:block&gt;
    &lt;/th:block&gt;
  &lt;/th:block&gt;
&lt;/select&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/9b67a823-3a44-4c2d-9c47-869c6ed6f7e7/image.png" alt=""></p>
<p>이런식으로..! selected에 따라 다른 페이지로 이동이 돼서 케이스별로 메뉴를 전부 다 적어줘야했다..ㅠ</p>
<h4 id="결제내역">결제내역</h4>
<pre><code class="language-html">&lt;div id=&quot;content&quot; class=&quot;content&quot;&gt;
  &lt;div class=&quot;content-all&quot;&gt;
    &lt;table style=&quot;width: 100%&quot;, id=&quot;mtable&quot;&gt;
      &lt;tbody&gt;
      &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;결제일&lt;/th&gt;
        &lt;th&gt;헬스장&lt;/th&gt;
        &lt;th&gt;트레이너&lt;/th&gt;
        &lt;th&gt;이용기간&lt;/th&gt;
        &lt;th&gt;가격&lt;/th&gt;
        &lt;th&gt;후기&lt;/th&gt;
      &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody id=&quot;tb&quot;&gt;
      &lt;div th:unless=&quot;${#lists.isEmpty(mPList)}&quot;&gt;
        &lt;div th:each=&quot; MPitem,status: ${mPList}&quot;&gt;
          &lt;tr class=&quot;payNreview&quot;&gt;
            &lt;td th:text=&quot;${MPitem.mpaydate}&quot;&gt;&lt;/td&gt;
            &lt;td th:text=&quot;${MPitem.mpaygym}&quot;&gt;&lt;/td&gt;
            &lt;td th:text=&quot;${MPitem.trainername}&quot;&gt;&lt;/td&gt;
            &lt;td th:text=&quot;${MPitem.mpayperiod}&quot;&gt;&lt;/td&gt;
            &lt;td th:text=&quot;${#numbers.formatInteger(MPitem.mpayprice, 3, &#39;COMMA&#39;) + &#39;원&#39;}&quot;&gt;&lt;/td&gt;
          &lt;/tr&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div th:if=&quot;${#lists.isEmpty(mPList)}&quot;&gt;
        &lt;td colspan=&quot;6&quot;&gt;결제 내역이 없습니다.&lt;/td&gt;
      &lt;/div&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paging-area&quot;&gt;
  &lt;div class=&quot;paging&quot; th:utext=&quot;${paging}&quot;&gt;&lt;/div&gt;
&lt;/div&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/3e487172-7b6e-4d67-baa5-dc5445a3ab05/image.png" alt=""></p>
<p>결제내역은 테이블 형식으로 불러온다.
원래는 후기를 등록하거나 삭제하는 후기관련 코드도 있는데 너무 길어져서..생략!</p>
<h4 id="controller로-전송ajax">controller로 전송(ajax)</h4>
<pre><code class="language-html">&lt;script th:inline=&quot;javascript&quot;&gt;

  //셀렉트박스 선택
  $(&quot;#sel&quot;).change(function () {
    let select = $(&quot;#sel&quot;).val();
    console.log(select); //daily, membership, pt

    let mpayType = &quot;&quot;;
    if (select === &quot;daily&quot;) {
      mpayType = &quot;dgpay&quot;;
    } else if (select === &quot;membership&quot;) {
      mpayType = &quot;gpay&quot;;
    } else if (select === &quot;pt&quot;) {
      mpayType = &quot;tpay&quot;;
    }

    sendObj = { &quot;mpayType&quot;: mpayType,
      &quot;membernum&quot;: [[${member.membernum}]],
      &quot;pageNum&quot;: 1
    };
    console.log(sendObj);

    //controller에 전송(ajax)
    $.ajax({
      url: &quot;myPayList&quot;,
      type: &quot;post&quot;,
      data: JSON.stringify(sendObj),
      contentType: &quot;application/json&quot;,
      success: function (res) {
        let mPList = res.mPList;
        console.log(mPList);
        let paging = res.paging;
        console.log(paging);
        $(&quot;.paging&quot;).html(paging);
        let ritem = res.rList;
        console.log(ritem);
        // let mpaynumR = ritem.mpaynum;
        // console.log(mpaynumR);

        if (res != null &amp;&amp; mPList.length != 0) {
          // 서버에서 받은 데이터를 처리하고 출력하는 로직을 여기에 작성
          // 예시: 테이블의 tbody에 결과 데이터를 추가하는 방식으로 출력
          var tbody = $(&quot;#tb&quot;);
          console.log(tbody);
          tbody.empty(); // 기존 데이터 삭제
          for (var i = 0; i &lt; mPList.length; i++) {
            var mpayItem = mPList[i];
            var member = [[${session.member}]];
            var row = &quot;&lt;tr class=&#39;payNreview&#39;&gt;&quot; +
                    &quot;&lt;td&gt;&quot; + mpayItem.mpaydate + &quot;&lt;/td&gt;&quot; +
                    &quot;&lt;td&gt;&quot; + mpayItem.mpaygym + &quot;&lt;/td&gt;&quot; +
                    &quot;&lt;td&gt;&quot; + mpayItem.trainername + &quot;&lt;/td&gt;&quot; +
                    &quot;&lt;td&gt;&quot; + mpayItem.mpayperiod + &quot;&lt;/td&gt;&quot; +
                    &quot;&lt;td&gt;&quot; + mpayItem.mpayprice.toLocaleString(&#39;ko-KR&#39;) + &#39;원&#39; + &quot;&lt;/td&gt;&quot;;

            tbody.append(row);
          }

        } else {
          var tbody = $(&quot;#tb&quot;);
          tbody.empty(); // 기존 데이터 삭제
          var row = &quot;&lt;tr&gt;&quot; +
                  &quot;&lt;td colspan=&#39;5&#39;&gt;&quot; + &quot;결제 내역이 없습니다.&quot; + &quot;&lt;/td&gt;&quot; +
                  &quot;&lt;/tr&gt;&quot;;
          tbody.append(row);
        }

      },
      error: function (err) {
        console.log(err);
        alert(&quot;오류가 발생했습니다.&quot;);
      }
    });
  });


&lt;/script&gt;</code></pre>
<p>여기에도 후기등록 및 삭제 관련 이벤트가 포함되어 있는데 생략했다.</p>
<ol>
<li>결제내역(전체, 회원권, 일일권, PT) 선택값 전달<code>mpayType</code></li>
<li>페이지 넘버 전달</li>
<li>사용자 회원 번호 전달</li>
</ol>
<p>자바스크립트는 위와 같은 이벤트를 처리하고 ajax로 컨트롤러에 값을 넘겨준다.</p>
<p>그럼 이제 컨트롤러에서는 이 값들을 어떻게 처리할까?
<strong>컨트롤러 속으로<del>~</del> 고고! 고고!</strong></p>
<blockquote>
<p>(추억의 개콘 고고 예술속으로...)<img src="https://velog.velcdn.com/images/dev_h_o/post/170e3c5f-b52c-460b-a073-0a046791fa74/image.png" alt=""></p>
</blockquote>
<h2 id="💡-2-컨트롤러">💡 2. 컨트롤러</h2>
<h4 id="결제내역-가져오기">결제내역 가져오기</h4>
<pre><code class="language-java">//결제 내역 가져오기
@GetMapping(&quot;myPayList&quot;)
public ModelAndView mPay(SearchDto search, HttpSession session){
    log.info(&quot;myPayList()&quot;);

    mv = mServ.mPay(search,session);
    return mv;
}</code></pre>
<p>마이페이지에서 결제내역 메뉴를 눌렀을 때 회원정보를 기준으로 전체 결제내역을 보여주는 메소드</p>
<h4 id="메뉴-선택에-맞춰-결제내역-보여주기">메뉴 선택에 맞춰 결제내역 보여주기</h4>
<pre><code class="language-java">//결제내역 조회(회원권, 일일권, PT)
@PostMapping(&quot;myPayList&quot;)
@ResponseBody
public Map&lt;String, Object&gt; selectedMpay(@RequestBody SearchDto search){
    log.info(&quot;selectedMpay()&quot;);

    Map&lt;String, Object&gt; rmap = mServ.selectedMpay(search);

    return rmap;
}</code></pre>
<p>회원권, 일일권 등 메뉴를 선택했을 때 정보를 필터링하여 내보내는 메소드</p>
<h2 id="💡-3-서비스">💡 3. 서비스</h2>
<h4 id="전체-결제내역-가져오기">전체 결제내역 가져오기</h4>
<pre><code class="language-java">public ModelAndView mPay(SearchDto search, HttpSession session) {
    log.info(&quot;GetMypayList()&quot;);
    mv = new ModelAndView();

    int num = search.getPageNum();
    // limit 0, 5 - 1페이지

    //출력할 게시물 수가 설정되지 않으면 기본값(5)로 설정
    if (search.getListCnt() == 0) {
        search.setListCnt(listCnt);
    }
    if(num == 0) num = 1;
    search.setPageNum((num - 1) * search.getListCnt()); // 보여질 게시글 목록의 수

    //결제내역 가져오기
    List&lt;MPayDto&gt; mPList = mDao.GetMypayList(search);

    //회원정보 가져오기
    MemberDto member = mDao.selectMemberSearch(search);

    mv.addObject(&quot;mPList&quot;, mPList);
    mv.addObject(&quot;member&quot;, member);

    //페이징 처리
    search.setPageNum(num);
    String pageHtml = null;
    String payKind = null;
    if(search.getMpayType() == null || search.getMpayType().equals(&quot;&quot;)){
        pageHtml = getPaging(search);
    }else {
        pageHtml = getMpayPaging(search);
        payKind = search.getMpayType();
    }

    mv.addObject(&quot;paging&quot;, pageHtml);
    mv.addObject(&quot;payType&quot;, payKind);

    //세션에 필요 정보 저장(pageNum)
    session.setAttribute(&quot;pageNum&quot;, num);


    mv.setViewName(&quot;myPayList&quot;);
    return mv;
}</code></pre>
<p>전체 결제내역만 가져오는 건 비교적 간단하다.
페이징 처리하고, 결제내역 가져오고, 회원정보 가져오고...
필요한 것들 다 가져왔으면 <code>ModelAndView</code>에 담고..
세션에 페이징 넘버 저장하고..
뷰 설정하고 리턴!</p>
<p>그럼 이제 선택 옵션을 추가하면 어떻게 될까?</p>
<h4 id="메뉴-선택에-맞춰-결제내역-보여주기회원권-일일권-">메뉴 선택에 맞춰 결제내역 보여주기(회원권, 일일권 ..)</h4>
<pre><code class="language-java">//결제내역 조회(회원권, 일일권, PT)
public Map&lt;String, Object&gt; selectedMpay(SearchDto search) {
    log.info(&quot;selectedMpay()&quot;);

    List&lt;MPayDto&gt; mPList = null;
    Map&lt;String, Object&gt; rmap = new HashMap&lt;&gt;();

    int num = search.getPageNum();

    //출력할 게시물 수가 설정되지 않으면 기본값(5)로 설정
    if (search.getListCnt() == 0) {
        search.setListCnt(listCnt);
    }
    if(num == 0) num = 1;
    search.setPageNum((num - 1) * search.getListCnt());

    try{
        mPList = mDao.selectedMpay(search);
        rmap.put(&quot;mPList&quot;, mPList);
        //페이징
        search.setPageNum(num);
        String pageHtml = getMpayPaging(search);
        rmap.put(&quot;paging&quot;, pageHtml);

        }

    } catch (Exception e){
        e.printStackTrace();
    }
    return rmap;</code></pre>
<p>메뉴에 맞게 결제내역을 가져오는 건 살짝 더 복잡하다.
필요한 것들 가져오고 리턴시켜주는 건 동일한데,
<code>Map</code>으로 받아서 그 안에 결제내역, 페이징 넘버, 리뷰를 담아 리턴한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/9b67a823-3a44-4c2d-9c47-869c6ed6f7e7/image.png" alt="">
비동기방식 ajax로 데이터를 넘기기 때문에 셀렉트박스에서 메뉴를 선택하면 화면전환없이 실시간으로 메뉴에 맞게 결과가 바뀐다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/ec13f7c4-0709-41f7-9b2a-df841f0745dd/image.png" alt=""></p>
<p>결제한 적 없는 일일권을 선택했더니 정상적으로 결제 내역이 없다고 뜬다.</p>
<h2 id="💡-4-dao데이터베이스">💡 4. DAO(데이터베이스)</h2>
<pre><code class="language-sql">//회원 결제내역 가져오기
&lt;select id=&quot;GetMypayList&quot; resultType=&quot;MPayDto&quot; parameterType=&quot;SearchDto&quot;&gt;
    select * from MPay where membernum=#{membernum}
    &lt;if test=&quot;mpayType != null&quot;&gt;
        and mpaynum like concat(#{mpayType},&#39;%&#39;)
    &lt;/if&gt;
    LIMIT #{pageNum}, #{listCnt}
&lt;/select&gt;

//회원 정보 가져오기
&lt;select id=&quot;selectMemberSearch&quot; resultType=&quot;MemberDto&quot; parameterType=&quot;SearchDto&quot;&gt;
    select * from member where membernum=#{membernum}
&lt;/select&gt;

//메뉴 선택에 따라 결제내역 가져오기
&lt;select id=&quot;selectedMpay&quot; resultType=&quot;MPayDto&quot; parameterType=&quot;SearchDto&quot;&gt;
    select * from mpay
    where membernum = #{membernum} and mpaynum like concat(#{mpayType},&#39;%&#39;)
        LIMIT #{pageNum}, #{listCnt}
&lt;/select&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/003ece47-217e-448a-b94e-51e31e758ae3/image.png" alt="">
결제번호인 <code>mpaynum</code>은 운동권 종류에 따라 형식이 달라져서 운동권을 기준으로 <code>mpayType</code>을 설정해주었다.</p>
<hr>

<p>페이징과 리뷰 메소드 처리한 것도 포함되어야 하는데 여기에서는 결제내역 위주의 메소드를 보여주고, 다음 포스팅에서는 페이징과 리뷰 관련 메소드를 다루겠습니다.😊~</p>
<h4 id="📅-date">📅 DATE</h4>
<p><code>2023.08.14 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 포트원(아임포트)결제api 사용하기 #ajax]]></title>
            <link>https://velog.io/@dev_h_o/Spring-%ED%8F%AC%ED%8A%B8%EC%9B%90%EC%95%84%EC%9E%84%ED%8F%AC%ED%8A%B8%EA%B2%B0%EC%A0%9Capi-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-ajax</link>
            <guid>https://velog.io/@dev_h_o/Spring-%ED%8F%AC%ED%8A%B8%EC%9B%90%EC%95%84%EC%9E%84%ED%8F%AC%ED%8A%B8%EA%B2%B0%EC%A0%9Capi-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-ajax</guid>
            <pubDate>Fri, 11 Aug 2023 08:17:54 GMT</pubDate>
            <description><![CDATA[<h2 id="✏️-결제api-프로세스">✏️ 결제api 프로세스</h2>
<ol>
<li>사용자가 상품을 선택하고 결제하기 버튼을 누른다.</li>
<li>결제창 팝업창 작동</li>
<li>결제 처리</li>
<li>데이터베이스에 데이터 저장</li>
<li>사용자는 결제완료 페이지로 이동</li>
</ol>
<h2 id="✏️-사전준비">✏️ 사전준비</h2>
<h4 id="1-포트원아임포트-회원가입">1. 포트원(아임포트) 회원가입</h4>
<p>결제api 활용 포스팅을 찾아보면 대부분 아임포트 결제api를 사용하는데 아임포트가 포트원으로 바뀌면서 사용방법도 조금 달라졌다.
가이드를 보면서 진행하면 훨 수월하니 링크 첨부!</p>
<p><strong>◼︎ 포트원 결제연동 가이드</strong>
<a href="https://developers.portone.io/docs/ko/ready/readme">https://developers.portone.io/docs/ko/ready/readme</a></p>
<p><strong>◼︎ 아임포트 결제연동 가이드</strong>
<a href="https://github.com/iamport/iamport-manual/tree/master">https://github.com/iamport/iamport-manual/tree/master</a></p>
<p><strong>◼︎ 포트원 로그인 페이지</strong>
<a href="https://admin.portone.io/auth/signin">https://admin.portone.io/auth/signin</a>
회원가입은 위 링크에서 메일주소로 처리할 수 있다.</p>
<h4 id="2-결제-api-발급받기">2. 결제 api 발급받기</h4>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/ff1cba3d-754f-421d-a755-c2ea9b341d6b/image.png" alt=""></p>
<p>사이드바의 결제 연동 메뉴에서 식별코드, REST API KEY를 확인할 수 있고 결제대행사를 추가할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/250adc6e-3bd7-478c-91f2-06b8a61c5a85/image.png" alt=""></p>
<p>카카오페이랑 KG이니시스를 테스트모드로 추가했다.</p>
<h2 id="✏️-스프링에서-데이터-처리하기">✏️ 스프링에서 데이터 처리하기</h2>
<h4 id="💻-html-작성-ajax로-넘겨주기">💻 html 작성 (ajax로 넘겨주기)</h4>
<pre><code class="language-html">&lt;script src=&quot;https://cdn.iamport.kr/v1/iamport.js&quot;&gt;&lt;/script&gt;</code></pre>
<p>html 상단에 iamport 스크립트 추가</p>
<pre><code class="language-html">&lt;div class=&quot;kg_pay_btn&quot;&gt;
  &lt;button type=&quot;button&quot; th:onclick=&quot;kg_request_pay()&quot;&gt;결제하기&lt;/button&gt;
&lt;/div&gt;
&lt;div class=&quot;ka_pay_btn&quot;&gt;
  &lt;button type=&quot;button&quot; th:onclick=&quot;ka_request_pay()&quot;&gt;카카오페이&lt;/button&gt;
&lt;/div&gt;</code></pre>
<p>kg 이니시스 버튼과 카카오페이 버튼 각각 하나씩 생성 후
클릭 이벤트를 걸어주었다.</p>
<pre><code class="language-javascript">&lt;script th:inline=&quot;javascript&quot;&gt;

  function kg_request_pay() {
    //전달할 데이터
    var selectedDate = document.getElementById(&quot;datepicker&quot;).value;
    var selectedGoodsName = document.querySelector(&quot;.kg_pay_btn&quot;).getAttribute(&quot;data-name&quot;);
    var selectedGoodsPrice = document.querySelector(&quot;.kg_pay_btn&quot;).getAttribute(&quot;data-price&quot;);
    var selectedGoodsNum = document.querySelector(&quot;.kg_pay_btn&quot;).getAttribute(&quot;data-goodsnum&quot;);

    // datepicker가 선택되지 않은 경우 알림창 띄움
    if (selectedDate == &quot;&quot;) {
      alert(&quot;운동 시작일을 선택해 주세요.&quot;);
      return;
    }

    //상품이 선택되지 않은 경우 알림창 띄움
    if(selectedGoodsName == null){
      alert(&quot;상품을 먼저 선택해 주세요.&quot;);
      return;
    }

    //kg이니시스 결제 API
    var IMP = window.IMP; // 생략가능
    IMP.init(&#39;###가맹점 식별코드###&#39;);  // 가맹점 식별코드

    // IMP.request_pay(param, callback) 결제창 호출
    IMP.request_pay({
      pg: &quot;html5_inicis&quot;,
      pay_method: &quot;card&quot;,
      merchant_uid: &quot;gpay_&quot; + new Date().getTime(),   // 주문번호
      name: [[${gym.gname}]] +&quot; &quot;+ selectedGoodsName,
      amount: selectedGoodsPrice,                         // 숫자 타입
      buyer_email: [[${member.mmail}]],
      buyer_name: [[${member.mname}]],
      buyer_tel: [[${member.mphone}]]
    }, function (rsp) { // callback
      console.log(rsp);
      if ( rsp.success ) { //결제 성공시
        var msg = &#39;결제가 완료되었습니다.&#39;;
        var result = {
          &quot;mpaynum&quot; : rsp.merchant_uid, //결제번호
          &quot;membernum&quot; :[[${member.membernum}]], //회원번호
          &quot;mpaymethod&quot;:rsp.pay_method, //결제수단
          &quot;mpayproduct&quot;:rsp.name, //헬스장 이름 + 상품이름
          &quot;mpayprice&quot;:rsp.paid_amount, // 결제금액
          &quot;mpaydate&quot; : new Date().toISOString().slice(0, 10), //결제일
          &quot;mpaygym&quot; : [[${gym.gname}]], //헬스장 이름
          &quot;mpayperiod&quot; : selectedDate, //상품이용기간
          &quot;mpaytime&quot; : &quot;&quot;,
          &quot;trainername&quot;:&quot;&quot;,
          &quot;ggoodsnum&quot;: selectedGoodsNum, //상품번호
          &quot;tgoodsint&quot; : null,
          &quot;gymnum&quot; : [[${gym.gymnum}]] //헬스장 고유번호
        }
        console.log(result);

        $.ajax({
          url:&#39;insertMPay&#39;,
          type:&#39;POST&#39;,
          contentType: &#39;application/json&#39;,
          data:JSON.stringify(result),
          success: function (res) {
            console.log(res);
            location.href=res;
          },
          error: function (err) {
            console.log(err);
          }
        }); //ajax
      } else {
          var msg = &#39;결제 실패&#39;;
          msg += &#39;\n에러내용 : &#39; + rsp.error_msg;
        }
      alert(msg);
    });
  }

  // 상품 선택 이벤트
  function toggleOn(element) {
    var ticketDivs = document.querySelectorAll(&#39;.ticket_list .on&#39;);
    for (var i = 0; i &lt; ticketDivs.length; i++) {
      ticketDivs[i].classList.remove(&#39;on&#39;);
    }
    element.classList.add(&#39;on&#39;);

    //결제 데이터 전달
    var selectedPrice = element.querySelector(&quot;p&quot;).innerText;
    var priceElement = document.getElementById(&quot;selectedPrice&quot;).querySelector(&quot;span&quot;);
    priceElement.innerText = selectedPrice;

    var selectedProduct = element.querySelector(&quot;h5&quot;).innerText;
    var productElement = document.getElementById(&quot;selectedProduct&quot;).querySelector(&quot;span&quot;);
    productElement.innerText = selectedProduct;

    // 선택된 상품 정보 가져오기(상품명, 금액)
    var selectedGoodsName = element.getAttribute(&quot;data-goodsname&quot;);
    var selectedGoodsPrice = element.getAttribute(&quot;data-goodsprice&quot;);
    var selectedGoodsNum = element.getAttribute(&quot;data-goodsnum&quot;);

    //kg 이니시스
    var nameElement = document.querySelector(&quot;.kg_pay_btn&quot;);
    nameElement.setAttribute(&quot;data-name&quot;, selectedGoodsName);
    nameElement.setAttribute(&quot;data-price&quot;, selectedGoodsPrice);
    nameElement.setAttribute(&quot;data-goodsnum&quot;, selectedGoodsNum);
  }
&lt;/script&gt;</code></pre>
<p>결제 성공 여부에 따라 사용자에게 결제 성공/실패로 메세지를 띄우고, 결제 정보는 ajax를 활용하여 컨트롤러에 전달한다.
너무 길어져서 카카오페이는 결제 넘기는 건 생략했는데 똑같은 방식으로 데이터를 넘기면 된다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/7e2d5f23-0848-48f4-9cd8-fea68f4d6d32/image.png" alt="">
헬스장 상품 클릭(회원권 or 일일권) ► 운동 시작날짜 선택 ► 결제하기 or 카카오페이 선택 ► 결제</p>
<blockquote>
<p>kg 이니시스 결제창<img src="https://velog.velcdn.com/images/dev_h_o/post/be366b5a-3664-4dab-b7f8-b03902461580/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>카카오페이 결제창<img src="https://velog.velcdn.com/images/dev_h_o/post/ba4d90c0-a87b-4e65-b8ee-d74a92ee2517/image.png" alt=""></p>
</blockquote>
<h4 id="💻-controller">💻 controller</h4>
<pre><code class="language-java">public class MPayController {
    @Autowired
    private MPayService mPServ;

    private ModelAndView mv;

    @PostMapping(&quot;insertMPay&quot;)
    @ResponseBody
    public String insertMPay(@RequestBody MPayDto mpay, HttpSession session, RedirectAttributes rttr){
        log.info(&quot;insertMPay()&quot;);

        String view = mPServ.insertMPay(mpay, session, rttr);
        return view;
    }

        @GetMapping(&quot;payment&quot;)
    public ModelAndView paymentContents(String mpaynum, HttpSession session){
        log.info(&quot;paymentContents()&quot;);

        mv = mPServ.paymentContents(mpaynum, session);
        return mv;
    }
}</code></pre>
<p>결제정보를 데이터베이스에 저장하기 위해 서비스로 mpy 정보와 session, rttr을 넘긴다.
결제가 완료되면 결제완료 페이지로 넘어가기 때문에 <code>payment</code> 페이지를 생성하여 결제완료 데이터를 갖고 넘어가도록 처리하였다.</p>
<h4 id="💻-service">💻 service</h4>
<pre><code class="language-java">//결제정보 데이터베이스로 넘기기
public String insertMPay(MPayDto mpay, HttpSession session, RedirectAttributes rttr) {
    log.info(&quot;insertMPay()&quot;);
    String view = null;
    String msg = null;

    try {
        mPDao.insertMPay(mpay);

        view = &quot;payment?mpaynum=&quot; + mpay.getMpaynum();
        log.info(view);
    } catch (Exception e){
        e.printStackTrace();
    }
    return view;

}</code></pre>
<p>Dao로 mpay 데이터를 넘겨주고, 결제완료 페이지에 결제 정보가 노출되도록 view를 설정한 뒤 return.</p>
<pre><code class="language-java">//결제완료 페이지 컨트롤
public ModelAndView paymentContents(String mpaynum, HttpSession session) {
  log.info(&quot;paymentContents()&quot;);
  mv = new ModelAndView();

  //주문정보 가져오기
  MPayDto mpay = mPDao.selectPayment(mpaynum);
  mv.addObject(&quot;mpay&quot;, mpay);
  int gymnum = mpay.getGymnum();
  int tgoodsint = mpay.getTgoodsint();

  //헬스장 정보 가져오기
  GymDto gym = GGDao.selectGym(gymnum);
  mv.addObject(&quot;gym&quot;, gym);
  //view 설정
  mv.setViewName(&quot;payment&quot;);

  return mv;
}</code></pre>
<p>결제완료 페이지에서는 주문번호에 맞는 결제정보와 헬스장 정보를 가져오고 <code>payment</code> 페이지에 나타낸다.</p>
<h4 id="💻-dao">💻 dao</h4>
<pre><code class="language-java">@Mapper
public interface MPayDao {
    //헬스장 결제 정보를 저장하는 메소드
    void insertMPay(MPayDto mpay);

    //사용자의 결제 정보를 가져오는 메소드
    MPayDto selectPayment(String mpaynum);
}</code></pre>
<h4 id="💻-daoxml">💻 dao.xml</h4>
<pre><code class="language-sql">//데이터베이스에 결제정보 넣기
&lt;insert id=&quot;insertMPay&quot; parameterType=&quot;mPayDto&quot;&gt;
    insert into mpay
    values (#{mpaynum},#{membernum}, #{mpaymethod}, #{mpayproduct}, #{mpayprice}, #{mpaydate},
            #{mpaygym}, #{mpayperiod}, #{mpaytime},#{trainername}, #{ggoodsnum}, null, #{gymnum}, 0)
&lt;/insert&gt;

//데이터베이스에서 해당 결제정보 가져오기
&lt;select id=&quot;selectPayment&quot; resultType=&quot;MPayDto&quot; parameterType=&quot;String&quot;&gt;
    select * from mpay where mpaynum=#{mpaynum}
&lt;/select&gt;</code></pre>
<h2 id="✏️-결제-완료">✏️ 결제 완료</h2>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/a4459105-508f-40f5-a029-c14f8b3db509/image.png" alt=""></p>
<p>결제가 정상적으로 처리되면 메세지와 함께 결제완료 페이지로 이동한다.
<img src="https://velog.velcdn.com/images/dev_h_o/post/fcb6ddc4-baea-4971-bbee-a1350d47e51d/image.png" alt=""></p>
<p>결제완료 페이지에는 헬스장의 기본 정보와 위치, 결제 정보 등이 노출된다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/559f5b6a-23b7-41b9-976d-27d92128a0b9/image.png" alt=""></p>
<p>마이페이지 결제내역에서도 결제내역을 확인할 수 있다.</p>
<h4 id="📅-date">📅 DATE</h4>
<p><code>2023.08.11 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 카카오 로그인 api 사용하기(REST API)]]></title>
            <link>https://velog.io/@dev_h_o/Spring-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-api-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0REST-API</link>
            <guid>https://velog.io/@dev_h_o/Spring-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-api-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0REST-API</guid>
            <pubDate>Wed, 02 Aug 2023 14:34:43 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-사전작업">📌 사전작업</h2>
<ol>
<li>카카오 로그인 api REST API 키 발급받기</li>
</ol>
<ul>
<li><p><a href="https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api">https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api</a>
<img src="https://velog.velcdn.com/images/dev_h_o/post/8627b9d4-2350-4b0a-b6cd-412bd0a7fd8b/image.png" alt=""></p>
</li>
<li><p>Redirect URI 등록 (내 어플리케이션 &gt; 앱 설정 &gt; 플랫폼)</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/a77582e7-ff68-4cda-87a0-683672a1dcc3/image.png" alt=""></p>
<ul>
<li>동의 항목 체크 (내 어플리케이션 &gt; 제품 설정 &gt; 카카오 로그인 &gt; 동의항목)
<img src="https://velog.velcdn.com/images/dev_h_o/post/f34a2fb4-a66e-4d87-96f2-c263cc053544/image.png" alt=""></li>
</ul>
<ol start="2">
<li>pom.xml에 라이브러리 추가<pre><code>&lt;!--카카오 로그인--&gt;
&lt;dependency&gt;
 &lt;groupId&gt;com.google.code.gson&lt;/groupId&gt;
 &lt;artifactId&gt;gson&lt;/artifactId&gt;
 &lt;version&gt;2.8.6&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
 &lt;groupId&gt;com.googlecode.json-simple&lt;/groupId&gt;
 &lt;artifactId&gt;json-simple&lt;/artifactId&gt;
 &lt;version&gt;1.1.1&lt;/version&gt;
&lt;/dependency&gt;</code></pre></li>
</ol>
<h2 id="📌-카카오-api-로그인-기능-구현">📌 카카오 api 로그인 기능 구현</h2>
<h3 id="1-카카오-로그인-서비스-과정-파악하기">1. 카카오 로그인 서비스 과정 파악하기</h3>
<p><a href="https://developers.kakao.com/docs/latest/ko/kakaologin/common#intro-login-process">☞ 카카오 로그인 서비스 과정</a></p>
<p>카카오 로그인 과정은 위 링크를 타고 들어가서 찬찬히 살펴보면 이해가 좀 되는 것 같기도 하고 안 되는 것 같기도 하다.^^</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/a14ce8fd-7aa7-406d-903e-29b65422d2c0/image.png" alt=""></p>
<h3 id="2-로그인-페이지에-카카오-로그인-버튼-만들기">2. 로그인 페이지에 카카오 로그인 버튼 만들기</h3>
<h4 id="👀-화면">👀 화면</h4>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/e0dcad7c-d4c6-4f0b-bd40-2c622316ab07/image.png" alt=""></p>
<h4 id="💻-memberloginformhtml">💻 memberLoginForm.html</h4>
<pre><code class="language-html">&lt;!-- 카카오 스크립트 --&gt;
&lt;script src=&quot;https://developers.kakao.com/sdk/js/kakao.js&quot;&gt;&lt;/script&gt;

...
&lt;div class=&quot;kakao-btn&quot; onclick=&quot;kakaoLogin()&quot;&gt;
    &lt;a&gt;카카오톡으로 간편로그인&lt;/a&gt;
&lt;/div&gt;
...
&lt;script th:inline=&quot;javascript&quot;&gt;
function kakaoLogin() {
$.ajax({
 url:&#39;/memberLoginForm/getKakaoAuthUrl&#39;,
 type:&#39;post&#39;,
 async: false,
 dataType: &#39;text&#39;,
 success: function (res) {
   location.href = res;
 }
});
}
&lt;/script&gt;</code></pre>
<p><code>memberLoginForm</code>페이지에서 카카오 로그인 기능을 호출하므로 ajax url에 <code>/memberLoginForm/getKakaoAuthUrl</code>로 적는다.
(<code>getKakaoAuthUrl</code>이 부분은 공통)</p>
<h3 id="3-controller에서-메소드-만들기">3. Controller에서 메소드 만들기</h3>
<h4 id="💻-logincontrollerjava">💻 LoginController.java</h4>
<pre><code class="language-java">//카카오 로그인 기능이 처리되는 페이지
@RequestMapping(value = &quot;/memberLoginForm/getKakaoAuthUrl&quot;)
public @ResponseBody String getKakaoAuthUrl(HttpServletRequest request) throws Exception {

    String reqUrl =
            &quot;https://kauth.kakao.com/oauth/authorize?client_id=발급받은 REST API 키&amp;redirect_uri=redirect_uri설정한 주소&amp;response_type=code&quot;;

    return reqUrl;
}   </code></pre>
<p><code>reqUrl</code>에 발급받은 api키와 대표 redirct_uri를 넣는다.</p>
<pre><code class="language-java">@RequestMapping(value = &quot;/auth_kakao&quot;)
public String oauthKakao(
        @RequestParam(value = &quot;code&quot;,required = false) String code
        , HttpSession session, RedirectAttributes rttr) throws Exception {

    log.info(&quot;#######&quot; + code);
    String access_Token = loginServ.getAccessToken(code);
    String view = loginServ.getuserinfo(access_Token, session, rttr);


    return view;
}</code></pre>
<p>access_Token을 보내 인가 코드를 받고 <code>getuserinfo</code> 메소드로 사용자 정보를 가져와서 리턴한다.</p>
<h3 id="4-service에서-데이터-처리하기">4. Service에서 데이터 처리하기</h3>
<h4 id="💻-loginservicejava">💻 LoginService.java</h4>
<pre><code class="language-java">public String getAccessToken(String authorize_code) {
    String access_Token = &quot;&quot;;
    String refresh_Token = &quot;&quot;;
    String reqURL = &quot;https://kauth.kakao.com/oauth/token&quot;;

    try {
        URL url = new URL(reqURL);

        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        //    POST 요청을 위해 기본값이 false인 setDoOutput을 true로 변경을 해주세요

        conn.setRequestMethod(&quot;POST&quot;);
        conn.setDoOutput(true);

        //    POST 요청에 필요로 요구하는 파라미터 스트림을 통해 전송
        // BufferedWriter 간단하게 파일을 끊어서 보내기로 토큰값을 받아오기위해 전송

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
        StringBuilder sb = new StringBuilder();
        sb.append(&quot;grant_type=authorization_code&quot;);
        sb.append(&quot;&amp;client_id=&quot;);  //발급받은 key
        sb.append(&quot;&amp;redirect_uri=&quot;);     // 본인이 설정해 놓은 redirect_uri 주소
        sb.append(&quot;&amp;code=&quot; + authorize_code);
        bw.write(sb.toString());
        bw.flush();

        //    결과 코드가 200이라면 성공
        // 여기서 안되는경우가 많이 있어서 필수 확인 !! **
        int responseCode = conn.getResponseCode();
        System.out.println(&quot;responseCode : &quot; + responseCode + &quot;확인&quot;);

        //    요청을 통해 얻은 JSON타입의 Response 메세지 읽어오기
        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line = &quot;&quot;;
        String result = &quot;&quot;;

        while ((line = br.readLine()) != null) {
            result += line;
        }
        System.out.println(&quot;response body : &quot; + result + &quot;결과&quot;);

        //    Gson 라이브러리에 포함된 클래스로 JSON파싱 객체 생성
        JsonParser parser = new JsonParser();
        JsonElement element = parser.parse(result);

        access_Token = element.getAsJsonObject().get(&quot;access_token&quot;).getAsString();
        refresh_Token = element.getAsJsonObject().get(&quot;refresh_token&quot;).getAsString();

        System.out.println(&quot;access_token : &quot; + access_Token);
        System.out.println(&quot;refresh_token : &quot; + refresh_Token);

        br.close();
        bw.close();
    } catch (IOException e) {

        e.printStackTrace();
    }
    return access_Token;
}</code></pre>
<p>access_Token을 컨트롤러로 리턴하고 이 access_Token으로 사용자 정보를 처리한다.</p>
<pre><code class="language-java"> public String getuserinfo(String access_Token, HttpSession session, RedirectAttributes rttr) {
    HashMap&lt;String, Object&gt; userInfo = new HashMap&lt;&gt;();
    log.info(&quot;getuserinfo()&quot;);

    String requestURL = &quot;https://kapi.kakao.com/v2/user/me&quot;;
    String view = null;
    String msg = null;

    try {
        URL url = new URL(requestURL); //1.url 객체만들기
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        //2.url 에서 url connection 만들기
        conn.setRequestMethod(&quot;GET&quot;); // 3.URL 연결구성
        conn.setRequestProperty(&quot;Authorization&quot;, &quot;Bearer &quot; + access_Token);

        //키 값, 속성 적용
        int responseCode = conn.getResponseCode(); //서버에서 보낸 http 상태코드 반환
        System.out.println(&quot;responseCode :&quot; + responseCode + &quot;여긴가&quot;);
        BufferedReader buffer = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        // 버퍼를 사용하여 읽은 것
        String line = &quot;&quot;;
        String result = &quot;&quot;;
        while ((line = buffer.readLine()) != null) {
            result += line;
        }
        //readLine()) ==&gt; 입력 String 값으로 리턴값 고정

        System.out.println(&quot;response body :&quot; + result);

        // 읽었으니깐 데이터꺼내오기
        JsonParser parser = new JsonParser();
        JsonElement element = parser.parse(result); //Json element 문자열변경
        JsonObject properties = element.getAsJsonObject().get(&quot;properties&quot;).getAsJsonObject();
        JsonObject kakao_account = element.getAsJsonObject().get(&quot;kakao_account&quot;).getAsJsonObject();

        String mnickname = properties.getAsJsonObject().get(&quot;nickname&quot;).getAsString();
        String mmail = kakao_account.getAsJsonObject().get(&quot;email&quot;).getAsString();

        //userInfo에 사용자 정보 저장
        userInfo.put(&quot;mid&quot;, mmail);
        userInfo.put(&quot;mnickname&quot;, mnickname);
        userInfo.put(&quot;mmail&quot;, mmail);

        log.info(String.valueOf(userInfo));

    } catch (Exception e) {
        e.printStackTrace();
    }

    MemberDto member = memberDao.findkakao(userInfo);
    // 저장되어있는지 확인
    log.info(&quot;S :&quot; + member);

    if (member == null) {
        //member null 이면 정보가 저장 안되어있는거라서 정보를 저장.
        memberDao.kakaoinsert(userInfo);
        //저장한 member 정보 다시 가져오기 HashMap이라 형변환 시켜줌
        member = loginnDao.selectMember((String)userInfo.get(&quot;mid&quot;));
        session.setAttribute(&quot;member&quot;, member);

        //로그인 처리 후 메인 페이지로 이동
        view = &quot;redirect:/&quot;;
        msg = &quot;로그인 성공&quot;;
    } else {
        session.setAttribute(&quot;member&quot;, member);
        view = &quot;redirect:/&quot;;
        msg = &quot;로그인 성공&quot;;

    }
    rttr.addFlashAttribute(&quot;msg&quot;, msg);
    return view;
}
}</code></pre>
<p><code>userInfo</code>를 HashMap으로 초기화했는데, member 정보를 다시 반환시켜주기 위해 select로 검색할 때는 (String)으로 변환시켜서 데이터를 가져온다.</p>
<ul>
<li><code>userInfo</code>데이터로 조회해서 이미 데이터베이스에 등록된 회원일 경우 회원정보 저장없이 세션에 담아 로그인 처리한다.</li>
<li>회원 테이블에 없는 <code>userInfo</code>일 경우 insert 처리</li>
</ul>
<h3 id="5-데이터베이스에-저장하기">5. 데이터베이스에 저장하기</h3>
<h4 id="💻-memberdaojava">💻 MemberDao.java</h4>
<pre><code class="language-java">@Mapper
public interface MemberDao {

    //이미 가입된 회원인지 확인하는 메소드
    MemberDto findkakao(HashMap&lt;String, Object&gt; userInfo);

    //카카오 로그인 회원정보 저장
    void kakaoinsert(HashMap&lt;String, Object&gt; userInfo);

    }</code></pre>
<h4 id="💻-memberdaoxml">💻 MemberDao.xml</h4>
<pre><code class="language-java">&lt;mapper namespace=&quot;com.project.ohgym.dao.MemberDao&quot;&gt;

    &lt;select id=&quot;findkakao&quot; resultType=&quot;MemberDto&quot; parameterType=&quot;HashMap&quot;&gt;
        select * from member where mid=#{mid} and mmail=#{mmail} and mnickname=#{mnickname}
    &lt;/select&gt;

    &lt;insert id=&quot;kakaoinsert&quot;&gt;
    insert into member
    values (null, #{mid}, null, #{mmail}, null, null, null, null, #{mnickname},
            null, null, null, null, null, null, null, null, null, null)
    &lt;/insert&gt;

&lt;/mapper&gt;</code></pre>
<h3 id="6-테스트">6. 테스트</h3>
<ol>
<li>카카오 로그인하기 버튼 클릭 &gt; 동의하고 계속하기</li>
</ol>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/1feba2cd-c1f5-4e32-b429-b94b58072beb/image.png" alt=""></p>
<ol start="2">
<li>로그인 성공 후 홈으로 이동</li>
</ol>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/e9726f67-f275-45cf-94d3-766b90677c7b/image.png" alt=""></p>
<p>로그인 성공 메세지를 <code>alert</code>으로 띄우고 메인 페이지로 이동</p>
<ol start="3">
<li>헤더 변경</li>
</ol>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/94ae7014-70f4-47a2-9e0e-14651d3c0c4b/image.png" alt=""></p>
<p>사용자 정보가 세션에 저장됨에 따라 헤더 정보도 바뀐다.
로그인 버튼은 로그아웃 버튼으로 바뀌고, 사용자 아이디와 마이페이지 버튼이 노출된다.</p>
<ol start="4">
<li>데이터베이스 확인
<img src="https://velog.velcdn.com/images/dev_h_o/post/1e2f0658-2d4a-48ed-b8cd-f40fcb0678a6/image.png" alt=""></li>
</ol>
<p>카카오 로그인 버튼으로 로그인한 데이터가 데이터베이스에 정상적으로 저장되었다.</p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.08.02 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 타임리프] 영화 정보 기록 페이지 만들기 3 (영화 등록)]]></title>
            <link>https://velog.io/@dev_h_o/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84-%EC%98%81%ED%99%94-%EC%A0%95%EB%B3%B4-%EA%B8%B0%EB%A1%9D-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-%EC%98%81%ED%99%94-%EB%93%B1%EB%A1%9D</link>
            <guid>https://velog.io/@dev_h_o/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84-%EC%98%81%ED%99%94-%EC%A0%95%EB%B3%B4-%EA%B8%B0%EB%A1%9D-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-%EC%98%81%ED%99%94-%EB%93%B1%EB%A1%9D</guid>
            <pubDate>Mon, 26 Jun 2023 09:08:26 GMT</pubDate>
            <description><![CDATA[<h1 id="영화를-등록해보자">영화를 등록해보자!</h1>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/832c32d6-2a04-4ab9-a80c-d5da12f687d0/image.png" alt=""></p>
<p>영화 등록 페이지를 생성하고 <code>쓰기</code>, <code>초기화</code>, <code>뒤로가기</code> 버튼 기능을 구현한다.</p>
<h2 id="🎈-영화-정보-등록하기">🎈 영화 정보 등록하기</h2>
<p>컨트롤러에서는 영화 정보 등록 폼 이동, 등록 처리 맵핑처리를 한다.
영화 정보에 들어갈 컨텐츠 : 영화 포스터, 제목, 감독, 국가, 장르, 주연배우, 개봉일, 영화 줄거리
버튼 : <code>W</code> - 쓰기 , <code>R</code> - 초기화 , <code>B</code> - 뒤로가기</p>
<h4 id="💻-moviecontroller-소스">💻 MovieController 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;writeForm&quot;)
public String writeForm(){
    log.info(&quot;writeForm()&quot;);
    return &quot;writeForm&quot;;
}

@PostMapping(&quot;writeProc&quot;)
public String writeProc(@RequestPart List&lt;MultipartFile&gt; files, Movie movie, HttpSession session, RedirectAttributes rttr){
    log.info(&quot;writeProc()&quot;);
    String view = mServ.insertMovie(files, movie, session, rttr);
    return view;
}</code></pre>
<p>**
⭐⭐ 헷갈릴 때 읽어보기 : <a href="https://middleearth.tistory.com/35">RequestBody vs RequestPart vs RequestParam vs ModelAttribute</a>**</p>
<h4 id="💻-writeformhtml-소스">💻 writeForm.html 소스</h4>
<pre><code class="language-html">&lt;div class=&quot;content&quot;&gt;
&lt;form th:action=&quot;@{writeProc}&quot; method=&quot;post&quot; class=&quot;write-form&quot; enctype=&quot;multipart/form-data&quot;&gt;
  &lt;h2&gt;영화 등록&lt;/h2&gt;
  &lt;div class=&quot;filebox&quot;&gt;
    &lt;label for=&quot;file&quot;&gt;포스터&lt;/label&gt;
    &lt;input type=&quot;file&quot; name=&quot;files&quot; id=&quot;file&quot;&gt;
    &lt;input type=&quot;text&quot; class=&quot;upload-name&quot; value=&quot;파일선택&quot; readonly&gt;
  &lt;/div&gt;
  &lt;input type=&quot;text&quot; class=&quot;write-input&quot; name=&quot;mname&quot; autofocus placeholder=&quot;제목&quot; required&gt;
  &lt;input type=&quot;text&quot; class=&quot;write-input&quot; name=&quot;mdirector&quot; placeholder=&quot;감독&quot; required&gt;
  &lt;input type=&quot;text&quot; class=&quot;write-input&quot; name=&quot;mnation&quot; placeholder=&quot;국가&quot; required&gt;
  &lt;input type=&quot;text&quot; class=&quot;write-input&quot; name=&quot;mgenre&quot; placeholder=&quot;장르&quot; required&gt;
  &lt;input type=&quot;text&quot; class=&quot;write-input&quot; name=&quot;mactor&quot; placeholder=&quot;주연배우&quot; required&gt;
  &lt;input type=&quot;date&quot; class=&quot;write-input&quot; name=&quot;mopen&quot; placeholder=&quot;개봉일&quot; required&gt;
  &lt;textarea rows=&quot;5&quot; class=&quot;write-input ta&quot; name=&quot;msynopsis&quot; placeholder=&quot;영화 개요&quot;&gt;&lt;/textarea&gt;
  &lt;div class=&quot;btn-area&quot;&gt;
    &lt;input type=&quot;submit&quot; class=&quot;btn-write&quot; value=&quot;W&quot;&gt;
    &lt;input type=&quot;reset&quot; class=&quot;btn-write&quot; value=&quot;R&quot;&gt;
    &lt;input type=&quot;button&quot; class=&quot;btn-write&quot; value=&quot;B&quot; id=&quot;backbtn&quot;&gt;
  &lt;/div&gt;
&lt;/form&gt;
&lt;/div&gt;&lt;!--content--&gt;</code></pre>
<p><code>writeForm.html</code>에서 입력받은 input값은 <code>Movie</code> entity에서 설정한 컬럼명과 동일하게 입력한다.
예) mname, mdirector, mnation ...</p>
<h4 id="💻-movieservicejava-소스">💻 MovieService.java 소스</h4>
<pre><code class="language-java">public String insertMovie(List&lt;MultipartFile&gt; files, Movie movie, HttpSession session, RedirectAttributes rttr) {
    log.info(&quot;insertMovie()&quot;);
    String view = null;
    String msg = null;
    //업로드하는 파일의 이름을 먼저 꺼낸다.
    String upFile = files.get(0).getOriginalFilename();

    try{
        //파일 업로드 처리
        if(!upFile.equals(&quot;&quot;)) {
            fileUpload(files, session, movie);
        }
        //DB에 영화정보 저장
        mRepo.save(movie);
        //insert, update 모두 save 메소드로 처리
        view = &quot;redirect:/&quot;;
        msg = &quot;등록 성공&quot;;
    } catch (Exception e){
        e.printStackTrace();
        view = &quot;redirect:writeForm&quot;;
        msg = &quot;등록 실패&quot;;
    }
    rttr.addFlashAttribute(&quot;msg&quot;, msg);
    return view;
}</code></pre>
<p>아래는 영화 등록할 때 사용되는 파일 업로드 처리 메소드 (<code>insertMovie</code>메소드에서 사용)</p>
<h4 id="💻-movieservicejava-소스-fileupload">💻 MovieService.java 소스 (fileUpload)</h4>
<pre><code class="language-java">private void fileUpload(List&lt;MultipartFile&gt; files, HttpSession session, Movie movie) throws IOException {
    log.info(&quot;fileUpload()&quot;);
    String sysname = null;
    String oriname = null;

    String realPath = session.getServletContext().getRealPath(&quot;/&quot;);
    realPath += &quot;upload/&quot;;
    File folder = new File(realPath);
    if(folder.isDirectory() == false){
        folder.mkdir();
    }

    MultipartFile mf = files.get(0);
    oriname = mf.getOriginalFilename();

    sysname = System.currentTimeMillis()
            + oriname.substring(oriname.lastIndexOf(&quot;.&quot;));

    File file = new File(realPath + sysname);
    mf.transferTo(file);

    movie.setMoriname(oriname);
    movie.setMsysname(sysname);
}</code></pre>
<h2 id="🎈-등록한-영화-정보-리스트로-출력하기">🎈 등록한 영화 정보 리스트로 출력하기</h2>
<h4 id="💻-moviecontroller-소스-1">💻 MovieController 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;/&quot;)
public ModelAndView home(Integer pageNum, HttpSession session){
    log.info(&quot;home()&quot;);

    //서비스 만들면 수정할 것
    //mv = new ModelAndView();
    mv = mServ.getMovieList(pageNum, session);
    mv.setViewName(&quot;home&quot;);

    return mv;
}</code></pre>
<p>영화 정보 목록은 메인 페이지에서 보여주므로 메인페이지 <code>/</code>에서 <code>getMovieList</code> 서비스 메소드로 처리한다.</p>
<h4 id="💻-movieservice-소스">💻 MovieService 소스</h4>
<pre><code class="language-java">public ModelAndView getMovieList(Integer pageNum, HttpSession session){
    log.info(&quot;getMovieList()&quot;);

    if(pageNum == null){
        pageNum = 1;
    }
    int listCnt = 5; //한페이지 당 5개씩 목록 출력

    //페이징 조건 생성(Pageable)
    Pageable pb = PageRequest.of((pageNum -1), listCnt, Sort.Direction.DESC, &quot;mcode&quot;);
    //of(시작번호, 목록개수, 정렬방식, 키필드명)

    Page&lt;Movie&gt; result = mRepo.findByMcodeGreaterThan(0L, pb); //Long 타입이라 0L입력

    //페이지 형태의 결과를 목록형태로 변환
    List&lt;Movie&gt; mList = result.getContent();

    //전체 페이지
    int totalPage = result.getTotalPages();

    //페이징용 html 작성
    String paging = getPaging(pageNum, totalPage);

    //세션에 페이지 번호 저장
    session.setAttribute(&quot;pageNum&quot;, pageNum);

    mv = new ModelAndView();
    mv.addObject(&quot;mList&quot;, mList);

    //페이징용 html 추가
    mv.addObject(&quot;paging&quot;, paging);

    //뷰네임 지정
    mv.setViewName(&quot;home&quot;);

    return mv;
}</code></pre>
<blockquote>
<p>영화 정보 리스트 불러온 모습
<img src="https://velog.velcdn.com/images/dev_h_o/post/48b9354f-88f8-4758-a779-89517244d700/image.png" alt=""></p>
</blockquote>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.06.26 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 모듈을 알아보자!]]></title>
            <link>https://velog.io/@dev_h_o/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%AA%A8%EB%93%88%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev_h_o/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%AA%A8%EB%93%88%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Wed, 14 Jun 2023 02:44:15 GMT</pubDate>
            <description><![CDATA[<p>모듈이란 함수나 변수 또는 클래스를 모아 놓은 파이썬 파일이다. 모듈은 다른 파이썬 프로그램에서 불러와 사용할 수 있게끔 만든 파이썬 파일이라고도 할 수 있다.</p>
<p>모듈은 다른 사람들이 이미 만들어 놓은 것을 사용할 수도 있고 우리가 직접 만들어서 사용할 수도 있다.</p>
<h2 id="모듈-만들기">모듈 만들기</h2>
<pre><code class="language-python"># mod1.py
def add(a, b):
    return a + b

def sub(a, b): 
    return a-b</code></pre>
<p><code>add</code>와 <code>sub</code> 함수가 들어있는 파이썬 파일을 mod1.py로 저장하고 디렉토리에 저장한다. (기억하기 쉬운 경로에 저장하기)</p>
<blockquote>
<p>파이썬 확장자 .py로 만든 파이썬 파일은 모두 모듈이다</p>
</blockquote>
<h2 id="모듈-불러오기">모듈 불러오기</h2>
<p><code>mod1.py</code>를 저장한 디렉토리로 이동 후 인터프리터에서 <code>mod1.py</code>을 꺼내온다.</p>
<pre><code class="language-python">import mod1

print(mod1.add(3,4))

print(mod1.sub(4,2))</code></pre>
<p><code>import</code> 명령어로 <code>mod1</code> 파일을 불러오고 함수를 사용하기 위해 <code>mod1.add</code> 명령어를 사용하였다.</p>
<blockquote>
<p>import는 현재 디렉터리에 있는 파일이나 파이썬 라이브러리가 저장된 디렉터리에 있는 모듈만 불러올 수 있다. 파이썬 라이브러리는 파이썬을 설치할 때 자동으로 설치되는 파이썬 모듈을 말한다.</p>
</blockquote>
<p><strong>import 사용방법</strong></p>
<pre><code>import 모듈이름

# 함수 1개 불러오기
from 모듈이름 import 모듈함수

# 함수 여러 개 불러오기
from 모듈이름 import 모듈함수1, 모듈함수2, ...

# 함수 전부 불러오기
from mod1 import *</code></pre><p>from ~ import를 사용하면 모듈 이름을 붙이지 않고 바로 해당 모듈의 함수를 사용할 수 있다.</p>
<pre><code class="language-python">from mod1 import add
add(3,4)
# 결과 7</code></pre>
<h2 id="클래스나-변수-등을-포함한-모듈">클래스나 변수 등을 포함한 모듈</h2>
<h4 id="클래스나-변수-등을-포함한-모듈-1">클래스나 변수 등을 포함한 모듈</h4>
<pre><code class="language-python"># mod2.py
PI = 3.141592

class Math:
    def solv(self, r):
        return PI * (r ** 2)

def add(a, b):
    return a + b</code></pre>
<p><code>mod2.py</code>파일은 원의 넓이를 계산하는 Math 클래스와 두 값을 더하는 add 함수, 원주율 값에 해당되는 PI 변수처럼 클래스, 함수, 변수 등을 모두 포함하고 있다.</p>
<h4 id="mod2py-사용하기">mod2.py 사용하기</h4>
<pre><code class="language-python">import mod2
print(mod2.PI)
# 결과 : 3.141592</code></pre>
<p><code>mod2.PI</code>를 입력해서 <code>mod2.py</code>파일에 있는 PI 변수 값을 사용할 수 있다.</p>
<pre><code class="language-python">a = mod2.Math()
print(a.solv(2))
# 12.566368</code></pre>
<p>모듈 안에 있는 클래스를 사용하려면 <code>.</code> 도트 연산자로 클래스 이름 앞에 모듈 이름을 먼저 입력하면 된다.</p>
<h2 id="다른-디렉터리에서-모듈-불러오기">다른 디렉터리에서 모듈 불러오기</h2>
<h4 id="1-syspathappend-사용하기">1. sys.path.append 사용하기</h4>
<pre><code># sys 모듈을 불러온다.
import sys

# sys.path로 디렉터리를 확인한다.

# 원하는 파일이 들어있는 디렉터리를 추가한다.(append)
sys.path.appen(&quot;C:/doit/mymod&quot;)

# sys.path로 추가되었는지 확인
sys.path</code></pre><p><code>sys</code> 모듈을 사용하면 다른 디렉터리에 있는 모듈도 불러와서 사용할 수 있다.</p>
<h4 id="2-pythonpath-환경-변수-사용하기">2. PYTHONPATH 환경 변수 사용하기</h4>
<pre><code class="language-python">C:\doit&gt;set PYTHONPATH=C:\doit\mymod
C:\doit&gt;python
&gt;&gt;&gt; import mod2
&gt;&gt;&gt; print(mod2.add(3,4))
7</code></pre>
<p>set 명령어를 사용해 <code>PYTHONPATH</code> 환경 변수에 mod2.py 파일이 있는 <code>C:\doit\mymod</code> 디렉터리를 설정한다.</p>
<p>그러면 디렉터리 이동이나 별도의 모듈 추가 작업 없이 mod2 모듈을 불러와서 사용할 수 있다.</p>
<blockquote>
<p>맥이나 유닉스 환경에서는 set 대신 export 명령을 사용</p>
</blockquote>
<p><strong>참고</strong>
<a href="https://wikidocs.net/29">https://wikidocs.net/29</a></p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.06.14 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 클래스(class)를 알아보자!]]></title>
            <link>https://velog.io/@dev_h_o/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%81%B4%EB%9E%98%EC%8A%A4class%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev_h_o/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%81%B4%EB%9E%98%EC%8A%A4class%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 13 Jun 2023 06:30:38 GMT</pubDate>
            <description><![CDATA[<h2 id="클래스는-왜-필요한가">클래스는 왜 필요한가?</h2>
<p>파이썬은 객체지향 언어이므로 클래스를 잘만 활용하면 여기저기 조립하듯이 편리하게 활용할 수 있다.</p>
<h4 id="✔-입력-값을-이전에-계산한-결과에-더한-후-돌려주는-add-함수">✔ 입력 값을 이전에 계산한 결과에 더한 후 돌려주는 <code>add</code> 함수</h4>
<pre><code class="language-python">result = 0

def add(num):
    global result
    result += num
    return result

print(add(3)) #출력 : 3
print(add(4)) #출력 : 7</code></pre>
<p>이러한 <code>add</code>함수를 이용해서 각각 여러 개의 값을 출력하고 싶을 때 클래스를 활용할 수 있다.</p>
<h4 id="✔-클래스-활용">✔ 클래스 활용</h4>
<pre><code class="language-python">class Calculator:
    def __init__(self):
        self.result = 0

    def add(self, num):
        self.result += num
        return self.result

cal1 = Calculator() # 객체
cal2 = Calculator() # 객체

print(cal1.add(3))
print(cal1.add(4))
print(cal2.add(3))
print(cal2.add(7))</code></pre>
<h4 id="✔-실행-결과">✔ 실행 결과</h4>
<pre><code class="language-python">3
7
3
10</code></pre>
<p>cal1과 cal2가 각각 <code>Calculator</code>의 클래스를 참조하여 add함수를 독립적으로 사용하고 있다.</p>
<h2 id="클래스와-객체">클래스와 객체</h2>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/0f67fcb6-408b-454b-ac6f-322325a7c603/image.png" alt=""></p>
<p>클래스와 객체의 관계를 나타내주는 그림</p>
<p>쿠키를 만드는 과자 틀과 쿠키들</p>
<ul>
<li>쿠키 틀 → 클래스(class)</li>
<li>쿠키 틀로 만든 쿠키 → 객체(object)</li>
</ul>
<p>클래스는 무엇인가를 계속해서 만들어낼 수 있은 설계 도면이고, 객체는 클래스로 만든 결과물로 볼 수 있다.</p>
<h4 id="✔-클래스의-중요한-특징">✔ 클래스의 중요한 특징</h4>
<ul>
<li><p>객체마다 고유한 성격을 가지므로 객체를 변형하더라도 다른 객체에는 영향을 주지 않는다.</p>
</li>
<li><p>메소드(Method) : 클래스 안에 구현된 함수</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/8b59ae7f-3b69-47a3-9964-8b68000fa28a/image.png" alt=""></p>
<ul>
<li>파이썬 메서드의 첫 번째 매개변수 이름은 관례적으로 self를 사용한다. (객체를 호출할 때 호출한 객체 자신이 전달되기 때문에)</li>
</ul>
<h4 id="💥-객체와-인스턴스의-차이">💥 객체와 인스턴스의 차이</h4>
<p>인스턴스 : 클래스로 만든 객체</p>
<p>인스턴스라는 말은 특정 객체가 어떤 클래스의 객체인지를 관계 위주로 설명할 때 사용한다.
보통 a 자체만을 설명할 때는 &quot;a는 객체이다.&quot;
a와 클래스와의 관계를 설명할 때는 &quot;a는 Cookie의 인스턴스이다.&quot;라고 표현한다.</p>
<h2 id="생성자constructor">생성자(Constructor)</h2>
<p>생성자(Constructor)란 객체가 생성될 때 자동으로 호출되는 메서드를 의미한다.</p>
<p>파이썬 메소드 이름으로 <code>__init__</code>를 사용하면 이 메서드는 생성자가 된다.(초기화 메소드)</p>
<pre><code class="language-python">def __init__(self, first, second):
    self.first = first
    self.second = second</code></pre>
<p>이 <code>__init__</code> 메소드는 이름을 <code>__init__</code>으로 했기 때문에 생성자로 인식되어 객체가 생성되는 시점에 자동으로 호출이 된다.</p>
<h4 id="계산기-클래스에서의-활용fourcal">계산기 클래스에서의 활용(FourCal)</h4>
<pre><code class="language-python">class FourCal:
    def __init__(self, first, second):
        self.first = first
        self.second = second
    def setdata(self, first, second):
        self.first = first
        self.second = second
    def add(self):
        result = self.first + self.second
        return result
    def mul(self):
        result = self.first * self.second
        return result
    def sub(self):
        result = self.first - self.second
        return result
    def div(self):
        result = self.first / self.second
        return result</code></pre>
<p><code>__init__</code> 메소드로 생성자를 구현함으로써 초깃값을 사용할 수 있게 된다.</p>
<h4 id="초깃값-사용-예">초깃값 사용 예</h4>
<pre><code class="language-python"># 초깃값 설정
a = FourCal(4, 2)
a.first # 4
a.second # 2

# 더하기, 빼기
a.add() # 6
a.sub() # 2</code></pre>
<h2 id="클래스의-상속">클래스의 상속</h2>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/f07fe8a3-e955-4664-bc26-e860cee46ea5/image.jpg" alt=""></p>
<p><del>드라마 상속자들이 생각나는 클래스의 상속...</del></p>
<p>상속(Inheritance)이란 &quot;물려받다&quot;라는 뜻으로, &quot;재산을 상속받다&quot;라고 할 때의 상속과 같은 의미로 클래스에도 이 개념을 적용할 수 있다.</p>
<p>클래스를 상속하기 위해서는 다음처럼 클래스 이름 뒤 괄호 안에 상속할 클래스 이름을 넣어주면 된다.</p>
<pre><code class="language-python">class 클래스 이름(상속할 클래스 이름)</code></pre>
<h4 id="✔-클래스-상속-예시">✔ 클래스 상속 예시</h4>
<pre><code class="language-python">class MoreFourCal(FourCal):
    pass</code></pre>
<p><code>pass</code> : 함수, 클래스에 내용을 작성하지 않는 빈 함수, 빈 클래스를 만들 때 사용하는 키워드</p>
<p>MoreFourCal 클래스는 FourCal 클래스를 상속했으므로 FourCal 클래스의 모든 기능을 사용할 수 있다.</p>
<h4 id="✔-상속받은-클래스-사용하기">✔ 상속받은 클래스 사용하기</h4>
<pre><code class="language-python">a = MoreFourCal(4, 2)

a.add()
a.mul()
a.sub()
a.div()</code></pre>
<h3 id="상속을-사용하는-이유">상속을 사용하는 이유</h3>
<p>보통 상속은 기존 클래스를 변경하지 않고 기능을 추가하거나 기존 기능을 변경하려고 할 때 사용한다.</p>
<p>기존 클래스가 라이브러리 형태로 제공되거나 수정이 허용되지 않는 상황이라면 상속을 사용해야 한다.</p>
<h2 id="메소드-오버라이딩">메소드 오버라이딩</h2>
<p>부모 클래스(상속한 클래스)에 있는 메서드를 동일한 이름으로 다시 만드는 것 = 덮어쓰기</p>
<p>메소드 오버라이딩을 하면 부모 클래스의 메소드 대신 오버라이딩한 메소드가 호출된다.</p>
<p>특정 메소드만 가져와서 코드를 다르게 요리해서 활용하고 싶을 때 유용할 듯!</p>
<h4 id="✔-메소드-오버라이딩-예시">✔ 메소드 오버라이딩 예시</h4>
<pre><code class="language-python">class SafeFourCal(FourCal):
 def div(self):
     if self.second == 0:  # 나누는 값이 0인 경우 0을 리턴하도록 수정
         return 0
     else:
         return self.first / self.second</code></pre>
<h2 id="클래스-변수">클래스 변수</h2>
<p>객체 변수는 다른 객체들의 영향을 받지 않고 독립적으로 그 값을 유지한다.</p>
<p>반면 클래스 변수는 클래스로 만든 모든 객체에 값이 공유된다는 특징이 있다.</p>
<ul>
<li>사용 예 : <code>클래스이름.클래스변수</code></li>
</ul>
<h4 id="✔-클래스-변수">✔ 클래스 변수</h4>
<pre><code class="language-python"># 클래스 변수선언
class Family:
    lastname = &quot;박&quot;

#클래스 변수 사용
Family.lastname
# 실행결과 : 박</code></pre>
<p><strong>참고</strong>
<a href="https://wikidocs.net/28">https://wikidocs.net/28</a></p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.06.13 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 자료형에 대해서 알아보자!]]></title>
            <link>https://velog.io/@dev_h_o/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%90%EB%A3%8C%ED%98%95%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev_h_o/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%90%EB%A3%8C%ED%98%95%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 08 Jun 2023 07:52:20 GMT</pubDate>
            <description><![CDATA[<h2 id="자료형">자료형?</h2>
<p>숫자, 문자열 등 자료 형태로 사용하는 모든 것을 뜻하며 프로그래밍의 가장 기초단계에서 배우는 단위라고 볼 수 있다. 그만큼 만만하면서도 중요하다는 뜻!</p>
<blockquote>
<p>파이썬(Python)
1991년에 발표된 인터프리터 방식의 프로그래밍 언어로 창시자는 귀도 반 로섬(Guido van Rossum)씨다.</p>
</blockquote>
<h3 id="파이썬의-기본-문법">파이썬의 기본 문법</h3>
<p><strong>1. 세미콜론(;)</strong> - 문장의 끝을 나타내는 기호로 프로그래밍에서 자주 쓰이는 단골 기호지만, 울 파이썬은 이런 거 필요없다. 쓰고 싶음 써도 된다.</p>
<pre><code class="language-python">car_company_1 = &#39;Ferrari&#39;
car_detail_1 = [
    {&#39;color&#39; : &#39;White&#39;}, 
    {&#39;horsepower&#39;: 400},
    {&#39;price&#39;: 8000}
]</code></pre>
<p><strong>2. 주석</strong> - # (한줄, 블록 주석 구분 없음)</p>
<pre><code class="language-python"># 파이썬 주석 style~~~~

### 이렇게도 쓰고

# 요렇게도
# 씁니다.</code></pre>
<p>*<em>3. 들여쓰기(파이썬의 핵심) *</em>
  제어문 블록이나 함수의 문장을 작성할 때
  반드시 들여쓰기를 해야 한다.</p>
<p>** 자바** (들여쓰기를 안해도 상관없음.)</p>
<pre><code class="language-java">for(int i = 0; i &lt; 10; i++){
println(i);
println(&quot;hello&quot;);
}</code></pre>
<p>** 파이썬**</p>
<pre><code class="language-python">for i in range(10):
    print(i)
    print(&quot;hello&quot;)</code></pre>
<p>파이썬에서 들여쓰기 방법은 공백(스페이스) 2칸, 4칸, 탭(tab - 8칸) 등 여러가지가 있지만 파이썬 코딩 스타일 가이드(PEP 8)에서는 4칸을 기본으로 규정하고 있다.</p>
<h2 id="숫자형">숫자형</h2>
<p>주로 사용하는 정수(int)와 실수(float)</p>
<pre><code class="language-python"># 정수형
a = 123
a = -178

# 실수형
a = 1.2
a = -3.45</code></pre>
<h2 id="문자열">문자열</h2>
<p>여러 줄인 문자열을 변수에 대입하고 싶을 때 사용하는 방법</p>
<pre><code>Life is too short
You need python</code></pre><pre><code class="language-python">#이스케이프 코드 삽입하기(\n)
multiline = &quot;Life is too short\nYou need python&quot;

# 연속된 작은따옴표 3개(&#39;&#39;&#39;) 또는 큰따옴표 3개(&quot;&quot;&quot;) 사용하기
multiline=&#39;&#39;&#39;
    Life is too short
    You need python
    &#39;&#39;&#39;</code></pre>
<h4 id="자주-사용하는-이스케이프-코드">자주 사용하는 이스케이프 코드</h4>
<blockquote>
</blockquote>
<p>\n    : 문자열 안에서 줄을 바꿀 때 사용
\t    : 문자열 사이에 탭 간격을 줄 때 사용
\    : 문자 \를 그대로 표현할 때 사용</p>
<p><strong>참고</strong>
<a href="https://wikidocs.net/14">https://wikidocs.net/14</a></p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.06.08 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 타임리프] 영화 정보 기록 페이지 만들기 2 (페이징)]]></title>
            <link>https://velog.io/@dev_h_o/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84-%EC%98%81%ED%99%94-%EC%A0%95%EB%B3%B4-%EA%B8%B0%EB%A1%9D-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0-2-%ED%8E%98%EC%9D%B4%EC%A7%95</link>
            <guid>https://velog.io/@dev_h_o/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84-%EC%98%81%ED%99%94-%EC%A0%95%EB%B3%B4-%EA%B8%B0%EB%A1%9D-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0-2-%ED%8E%98%EC%9D%B4%EC%A7%95</guid>
            <pubDate>Wed, 07 Jun 2023 03:34:23 GMT</pubDate>
            <description><![CDATA[<h1 id="페이징-처리를-해보자">페이징 처리를 해보자!</h1>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/271c7cd4-44f4-48d0-963f-12c8712899d0/image.png" alt=""></p>
<p>데이터가 많다면 일정한 간격으로 쪼개서 페이징 처리를 해주는 것이 사용자에게 편리하다.
그동안 웹서핑을 하면서 편하게 버튼 눌러가면서 읽기만 했지.....페이징 처리가 이렇게 복잡미묘할 줄이야...</p>
<h2 id="✅-페이징-처리를-위한-준비">✅ 페이징 처리를 위한 준비</h2>
<h4 id="💻-entity-소스">💻 Entity 소스</h4>
<p><code>엔티티</code> :  데이터베이스 테이블과 매핑되는 자바 클래스</p>
<pre><code class="language-java">@Entity
@Table(name=&quot;movietbl&quot;)
@Data
public class Movie {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long mcode;

    @Column(nullable = false, length = 100 )
    private String mname;

    @Column(nullable = false, length = 50)
    private String mdirector;

    @Column(nullable = false, length = 50)
    private String mnation;

    @Column(nullable = false, length = 100)
    private String mgenre;

    @Column(nullable = false, length = 100)
    private String mactor;

    @Column(nullable = false, length = 10)
    private String mopen;

    @Column(length = 2000)
    private String msynopsis;

    @Column(length = 50)
    private String moriname;

    @Column(length = 50)
    private String msysname;
}</code></pre>
<p>영화 정보를 기록하는 엔티티 클래스를 만든다. 엔티티의 속성값을 html 페이지에서 사용한다.</p>
<h4 id="💻-repository-소스">💻 Repository 소스</h4>
<pre><code class="language-java">public interface MovieRepository extends JpaRepository&lt;Movie, Long&gt; {
    //DB CRUD(insert, update, delete, select 용 인터페이스)
    //기본으로 전체 내용 삽입, 전체 내용 수정, 행 삭제
    //전체 내용 검색 및 키 검색용 메소드를 제공.

    //페이지 처리
    Page&lt;Movie&gt; findByMcodeGreaterThan(Long mcode, Pageable pb);
}</code></pre>
<p>엔티티만으로는 데이터베이스에 데이터를 저장하거나 조회 할 수 없으므로 데이터 처리를 위해서는 실제 데이터베이스와 연동하는 JPA 리포지터리를 생성한다.</p>
<h2 id="✅-페이징-서비스-처리">✅ 페이징 서비스 처리</h2>
<h4 id="💻-pagingutil-소스">💻 PagingUtil 소스</h4>
<pre><code class="language-java">@AllArgsConstructor
public class PagingUtil {
    private int totalPage; //전체 페이지 개수
    private int pageNum; //현재 보이는 페이지 번호
    private int pageCnt; //페이지당 보여질 번호 개수
    private String urlStr; //링크 url

    public String makePaging(){
        String pageHtml = null;
        StringBuffer sb = new StringBuffer();

        //현재 그룹
        int curGroup = (pageNum % pageCnt) &gt; 0?
                pageNum/pageCnt +1 :
                pageNum/pageCnt;

        //그룹의 시작번호
        int start = (curGroup * pageCnt) - (pageCnt -1);
        //그룹의 끝번호
        int end = (curGroup * pageCnt) &gt;= totalPage ?
                totalPage : curGroup * pageCnt;

        //page html 작성
        if(start != 1){
            sb.append(&quot;&lt;a class=&#39;pno&#39; href=&#39;/&quot; + urlStr + &quot;pageNum=&quot; + (start -1) + &quot;&#39;&gt;◀&lt;/a&gt;&quot;);
        }

        for(int i = start; i &lt;= end; i++){
            if(pageNum == i){
                sb.append(&quot;&lt;font class=&#39;pno&#39;&gt;&quot; + i + &quot;&lt;/font&gt;&quot;);
            }
            else{
                sb.append(&quot;&lt;a class=&#39;pno&#39; href=&#39;/&quot; + urlStr + &quot;pageNum=&quot; + i + &quot;&#39;&gt;&quot; + i +&quot; &lt;/a&gt;&quot;);
            }
        }

        if(end != totalPage){
            sb.append(&quot;&lt;a class=&#39;pno&#39; href=&#39;/&quot; + urlStr + &quot;pageNum=&quot; + (end + 1) + &quot;&#39;&gt;▶&lt;/a&gt;&quot;);
        }//&lt;a class=&#39;pno&#39; href=&#39;/?pageNum=6&gt;▶&lt;/a&gt;

        //Stringbuffer -&gt; String 변환
        pageHtml = sb.toString();

        return pageHtml;
    }
}</code></pre>
<p>웹 페이지에서 처리된 페이징 버튼을 표현한다.
<code>@AllArgsConstructor</code> : 모든 필드 값을 파라미터로 받는 생성자를 만드는 어노테이션</p>
<h4 id="💻-service-소스getmovielist">💻 Service 소스(getMovieList)</h4>
<pre><code class="language-java">public class MovieService {
    @Autowired
    private MovieRepository mRepo;

    private ModelAndView mv;

    public ModelAndView getMovieList(Integer pageNum, HttpSession session){
        log.info(&quot;getMovieList()&quot;);

        if(pageNum == null){
            pageNum =1;
        }
        int listCnt = 5; //한 페이지당 5개씩 목록 출력

        //페이징 조건 생성(Pageable)
        Pageable pb = PageRequest.of((pageNum -1), listCnt, Sort.Direction.DESC, &quot;mcode&quot;);
        //of(시작번호, 목록개수, 정렬방식, 키필드명)

        Page&lt;Movie&gt; result = mRepo.findByMcodeGreaterThan(0L, pb);

        //페이지 형태의 결과를 목록형태로 변환
        List&lt;Movie&gt; mList = result.getContent();

        //전체 페이지
        int totalPage = result.getTotalPages();

        //페이징용 html 작성 //getPaging 서비스 메소드 생성
        String paging = getPaging(pageNum, totalPage);

        //세션에 페이지 번호 저장
        session.setAttribute(&quot;pageNum&quot;, pageNum);

        mv = new ModelAndView();
        mv.addObject(&quot;mList&quot;, mList);

        //페이징용 html 추가
        mv.addObject(&quot;paging&quot;, paging);

        //뷰네임 지정
        mv.setViewName(&quot;home&quot;);


        return mv;
    }
}</code></pre>
<p><code>getMovieList</code>의 역할</p>
<ul>
<li>한 페이지당 글 목록 5개씩 출력하도록 지정</li>
<li>페이징 조건 생성 → <code>pb</code></li>
<li><code>mRepo</code> 데이터 가져오기</li>
<li>페이징용 html 작성한 메소드 가져오기 → <code>getPaging</code></li>
<li>세션에 페이지 번호 저장</li>
<li><code>ModelAndView</code>로 값 저장 후 <code>home</code>으로 리턴</li>
</ul>
<h4 id="💻-service-소스getpaging">💻 Service 소스(getPaging)</h4>
<pre><code class="language-java">//페이징 구간 설정
private String getPaging(Integer pageNum, int totalPage) {
    log.info(&quot;getPaging()&quot;);
    String pageHtml = null;
    int pageCnt = 2;
    String urlStr = &quot;?&quot;;

    PagingUtil paging = new PagingUtil(totalPage, pageNum, pageCnt, urlStr);

    pageHtml = paging.makePaging();

    return pageHtml;
}</code></pre>
<p><code>PagingUtil</code>에서 가져온 정보로 <code>pageHtml</code>을 생성하고 return한다.</p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.06.07 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 타임리프] 영화 정보 기록 페이지 만들기 1 (메인 페이지)]]></title>
            <link>https://velog.io/@dev_h_o/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84-%EC%98%81%ED%99%94-%EC%A0%95%EB%B3%B4-%EA%B8%B0%EB%A1%9D-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-%EB%A9%94%EC%9D%B8-%ED%8E%98%EC%9D%B4%EC%A7%80</link>
            <guid>https://velog.io/@dev_h_o/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84-%EC%98%81%ED%99%94-%EC%A0%95%EB%B3%B4-%EA%B8%B0%EB%A1%9D-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-%EB%A9%94%EC%9D%B8-%ED%8E%98%EC%9D%B4%EC%A7%80</guid>
            <pubDate>Mon, 05 Jun 2023 07:44:21 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev_h_o/post/b0dee6b0-43ba-46c5-9580-615af0470f45/image.png" alt=""></p>
<p>영화 정보를 등록하고 확인할 수 있는 페이지를 스프링과 타임리프를 이용해서 만들어본다.</p>
<blockquote>
<p>타임리프는 스프링 부트에서 공식적으로 지원하는 View 템플릿으로
JSP와 달리 html 확장자를 갖고 있어 JSP처럼 Servlet이 문서를 표현하는 방식이 아니기 때문에 서버 없이도 동작 가능하다.</p>
</blockquote>
<h2 id="🌱-인텔리제이-세팅하기">🌱 인텔리제이 세팅하기</h2>
<p>프로젝트 생성 후 <code>Java</code>, <code>Maven</code>, <code>Jar</code> 선택</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/09c2de0a-6ec2-447d-a59a-1992e6d8c98e/image.png" alt=""></p>
<p>필요 <code>dependencies</code></p>
<ul>
<li>Spring Boot DevTools, Lombok, Spring Web, Thymeleaf, Spring Data Jpa, MySQL Driver
<img src="https://velog.velcdn.com/images/dev_h_o/post/36e1e624-9a57-4162-9fa7-c1dbbd877b1f/image.png" alt=""></li>
</ul>
<p><code>application.properties</code>에서 필요한 속성들을 설정한다.</p>
<pre><code># static resource
spring.web.resources.static-locations=classpath:static/

# datasource(DB)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/berrydb?serverTimezone=Asia/Seoul
spring.datasource.username=buser
spring.datasource.password=12341234

# port number
server.port = 80

# jpa setting
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=update

# JPA log setting
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
logging.level.org.hibernate.type.descriptor.sql=trace

# thymeleaf setting
spring.thymeleaf.cache=false
spring.devtools.restart.enabled=true

# multi-part file upload setting
spring.servlet.multipart.max-file-size=4MB
spring.servlet.multipart.max-request-size=8MB

# error page setting
server.error.whitelabel.enabled=false
server.error.path=/error/
server.error.include-stacktrace=always
server.error.include-message=always
server.error.include-exception=true</code></pre><h2 id="🌱-메인페이지-만들기껍데기">🌱 메인페이지 만들기(껍데기)</h2>
<p>스프링과 타임리프를 활용한 웹 페이지 구현에 집중해야 하므로 <code>css</code> 기록은 생략!</p>
<h4 id="💻-header-소스">💻 header 소스</h4>
<pre><code class="language-html">&lt;div class=&quot;top-bar&quot;&gt;
  &lt;img th:src=&quot;@{images/mlogo.png}&quot; alt=&quot;로고&quot; class=&quot;logo&quot; th:onclick=&quot;|location.href=&#39;@{/}&#39;|&quot;&gt;
  &lt;h2&gt;영화정보 사이트&lt;/h2&gt;
&lt;/div&gt;</code></pre>
<p><code>header</code> 부분의 로고를 클릭하면 메인 페이지로 돌아올 수 있게 <code>onclick</code> 이벤트를 걸어준다.</p>
<h4 id="💻-footer-소스">💻 footer 소스</h4>
<pre><code class="language-html">&lt;div class=&quot;footer-bar&quot;&gt;
    &lt;img th:src=&quot;@{images/mlogo.png}&quot; class=&quot;footer-logo&quot;&gt;
    &lt;span&gt;&amp;copy;ICIA IoT 2023&lt;/span&gt;
&lt;/div&gt;</code></pre>
<h4 id="💻-contents-소스">💻 contents 소스</h4>
<pre><code class="language-html">&lt;div class=&quot;wrap&quot;&gt;
    &lt;th:block th:insert=&quot;~{header.html}&quot;&gt;&lt;/th:block&gt;
    &lt;div class=&quot;content&quot;&gt;
        &lt;div class=&quot;board-form&quot;&gt;
            &lt;div class=&quot;list-title&quot;&gt;
                &lt;h2 class=&quot;form-header&quot;&gt;영화 목록&lt;/h2&gt;
                &lt;button class=&quot;wr-btn&quot; th:onclick=&quot;|location.href=&#39;@{writeForm}&#39;|&quot;&gt;영화 등록&lt;/button&gt;
            &lt;/div&gt;
            &lt;div class=&quot;data-area&quot;&gt;
                &lt;th:block th:if=&quot;${#lists.isEmpty(mList)}&quot;&gt;
                    &lt;div class=&quot;movie-item&quot; style=&quot;height: 100px;&quot;&gt;등록된 영화가 없습니다.&lt;/div&gt;
                &lt;/th:block&gt;
                &lt;th:block th:unless=&quot;${#lists.isEmpty(mList)}&quot;&gt;
                    &lt;th:block th:each=&quot;mitem:${mList}&quot;&gt;
                        &lt;div class=&quot;movie-item&quot;&gt;
                            &lt;th:block th:if=&quot;${mitem.msysname} == null&quot;&gt;
                                &lt;img class=poster-pre th:src=&quot;@{images/no_image.jpg}&quot;&gt;
                            &lt;/th:block&gt;
                            &lt;th:block th:unless=&quot;${mitem.msysname} == null&quot;&gt;
                                &lt;img class=&quot;poster-pre&quot; th:src=&quot;@{upload/}+${mitem.msysname}&quot;&gt;
                            &lt;/th:block&gt;
                            &lt;div class=&quot;info-pre&quot;&gt;
                                &lt;div class=&quot;title-pre&quot;&gt;
                                    &lt;a th:href=&quot;@{detail(mcode=${mitem.mcode})}&quot; th:text=&quot;${mitem.mname}&quot;&gt;&lt;/a&gt;
                                &lt;/div&gt;
                            &lt;div th:text=&quot;${mitem.mdirector}&quot;&gt;&lt;/div&gt;
                            &lt;div th:text=&quot;${mitem.mopen}&quot;&gt;&lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/th:block&gt;
                &lt;/th:block&gt;
            &lt;/div&gt;&lt;!--data-area--&gt;
            &lt;div class=&quot;paging-area&quot;&gt;
                &lt;div class=&quot;paging&quot; th:utext=&quot;${paging}&quot;&gt;&lt;/div&gt;
            &lt;/div&gt;&lt;!--paging-area--&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;th:block th:insert=&quot;~{footer.html}&quot;&gt;&lt;/th:block&gt;
&lt;/div&gt;&lt;!--wrap--&gt;</code></pre>
<ul>
<li>영화등록 버튼을 누르면 <code>writeForm</code> 페이지로 이동하게끔 이벤트를 걸어준다.</li>
<li>등록된 영화 정보가 없을 때는 등록된 영화가 없다는 문구가 출력된다.</li>
</ul>
<h4 id="💻-javascript-소스">💻 javascript 소스</h4>
<pre><code class="language-javascript">&lt;script th:inline=&quot;javascript&quot;&gt;
    $(function () {
        let m = [[${msg}]];//msg가 없으면 null.
        if (m != null) {
            alert(m);
        }
    });
&lt;/script&gt;</code></pre>
<p><code>alert</code> 로 메세지를 띄워주는 스크립트를 작성한다.</p>
<h2 id="🌱-메인페이지-만들기알맹이">🌱 메인페이지 만들기(알맹이)</h2>
<h4 id="💻-controller-소스">💻 Controller 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;/&quot;)
public ModelAndView home(Integer pageNum, HttpSession session){
    log.info(&quot;home()&quot;);

    //서비스 만들면 수정할 것
    //mv = new ModelAndView();
    mv = mServ.getMovieList(pageNum, session);
    mv.setViewName(&quot;home&quot;);

    return mv;
}</code></pre>
<p>페이징 처리를 위해 페이지번호와 세션을 가져와서 서비스에 넘긴다.</p>
<h4 id="💻-service-소스">💻 Service 소스</h4>
<pre><code class="language-java"> @Autowired
//리포지토리 가져오기
private MovieRepository mRepo;

public ModelAndView getMovieList(Integer pageNum, HttpSession session){
    log.info(&quot;getMovieList()&quot;);

    if(pageNum == null){
        pageNum = 1;
    }
    int listCnt = 5; //한페이지 당 5개씩 목록 출력

    //페이징 조건 생성(Pageable)
    Pageable pb = PageRequest.of((pageNum -1), listCnt, Sort.Direction.DESC, &quot;mcode&quot;);
    //of(시작번호, 목록개수, 정렬방식, 키필드명)

    Page&lt;Movie&gt; result = mRepo.findByMcodeGreaterThan(0L, pb); //Long 타입이라 0L입력

    //페이지 형태의 결과를 목록형태로 변환
    List&lt;Movie&gt; mList = result.getContent();

    //전체 페이지
    int totalPage = result.getTotalPages();

    //페이징용 html 작성
    String paging = getPaging(pageNum, totalPage);

    //세션에 페이지 번호 저장
    session.setAttribute(&quot;pageNum&quot;, pageNum);

    mv = new ModelAndView();
    mv.addObject(&quot;mList&quot;, mList);

    //페이징용 html 추가
    mv.addObject(&quot;paging&quot;, paging);

    //뷰네임 지정
    mv.setViewName(&quot;home&quot;);

    return mv;
}</code></pre>
<p><code>GreaterThan</code> : Repository 메소드 중 범위에 따라 데이터를 가져오도록 처리하는 메소드 작명법</p>
<p>이 영화 정보가 저장된 리스트를 가져오는 <code>getMovieList</code> 서비스는 다음 항목을 처리한다.</p>
<ul>
<li>페이징 처리 (데이터 개수가 많으면 5개씩 쪼개서 페이지를 분할 처리)</li>
<li>페이징에 필요한 html 요소 메소드를 가져온다.<ul>
<li>페이징 유틸</li>
</ul>
</li>
<li>저장된 영화 정보 데이터를 <code>mList</code>로 반환한다.</li>
</ul>
<p>이러한 것들을 처리하기 위해서는 ...또 필요한 것이 있다.
테이블을 생성하는 <code>엔티티</code>와 DB CRUD(insert, update, delete, select 용 인터페이스)에 필요한 <code>리포지토리</code>...!</p>
<p>그것은 다음 포스팅ㅇㅔ....</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/43ffc783-20d0-4984-bea6-e5f699c0019d/image.png" alt=""></p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.06.05 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA 맵에 대해서 알아보자! (개념, 데이터 사용)]]></title>
            <link>https://velog.io/@dev_h_o/JAVA-%EB%A7%B5%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-%EA%B0%9C%EB%85%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@dev_h_o/JAVA-%EB%A7%B5%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-%EA%B0%9C%EB%85%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Sat, 03 Jun 2023 05:06:13 GMT</pubDate>
            <description><![CDATA[<h2 id="map이란">Map이란?</h2>
<p>Map은 리스트나 배열처럼 순차적으로 해당 요소 값을 구하지 않고 <code>key</code>를 통해 <code>value</code>를 얻는다.</p>
<p><strong>Map의 특징</strong></p>
<ul>
<li>키는 중복될 수 없고 값은 중복을 허용한다.</li>
<li>특정 키를 사용하여 값을 검색하거나, 새로운 키-값 쌍을 추가하고, 기존의 값을 업데이트하거나 제거할 수 있다.</li>
<li>데이터의 검색 속도가 중요한 경우나, 고유한 키를 사용하여 데이터를 저장하고자 할 때 유용하다.</li>
<li><code>{이름:홍길동}</code>, <code>{고양이:cat}</code> 과 같이 대응 관계를 쉽게 표현할 수 있는 자료형</li>
</ul>
<h2 id="map에서-데이터-추가하기">Map에서 데이터 추가하기</h2>
<p><code>put</code> 메소드를 이용하여 맵에 원하는 값을 매칭시켜서 추가할 수 있다.</p>
<p><strong>HashMap</strong></p>
<p>HashMap은 자바에서 제공하는 맵(Map) 인터페이스를 구현한 클래스 중 하나로 해시 테이블(hash table)을 기반으로한 맵이다.
키(key)와 값(value)의 쌍을 저장하고 검색하는 데 사용된다.</p>
<pre><code class="language-java">import java.util.HashMap;

public class MapExample {
    public static void main(String[] args) {
        Map&lt;String, String&gt; dictionary = new HashMap&lt;&gt;();
        dictionary.put(&quot;chicken&quot;, &quot;닭&quot;);
        dictionary.put(&quot;hippo&quot;, &quot;하마&quot;);
        }
}</code></pre>
<p><code>chicken:닭</code> , <code>hippo:하마</code> 값을 dictionary에 넣어주었다.</p>
<h2 id="map에서-데이터-가져오기">Map에서 데이터 가져오기</h2>
<p><code>get</code> : key에 해당하는 value값을 얻기 위해서 사용하는 메소드</p>
<pre><code class="language-java">System.out.println(dictionary.get(&quot;chicken&quot;));</code></pre>
<p>해석 : dictionary에서 chicken에 해당하는 value 가져오렴</p>
<pre><code>닭</code></pre><h2 id="map에서-데이터-제거하기">Map에서 데이터 제거하기</h2>
<p>데이터를 제거할 때도 <code>put</code> 메소드를 사용할 수 있다.</p>
<pre><code class="language-java">dictionary.put(&quot;chicken&quot;, null);</code></pre>
<p>해석 : dictionary에 있는 chicken 키에 해당하는 값 null로 매칭하렴</p>
<p>이렇게 하면 chicken에 해당하는 값이 null값으로 변경되지만 데이터는 아직 남아있는 상태라서 size 명령어로 사이즈 크기를 확인하면 데이터가 그대로 남아있는 것으로 인식된다.</p>
<p>그래서 깔끔하게 지워버릴 때는 <code>remove</code>를 사용하면 된다.</p>
<pre><code class="language-java">dictionary.remove(&quot;chicken&quot;);
dictionary.remove(&quot;hippo&quot;);   </code></pre>
<p>키값에 해당되는 데이터(key, value)를 삭제 후 그 value값을 리턴하는 명령어</p>
<pre><code class="language-java">System.out.println(dictionary.remove(&quot;chicken&quot;)); //&quot;닭&quot; 출력</code></pre>
<h2 id="map의-크기-확인하기">Map의 크기 확인하기</h2>
<p><code>size</code> : Map의 개수를 리턴하는 메소드</p>
<pre><code class="language-java">if (dictionary.isEmpty()) {
    System.out.println(&quot;단어가 하나도 없습니다.&quot;);
    System.exit(0);
} else {
    System.out.println(dictionary.size() + &quot; 개의 단어가 있습니다.&quot;);
}
System.out.println(dictionary.size() + &quot;개의 단어가 있습니다.&quot;);</code></pre>
<p>dictionary가 비어 있는 상태인지 <code>isEmpty()</code>로 확인하고 맞다면 <code>&quot;단어가 하나도 없습니다.&quot;</code>를 출력한다.
아니라면 <code>size</code>로 dictionary의 개수를 확인해서 출력한다.</p>
<h2 id="반복문에서-map-이용하기">반복문에서 Map 이용하기</h2>
<p><code>keySet()</code>메서드는 맵의 모든 키를 모아서 Set 자료형으로 리턴한다.</p>
<pre><code class="language-java">import java.util.HashMap;

public class Practice {
    public static void main(String[] args) {
        HashMap&lt;String, String&gt; map = new HashMap&lt;&gt;();
        map.put(&quot;people&quot;, &quot;사람&quot;);
        map.put(&quot;soccer&quot;, &quot;축구&quot;);
        System.out.println(map.keySet()); // [people, soccer] 출력
    }
} </code></pre>
<p><strong>반복문으로 표현하기(for)</strong></p>
<pre><code class="language-java">public class Practice {
    public static void main(String[] args) {
        HashMap&lt;String, String&gt; map = new HashMap&lt;&gt;();
        map.put(&quot;people&quot;, &quot;사람&quot;);
        map.put(&quot;soccer&quot;, &quot;축구&quot;);

        Collection&lt;String&gt; keys = map.keySet();
        for (String abc : keys) {
            System.out.println(abc);// [people, soccer] 출력
        }
    }
}</code></pre>
<p><strong>키랑 값을 동시에 보려면 어떻게 해야할까?</strong></p>
<p><code>entryset</code> : 맵에 저장된 키-값 데이터를 표현하는 entry 객체의 집합(Set)을 반환한다.</p>
<pre><code class="language-java">import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Practice {
    public static void main(String[] args) {
        HashMap&lt;String, String&gt; map = new HashMap&lt;&gt;();
        map.put(&quot;people&quot;, &quot;사람&quot;);
        map.put(&quot;soccer&quot;, &quot;축구&quot;);

        Set&lt;Map.Entry&lt;String, String&gt;&gt; entries = map.entrySet();

        for (Map.Entry&lt;String, String&gt; entry : entries) {
            String abc = entry.getKey();
            String korean = entry.getValue();

            System.out.println(abc + &quot;: &quot; + korean);
        }
    }
}</code></pre>
<p><strong>출력 결과</strong></p>
<pre><code>soccer: 축구
people: 사람</code></pre><p><code>entrySet()</code>메소드를 사용하여 <code>Map.Entry</code> 객체들의 Set을 얻은 후 반복문을 통해 각 <code>Map.Entry</code> 객체에서 키와 값을 추출하여 출력한다. </p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.06.03 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java Spring JSP웹페이지 만들기9 (뒤로 가기, 게시글 수정 기능)]]></title>
            <link>https://velog.io/@dev_h_o/Java-Spring-JSP%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B09-%EB%92%A4%EB%A1%9C-%EA%B0%80%EA%B8%B0-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%88%98%EC%A0%95-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@dev_h_o/Java-Spring-JSP%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B09-%EB%92%A4%EB%A1%9C-%EA%B0%80%EA%B8%B0-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%88%98%EC%A0%95-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Fri, 02 Jun 2023 07:20:42 GMT</pubDate>
            <description><![CDATA[<p>이제 거의 다 만들어진건가...? 하면 계속 늘어나는 것들..</p>
<p>그동안 생각없이 사용해왔던 모든 웹 서비스에게 무한히 감사드립니다...
지금 사용하고 있는 벨로그 서비스도 넘 감사해요..
<img src="https://velog.velcdn.com/images/dev_h_o/post/263fec96-462f-4d31-98cb-79270024996c/image.png" alt=""></p>
<h1 id="💞-게시글-기능-추가하기-수정-뒤로가기">💞 게시글 기능 추가하기 (수정, 뒤로가기)</h1>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/a47f5b26-f819-4a23-8486-14319f996044/image.png" alt=""></p>
<blockquote>
<p><code>U</code> 버튼 : 게시글 수정하기
<code>B</code> 버튼 : 뒤로가기(글 목록 페이지로 이동)</p>
</blockquote>
<pre><code class="language-html">//버튼 영역
&lt;div class=&quot;btn-area&quot;&gt;
  &lt;button class=&quot;btn-write&quot; id=&quot;upbtn&quot; onclick=&quot;upboard(&#39;${board.b_num}&#39;)&quot;&gt;U&lt;/button&gt;
  &lt;button class=&quot;btn-write&quot; id=&quot;delbtn&quot; onclick=&quot;delCheck(&#39;${board.b_num}&#39;)&quot;&gt;D&lt;/button&gt;
  &lt;button class=&quot;btn-sub&quot; onclick=&quot;backbtn()&quot;&gt;B&lt;/button&gt;
&lt;/div&gt;</code></pre>
<p>버튼 영역에서 <code>#upbtn</code>과 <code>backbtn()</code> 처리!</p>
<h4 id="✏-작업-순서">✏ 작업 순서</h4>
<ol>
<li><code>backbtn()</code>이 간단하니까 먼저 처리
( <code>boardContents</code> , <code>SearchDto</code> )</li>
<li><code>U</code> 버튼을 누르면 수정용 updateForm 페이지를 불러와서 수정할 내용을 작성하게끔 만들어준다.
( <code>boardContents</code> , <code>boardController</code> , <code>updateForm</code> )</li>
<li>글 내용 영역 수정 및 데이터베이스 업로드
( <code>updateForm</code> , <code>boardController</code> , <code>BoardService</code> , <code>BoardDao</code> )</li>
<li>파일을 추가하거나 삭제도 가능하게끔 파일 수정 기능 만들기
( <code>updateForm</code> , <code>boardController</code> , <code>BoardService</code> , <code>BoardDao</code> )<br>

</li>
</ol>
<h3 id="🔻-뒤로가기-기능">🔻 뒤로가기 기능</h3>
<h4 id="💻-boardcontents-소스">💻 BoardContents 소스</h4>
<pre><code class="language-javascript">function backbtn(){
let urlstr = &quot;/list?&quot;;
let col = &quot;${sdto.colname}&quot;;
let keyw = &quot;${sdto.keyword}&quot;;

if(col == null || col == &#39;&#39;){//검색을 수행하지 않은 경우
  urlstr += &quot;pageNum=${pageNum}&quot;;
} else{//검색을 한 경우
  urlstr += &quot;colname=${sdto.colname}&amp;keyword=${sdto.keyword}&amp;pageNum=${sdto.pageNum}&quot;;
}
console.log(urlstr);
location.href = urlstr;
}</code></pre>
<p><code>list</code> urlstr에서 참조하는 sdto.colname과 sdto.keyword를 확인하고 검색 여부에 따라 이동하는 페이지가 달라진다.</p>
<h4 id="💻-boardcontroller-소스">💻 BoardController 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;list&quot;)
public ModelAndView boardList(SearchDto sdto, HttpSession session){
    log.info(&quot;boardList()&quot;);
    mv = bServ.getBoardList(sdto,session); //서비스에서 데이터 삽입 및 목적페이지 지정
    return mv;
}</code></pre>
<h4 id="💻-searchdto-소스">💻 SearchDto 소스</h4>
<pre><code class="language-java">@Data
public class SearchDto {
    private String colname;
    private String keyword;
    private int pageNum;//보여질 페이지 번호
    private int listCnt;//페이지 당 출력할 게시글 개수
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/346abc8c-63af-4bb2-88a6-d2a16e46c897/image.png" alt=""></p>
<p>검색을 하지 않은 상태에서 게시글 상세보기 페이지의 <code>B</code> 버튼을 누르면 원래 사용자가 원래 있던 페이지로 이동한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/7611f94b-fb6f-48fa-807f-6c809dad849d/image.png" alt=""></p>
<p>만약 이런식으로 검색창에 검색을 한 뒤 게시글을 클릭했다가 <code>B</code> 버튼을 누른다면 <code>urlstr</code> 가
<code>http://localhost/list?colname=b_title&amp;keyword=페이지&amp;pageNum=1</code> 로 저장되어 해당 페이지로 그대로 돌아오게 된다.</p>
<h3 id="🔻-게시글-수정하기-jsp-만들기">🔻 게시글 수정하기 (JSP 만들기)</h3>
<p>게시글 수정용 <code>updateForm</code> jsp 페이지를 만든다.</p>
<h4 id="💻-boardcontents-소스-1">💻 BoardContents 소스</h4>
<pre><code class="language-javascript">  function upboard(bnum) {
    location.href =&quot;/updateForm?b_num=&quot; +bnum;
  }</code></pre>
<p><code>BoardContents</code>에서 수정 버튼을 누르면 해당 글 번호의 <code>updateForm</code>으로 이동한다.</p>
<h4 id="💻-updateform-소스-글쓰기-영역">💻 updateForm 소스 (글쓰기 영역)</h4>
<pre><code class="language-html">&lt;h2 class=&quot;login-header&quot;&gt;글수정&lt;/h2&gt;
&lt;!-- 로그인한 id(숨김), 제목, 내용 --&gt;
&lt;input type=&quot;hidden&quot; name=&quot;b_id&quot; value=&quot;${mb.m_id}&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;b_num&quot; value=&quot;${board.b_num}&quot;&gt;
&lt;input type=&quot;text&quot; class=&quot;write-input&quot; name=&quot;b_title&quot;
       autofocus placeholder=&quot;제목&quot; required value=&quot;${board.b_title}&quot;&gt;
&lt;textarea name=&quot;b_contents&quot; class=&quot;write-input ta&quot; rows=&quot;15&quot; placeholder=&quot;내용을 적어주세요.&quot;&gt;${board.b_contents}&lt;/textarea&gt;
</code></pre>
<p>글쓰기 페이지인 <code>writeForm</code>과 비슷하지만 <code>updateForm</code>은 사용자가 기존에 입력했었던 데이터들이 페이지에 미리 입력되어 있다.
(수정하기 위한 페이지이므로)</p>
<h4 id="💻-boardcontroller-소스-1">💻 BoardController 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;updateForm&quot;)
public ModelAndView updateForm(Integer b_num){
    log.info(&quot;updateForm()&quot;);
    mv = bServ.updateForm(b_num);
    return mv;
}</code></pre>
<h4 id="💻-boardservice-소스">💻 BoardService 소스</h4>
<pre><code class="language-java">public ModelAndView updateForm(Integer b_num){
    log.info(&quot;updateForm()&quot;);
    //게시글 내용 가져오기
    BoardDto board = bDao.selectBoard(b_num);

    //파일 목록 가져오기
    List&lt;BfileDto&gt; fList = bDao.selectFiles(b_num);

    //mv에 추가.
    mv = new ModelAndView();
    mv.addObject(&quot;board&quot;, board);
    mv.addObject(&quot;fList&quot;, fList);

    //view 지정
    mv.setViewName(&quot;updateForm&quot;);
    return mv;
}</code></pre>
<h4 id="💻-boarddaojava-소스">💻 BoardDao.java 소스</h4>
<pre><code class="language-java">@Mapper
public interface BoardDao {
    //게시글 1개 가져오는 메소드 선언
    BoardDto selectBoard(Integer b_num);
}</code></pre>
<h4 id="💻-boarddaoxml-소스">💻 BoardDao.xml 소스</h4>
<pre><code class="language-sql">&lt;select id=&quot;selectBoard&quot; resultType=&quot;BoardDto&quot; parameterType=&quot;Integer&quot;&gt;
    select * from blist where b_num=#{b_num}
&lt;/select&gt;</code></pre>
<p>수정할 게시글 데이터를 가져오는 쿼리문</p>
<h3 id="🔻-파일-수정">🔻 파일 수정</h3>
<h4 id="💻-updateform-소스-파일처리-영역">💻 updateForm 소스 (파일처리 영역)</h4>
<pre><code class="language-html">&lt;div class=&quot;filebox&quot;&gt;
    &lt;!--첨부된 파일 목록 출력--&gt;
    &lt;div id=&quot;bfile&quot; style=&quot;margin-bottom: 10px;&quot;&gt;
        &lt;c:if test=&quot;${empty fList}&quot;&gt;
            &lt;label style=&quot;width: 100%;&quot;&gt;첨부파일 없음&lt;/label&gt;
        &lt;/c:if&gt;
        &lt;c:if test=&quot;${!empty fList}&quot;&gt;
            &lt;c:forEach var=&quot;f&quot; items=&quot;${fList}&quot;&gt;
                &lt;label style=&quot;width: 100%;&quot; onclick=&quot;del(&#39;${f.bf_sysname}&#39;)&quot;&gt;
                    ${f.bf_oriname}
                &lt;/label&gt;
            &lt;/c:forEach&gt;
        &lt;/c:if&gt;
    &lt;/div&gt;
    &lt;!--새로운 파일 첨부--&gt;
    &lt;label for=&quot;file&quot;&gt;파일 추가&lt;/label&gt;
    &lt;input type=&quot;file&quot; name=&quot;files&quot; id=&quot;file&quot; multiple&gt;
    &lt;input type=&quot;text&quot; class=&quot;upload-name&quot; value=&quot;파일선택&quot; readonly&gt;
&lt;/div&gt;</code></pre>
<h4 id="💻-updateform-소스-script-소스">💻 updateForm 소스 (script 소스)</h4>
<pre><code class="language-javascript">//파일 제목 처리용 함수
$(&quot;#file&quot;).on(&quot;change&quot;,function(){
    //파일 선택 창에서 업로드할 파일을 선택한 후 &#39;열기&#39; 버튼을 누르면 change 이벤트가 발생.
    console.log($(&quot;#file&quot;));

    let files = $(&quot;#file&quot;)[0].files;
    console.log(files);

    let fileName = &quot;&quot;;
    if(files.length &gt; 1){
        fileName = files[0].name + &quot; 외 &quot; + (files.length -1) + &quot;개&quot;;
    } else if(files.length == 1){
        fileName = files[0].name;
    } else {
        fileName = &quot;파일선택&quot;;
    }
    $(&quot;.upload-name&quot;).val(fileName);
});</code></pre>
<h4 id="💻-boardcontroller-소스-2">💻 BoardController 소스</h4>
<pre><code class="language-java">@PostMapping(&quot;updateProc&quot;)
public String updateProc(@RequestPart List&lt;MultipartFile&gt; files
                         , BoardDto board
                         , HttpSession session
                         , RedirectAttributes rttr){
    log.info(&quot;updateProc()&quot;);
    String view = bServ.updateBoard(files, board, session, rttr);
    return view;
}</code></pre>
<p>파일 제목 처리용 함수로 파일 추가를 하면 <code>fileName</code> 영역에 추가한 파일 이름이 노출된다.</p>
<blockquote>
<p>글 수정 페이지
<img src="https://velog.velcdn.com/images/dev_h_o/post/7bc881a3-2d51-42bb-82cd-08471e21d749/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><code>123.jpg</code> 파일을 추가한 모습<img src="https://velog.velcdn.com/images/dev_h_o/post/741526eb-4900-48a8-b4e7-1e9deaad347d/image.png" alt=""></p>
</blockquote>
<h3 id="🔻-수정-처리가-다-되었다면-데이터베이스에-수정한-게시글-저장까지-하자">🔻 수정 처리가 다 되었다면 데이터베이스에 수정한 게시글 저장까지 하자!</h3>
<h4 id="💻-boardcontroller-소스-3">💻 BoardController 소스</h4>
<pre><code class="language-java">@PostMapping(&quot;updateProc&quot;)
public String updateProc(@RequestPart List&lt;MultipartFile&gt; files
                         , BoardDto board
                         , HttpSession session
                         , RedirectAttributes rttr){
    log.info(&quot;updateProc()&quot;);
    String view = bServ.updateBoard(files, board, session, rttr);
    return view;
}</code></pre>
<h4 id="💻-boarddaojava-소스-1">💻 BoardDao.java 소스</h4>
<pre><code class="language-java"> //게시글 수정 메소드 선언
void updateBoard(BoardDto board);</code></pre>
<h4 id="💻-boarddaoxml-소스-1">💻 BoardDao.xml 소스</h4>
<pre><code class="language-sql">&lt;update id=&quot;updateBoard&quot; parameterType=&quot;BoardDto&quot;&gt;
    update board set b_title=#{b_title}, b_contents=#{b_contents}
    where b_num=#{b_num}
&lt;/update&gt;</code></pre>
<h3 id="🔻-파일-삭제">🔻 파일 삭제</h3>
<h4 id="💻-updateform-소스-script-소스-1">💻 updateForm 소스 (script 소스)</h4>
<pre><code class="language-javascript">//파일 삭제 처리 함수
function del(sysname) {
    //alert(sysname);
    let con = confirm(&quot;파일을 삭제할까요?&quot;);

    if(con == true){
        //삭제할 파일명
        let objdata = {&quot;sysname&quot;:sysname};
        //파일 목록을 다시 불러오기 위해 게시글 번호 추가
        objdata.bnum = ${board.b_num};
        console.log(objdata);

        $.ajax({
            url:&quot;delFile&quot;,
            type: &quot;post&quot;,
            data: objdata,
            success: function (res) {
                console.log(res);
                console.log(res.length);

                let flist = &quot;&quot;;
                if(res.length == 0){
                    flist += &#39;&lt;label style=&quot;width: 100%;&quot;&gt;첨부파일 없음&lt;/label&gt;&#39;;
                }
                else {
                    for(let f of res){
                        flist += &#39;&lt;label style=&quot;width: 100%;&quot; onclick=&quot;del(\&#39;&#39;
                                 + f.bf_sysname + &#39;\&#39;)&quot;&gt;&#39;
                                 + f.bf_oriname + &#39;&lt;/label&gt;&#39;;
                    }
                }
                $(&quot;#bfile&quot;).html(flist);
            },
            error: function (err) {
                console.log(err);
                alert(&quot;삭제 실패&quot;);
            }
        });
    }
}</code></pre>
<p>기존에 업로드 해뒀던 파일을 삭제하는 함수로 파일을 클릭하면 삭제 확인용 알림창이 뜨면서 삭제 처리를 할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/848080c0-20f2-4af5-9123-51eaed87f0f0/image.png" alt=""></p>
<h4 id="💻-boardcontroller-소스-4">💻 BoardController 소스</h4>
<pre><code class="language-java">@PostMapping(&quot;delFile&quot;)
@ResponseBody
public List&lt;BfileDto&gt; delFile(String sysname, Integer bnum, HttpSession session){

    log.info(&quot;delFile()&quot;);
    List&lt;BfileDto&gt; fList = bServ.fileDelete(sysname, bnum, session);

    return fList;
}</code></pre>
<h4 id="💻-boardservice-소스-1">💻 BoardService 소스</h4>
<pre><code class="language-java">public List&lt;BfileDto&gt; fileDelete(String sysname, Integer bnum, HttpSession session){
    log.info(&quot;fileDelete()&quot;);
    List&lt;BfileDto&gt; fList = null;

    //파일 삭제를 위한 실제 경로 구하기
    String realpath = session.getServletContext().getRealPath(&quot;/&quot;);
    realpath += &quot;upload/&quot; + sysname;

    try{
        //파일 삭제
        File file = new File(realpath);
        if(file.exists()){
            if(file.delete()){
                //파일 정보 삭제(파일 삭제 성공 시)
                bDao.deleteSingleFile(sysname);
                //나머지 파일 목록 다시 가져오기
                fList = bDao.selectFiles(bnum);
            }
        }
    } catch (Exception e){
        e.printStackTrace();
    }
    return fList;
}</code></pre>
<h4 id="💻-boarddaojava-소스-2">💻 BoardDao.java 소스</h4>
<pre><code class="language-java">//파일 삭제 메소드 선언(개별 파일)
void deleteSingleFile(String sysname);
//파일 목록 가져오는 메소드 선언
List&lt;BfileDto&gt; selectFiles(Integer b_num);</code></pre>
<p>선택한 파일을 삭제 후 삭제한 파일 목록을 다시 가져온다.</p>
<h4 id="💻-boarddaoxml-소스-2">💻 BoardDao.xml 소스</h4>
<pre><code class="language-sql">&lt;delete id=&quot;deleteSingleFile&quot; parameterType=&quot;String&quot;&gt;
    delete from boardfile where bf_sysname=#{sysname}
&lt;/delete&gt;

&lt;select id=&quot;selectFiles&quot; resultType=&quot;BfileDto&quot; parameterType=&quot;Integer&quot;&gt;
    select * from boardfile where bf_bnum=#{b_num}
&lt;/select&gt;</code></pre>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.06.02 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[spring 타임리프와 JPA 사용해서 데이터 입력 페이지 만들기]]></title>
            <link>https://velog.io/@dev_h_o/spring-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84%EC%99%80-JPA-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9E%85%EB%A0%A5-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dev_h_o/spring-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84%EC%99%80-JPA-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9E%85%EB%A0%A5-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 01 Jun 2023 07:42:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev_h_o/post/6c1876f3-0e68-4223-b6b2-51fd644b9b29/image.png" alt=""></p>
<p>타임리프와 JPA를 활용해서 데이터를 입력하고 검색하는 페이지를 만든다.</p>
<p><strong>Point</strong></p>
<ul>
<li>데이터입력 링크를 누르면 데이터 입력 페이지로 넘어간다.</li>
<li>데이터 목록 섹션에서 데이터 목록을 출력한다.</li>
<li>문자열을 검색하면 해당 컬럼을 보여준다.</li>
</ul>
<h2 id="🔰-jpa란">🔰 JPA란?</h2>
<pre><code>자바 어플리케이션에서 관계형 데이터베이스를 사용하는
방식을 정의한 인터페이스 관련 API.</code></pre><h4 id="관계형-데이터베이스rdb">관계형 데이터베이스(RDB)</h4>
<pre><code>테이블(엔티티)과 테이블 간의 관계로
데이터를 저장하는 방식의 데이터베이스
    - MySQL, 오라클, 마리아DB 등.</code></pre><p>Hibernate : JPA의 구현체 (JPA는 인터페이스, Hibernate는 구현 클래스)</p>
<p>Spring Data JPA : JPA와 Hibernate를 사용하기 쉽게 만든 Spring 라이브러리.</p>
<p>Entity 클래스(DTO)를 구현하면 해당 클래스에서 지정한 테이블 이름 및 컬럼 이름으로
DB 테이블을 자동으로 생성하며, DB CRUD에 대한 메소드도 제공.
메소드 이름으로 SQL 쿼리문을 생성하는 방식을 사용한다.</p>
<h2 id="🔰-applicationproperties-세팅">🔰 application.properties 세팅</h2>
<h4 id="jpa-초기화-전략-설정aka-jpa-설정">JPA 초기화 전략 설정(a.k.a JPA 설정)</h4>
<p><code>spring.jpa.generate-ddl</code> : true로 설정하면 해당 데이터를 근거로 서버 시작 시에 DDL문을 생성하여 DB에 적용.
DDL 생성 시 데이터베이스 고유의 기능을 사용하는지에 대한 유무 체크.
→ <code>false</code>로 설정.</p>
<p><code>spring.jpa.hibernate.ddl-auto</code> : &#39;create table&#39; 관련 설정.</p>
<ul>
<li>none : 아무런 작업도 하지 않음.(DB에 테이블을 따로 생성)</li>
<li>create : 서버가 시작할 때 기본 테이블을 DROP(제거)하고 새 DDL을 실행(테이블 재생성)</li>
<li>create-drop : 서버가 시작할 때 DROP 및 CREATE하고, 서버가 종료될 때 DROP 실행.</li>
<li>update : 기존 테이블에 해당하는 Entity 클래스가 변경되면 기존 테이블을 DROP하고,새 클래스에 맞게 테이블 생성. 변경된 내용이 없으면 테이블 유지.</li>
<li>validate : Entity와 테이블이 잘 맵핑되어 있는지 확인하여, 맞지 않을 경우 프로그램을 종료시킨다.</li>
</ul>
<p><code>spring.jpa.database-platform</code> : 각 DBMS에 맞게 SQL을 생성하도록 도와주는 dialect(방언) 객체를 지정한다.</p>
<h4 id="tyhmeleaf-설정">tyhmeleaf 설정</h4>
<pre><code># DevTools setting
spring.devtools.livereload.enabled=true
spring.devtools.restart.enabled=true
spring.thymeleaf.cache=false

# static resource
spring.web.resources.static-locations=classpath:static/</code></pre><h4 id="db-설정">DB 설정</h4>
<pre><code># datasource setting
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/berrydb?serverTimezone=Asia/Seoul
#DB유저 id, 비밀번호
spring.datasource.username=buser
spring.datasource.password=12341234</code></pre><h4 id="jpa-설정">JPA 설정</h4>
<pre><code># JPA setting
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
spring.jpa.generate-ddl=false

spring.jpa.hibernate.ddl-auto=update

# JPA log setting
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
logging.level.org.hibernate.type.descriptor.sql=trace</code></pre><h2 id="🔰-entity-repository-생성">🔰 entity, repository 생성</h2>
<h4 id="entity-class">Entity class</h4>
<pre><code>DB 테이블과 연계하기 위한 클래스.
DTO의 역할도 함께 처리할 수 있음.(따로 작성하는 경우가 일반적임)</code></pre><h4 id="repository-인터페이스">Repository 인터페이스</h4>
<pre><code>DAO 역할을 하는 인터페이스.
이 인터페이스 내부에 다양한 작업을 위한 메소드를 작명 규칙에 맞게 작성한다.</code></pre><h4 id="💻-jpadata-소스-entity-class">💻 JpaData 소스 (Entity class)</h4>
<pre><code class="language-java">@Entity //entity임을 선언하는 어노테이션(DB DDL 생성 시 활요)
@Table(name=&quot;jpatbl&quot;) //DB 테이블의 이름을 지정하는 어노테이션. 생략 시 클래스 이름으로 테이블 생성.
@Data //Lombok 어노테이션
public class JpaData {
    @Id //필드를 테이블의 기본키로 설정하는 어노테이션
    @GeneratedValue(strategy = GenerationType.IDENTITY) //자동으로 생성되는 키값에 대한 설정
    private Long code;

    @Column(name = &quot;str_data&quot;, nullable = false, length = 50) //nullable=false : null을 허용하지 않는다.
    private String strdata;

    @Column(name = &quot;int_data&quot;)
    private int intdata;

    @Column(name = &quot;reg_date&quot;)
    @CreationTimestamp //작성일 기준으로 db에 저장한다.
    private Timestamp regdate;
}</code></pre>
<p><code>jpatbl</code> 테이블을 만들어주는 entity class</p>
<h4 id="💻-jpadatarepository-소스">💻 JpaDataRepository 소스</h4>
<pre><code class="language-java">public interface JpaDataRepository extends JpaRepository&lt;JpaData, Long&gt; {
    //DB CRUD(insert, update, delete, select 용 인터페이스)
    //기본으로 전체 내용 삽입, 전체 내용 수정, 행 삭제
    //전체 내용 검색 및 키 검색용 메소드를 제공.
    List&lt;JpaData&gt; findByStrdata(String strdata);
    //SELECT * FROM jpatbl WHERE str_data = &#39;abcd&#39;
}</code></pre>
<p>인터페이스로 작성하며 메소드를 이용해서 데이터를 조작한다.</p>
<h2 id="🔰-데이터-입력">🔰 데이터 입력</h2>
<h4 id="💻-homehtml-소스">💻 home.html 소스</h4>
<pre><code class="language-html">&lt;html lang=&quot;ko&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;

&lt;h1&gt;첫 페이지&lt;/h1&gt;
&lt;a th:href=&quot;@{writeForm}&quot;&gt;[데이터입력]&lt;/a&gt;</code></pre>
<p><code>home</code>페이지에서는 데이터 입력 클릭창만 보여주고 <code>[데이터입력]</code> 버튼 클릭스 <code>writeForm</code> 페이지로 이동한다.</p>
<h4 id="💻-writeformhtml-소스">💻 writeForm.html 소스</h4>
<pre><code class="language-html">&lt;html lang=&quot;ko&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;

&lt;h1&gt;데이터 입력&lt;/h1&gt;
&lt;form th:action=&quot;@{dataProc}&quot; method=&quot;post&quot;&gt;
  &lt;input type=&quot;text&quot; name=&quot;strdata&quot;&gt;&lt;br&gt;
  &lt;input type=&quot;number&quot; name=&quot;intdata&quot;&gt;&lt;br&gt;
  &lt;input type=&quot;submit&quot; value=&quot;Send&quot;&gt;
&lt;/form&gt;</code></pre>
<p><code>writeForm</code> 페이지에서는 데이터 입력 창을 보여주고 해당 데이터는 <code>dataProc</code>를 통해서 처리하도록 <code>th:action</code> 태그를 걸어준다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/d28c70c6-eae1-49fa-84a5-4df7b2341e1f/image.png" alt=""></p>
<h4 id="💻-homecontroller-소스">💻 HomeController 소스</h4>
<pre><code class="language-java">//writeForm.html로 보내주는 컨트롤러 동작
@GetMapping(&quot;writeForm&quot;)
public String writeForm(){
    log.info(&quot;writeForm()&quot;);
    return &quot;writeForm&quot;;
}

//서비스의 insertData 작업을 받아서 view로 결과를 내보낸다.
@PostMapping(&quot;dataProc&quot;)
public String dataProc(JpaData data){
    log.info(&quot;dataProc()&quot;);
    String view = jServ.insertData(data);

    return view;
}</code></pre>
<h4 id="💻-jpadataservice-소스">💻 JpaDataService 소스</h4>
<pre><code class="language-java">@Autowired
private JpaDataRepository jRepo;

public String insertData(JpaData data){
    log.info(&quot;insertData()&quot;);
    String view = null;

    try{
        jRepo.save(data);//insert/update
        view = &quot;redirect:/&quot;;
    } catch (Exception e){
        e.printStackTrace();
        view = &quot;redirect:writeForm&quot;;
    }
    return view;
}</code></pre>
<p><code>JpaData</code>를 <code>data</code>로 받아서 리포지토리로 넘겨 사용자가 입력한 데이터를 DB에 저장한다.
저장에 성공하면 <code>redirect</code>를 이용해서 메인 페이지로 이동하고, 실패 시에는 <code>writeForm</code> 화면을 유지한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/cf747d59-2e2a-436e-8a6f-af574e9c3460/image.png" alt=""></p>
<p><code>input</code> 입력창에 데이터를 입력 후 <code>Send</code>로 데이터를 전송하면</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/c724d76f-01d3-4ec1-87b8-33595b94c28d/image.png" alt=""></p>
<p>첫페이지로 이동하면서 데이터 목록에 방금 입력한 데이터와 함께 데이터 목록이 출력된다.</p>
<h2 id="🔰-데이터-목록">🔰 데이터 목록</h2>
<h4 id="💻-homehtml-소스-1">💻 home.html 소스</h4>
<pre><code class="language-html">&lt;html lang=&quot;ko&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;!-- 데이터 목록 --&gt;
&lt;h2&gt;데이터 목록&lt;/h2&gt;
&lt;table border=&quot;1&quot;&gt;
    &lt;thead&gt;
    &lt;tr&gt;
        &lt;th width=&quot;50&quot;&gt;번호&lt;/th&gt;
        &lt;th width=&quot;200&quot;&gt;문자열&lt;/th&gt;
        &lt;th width=&quot;100&quot;&gt;숫자&lt;/th&gt;
        &lt;th width=&quot;200&quot;&gt;날짜와 시간&lt;/th&gt;
    &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
    &lt;th:block th:if=&quot;${#lists.isEmpty(jList)}&quot;&gt;
        &lt;tr&gt;&lt;td colspan=&quot;4&quot;&gt;데이터가 없습니다.&lt;/td&gt;&lt;/tr&gt;
    &lt;/th:block&gt;
    &lt;th:block th:unless=&quot;${#lists.isEmpty(jList)}&quot;&gt;
        &lt;th:block th:each=&quot;item:${jList}&quot;&gt;
            &lt;tr&gt;
                &lt;td th:text=&quot;${item.code}&quot;&gt;&lt;/td&gt;
                &lt;td th:text=&quot;${item.strdata}&quot;&gt;&lt;/td&gt;
                &lt;td th:text=&quot;${item.intdata}&quot;&gt;&lt;/td&gt;
                &lt;td th:text=&quot;${#dates.format(item.regdate,&#39;yyyy-MM-dd HH:mm:ss&#39;)}&quot;&gt;&lt;/td&gt;
            &lt;/tr&gt;
        &lt;/th:block&gt;
    &lt;/th:block&gt;
    &lt;/tbody&gt;
&lt;/table&gt;</code></pre>
<p>DB에 저장된 데이터 목록을 가져오는 html소스
타임리프로 <code>jList</code>를 불러온다.</p>
<h4 id="💻-homecontroller-소스-1">💻 HomeController 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;/&quot;)
public ModelAndView home(){
    log.info(&quot;home()&quot;);
    mv = jServ.getList();
    return mv;
}</code></pre>
<p>서비스에서 <code>getList</code> 메소드 작업해서 ModelAndView로 가져오는 역할</p>
<h4 id="💻-jpadataservice-소스-1">💻 JpaDataService 소스</h4>
<pre><code class="language-java">@Autowired
private JpaDataRepository jRepo;

private ModelAndView mv;

public ModelAndView getList(){
    log.info(&quot;getList()&quot;);
    mv = new ModelAndView();
    mv.setViewName(&quot;home&quot;);
    //목록 추가 작업
    List&lt;JpaData&gt; jList = jRepo.findAll();
    //findAll() -&gt; SELECT * FROM table
    mv.addObject(&quot;jList&quot;, jList);

    return mv;
}</code></pre>
<p>데이터를 home.html로 반환하기 위해서 ModelAndView로 <code>getList</code> 메소드를 생성하고 리포지토리를 사용해서 <code>JpaData</code> 데이터를 <code>jList</code>로 반환한다.</p>
<p>만약 리스트가 없다면 <code>데이터가 없습니다.</code> 문자열을 내보낸다.</p>
<h2 id="🔰-데이터-검색">🔰 데이터 검색</h2>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/ea3782fd-b762-4990-8744-306ff2b54006/image.png" alt=""></p>
<p>검색할 문자열을 <code>input</code> 창에 넣고 검색을 누르면</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/724461e5-6641-4150-befd-fa733ba55576/image.png" alt=""></p>
<p>데이터 목록에서 검색한 결과값만 나타나는 것을 확인할 수 있다.</p>
<h4 id="💻-homehtml-소스-2">💻 home.html 소스</h4>
<pre><code class="language-html">&lt;html lang=&quot;ko&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;

&lt;form th:action=&quot;@{search}&quot; method=&quot;get&quot;&gt;
    &lt;input type=&quot;text&quot; name=&quot;keyword&quot; placeholder=&quot;검색할 문자열을 입력하세요.&quot;&gt;
    &lt;input type=&quot;submit&quot; value=&quot;검색&quot;&gt;
&lt;/form&gt;</code></pre>
<h4 id="💻-homecontroller-소스-2">💻 HomeController 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;search&quot;)
public ModelAndView search(String keyword){
    log.info(&quot;search()&quot;);
    mv = jServ.getData(keyword);

    return mv;
}</code></pre>
<p>컨트롤러는 <code>search</code> action태그를 받고 <code>getData</code> 서비스 처리로 넘겨주는 역할을 한다.</p>
<h4 id="💻-jpadataservice-소스-2">💻 JpaDataService 소스</h4>
<pre><code class="language-java">public ModelAndView getData(String keyword) {
    log.info(&quot;getData()&quot;);
    mv = new ModelAndView();
    mv.setViewName(&quot;home&quot;);
    List&lt;JpaData&gt; jList = jRepo.findByStrdata(keyword);
    mv.addObject(&quot;jList&quot;, jList);
    return mv;
}</code></pre>
<p>검색 문자열을 <code>keyword</code>로 보내서 해당 문자열의 데이터만 보여지도록 한다.</p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.06.01 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[spring 타임리프와 jsp 같은 페이지 다른 코드 비교]]></title>
            <link>https://velog.io/@dev_h_o/spring-thymeleaf-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9B%B9-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dev_h_o/spring-thymeleaf-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9B%B9-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Wed, 31 May 2023 07:43:42 GMT</pubDate>
            <description><![CDATA[<p><code>jsp</code>로 만들었던 웹 페이지를 타임리프로 재구성 했을 때(회원가입 페이지) 타임리프에서 다르게 사용하는 태그들을 비교해본다.</p>
<p><strong><a href="https://velog.io/write?id=cd13b953-57e2-4143-abe2-28023a96b417">1. jsp 웹 페이지 만들기(회원가입)</a></strong></p>
<p><strong><a href="https://github.com/qkrtiger/Java-Spring/tree/main/thymeleafboard">2. 파일 링크</a></strong></p>
<h3 id="🔻-메인-페이지">🔻 메인 페이지</h3>
<p>메인페이지에서 크게 달라진 부분은 없다.
메세지를 내보내는 자바스크립트에서 타임리프를 사용하는 방식만 다름!</p>
<h4 id="💻-thymeleaf-homehtml">💻 thymeleaf (home.html)</h4>
<pre><code class="language-javascript">&lt;html lang=&quot;ko&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;script th:inline=&quot;javascript&quot;&gt;
    $(function () {
        let m = [[${msg}]];
        if(m != null){
            alert(m);
        }

        //bxSlider 설정용 스크립트
        $(&quot;.slider&quot;).bxSlider({
            auto: true,
            slideWidth: 600,
        })
    });
&lt;/script&gt;</code></pre>
<h4 id="💻-jsp-homejsp">💻 jsp (home.jsp)</h4>
<pre><code class="language-javascript">&lt;%@ page contentType=&quot;text/html;charset=UTF-8&quot; language=&quot;java&quot; %&gt;
&lt;script&gt;
    $(function(){
        let m = &quot;${msg}&quot;;
        if(m != &quot;&quot;){
            alert(m);
        }

        //bxSlider 설정용 스크립트
        $(&quot;.slider&quot;).bxSlider({
            auto: true,
            slideWidth: 600,
        })
    });
&lt;/script&gt;
</code></pre>
<p>타임리프에서는 <code>msg</code> 변수를 <code>[[${msg}]]</code> 대괄호로 감싸서 태그하는데 jsp에서는 간단하게 EL로 표현한다. <code>${msg}</code></p>
<h3 id="🔻-header">🔻 header</h3>
<h4 id="💻-thymeleaf">💻 thymeleaf</h4>
<pre><code class="language-html">&lt;ul&gt;
&lt;li class=&quot;suc&quot; id=&quot;mname&quot;&gt;테스트님&lt;/li&gt;
&lt;li class=&quot;suc&quot;&gt;&lt;a th:href=&quot;@{logout}&quot;&gt;Logout&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;bef&quot;&gt;&lt;a th:href=&quot;@{loginForm}&quot;&gt;Login&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;bef&quot;&gt;&lt;a th:href=&quot;@{joinForm}&quot;&gt;Join&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<h4 id="💻-jsp">💻 jsp</h4>
<pre><code class="language-html">&lt;ul&gt;
  &lt;li class=&quot;suc&quot; id=&quot;mname&quot;&gt;테스트님&lt;/li&gt;
  &lt;li class=&quot;suc&quot;&gt;&lt;a href=&quot;logout&quot;&gt;Logout&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;bef&quot;&gt;&lt;a href=&quot;loginForm&quot;&gt;Login&lt;/a&gt;&lt;/li&gt;
  &lt;li class=&quot;bef&quot;&gt;&lt;a href=&quot;joinForm&quot;&gt;Join&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;</code></pre>
<p>타임리프에서는 링크를 연결할 때 <code>@</code>를 붙여서 연결해주기 때문에 <code>th:href=&quot;@{logout}&quot;</code> 이런 형식으로 링크 태그를 만들어준다.</p>
<h4 id="💻-thymeleaf-1">💻 thymeleaf</h4>
<pre><code class="language-javascript">&lt;script th:inline=&quot;javascript&quot;&gt;
  function goHome() {
    //1차로 세션에서 mb(MemberDto)를 꺼내고
    //2차로 mb에서 m_id를 꺼내는 방식으로 처리
    let mb = [[${session.mb}]];
    if(mb != null){
      id = mb.m_id;
    }

    if(mb == null){//로그인 전
      location.href = &quot;/&quot;;
    } else {
      location.href = &quot;list?pageNum=1&quot;;
    }
  }
&lt;/script&gt;</code></pre>
<h4 id="💻-jsp-1">💻 jsp</h4>
<pre><code class="language-javascript">&lt;script&gt;
  function gohome() {
    let id = &quot;${mb.m_id}&quot;;

    if(id == &quot;&quot;){ //로그인 전
      location.href = &quot;/&quot;;
    } else {  //로그인 후
      location.href = &quot;list?pageNum=1&quot;;
    }
  }
&lt;/script&gt;</code></pre>
<p>세션으로 보낸 값을 받아올 때 타임리프에서는 <code>session.</code>을 붙여서 데이터를 꺼내올 수 있다. 이때 서비스에서 받아온 mb 변수의 id값을 한번에 <code>session.mb.id</code> 이런식으로 불러올 수는 없어서</p>
<pre><code class="language-javascript">    let mb = [[${session.mb}]];
    if(mb != null){
      id = mb.m_id;
    }</code></pre>
<p>mb 먼저 가져오고 id 변수로 다시 <code>m_id</code>값을 받아오게끔 처리하였다.</p>
<p><code>session.setAttribute(&quot;식별자&quot;, data);</code> → <code>th:text=&quot;${session.식별자}&quot;</code></p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/f3259e59-49dd-46fd-98b9-a2442a39a309/image.png" alt=""></p>
<p>타임리프로 회원가입 페이지 만들기 완성!</p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.05.31 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Thymeleaf 타임리프 2 : 데이터 처리, 제어용 태그, 연산식 활용]]></title>
            <link>https://velog.io/@dev_h_o/Spring-Thymeleaf-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84-2-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC-%EC%A0%9C%EC%96%B4%EC%9A%A9-%ED%83%9C%EA%B7%B8-%EC%97%B0%EC%82%B0%EC%8B%9D-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@dev_h_o/Spring-Thymeleaf-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84-2-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC-%EC%A0%9C%EC%96%B4%EC%9A%A9-%ED%83%9C%EA%B7%B8-%EC%97%B0%EC%82%B0%EC%8B%9D-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Tue, 30 May 2023 07:36:32 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-타임리프의-데이터-처리-방법을-알아보자">✅ 타임리프의 데이터 처리 방법을 알아보자!</h2>
<h3 id="dto-데이터-처리">Dto 데이터 처리</h3>
<pre><code class="language-java">//컨트롤러
    model.addObject(&quot;식별자&quot;, dto);
//html에서 사용할 때
    → th:text=&quot;${식별자.필드명}&quot;
    → th:text=&quot;${식별자[&#39;필드명&#39;]}&quot;
    → th:text=&quot;${식별자.get필드()}&quot;</code></pre>
<h5 id="💻-dto-소스">💻 Dto 소스</h5>
<pre><code class="language-java">@Data
public class PersonDto {
    private String pname;
    private int age;
    private String phone;
}</code></pre>
<h4 id="💻-controller-소스">💻 Controller 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;/&quot;)
public String home(Model model) {
    log.info(&quot;home()&quot;);
    //Dto 활용
    PersonDto person = new PersonDto();
    person.setPname(&quot;홍길동&quot;);
    person.setAge(20);
    person.setPhone(&quot;010-1234-5678&quot;);
    //person을 &quot;pe&quot;로 호출한다.
    model.addAttribute(&quot;pe&quot;, person);

    return &quot;home&quot;;
}</code></pre>
<h5 id="💻-html-소스">💻 Html 소스</h5>
<pre><code class="language-html">&lt;h2&gt;연락처&lt;/h2&gt;
&lt;p&gt;이름 : &lt;span th:text=&quot;${pe.pname}&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이름 : [[${pe.pname}]]&lt;/p&gt;
&lt;p th:text=&quot;&#39;이름 : &#39; + ${pe.pname}&quot;&gt;&lt;/p&gt;
&lt;p&gt;나이 : [[${pe[&#39;age&#39;]}]]&lt;/p&gt;
&lt;p&gt;연락처 : [[${pe.getPhone()}]]&lt;/p&gt;</code></pre>
<h5 id="👍-결과">👍 결과</h5>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/02fbb260-c85c-4254-b2a3-b07234acf70b/image.png" alt=""></p>
<h3 id="session에-저장한-데이터-꺼내기">Session에 저장한 데이터 꺼내기</h3>
<p> 식별자 앞에 <code>session.</code>을 붙인다.</p>
<p><code>session.setAttribute(&quot;식별자&quot;, data);</code> → <code>th:text=&quot;${session.식별자}&quot;</code></p>
<h4 id="💻-controller-소스-1">💻 Controller 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;second&quot;)
public ModelAndView second(HttpSession session){
    log.info(&quot;second()&quot;);
    ModelAndView mv = new ModelAndView();
    mv.setViewName(&quot;second&quot;);

    //로그인을 성공했다면(가정)
    session.setAttribute(&quot;id&quot;, &quot;user01&quot;);

    return mv;
}</code></pre>
<h4 id="💻-html-소스-1">💻 Html 소스</h4>
<pre><code class="language-html">&lt;p&gt;Session에서 데이터 꺼내기&lt;/p&gt;
&lt;p th:text=&quot;${session.id} + &#39;님 반갑습니다.&#39;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Hello world&lt;/p&gt;
&lt;p&gt;&lt;a th:href=&quot;@{logout}&quot;&gt;[로그아웃]&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;</code></pre>
<h5 id="👍-결과-1">👍 결과</h5>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/52ab1a12-398f-4467-9aa8-a52bfb152d7c/image.png" alt=""></p>
<p><code>${session.id}</code>으로 가져온 아이디 <code>user01</code>이 호출되었다.</p>
<p>참고) 자바스크립트에서 꺼낼 때 : <code>let val = [[${session.식별자}]]</code></p>
<h3 id="redirectattribute에-저장한-데이터-꺼내기">RedirectAttribute에 저장한 데이터 꺼내기</h3>
<p>주로 javascript에서 사용한다.</p>
<pre><code class="language-java">rttr.addFlashAttribute(&quot;식별자&quot;, data);
→ let 변수 = [[${식별자}]];
값이 없으면 null.
if(변수 != null) ← 조건식 작성시 활용</code></pre>
<p>로그아웃 처리 프로세스에 활용!
페이지에서 로그아웃 버튼을 누르면 home페이지로 리다이렉트하면서 메세지를 전달하고 로그아웃처리하는 방식으로 샘플링하였다.</p>
<blockquote>
<p>두번째 페이지에서 로그아웃 버튼을 누르면 home페이지로 설정한 메인 페이지로 redirect 되면서 알림창이 뜬다.
<img src="https://velog.velcdn.com/images/dev_h_o/post/ef4eedb0-a48c-4b76-8aee-ec391acb67da/image.png" alt=""><img src="https://velog.velcdn.com/images/dev_h_o/post/eeb83091-06f2-40e1-8eb8-56a1b364fcdd/image.png" alt=""></p>
</blockquote>
<h4 id="💻-controller-소스-2">💻 Controller 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;logout&quot;)
public String logout(RedirectAttributes rttr){
    log.info(&quot;logout()&quot;);
    String view = &quot;redirect:/&quot;;
    String msg = &quot;로그아웃&quot;;
    rttr.addFlashAttribute(&quot;msg&quot;, msg);
    return view;
}</code></pre>
<h5 id="💻-html-소스javascript">💻 Html 소스(javascript)</h5>
<pre><code class="language-javascript">&lt;script th:inline=&quot;javascript&quot;&gt;
    let m = [[${msg}]];
    if(m != null){
        alert(m);
    }
&lt;/script&gt;</code></pre>
<p><code>msg</code>를 식별자로 불러오고 <code>alert</code> 명령어로 알림창을 띄운다.</p>
<h2 id="✅-타임리프의-제어용-태그를-알아보자">✅ 타임리프의 제어용 태그를 알아보자!</h2>
<p><strong>th:block</strong></p>
<pre><code>- 제어문 및 객체 설정에 사용하는 태그.
- 일반적으로는 조건식, 반복식 등에 활용한다.
- 상위 요소가 지정되지 않은 객체의 출력에도 활용.</code></pre><p><strong>th:block을 활용한 제어 속성</strong></p>
<pre><code>1) th:if - if문에 해당하는 속성.
   th:unless - if문의 else에 해당.
2) th:switch, th:case - switch, case에 해당하는 속성.
3) th:each - for문에 해당하는 속성.
    &lt;th:block th:each=&quot;변수:${목록}&gt;...&lt;/th:block&gt;
    &lt;th:block th:each=&quot;변수,status:${목록}&quot;&gt;
    status 항목을 사용하면 반복 상태의 정보를 구할 수 있음.
    - index : 반복 순번(0부터 시작)
    - count : 반복 횟수(1부터 시작)
    - odd : 홀수 행인지 확인(홀수행이면 true)
    - even : 짝수 행인지 확인(짝수행이면 true)
    - first : 첫번째 행인지 확인(첫 행이면 true)
    - last : 마지막 행인지 확인(마지막 행이면 true)
    - size : 총 반복수(요소 수)
    - current : 현재 반복이 수행되는 요소

참고) List(목록)의 null 체크 : ${#lists.isEmpty(목록)}
    객체(DTO)의 null 체크 : ${#object.isNull(dto)}</code></pre><h4 id="💻-controller-소스-3">💻 Controller 소스</h4>
<pre><code class="language-java">@GetMapping(&quot;fourth&quot;)
public ModelAndView fourth(){
    log.info(&quot;fourth()&quot;);
    ModelAndView mv = new ModelAndView();
    mv.setViewName(&quot;fourth&quot;);
    mv.addObject(&quot;id&quot;, &quot;hong01&quot;);
    mv.addObject(&quot;age&quot;, 25);

    List&lt;PersonDto&gt; pList = new ArrayList&lt;&gt;();
    for(int i = 0; i&lt;5; i++){
        PersonDto per = new PersonDto();
        per.setPname(&quot;사람&quot; + i);
        per.setAge(20 + i);
        per.setPhone(&quot;010-1234-567&quot; + i);
        pList.add(per);
    }
    mv.addObject(&quot;pList&quot;, pList);
    return mv;
}</code></pre>
<p><code>ModelAndView</code>로 사용자의 id와 age를 각각 <code>hong01</code>, <code>25</code>로 넘겨주었다.
그리고 리스트를 만들어 <code>setPname</code>, <code>setAge</code>, <code>setPhone</code>의 값을 1씩 증가하게끔 for문으로 감싸주었다.</p>
<h5 id="💻-html-소스-2">💻 Html 소스</h5>
<p><code>th:if</code>와 <code>th:switch</code> 활용</p>
<pre><code class="language-html">&lt;html lang=&quot;ko&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;

  &lt;h2&gt;th:if&lt;/h2&gt;
  //id변수가 null이 아니라면 id값과 p태그의 내용을 출력한다.
  &lt;th:block th:if=&quot;${id != null}&quot;&gt;
    &lt;p th:text=&quot;${id} + &#39;님 반갑습니다.&#39;&quot;&gt;&lt;/p&gt;
  &lt;/th:block&gt;
  &lt;th:block th:unless=&quot;${id != null}&quot;&gt;
    &lt;p&gt;안녕하세요.&lt;/p&gt;
  &lt;/th:block&gt;
  &lt;hr&gt;
  &lt;h2&gt;th:switch&lt;/h2&gt;
  //age값을 10으로 나눠 case에 해당하는 값의 p태그를 출력한다.
  &lt;th:block th:switch=&quot;${age/10}&quot;&gt;
    &lt;p th:case=&quot;2&quot;&gt;당신은 20대 입니다.&lt;/p&gt;
    &lt;p th:case=&quot;3&quot;&gt;당신은 30대 입니다.&lt;/p&gt;
    &lt;p th:case=&quot;4&quot;&gt;당신은 40대 입니다.&lt;/p&gt;
  &lt;/th:block&gt;</code></pre>
<h5 id="👍-결과-2">👍 결과</h5>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/e7fb614b-3eff-4ecf-aeb5-6a379cdb071b/image.png" alt=""></p>
<h5 id="💻-html-소스-3">💻 Html 소스</h5>
<p><code>th:each</code> 를 활용한 반복문</p>
<pre><code class="language-html">&lt;h2&gt;th:each&lt;/h2&gt;
&lt;ul&gt;
  //만약 리스트가 비어있을 경우
  &lt;th:block th:if=&quot;${#lists.isEmpty(pList)}&quot;&gt;
    &lt;li&gt;출력할 목록이 없습니다.&lt;/li&gt;
  &lt;/th:block&gt;
  //else
  &lt;th:block th:unless=&quot;${#lists.isEmpty(pList)}&quot;&gt;
    //반복문 th:block th:each=&quot;변수,status:${목록}&quot;
    &lt;th:block th:each=&quot;item,status:${pList}&quot;&gt;
      &lt;fieldset&gt;
        //캡션 제목 count 증가
        &lt;legend th:text=&quot;${status.count}&quot;&gt;&lt;/legend&gt;
        //이름, 나이, 연락처 count 증가
        &lt;li th:text=&quot;&#39;이름 : &#39;+ ${item.pname}&quot;&gt;&lt;/li&gt;
        &lt;li th:text=&quot;&#39;나이 : &#39;+ ${item.age}&quot;&gt;&lt;/li&gt;
        &lt;li th:text=&quot;&#39;연락처 : &#39;+ ${item.phone}&quot;&gt;&lt;/li&gt;
      &lt;/fieldset&gt;
    &lt;/th:block&gt;
  &lt;/th:block&gt;
&lt;/ul&gt;</code></pre>
<h5 id="👍-결과-3">👍 결과</h5>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/52ebc329-1989-404d-8979-ea13bbc0955b/image.png" alt=""></p>
<h2 id="✅-타임리프의-연산식을-알아보자">✅ 타임리프의 연산식을 알아보자!</h2>
<p><strong>Thymeleaf 내에서의 연산식 활용</strong></p>
<p>1) 산술 연산 : +, -, *, /, %
2) 비교 연산 : ==, !=, &gt;, &gt;=, &lt;, &lt;=
3) 논리 연산 : &amp;&amp;, ||, !
4) 조건 연산 : (조건식) ? A : B
5) 기본 표현식(Default Expression. 또는 Elvis Operator)
<code>th:text=&quot;${data}?:값1&quot;</code> ➡ data가 null이 아니면 data, null이면 &#39;값1&#39;을 출력</p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.05.30 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Thymeleaf 타임리프 1 :  세팅하기, 활용법]]></title>
            <link>https://velog.io/@dev_h_o/Spring-Thymeleaf-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84-1-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0-%ED%99%9C%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@dev_h_o/Spring-Thymeleaf-%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84-1-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0-%ED%99%9C%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Tue, 30 May 2023 03:54:38 GMT</pubDate>
            <description><![CDATA[<h2 id="🧐-thymeleaf란">🧐 Thymeleaf란?</h2>
<p>Spring boot에서 view(HTML)를 처리하는 기본 방식 중 하나.</p>
<ul>
<li>resources/template에 페이지의 틀을 저장하는 방식.</li>
<li>jsp처럼 prefix와 suffix를 설정할 필요가 없으며,</li>
<li>webapp/WEB-INF/views 폴더를 만들 필요도 없음.</li>
<li>Controller에서 데이터를 전송하는 방식은 동일함.</li>
<li>HTML 태그의 형식을 그대로 사용.</li>
<li>서버 재시작 없이 view 자동 업데이트 처리
  view에만 해당됨.(java 코드의 수정 반영은 서버 재시작 필요)</li>
</ul>
<h3 id="thymeleaf-세팅하기">Thymeleaf 세팅하기</h3>
<blockquote>
<p>인텔리제이 프로젝트 생성 화면<img src="https://velog.velcdn.com/images/dev_h_o/post/2a6cb959-3958-4d84-9736-33a9a2ca0a93/image.png" alt=""></p>
</blockquote>
<ul>
<li><p>Spring Boot DevTools 포함(프로젝트 생성 시)
<img src="https://velog.velcdn.com/images/dev_h_o/post/eb466236-b6a6-4bc7-ad09-08c36d1eae22/image.png" alt=""></p>
</li>
<li><p>application.properties에 devtools 설정</p>
<pre><code>#devTools setting
spring.devtools.livereload.enabled=true
spring.devtools.restart.enabled=true
spring.thymeleaf.cache=false</code></pre></li>
<li><p>File &gt; Setting...</p>
<ul>
<li><p>Build, Execution, Deployment &gt; Compiler</p>
</li>
<li><blockquote>
<p><code>Build project automatically</code> 체크
<img src="https://velog.velcdn.com/images/dev_h_o/post/1b10558c-8ba5-4d0f-8c28-5c414d7a8f60/image.png" alt=""></p>
</blockquote>
<ul>
<li>Advanced Setting</li>
</ul>
</li>
<li><blockquote>
<p><code>Allow auto-make to start even if developed application is currently running</code> 체크
<img src="https://velog.velcdn.com/images/dev_h_o/post/d2ddf5e3-dde1-4ecf-ac59-6453553b9678/image.png" alt=""></p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h3 id="thymeleaf-활용법">Thymeleaf 활용법</h3>
<pre><code>1) templates 폴더에 HTML 문서로 작성.
2) &lt;html&gt; 태그에 xmlns:th=&quot;http://www.thymeleaf.org&quot;속성 포함.</code></pre><p><strong>html 페이지에서 thymeleaf 속성 포함시키기</strong></p>
<pre><code class="language-html">&lt;html lang=&quot;ko&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;</code></pre>
<h3 id="thymeleaf의-데이터-출력-방식">Thymeleaf의 데이터 출력 방식</h3>
<p>1) 기본적인 데이터 출력은 EL 형식을 사용 : <code>${...}</code>
2) 링크(url) :<code>@{link}</code> (a 태그나 form 태그의 action 등)
3) 객체의 필드(멤버변수) : <code>*{field}</code></p>
<h4 id="💻-controller-소스">💻 Controller 소스</h4>
<pre><code class="language-java">@Controller
@Slf4j
public class HomeController {
    @GetMapping(&quot;/&quot;)
    public String home(Model model) {
        log.info(&quot;home()&quot;);
        //문자열 전송
        model.addAttribute(&quot;d1&quot;, &quot;서버로부터 &lt;b&gt;전송&lt;/b&gt;&quot;);
        String str = &quot;결과 메시지&quot;;
        int num = 300;
        model.addAttribute(&quot;d2&quot;, str);
        model.addAttribute(&quot;d3&quot;, num);

        PersonDto person = new PersonDto();
        person.setPname(&quot;홍길동&quot;);
        person.setAge(20);
        person.setPhone(&quot;010-1234-5678&quot;);
        model.addAttribute(&quot;pe&quot;, person);

        return &quot;home&quot;;
    }</code></pre>
<p>컨트롤러 소스에서 <code>home.html</code>로 <code>@Getmapping</code>을 걸어주고 해당 페이지에서 사용할 속성을 입력한다.</p>
<h4 id="💻-homehtml-소스">💻 home.html 소스</h4>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
//타임리프 사용
&lt;html lang=&quot;ko&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;Thymeleaf Home&lt;/title&gt;
    &lt;script th:inline=&quot;javascript&quot;&gt;
        let m = [[${msg}]];
        if(m != null){
            alert(m);
        }
    &lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Thymeleaf 첫 페이지&lt;/h1&gt;
&lt;p th:text=&quot;${d1}&quot;&gt;&lt;/p&gt;
&lt;p&gt;[[${d2}]]&lt;/p&gt;
&lt;p th:utext=&quot;${d1}&quot;&gt;&lt;/p&gt;
&lt;p&gt;[(${d1})]&lt;/p&gt;
&lt;input type=&quot;number&quot; th:value=&quot;${d3}&quot;&gt;
&lt;hr&gt;
&lt;h2&gt;연락처&lt;/h2&gt;
&lt;p&gt;이름 : &lt;span th:text=&quot;${pe.pname}&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이름 : [[${pe.pname}]]&lt;/p&gt;
&lt;p th:text=&quot;&#39;이름 : &#39; + ${pe.pname}&quot;&gt;&lt;/p&gt;
&lt;p&gt;나이 : [[${pe[&#39;age&#39;]}]]&lt;/p&gt;
&lt;p&gt;연락처 : [[${pe.getPhone()}]]&lt;/p&gt;
&lt;hr&gt;
&lt;a th:href=&quot;@{second}&quot;&gt;[두번째]&lt;/a&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h4 id="😎-출력-화면">😎 출력 화면</h4>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/ec39dea8-1915-4888-ab44-23eb5b67c8a3/image.png" alt=""></p>
<h3 id="텍스트-형태로-출력하는-형식">텍스트 형태로 출력하는 형식</h3>
<p><strong>1. <code>th:text</code>, <code>th:utext</code></strong>
요소의 내용으로 출력할 때 사용하는 속성.</p>
<p>1) <code>&lt;p th:text=&quot;${...}&quot;&gt;&lt;/p&gt;</code> ➡ innerText 형식
2) <code>&lt;p&gt;[[${...}]]&lt;/p&gt;</code> ➡ innerText 형식
3) <code>&lt;p th:utext=&quot;${...}&quot;&gt;&lt;/p&gt;</code> ➡ innerHTML 형식
4) <code>&lt;p&gt;[(${...})]&lt;/p&gt;</code> ➡ innerHTML 형식
<em>(innerHTML과 innerText 형식의 차이는 문장 내의 태그 표현)</em></p>
<p><strong>2. th:value</strong>
input 태그의 초기값으로 출력할 때 사용하는 속성.</p>
<p><strong>3. th:inline</strong>
자바스크립트 태그에 사용하는 속성.
    <code>&lt;script ht:inline=&quot;javascript&quot;&gt;...&lt;/script&gt;</code></p>
<p><strong>4. th:href</strong>
링크 연결 속성(<code>&lt;a&gt;</code> 태그에서 사용)</p>
<ul>
<li>일반 html
<code>&lt;a href=&quot;test?d1=100&amp;d2=200&quot;&gt;전달&lt;/a&gt;</code></li>
<li>Thymeleaf
<code>&lt;a th:href=&quot;@{test(d1=100,d2=200)}&quot;&gt;전달&lt;/a&gt;</code></li>
</ul>
<p><strong>5. th:action</strong>
form 태그의 action 속성과 동일.
<code>&lt;form th:action=&quot;@{url}&quot;&gt;</code></p>
<p><strong>6. th:object</strong>
Dto와 같은 객체로 데이터를 전송한 경우 이 속성에 객체를 지정하고 필드의 데이터는 <code>*{필드}</code> 형식으로 가져올 수 있다.</p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.05.30 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[spring 회원 관리 예제 실습 : 서비스 테스트 케이스]]></title>
            <link>https://velog.io/@dev_h_o/spring-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%8B%A4%EC%8A%B5-%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BC%80%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@dev_h_o/spring-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%8B%A4%EC%8A%B5-%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BC%80%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Mon, 29 May 2023 10:49:27 GMT</pubDate>
            <description><![CDATA[<h2 id="✌️-회원관리-프로젝트">✌️ 회원관리 프로젝트</h2>
<p>작성한 회원 서비스가 정상적으로 작동하는지 확인하기 위해 테스트 케이스를 작성한다.</p>
<p><strong>클래스에서 바로 테스트 케이스 만드는 방법!</strong>
단축키 : <code>control</code> + <code>Shift</code> + <code>T</code></p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/749a8072-f803-4696-8676-c679ebb1cfeb/image.png" alt="">
테스트할 메소드를 선택하고 <code>OK</code> 버튼을 누른다.</p>
<blockquote>
<p>테스트 케이스 만들어진 모습<img src="https://velog.velcdn.com/images/dev_h_o/post/e3507205-c5a3-445e-a270-e80448a92ca1/image.png" alt=""></p>
</blockquote>
<p><strong>테스트 할 때는 직관적으로 확인하기 위해서 한글로 클래스를 작성해도 상관없다. (빌드할 때 테스트는 포함되지 않으므로!)</strong></p>
<h4 id="💻-memberservicetest-소스">💻 MemberServiceTest 소스</h4>
<pre><code class="language-java">class MemberServiceTest {
    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

    //테스트가 끝난 다음에는 저장소 안의 데이터를 지워줘야 한다.
    @AfterEach
    public void afterEach(){
        memberRepository.clearStore();
    }

    @Test
    void 회원가입() {
        //given
        Member member = new Member();
        member.setName(&quot;hello&quot;);

        //when
        Long saveId = memberService.join(member);

        //then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외() {
        //given
        Member member1 = new Member();
        member1.setName(&quot;spring&quot;);

        Member member2 = new Member();
        member2.setName(&quot;spring&quot;);

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -&gt; memberService.join(member2));

        assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
}</code></pre>
<p>이때, 테스트 클래스에서 멤버 리포지토리를 다시 생성하게 되면 멤버 서비스에서 사용하는 리포지토리와 서로 다른 인스턴스가 된다.</p>
<p>그래서 테스트 클래스에서는 멤버 리포지토리를 따로 호출하지 않고 서비스에서 상속자로 <code>Generate</code>한다.</p>
<h4 id="💻-memberservice-소스">💻 MemberService 소스</h4>
<pre><code class="language-java">private final MemberRepository memberRepository;

public MemberService(MemoryMemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}</code></pre>
<p><strong>MemberServiceTest 에서 활용하는 부분</strong></p>
<pre><code class="language-java">@BeforeEach
public void beforeEach() {
    memberRepository = new MemoryMemberRepository();
    MemberService memberService1 = memberService;
    memberService1 = new MemberService(memberRepository);
}</code></pre>
<h3 id="활용한-단축키">활용한 단축키</h3>
<p>변수 추출하기 단축키 : <code>cmd</code> + <code>option</code> + <code>v</code>
같은 단어끼리 묶어서 리팩토링 : <code>shift</code> + <code>f6</code>
Generate : <code>control</code> + <code>enter</code></p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.05.29 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[spring 회원 관리 예제 실습 : 회원 서비스 개발]]></title>
            <link>https://velog.io/@dev_h_o/spring-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%8B%A4%EC%8A%B5-%ED%9A%8C%EC%9B%90-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@dev_h_o/spring-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%8B%A4%EC%8A%B5-%ED%9A%8C%EC%9B%90-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Sun, 28 May 2023 12:42:59 GMT</pubDate>
            <description><![CDATA[<h2 id="✌️-회원관리-프로젝트">✌️ 회원관리 프로젝트</h2>
<p>회원 관리에 필요한 멤버 저장소를 만들었다면 이번엔 작동할 수 있는 회원 서비스 처리를 작성한다.</p>
<h3 id="회원관리-서비스-개발">회원관리 서비스 개발</h3>
<p><code>service</code> 패키지를 만들고 그 안에 <code>MemberService</code> 클래스를 생성한다.</p>
<pre><code class="language-java">private final MemberRepository memberRepository = new MemoryMemberRepository();</code></pre>
<p>리포지토리 불러오기</p>
<h4 id="💻-memberservice-소스">💻 MemberService 소스</h4>
<pre><code class="language-java">//회원가입
public Long join(Member member){
    //같은 이름이 있는 중복 회원은 거부
    //command+option+v 단축키로 옵셔널 변환해줌..
    Optional&lt;Member&gt; result = memberRepository.findByName(member.getName());
    result.ifPresent(m -&gt; {
        throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
    });
    memberRepository.save(member);
    return member.getId();</code></pre>
<p>중복 이름은 회원가입이 불가능하다는 전제로 이미 존재하는 이름이 있을 경우엔 <code>이미 존재하는 회원입니다.</code> 메세지를 출력한다.</p>
<pre><code class="language-java">memberRepository.findByName(member.getName())
        .ifPresent(m -&gt; {
            throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
        });

memberRepository.save(member);
return member.getId();</code></pre>
<p>지금의 내가 봤을 땐 <code>Optional</code>이 붙어있어도 충분히 멋있어보이는데 강의에서는 <code>Optional</code>을 생략하고 코드를 좀 더 깔끔하게 다듬는다.</p>
<p>덕분에 나에겐 약간의 어려움이 더해졌다.</p>
<p>그리고 이렇게 정리가 중복이름 검증용 코드를 메소드로 묶어서 한번 더 코드 정리를...!!</p>
<p><strong>코드 블럭 메소드로 만들기</strong></p>
<blockquote>
<ol>
<li>코드 묶어서 드래그 후 <code>control</code> + <code>T</code> 누른 뒤 Extract Method 선택<img src="https://velog.velcdn.com/images/dev_h_o/post/1470aaf1-06f2-4316-a11f-9bf6bbb08c02/image.png" alt=""></li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li>메소드 이름 지정을 위해 More options에서 이름을 설정해준다.<img src="https://velog.velcdn.com/images/dev_h_o/post/07ffcc66-6e6c-47b7-a3ad-4a20111b08b7/image.png" alt=""></li>
</ol>
</blockquote>
<blockquote>
<ol start="3">
<li>강의 따라서 똑같이 <code>validateDuplicateMember</code>로 메소드 이름을 바꿔주었다.<img src="https://velog.velcdn.com/images/dev_h_o/post/b33a00c6-85ff-4b65-83be-6e0594c0b047/image.png" alt=""></li>
</ol>
</blockquote>
<pre><code class="language-java">//회원가입
public Long join(Member member){
    //같은 이름이 있는 중복 회원은 거부
    validateDuplicateMember(member); //중복회원 검증용 메소드
    //중복회원 검증 통과하면 회원 정보를 저장한다.
    memberRepository.save(member);
    return member.getId();
}

private void validateDuplicateMember(Member member) {
    memberRepository.findByName(member.getName())
            .ifPresent(m -&gt; {
                throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
            });
}</code></pre>
<p>그리하여 이렇게 제법 깔끔한 회원가입 서비스 코드가 작성되었다.
중복회원 검증용 메소드를 통과하면 회원 가입이 되는 흐름</p>
<h4 id="💻-memberservice-소스-1">💻 MemberService 소스</h4>
<pre><code class="language-java">//전체 회원 조회
public List&lt;Member&gt; findMembers(){
    return memberRepository.findAll();
}

public Optional&lt;Member&gt; findOne(Long memberId){
    return memberRepository.findById(memberId);
}</code></pre>
<p>전체 회원 목록을 조회하는 메소드도 작성한다.</p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.05.28 작성</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[spring 회원 관리 예제 실습 : 회원 도메인과 리포지토리 만들기, 테스트 케이스 작성]]></title>
            <link>https://velog.io/@dev_h_o/spring-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%8B%A4%EC%8A%B5-%ED%9A%8C%EC%9B%90-%EB%8F%84%EB%A9%94%EC%9D%B8%EA%B3%BC-%EB%A6%AC%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BC%80%EC%9D%B4%EC%8A%A4-%EC%9E%91%EC%84%B1</link>
            <guid>https://velog.io/@dev_h_o/spring-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%8B%A4%EC%8A%B5-%ED%9A%8C%EC%9B%90-%EB%8F%84%EB%A9%94%EC%9D%B8%EA%B3%BC-%EB%A6%AC%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BC%80%EC%9D%B4%EC%8A%A4-%EC%9E%91%EC%84%B1</guid>
            <pubDate>Sat, 27 May 2023 12:56:40 GMT</pubDate>
            <description><![CDATA[<h2 id="✌️-회원관리-프로젝트">✌️ 회원관리 프로젝트</h2>
<h3 id="비즈니스-요구사항">비즈니스 요구사항</h3>
<ul>
<li>데이터 : 회원ID, 이름</li>
<li>기능 : 회원 등록, 조회</li>
<li>아직 데이터 저장소가 선저오디지 않음(가상의 시나리오)</li>
<li>일반적인 웹 애플리케이션 계층 구조
<img src="https://velog.velcdn.com/images/dev_h_o/post/52dc8a35-387c-45cf-94a5-bab61fa85e32/image.png" alt=""></li>
<li>서비스 : 핵심 비즈니스 로직 구현</li>
<li>리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리</li>
<li>도메인 : 비즈니스 도메인 객체, 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨</li>
<li>아직 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계</li>
<li>데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정</li>
<li>개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용</li>
</ul>
<p>처음엔 객체 지향이 너무 어색하고 헷갈렸으나 이것도 익숙해져서인지 이제는 좀 반갑고..친숙해지기 시작했다.</p>
<h3 id="회원관리-도메인과-리포지토리-만들기">회원관리 도메인과 리포지토리 만들기</h3>
<h4 id="💻-member-소스">💻 Member 소스</h4>
<pre><code class="language-java">public class Member {
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}</code></pre>
<p><code>id</code>와 <code>name</code> 선언 후 <code>control</code> + <code>Enter</code>키로 Getter and Setter를 세팅한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/b8cac347-2f36-4e8f-a469-a7859fe87d7e/image.png" alt=""></p>
<p>멤버의 정보를 작성한 뒤 이 녀석들을 저장할 저장소를 만들어준다.</p>
<h4 id="💻-memberrepository-소스-interface">💻 MemberRepository 소스 (Interface)</h4>
<pre><code class="language-java">public interface MemberRepository {
    Member save(Member member); //저장소에 회원 저장
    Optional&lt;Member&gt; findById(Long id); //id를 찾는 기능
    Optional&lt;Member&gt; findByName(String name); //이름을 찾는 기능
    List&lt;Member&gt; findAll(); //전체 정보를 찾는 기능
}</code></pre>
<h4 id="💻-memorymemberrepository-소스">💻 MemoryMemberRepository 소스</h4>
<p><code>MemberRepository</code> 인터페이스에서 선언한 기능의 세부 코드를 작성한다.</p>
<pre><code class="language-java">//회원 정보 저장 메소드 불러오기(implements)
public class MemoryMemberRepository implements MemberRepository{

    private static Map&lt;Long, Member&gt; store = new HashMap&lt;&gt;();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        //null값이 나올 것을 위한 Optional 처리
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        //루프를 돌면서 이름을 찾는다.
        return store.values().stream()
                .filter(member -&gt; member.getName().equals(name))
                .findAny();
    }

    @Override
    public List&lt;Member&gt; findAll() {
        return new ArrayList&lt;&gt;(store.values());
    }
}</code></pre>
<p>회원 이름과 이름을 등록하고 검색해서 가져올 수 있는 처리
코드를 작성했으면 제대로 돌아가는지 테스트가 필요하다.</p>
<h3 id="회원-리포지토리-테스트-케이스-작성">회원 리포지토리 테스트 케이스 작성</h3>
<p><code>test</code> 디렉토리에 테스트용 클래스를 생성하고 테스트용 코드를 작성한다.</p>
<p><strong>테스트를 해야하는 이유?</strong>
개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다. 이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다. 자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.</p>
<h4 id="💻-memorymemberrepository-소스-1">💻 MemoryMemberRepository 소스</h4>
<pre><code class="language-java">@Test
public void save(){
    Member member = new Member();
    member.setName(&quot;spring&quot;);

    repository.save(member);
    //id를 가지고 와서 같은지 비교하는 테스트
    Member result = repository.findById(member.getId()).get();
    System.out.println(&quot;result = &quot;+ (result == member));

    //검증
    //Assertions.assertEquals(member, result);
    assertThat(member).isEqualTo(result);
}</code></pre>
<p><code>@Test</code> 를 어노테이션하고 저장된 id가 회원정보와 일치하는지 <code>assertThat(member).isEqualTo(result);</code>
 명령어로 확인한다.</p>
<p> <img src="https://velog.velcdn.com/images/dev_h_o/post/a1885172-f3bf-46f4-87cc-47073fe26851/image.png" alt=""></p>
<p>테스트 하려면 해당 메소드 왼쪽의 테스트 <code>run()</code> 버튼을 눌러서 결과값을 확인할 수 있다.</p>
<p>전체 메소드를 동시에 테스트 하려면 클래스 시작점에서 테스 버튼을 누르면 된다.</p>
<p><strong>테스트에서 주의해야 할 점!</strong></p>
<p>테스트는 메소드가 작성된 순서대로 진행되지 않으므로 저장된 데이터끼리 엉켜서 정상 작동하는 코드임에도 오류가 발생할 수 있다.</p>
<blockquote>
<p>findByName()에서 에러가 발생한 모습
<img src="https://velog.velcdn.com/images/dev_h_o/post/cc8635aa-6271-455e-93e5-cd42d344a775/image.png" alt=""></p>
</blockquote>
<p>그래서 각 테스트마다 테스트 완료 후 저장소 안의 데이터를 지워주는 작업을 해줘야 한다.</p>
<h4 id="💻-memorymemberrepository-소스-2">💻 MemoryMemberRepository 소스</h4>
<pre><code class="language-java">public void clearStore() {
    store.clear();
}</code></pre>
<h4 id="💻-testmemorymemberrepository-소스">💻 test/MemoryMemberRepository 소스</h4>
<pre><code class="language-java">//테스트가 끝난 다음에는 저장소 안의 데이터를 지워줘야 한다.
@AfterEach
public void afterEach(){
    repository.clearStore();
}</code></pre>
<p>저장소 안의 데이터를 지우기 위해 <code>MemoryMemberRepository</code> 클래스에 <code>clearStore()</code>를 만들어주고 테스트 클래스에서 <code>@AfterEach</code>로 하나의 테스트가 끝날 때마다 <code>clearStore()</code> 명령어가 작동하게끔 설정해주었다.</p>
<p><img src="https://velog.velcdn.com/images/dev_h_o/post/be20ec8e-8c8a-474d-bb28-07b10983020d/image.png" alt=""></p>
<p>3개의 테스트 모두 정상작동되는 것 확인!</p>
<h4 id="📅--date">📅  DATE</h4>
<p><code>2023.05.27 작성</code></p>
]]></description>
        </item>
    </channel>
</rss>