<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>yujindeang_.log</title>
        <link>https://velog.io/</link>
        <description>개발전공 대학생</description>
        <lastBuildDate>Thu, 14 May 2026 06:29:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>yujindeang_.log</title>
            <url>https://velog.velcdn.com/images/yujindeang_/profile/13e8dfcb-fa3c-4c30-8cae-e5617b6cdf70/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. yujindeang_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yujindeang_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[UMC 10기 - Spring Boot 스터디 #7]]></title>
            <link>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-7</link>
            <guid>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-7</guid>
            <pubDate>Thu, 14 May 2026 06:29:11 GMT</pubDate>
            <description><![CDATA[<p>이번 8주차에서는 
Spring Boot에서 자주 사용되는 페이지네이션 방식과 Validation, 
그리고 JPA 연관관계를 활용한 객체 그래프 탐색에 대해 학습했습니다.</p>
<p>특히 단순 CRUD 구현을 넘어서:</p>
<ul>
<li>Offset Pagination (Page)</li>
<li>Cursor Pagination (Slice)</li>
<li>Java Stream API</li>
<li>객체 그래프 탐색</li>
<li><code>@Valid</code> 기반 검증 처리</li>
</ul>
<p>등 실무에서도 자주 등장하는 개념들을 직접 API로 구현해보며 익힐 수 있었습니다.</p>
<br>

<p>그리고 이번 미션에서는:</p>
<ul>
<li>내가 진행중인 미션 조회 API</li>
<li>내가 작성한 리뷰 조회 API</li>
<li>Validation 예외 처리</li>
</ul>
<p>를 중심으로 구현을 진행했다.</p>
<hr>
<br>

<h2 id="1-page와-slice">1. Page와 Slice</h2>
<blockquote>
<p>Spring Data JPA에서는 페이지네이션을 위해 Page와 Slice를 제공함</p>
</blockquote>
<h3 id="1-page">1) Page</h3>
<p>: 전체 데이터 수까지 함께 조회하는 방식</p>
<pre><code class="language-java">Page&lt;Participate&gt;</code></pre>
<p><strong>특징</strong></p>
<ul>
<li>전체 페이지 수 제공</li>
<li>전체 데이터 개수 제공</li>
<li>count query 추가 실행</li>
</ul>
<table>
<thead>
<tr>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>페이지 번호 기반 UI 구현에 적합</td>
<td>count query 때문에 성능 비용 발생 가능</td>
</tr>
<tr>
<td>전체 페이지 계산 가능</td>
<td>대용량 데이터에서는 성능 저하 가능</td>
</tr>
</tbody></table>
<br>

<p><strong>사용 예시</strong></p>
<pre><code class="language-java">PageRequest pageRequest =
        PageRequest.of(page, 3);
Page&lt;Participate&gt; participatePage =
        participateRepository
                .findAllByMemberIdAndStatus(
                        memberId,
                        ParticipatedStatus.CHALLERGING,
                        pageRequest
                );</code></pre>
<br>

<h3 id="2-slice">2) Slice</h3>
<p>: 다음 페이지 존재 여부만 확인하는 방식</p>
<pre><code class="language-java">Slice&lt;Review&gt;</code></pre>
<p><strong>특징</strong></p>
<ul>
<li>count query 실행 X</li>
<li>다음 데이터 존재 여부만 확인</li>
<li>cursor pagination에 적합</li>
</ul>
<table>
<thead>
<tr>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>성능 효율적</td>
<td>전체 페이지 수 확인 불가</td>
</tr>
<tr>
<td>무한 스크롤 구현에 적합</td>
<td></td>
</tr>
</tbody></table>
<p><strong>사용 예시</strong></p>
<pre><code class="language-java">Slice&lt;Review&gt; reviewSlice =
        reviewRepository
                .findByMemberIdAndIdLessThanOrderByIdDesc(
                        memberId,
                        cursor,
                        pageable
                );</code></pre>
<br>

<h2 id="2-java-stream-api">2. Java Stream API</h2>
<ul>
<li>컬렉션 데이터를 선언형 방식으로 처리하기 위한 API이고,</li>
<li>반복문 대신 stream을 사용하여 가독성을 높일 수 있음</li>
</ul>
<h3 id="map">map()</h3>
<blockquote>
<p>map()은 데이터를 다른 형태로 변환할 때 사용</p>
</blockquote>
<pre><code class="language-java">return missionList.stream()
        .map(MissionConverter::toMissionPreviewDTO)
        .toList();</code></pre>
<p><strong>의미</strong>
Mission 객체 → MissionPreviewDTO 변환</p>
<br>

<h2 id="3-객체-그래프-탐색">3. 객체 그래프 탐색</h2>
<p>JPA에서는 연관관계를 통해 다른 Entity 데이터에 접근할 수 있음</p>
<p>예를 들어:</p>
<pre><code>Participate
 └── Mission</code></pre><p><strong>관계가 존재할 때:</strong></p>
<pre><code class="language-java">participate.getMission()</code></pre>
<p>을 통해 Mission 객체에 접근 가능</p>
<br>

<p><strong>사용 예시</strong></p>
<pre><code class="language-java">Mission mission = participate.getMission();
mission.getContent()
mission.getPoint()
mission.getEndDate()</code></pre>
<h4 id="장점">장점</h4>
<ol>
<li>객체지향적인 코드 작성 가능</li>
<li>JOIN SQL을 직접 작성하지 않아도 됨</li>
</ol>
<br>

<h4 id="주의점">주의점</h4>
<p>: Lazy Loading으로 인해 N+1 문제가 발생할 수 있다.</p>
<br>

<h2 id="4-valid-vs-validated">4. @Valid vs @Validated</h2>
<h3 id="valid">@Valid</h3>
<ul>
<li>Java 표준 Validation Annotation</li>
<li>주로 Request Body 검증 시 사용함<pre><code class="language-java">@RequestBody @Valid MissionReqDTO.CreateMission dto</code></pre>
</li>
</ul>
<h3 id="validated">@Validated</h3>
<ul>
<li>Spring에서 제공하는 확장 Validation 기능</li>
</ul>
<p><strong>특징 :</strong></p>
<ol>
<li>그룹 검증 가능</li>
<li>클래스 레벨 검증 가능</li>
</ol>
<h3 id="차이점-정리">차이점 정리</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>@Valid</th>
<th>@Validated</th>
</tr>
</thead>
<tbody><tr>
<td>제공</td>
<td>Java 표준</td>
<td>Spring</td>
</tr>
<tr>
<td>그룹 검증</td>
<td>X</td>
<td>O</td>
</tr>
<tr>
<td>사용 위치</td>
<td>DTO</td>
<td>클래스/메서드</td>
</tr>
<tr>
<td>주 사용 목적</td>
<td>Request Body 검증</td>
<td>고급 Validation</td>
</tr>
</tbody></table>
<br>

<hr>
<br>


<h2 id="미션-정리">미션 정리</h2>
<h3 id="미션-1-내가-진행중인-미션-조회-api">미션 1) 내가 진행중인 미션 조회 API</h3>
<ul>
<li>Offset 기반 페이지네이션(Page) 적용</li>
<li>Request Body를 통해 memberId를 전달받도록 구현</li>
<li><code>ParticipatedStatus.CHALLERGING</code> 상태의 미션만 조회하도록 구현 <br> <img width="400" alt="image" src="https://github.com/user-attachments/assets/d9cc22f8-114f-48ee-a338-297e1897840c" />
 <img width="400" alt="image" src="https://github.com/user-attachments/assets/0fccbe50-db08-497b-9a88-f1c489afbd5b" />

</li>
</ul>
<br>

<h3 id="미션-2-내가-생성한-리뷰-조회-api">미션 2) 내가 생성한 리뷰 조회 API</h3>
<ul>
<li>Cursor 기반 페이지네이션(Slice) 적용</li>
<li>사진 데이터 제외 후 리뷰 정보만 반환</li>
<li>정렬 기준을 query parameter로 분기 처리</li>
</ul>
<ol>
<li><code>sort=id</code>→ 최신 리뷰(ID DESC)</li>
<li><code>sort=stars</code> → 별점 기준 정렬(STARS DESC)</li>
</ol>
<br>

<h3 id="미션-3-validation-적용">미션 3) Validation 적용</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/f2a2c612-52df-4c4e-81aa-735877796868/image.png" alt=""></p>
<ul>
<li><p>Request Body DTO에 Validation Annotation 적용</p>
<ol>
<li><code>@NotBlank</code></li>
<li><code>@NotNull</code></li>
</ol>
<img width="400" alt="image" src="https://github.com/user-attachments/assets/55fab7fb-f91f-4f68-96ff-602f1340c3bd" />
</li>
<li><p><code>@Valid</code>를 통한 요청값 검증 적용 <br></p>
 <img width="400" alt="image" src="https://github.com/user-attachments/assets/4750e267-97a4-434e-8074-5f4c0ef0c2b5" />
</li>
<li><p><code>GeneralExceptionAdvice</code>에서 <code>MethodArgumentNotValidException</code> 처리 구현 </p>
 <img width="400" alt="image" src="https://github.com/user-attachments/assets/09964118-d69f-4d19-8415-36045917744c" />

</li>
</ul>
<p><br><br><br></p>
<hr>
<p><br><br></p>
<p>이번 주차에서는 단순 CRUD 구현을 넘어 실제 서비스에서 자주 사용되는 페이지네이션과 Validation 처리 방식을 직접 구현해볼 수 있었다.</p>
<p>특히:</p>
<ul>
<li>Offset Pagination과 Cursor Pagination의 차이</li>
<li><code>Page</code>와 <code>Slice</code>의 사용 목적</li>
<li>객체 그래프 탐색</li>
<li><code>Validation</code>  예외 처리</li>
</ul>
<p>등을 직접 API에 적용해보며 개념을 더 명확하게 이해할 수 있었습니다 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UMC 10기 - Spring Boot 스터디 #6]]></title>
            <link>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-6</link>
            <guid>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-6</guid>
            <pubDate>Thu, 14 May 2026 04:44:50 GMT</pubDate>
            <description><![CDATA[<br>

<p>이번 주차부터는 Spring Boot에서 가장 중요하다고 느껴지는 JPA를 본격적으로 학습했다.<br>단순히 CRUD를 구현하는 것을 넘어서
객체와 데이터베이스를 어떻게 연결하고 관리하는지에 대한 흐름을 이해할 수 있었고<br>실제로 연관관계 매핑과 API 구현까지 진행해보며 JPA의 동작 방식을 조금 더 체감할 수 있었다.</p>
<br>
바로 6주차 스터디 개념 정리랑 미션 내용 정리해보겠습니다 ~!
<br>
<br>
<br>

<h2 id="1-객체-지향과-rdb의-패러다임-차이">1. 객체 지향과 RDB의 패러다임 차이</h2>
<p>자바는 객체 중심으로 데이터를 관리하지만, 데이터베이스는 테이블 중심으로 데이터를 저장한다.
이 과정에서 객체 ↔ 테이블 변환 작업이 반복되며 패러다임 불일치가 발생한다.
예를 들어 JDBC만 사용할 경우:</p>
<ul>
<li>직접 SQL 작성</li>
<li>객체와 테이블 매핑</li>
<li>CRUD 반복 작업</li>
</ul>
<p>등을 개발자가 모두 처리해야 한다.
이 문제를 해결하기 위해 등장한 것이 <strong>JPA</strong>이다.
<br></p>
<h2 id="2-jpa란">2. JPA란?</h2>
<blockquote>
<p>JPA(Java Persistence API)는 자바 ORM 기술의 표준 인터페이스이다.
객체와 데이터베이스 테이블을 자동으로 매핑해주며, 반복적인 SQL 작성을 줄여준다.</p>
</blockquote>
<p><strong>대표 구현체:</strong></p>
<ul>
<li>Hibernate</li>
<li>JPA의 핵심 장점:</li>
<li>객체 중심 개발 가능</li>
<li>생산성 증가</li>
<li>유지보수 편리</li>
<li>SQL 자동 생성<br>


</li>
</ul>
<h2 id="3-영속성-컨텍스트persistence-context">3. 영속성 컨텍스트(Persistence Context)</h2>
<blockquote>
<p>JPA의 핵심 개념 중 하나로, 엔티티를 관리하는 논리적 공간이다.</p>
</blockquote>
<p><strong>주요 기능:</strong></p>
<ul>
<li>1차 캐시</li>
<li>변경 감지(Dirty Checking)</li>
<li>쓰기 지연 SQL 저장소</li>
<li>지연 로딩</li>
</ul>
<h3 id="1차-캐시">1차 캐시</h3>
<p>같은 엔티티를 다시 조회할 경우 DB가 아닌 캐시에서 조회한다.
→ 불필요한 SQL 감소</p>
<h3 id="변경-감지dirty-checking">변경 감지(Dirty Checking)</h3>
<p>엔티티 값을 수정하면 JPA가 변경 사항을 자동으로 감지한다.</p>
<pre><code class="language-java">member.setName(&quot;UMC&quot;);</code></pre>
<p>별도의 update 쿼리를 작성하지 않아도 commit 시점에 UPDATE SQL이 실행된다.
<br></p>
<h2 id="4-flush와-commit-차이">4. flush와 commit 차이</h2>
<h3 id="flush">flush</h3>
<ul>
<li>변경 내용을 DB에 반영</li>
<li>트랜잭션은 유지됨</li>
</ul>
<h3 id="commit">commit</h3>
<ul>
<li>flush 이후 최종 저장</li>
<li>트랜잭션 종료</li>
</ul>
<p>즉:</p>
<pre><code>flush → SQL 반영
commit → 최종 확정</code></pre><br>

<h2 id="5-연관관계-매핑">5. 연관관계 매핑</h2>
<blockquote>
<p>JPA에서는 객체 간 관계를 어노테이션으로 표현한다.</p>
</blockquote>
<p><strong>대표 어노테이션:</strong></p>
<ul>
<li><code>@ManyToOne</code></li>
<li><code>@OneToMany</code></li>
<li><code>@OneToOne</code></li>
</ul>
<p><strong>예시:</strong></p>
<pre><code class="language-java">@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = &quot;member_id&quot;)
private Member member;</code></pre>
<br>


<h2 id="6-즉시-로딩-vs-지연-로딩">6. 즉시 로딩 vs 지연 로딩</h2>
<h3 id="즉시-로딩eager">즉시 로딩(EAGER)</h3>
<blockquote>
<p>연관 객체를 함께 조회한다.</p>
</blockquote>
<p><strong>장점:</strong></p>
<ul>
<li>바로 사용 가능</li>
</ul>
<p><strong>단점:</strong></p>
<ul>
<li>불필요한 조회 발생 가능</li>
<li>N+1 문제 발생 위험</li>
</ul>
<h3 id="지연-로딩lazy">지연 로딩(LAZY)</h3>
<blockquote>
<p>실제로 사용할 때 조회한다.</p>
</blockquote>
<p>장점:</p>
<ul>
<li>성능 최적화에 유리</li>
<li>실무에서 많이 사용</li>
</ul>
<br>

<h2 id="7-n1-문제">7. N+1 문제</h2>
<blockquote>
<p>연관 데이터를 조회하는 과정에서 불필요한 쿼리가 반복 실행되는 문제이다.</p>
</blockquote>
<p><strong>예시:</strong></p>
<ul>
<li>회원 목록 조회 1번</li>
<li>회원마다 게시글 조회 N번</li>
<li><em>→ 총 N+1번 SQL 실행*</em>  : 성능 저하의 주요 원인이 된다.<br>

</li>
</ul>
<h2 id="8-jpql">8. JPQL</h2>
<p>JPQL은 엔티티 객체를 대상으로 작성하는 객체 지향 쿼리 언어이다.
SQL과 유사하지만 테이블이 아닌 엔티티 기준으로 동작한다.</p>
<pre><code class="language-java">@Query(&quot;SELECT m FROM Member m WHERE m.name = :name&quot;)</code></pre>
<br>

<h2 id="9-fetch-join과-entitygraph">9. Fetch Join과 @EntityGraph</h2>
<h3 id="fetch-join">Fetch Join</h3>
<blockquote>
<p>연관 엔티티를 한 번의 쿼리로 함께 조회하는 기능
→ <strong>N+1 문제 해결</strong>에 사용</p>
</blockquote>
<h3 id="entitygraph"><code>@EntityGraph</code></h3>
<blockquote>
<p>Fetch Join을 어노테이션 기반으로 편리하게 사용하는 방식</p>
</blockquote>
<pre><code class="language-java">@EntityGraph(attributePaths = {&quot;orders&quot;})</code></pre>
<p>Repository 메서드와 함께 사용할 수 있어 코드가 간결함</p>
<br>

<hr>
<br>


<h2 id="이번주차-과제">이번주차 과제</h2>
<p><a href="https://github.com/SWU-UMC/10th_SpringBoot_B/pull/11">6주차 미션 PR </a></p>
<h3 id="미션-1-마이페이지-조회-api-구현">미션 1) 마이페이지 조회 API 구현</h3>
<p>GET <code>/members/home/my</code></p>
<ul>
<li>memberId를 기반으로 사용자 정보 조회하도록 memberId params 추가</li>
<li>사용자 이름, 이메일, 포인트 반환</li>
<li><strong>실행결과</strong> 
<img src="https://velog.velcdn.com/images/yujindeang_/post/b3d177d7-78dc-4aba-b51f-dfd671ab43cf/image.png" alt=""></li>
</ul>
<br>

<h3 id="미션-2-리뷰-작성-api-구현">미션 2) 리뷰 작성 API 구현</h3>
<p>POST <code>/review/markets/{marketId}</code></p>
<ul>
<li>memberId와 marketId를 기반으로 리뷰 작성 기능 구현</li>
<li>리뷰 저장 후 reviewId 및 성공 메시지 반환</li>
<li><strong>실행결과</strong>
<img src="https://velog.velcdn.com/images/yujindeang_/post/aee1d5c9-788b-4985-b70a-a98669d52ac7/image.png" alt=""></li>
</ul>
<br>

<h3 id="미션-3-진행중--완료-미션-조회-api-구현">미션 3) 진행중 / 완료 미션 조회 API 구현</h3>
<p>GET <code>/mission</code></p>
<ul>
<li>memberId와 ParticipatedStatus(CHALLENGING / COMPLETE)를 기반으로 참여 미션 목록 조회</li>
<li><code>Pageable</code> 기반 페이징 처리 구현</li>
<li><code>@query</code> + JOIN FETCH를 사용하여 Mission 연관 데이터 조회</li>
</ul>
<pre><code class="language-sql">@query + JOIN FETCH를 사용하여 Mission 연관 데이터 조회
@Query(&quot;&quot;&quot; 
    SELECT p 
    FROM Participate p 
        JOIN FETCH p.mission m 
        WHERE p.member.id = :memberId 
        AND p.status = :status 
&quot;&quot;&quot;)</code></pre>
<ul>
<li><strong>실행 결과</strong>
<img src="https://velog.velcdn.com/images/yujindeang_/post/2d481a67-5e97-49bb-b7fa-7a80bd8d8108/image.png" alt=""></li>
</ul>
<br>

<h3 id="미션-4-홈-화면-지역별-진행-중인-미션-조회-api-구현">미션 4) 홈 화면 지역별 진행 중인 미션 조회 API 구현</h3>
<p>GET <code>/mission/home</code></p>
<ul>
<li>regionName 기반으로 특정 지역의 진행중(<code>IN_PROGRESS</code>) 미션 목록 조회</li>
<li><code>Pageable</code> 기반 페이징 처리 구현</li>
<li><code>@query</code> + JOIN FETCH를 사용하여 Market, Region 연관 데이터 조회<pre><code class="language-sql">@Query(&quot;&quot;&quot; 
SELECT m FROM Mission m 
JOIN FETCH m.market mk 
JOIN FETCH mk.region r WHERE r.name = :regionName 
        AND m.missionStatus = :status 
&quot;&quot;&quot;)</code></pre>
</li>
<li><strong>실행결과</strong>
<img src="https://velog.velcdn.com/images/yujindeang_/post/adf2af82-182a-415e-8f4e-a65c07f384eb/image.png" alt=""></li>
</ul>
<br>
<br>

<h2 id="6주차-스터디-느낀점">6주차 스터디 느낀점</h2>
<p>이번 주차를 통해 JPA가 단순히 SQL을 대신 작성해주는 기술이 아니라,
객체 중심으로 개발할 수 있도록 도와주는 ORM 기술이라는 점을 이해할 수 있었다.</p>
<p>특히 연관관계 매핑과 지연 로딩, Fetch Join을 직접 적용해보며
성능 최적화 관점에서도 많은 고민이 필요한 기술이라는 점을 느꼈고</p>
<p>앞으로는 단순 기능 구현을 넘어
쿼리 최적화와 객체 그래프 탐색 흐름까지 함께 고려하며 개발하는 연습을 해야겠다고 생각했습니다 .</p>
<p><br><br><br></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UMC 10기 Spring Boot 스터디 #5 - API 계층 구조와 응답 통일]]></title>
            <link>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-5-API-%EA%B3%84%EC%B8%B5-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9D%91%EB%8B%B5-%ED%86%B5%EC%9D%BC</link>
            <guid>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-5-API-%EA%B3%84%EC%B8%B5-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9D%91%EB%8B%B5-%ED%86%B5%EC%9D%BC</guid>
            <pubDate>Wed, 06 May 2026 11:19:45 GMT</pubDate>
            <description><![CDATA[<h2 id="이번-주차에서-배운-것">이번 주차에서 배운 것</h2>
<p>이번 주차에서는 단순히 API를 만드는 것이 아니라,
Spring Boot에서 계층을 어떻게 나누고 응답과 예외를 어떻게 관리하는지 학습했습니다.</p>
<h4 id="특히-포인트-5가지">특히 포인트 5가지</h4>
<ul>
<li>Controller / Service / Repository 역할</li>
<li>DTO 사용 이유</li>
<li>ApiResponse를 통한 응답 통일</li>
<li>Exception Handling 구조</li>
<li>Swagger를 이용한 API 테스트</li>
</ul>
<p>를 중심으로 정리했습니다.</p>
<p><br/><br/></p>
<h2 id="1-controller--service--repository-구조">1. Controller / Service / Repository 구조</h2>
<h3 id="1-1-controller">1-1. Controller</h3>
<p>Controller는 클라이언트의 요청을 가장 먼저 받는 계층입니다. </p>
<h4 id="controller의-주요-역할">Controller의 주요 역할</h4>
<ul>
<li>HTTP 요청 받기</li>
<li>Request Body / Query Parameter 매핑</li>
<li>Service 호출</li>
<li>결과 응답 반환</li>
</ul>
<p><br /> 예시:</p>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/members&quot;)
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    @PostMapping
    public ApiResponse&lt;MemberResDTO.MemberInfoDTO&gt; getMember(
            @RequestBody MemberReqDTO.MemberRequestDTO request
    ) {
        return ApiResponse.onSuccess(
                memberService.getMember(request)
        );
    }
}</code></pre>
<h3 id="1-2-service">1-2. Service</h3>
<p>Service는 실제 비즈니스 로직을 처리하는 계층이다.</p>
<h4 id="service의-주요-역할">Service의 주요 역할:</h4>
<ul>
<li>요청 데이터 처리</li>
<li>Repository 호출</li>
<li>비즈니스 로직 수행</li>
<li>응답 DTO 반환</li>
</ul>
<p><br /> 예시:</p>
<pre><code class="language-java">@Service
@RequiredArgsConstructor
@Transactional
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberResDTO.MemberInfoDTO getMember(Long memberId) {

        Member member = memberRepository.findById(memberId)
                .orElseThrow(() -&gt; new MemberException(MemberErrorCode.NOT_FOUND));

        return MemberConverter.toMemberInfoDTO(member);
    }
}</code></pre>
<h3 id="1-3-repository">1-3. Repository</h3>
<p>Repository는 DB와 직접 통신하는 계층이다.</p>
<p>JPA에서는 <code>JpaRepository</code>를 상속받아 사용함</p>
<pre><code class="language-java">public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {}</code></pre>
<p>기본적으로 다음 메서드를 제공한다.</p>
<ul>
<li>save()</li>
<li>findById()</li>
<li>delete()</li>
<li>findAll()</li>
</ul>
<p>또한 메서드 이름만으로 커스텀 조회도 가능하다.</p>
<pre><code class="language-java">findByEmail(String email)</code></pre>
<p><br/><br/></p>
<h2 id="2-dto를-사용하는-이유">2. DTO를 사용하는 이유</h2>
<p>DTO(Data Transfer Object)는 계층 간 데이터 전달 객체이다.
이번 주차에서는 Request DTO와 Response DTO를 분리해서 사용했다.</p>
<h4 id="dto를-사용하는-이유">DTO를 사용하는 이유</h4>
<ul>
<li>요청/응답 구조를 명확하게 관리 가능</li>
<li>Entity 직접 노출 방지</li>
<li>안정성 증가</li>
<li>유지보수 용이</li>
</ul>
<br/>

<h3 id="record-vs-static-class">record vs static class</h3>
<h4 id="span-stylebackground-color-f2e2c6recordspan"><span style="background-color: #f2e2c6">record</span></h4>
<pre><code class="language-java">public record MemberDTO(String name, String email) {}</code></pre>
<h4 id="특징">특징:</h4>
<ul>
<li>코드가 간결함</li>
<li>불변 객체 생성</li>
<li>Java 16 이상 지원</li>
</ul>
<br/>

<h4 id="span-stylebackground-color-f2e2c6static-classspan"><span style="background-color: #f2e2c6">static class</span></h4>
<pre><code class="language-java">@Getter
@AllArgsConstructor
public static class MemberDTO {
    private String name;
    private String email;
}</code></pre>
<h4 id="특징-1">특징:</h4>
<ul>
<li>유연한 구조 설계 가능</li>
<li>확장성이 높음</li>
</ul>
<p><br/><br/></p>
<h2 id="3-apiresponse를-이용한-응답-통일">3. ApiResponse를 이용한 응답 통일</h2>
<p>프로젝트에서는 API 응답 형식을 통일해서 사용했다.</p>
<pre><code class="language-java">{
  &quot;isSuccess&quot;: true,
  &quot;code&quot;: &quot;COMMON200&quot;,
  &quot;message&quot;: &quot;성공입니다.&quot;,
  &quot;result&quot;: {}
}</code></pre>
<br/>

<h4 id="응답-통일의-장점">응답 통일의 장점</h4>
<ul>
<li>프론트엔드와 협업이 쉬움</li>
<li>성공/실패 구조 일관성 유지</li>
<li>유지보수 편리</li>
<li>에러 처리 단순화</li>
</ul>
<h4 id="apiresponse-예시">ApiResponse 예시</h4>
<pre><code class="language-java">public class ApiResponse&lt;T&gt; {

    private Boolean isSuccess;
    private String code;
    private String message;
    private T result;
}</code></pre>
<p>여기서 제네릭 <T>를 사용해 다양한 타입의 응답을 처리할 수 있다.</p>
<p><br/><br/></p>
<h2 id="4-exception-handling">4. Exception Handling</h2>
<p>예외 상황에서도 응답 형식을 통일하기 위해 <code>@RestControllerAdvice</code>를 사용했다.</p>
<blockquote>
<p><strong>구조</strong>
Exception 발생→ ControllerAdvice에서 감지→ ApiResponse 형태로 반환</p>
</blockquote>
<h4 id="예시">예시:</h4>
<pre><code class="language-java">@RestControllerAdvice
public class GeneralExceptionAdvice {

    @ExceptionHandler(ProjectException.class)
    public ResponseEntity&lt;ApiResponse&lt;Void&gt;&gt; handleException(
            ProjectException e
    ) {
        return ResponseEntity
                .status(e.getErrorCode().getStatus())
                .body(ApiResponse.onFailure(e.getErrorCode(), null));
    }
}</code></pre>
<p><br/><br/></p>
<h2 id="5-optional-사용-이유">5. Optional 사용 이유</h2>
<p>JPA의 <code>findById()</code>는 <code>Optional</code>을 반환한다.</p>
<pre><code class="language-java">memberRepository.findById(id)</code></pre>
<p>따라서 값이 없을 경우를 안전하게 처리할 수 있다.</p>
<pre><code class="language-java">.orElseThrow(() -&gt; new MemberException(...))</code></pre>
<p>  이를 통해 <code>NullPointerException</code>을 방지할 수 있다.</p>
<br/> 

<hr>
<h2 id="이번-주차에서-느낀-점">이번 주차에서 느낀 점</h2>
<p>이번 주차를 진행하면서 단순히 “API가 동작하는 것”보다
“왜 계층을 분리하는지”를 이해하는 것이 중요하다는 걸 느꼈다.</p>
<p>특히 <code>ApiResponse</code>와 <code>Exception Handling</code> 구조를 직접 적용해보면서,
프로젝트 전체의 응답 형식을 통일하는 이유를 체감할 수 있었다.</p>
<p>또한 DTO를 통해 Entity를 직접 노출하지 않는 구조가 유지보수 측면에서 훨씬 안전하다는 점도 이해하게 되었다.</p>
<p><br/><br/></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트로 내 사이트 만들기 ]]></title>
            <link>https://velog.io/@yujindeang_/%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%A1%9C-%EB%82%B4-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@yujindeang_/%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%A1%9C-%EB%82%B4-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Fri, 01 May 2026 06:44:11 GMT</pubDate>
            <description><![CDATA[<p>내 컴퓨터에서 새로운 파일 yujin_portfolio를 만들고
해당 파일을 visualstudio_code로 열었다.</p>
<p>그런 다음 이미 설치된 react가 있기 때문에, 바로 프로젝트 생성을 했다.</p>
<h3 id="❶-프로젝트-생성하기">❶ 프로젝트 생성하기</h3>
<pre><code class="language-javascript">% npm create vite@latest </code></pre>
<p>해당 명령어를 입력하게 되면</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/f837d5dd-b292-4706-8f83-1baae6f79e2d/image.png" alt=""></p>
<p>쥬륵주륵 설치가 될거고</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/3f3a0479-2545-4169-adcc-c08dd04af47d/image.png" alt=""></p>
<p>이렇게 local: <a href="http://localhost:5713/">http://localhost:5713/</a>
이 나오면 프로젝트가 생성되고 실행된 것
(접속하려면 command누른채로 커서 클릭해야함!!)</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/34f93bca-5ca9-4d92-8757-a4a9e70cb935/image.png" alt="">
접속하게 되면 리엑트 자체의 기본 화면이 나온다 
(이 화면이 나오면 성공)</p>
<br>

<blockquote>
<p>하나의 터미널에서 서버를 띄운 상태로 개발은 이어갈 수 없음. 
그래서 서버를 닫아야하는데, 그때는 <strong>컨트롤c</strong>를 누르면 됨 !!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/61ecf619-0d4e-4fd1-a808-741af881943e/image.png" alt=""></p>
<hr>
<p>그리고 개발을 계속 하는 전공자나, 
팀프로젝트로 진행되는 프로젝트로 하는 거라면 git을 사용하는게 좋다.
(팀프로젝트가 아니더라도 이 프로젝트의 과정을 처음부터 끝까지 남기기 위해선?..)</p>
<p>그치만 깃이 은근.... 비전공자가 사용하기엔 위험도 있고, 
공부는 필요해서 구조 다 잡고 배포할 때 그때 깃에 올려도 충분함!</p>
<p>그래서 지금 적는 깃 설정은 건너 뛰어도 됨!//</p>
<h3 id="❷-git-연결해두기">❷ git 연결해두기</h3>
<p>우선, 정말 처음이라 깃 계정도 없다 ! 
하면 <a href="https://github.com">https://github.com</a> 여기서 가입부터 해야함</p>
<p>계정이 있다면 바로 시작 </p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/a2ced6a7-06e1-4542-8bc0-e6a7ee7576ad/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/a96fdf75-fe50-490e-b1a2-16f7dd90466d/image.png" alt=""></p>
<p>레포지토리에 들어가서 오른쪽 상단을 보면 new 버튼이있음</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/c5be9bed-4d2e-4a1a-8c63-39b467109e68/image.png" alt=""></p>
<p>이름 설정하고 하단에 readme on으로 켜주고,
다른 사람들한테 이 파일 보이지 않게 하려면 private로 바꿔주면 된다!
(전 이게 개인 프로젝트니까 private로 생성했습니다)</p>
<hr>
<h3 id="❸-프로젝트에-깃-연결하기">❸ 프로젝트에 깃 연결하기</h3>
<pre><code>yujin_portfolio % cd yujin_blog
yujin_blog % git init
yujin_blog % git remote add origin https://github.com/yujining3827/yujin-site.git</code></pre><p>origin 뒤의
<a href="https://github.com/yujining3827/yujin-site.git">https://github.com/yujining3827/yujin-site.git</a> 이 링크는</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/beef5050-c6dd-4f61-a200-7d5ec3545d79/image.png" alt=""></p>
<p>여기서 바로 복붙 아이콘 눌러서 가져오면 됩니다.</p>
<pre><code>yujin_blog % git add .
yujin_blog % git commit -m &quot;init: react project&quot;</code></pre><p>초기 프로젝트 세팅을 저장하는 명령어고</p>
<p>yujin_blog % git push -u origin main --force</p>
<p>함부로 쓰면 안되는 git 명령어지만, 지금 로컬을 전체 덮어쓰기로 올리는 명령어입니다ㅎ,,</p>
<p>명심할게 정말 함부로 쓰면 안된다 !! 이정도??
<img src="https://velog.velcdn.com/images/yujindeang_/post/ea281ecf-4d57-4b96-aa74-5c9a16de75b2/image.png" alt=""></p>
<p>이렇게 하면 정상적으로 깃에 반영됨</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/bc2a5e1b-5106-44b1-b158-ff272907ac05/image.png" alt=""></p>
<p>깃헙 해당 레포에서 실시간으로 확인 가능 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UMC 10기 - Spring Boot 스터디 #4]]></title>
            <link>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-4</link>
            <guid>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-4</guid>
            <pubDate>Fri, 10 Apr 2026 04:51:32 GMT</pubDate>
            <description><![CDATA[<h3 id="4주차-프로젝트-세팅--아키텍처-구조-이해하기">4주차: 프로젝트 세팅 &amp; 아키텍처 구조 이해하기</h3>
<p>3주차에서는 HTTP 통신과 스프링의 기본 동작 원리에 대해 학습했다면,
이번 4주차에서는 드디어 실제 Spring Boot 프로젝트를 생성하고 구조를 설계하는 단계로 넘어갔다.</p>
<p>단순히 코드를 작성하는 것이 아니라,
어떻게 구조를 설계할 것인지를 고민하는 단계였다는 점이 가장 중요했다.</p>
<br>

<hr>
<h2 id="📌-아키텍처-구조란">📌 아키텍처 구조란?</h2>
<p>아키텍처 구조는 쉽게 말하면
프로젝트의 뼈대를 설계하는 것이다.</p>
<p>단순히 코드를 작성하는 것이 아니라,</p>
<p><strong>어떤 기준으로 파일을 나눌지
어떤 역할을 어디에 둘지
어떻게 확장할 수 있게 만들지</strong></p>
<p>를 미리 고민하는 과정이다.</p>
<blockquote>
<p><strong>✔ 왜 필요한가?</strong></p>
</blockquote>
<ul>
<li>유지보수가 쉬워짐</li>
<li>기능 추가 시 영향 범위 최소화</li>
<li>협업 시 구조 이해가 쉬움</li>
</ul>
<hr>
<br>

<h2 id="📌-아키텍처-구조의-종류">📌 아키텍처 구조의 종류</h2>
<h3 id="1️⃣-계층형-아키텍처">1️⃣ 계층형 아키텍처</h3>
<pre><code>Controller
Service
Repository</code></pre><p>이렇게 역할 기준으로 나누는 방식으로 대부분의 기본적인 Spring 구조</p>
<h3 id="2️⃣-도메인형-아키텍처">2️⃣ 도메인형 아키텍처</h3>
<p><strong>기능(도메인) 기준으로</strong> 구조 분리</p>
<pre><code>예:

user
mission
review</code></pre><p>각 도메인 안에 <code>controller</code> / <code>service</code> / <code>repository</code> 포함</p>
<h2 id="📌-이번-과제에서-사용한-구조">📌 이번 과제에서 사용한 구조</h2>
<p>이번 미션에서는
<strong>도메인형 아키텍처를 기반으로 프로젝트를 구성했다.</strong></p>
<blockquote>
<p>워크북 기준 도메인을 보면</p>
</blockquote>
<pre><code>사용자
미션
리뷰</code></pre><p>로 나눌 수 있었고,</p>
<p>내 ERD를 기준으로 다음과 같이 구성했다.
<img src="https://velog.velcdn.com/images/yujindeang_/post/823f2bac-375f-43a8-81db-7ad24340c7af/image.png" alt=""></p>
<h3 id="도메인-분리">&lt; 도메인 분리 &gt;</h3>
<p><strong>사용자</strong>
<code>user</code>, <code>preference</code>, <code>food_category</code>
<code>user_term_agreement</code>, <code>term, notification</code></p>
<p><strong>미션</strong>
<code>mission</code>, <code>participate</code>, <code>market</code>, <code>region</code></p>
<p><strong>리뷰</strong>
<code>review</code></p>
<hr>
<br>

<h2 id="📌-프로젝트-세팅">📌 프로젝트 세팅</h2>
<h3 id="1️⃣-spring-boot-프로젝트-생성">1️⃣ Spring Boot 프로젝트 생성</h3>
<ul>
<li>Gradle (Groovy)</li>
<li>Spring Web</li>
<li>Spring Data JPA</li>
<li>MySQL Driver</li>
<li>Lombok</li>
</ul>
<h3 id="2️⃣-환경-설정-applicationyml">2️⃣ 환경 설정 (application.yml)</h3>
<pre><code class="language-yml">spring:
  application:
    name: &quot;umc10th&quot;

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: ${DB_URL}
    username: ${DB_USER}
    password: ${DB_PW}

  jpa:
    database: mysql
    database-platform: org.hibernate.dialect.MySQLDialect
    show-sql: true
    hibernate:
      ddl-auto: update
</code></pre>
<p>👉 민감 정보는 .env로 분리해서 관리</p>
<hr>
<br>

<h2 id="📌-도메인형-패키지-구조">📌 도메인형 패키지 구조</h2>
<pre><code>domain
 ├── user
 ├── mission
 ├── review
 ├── notification
 ├── term
 ├── region
 └── preference

global
 ├── config
 ├── exception</code></pre><p>각 도메인 내부 구조:</p>
<pre><code>controller
service
repository
entity
dto
converter</code></pre><hr>
<br>

<h2 id="📌-swagger-설정">📌 Swagger 설정</h2>
<p>Swagger는
API 문서를 자동으로 생성하고 테스트할 수 있는 도구이다.</p>
<h3 id="❶-의존성-추가">❶ 의존성 추가</h3>
<pre><code>implementation &#39;org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.1&#39;</code></pre><h3 id="❷-swaggerconfig">❷ SwaggerConfig</h3>
<pre><code class="language-java">@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI swagger() {
        Info info = new Info()
                .title(&quot;UMC10th&quot;)
                .description(&quot;10기 Swagger&quot;)
                .version(&quot;0.0.1&quot;);

        return new OpenAPI()
                .info(info)
                .addServersItem(new Server().url(&quot;/&quot;));
    }
}</code></pre>
<h3 id="❸-결과-확인">❸ 결과 확인</h3>
<p><a href="http://localhost:8080/swagger-ui/index.html">http://localhost:8080/swagger-ui/index.html</a>
로 접속하면 API 문서를 UI로 확인 가능</p>
<hr>
<br>

<h2 id="📌-핵심-개념-정리">📌 핵심 개념 정리</h2>
<h3 id="1-dto">1. DTO</h3>
<ul>
<li>계층 간 데이터 전달 객체</li>
<li>Entity 직접 노출 방지</li>
</ul>
<h3 id="2-converter">2. Converter</h3>
<ul>
<li>DTO ↔ Entity 변환 담당</li>
<li>역할 분리 &amp; 유지보수성 증가</li>
</ul>
<h3 id="3-ddd-vs-도메인형-아키텍처">3. DDD vs 도메인형 아키텍처</h3>
<ul>
<li>DDD → 설계 철학</li>
<li>도메인형 아키텍처 → 코드 구조 방식</li>
</ul>
<hr>
<br>

<h2 id="📌-느낀-점">📌 느낀 점</h2>
<p>이번 4주차를 하면서 가장 크게 느낀 점은
<strong>“코드를 잘 짜는 것보다 구조를 잘 짜는 게 더 중요하다”</strong>는 것이었다.</p>
<p>처음에는 단순히 프로젝트를 생성하는 단계라고 생각했는데,
실제로는</p>
<p>ERD를 기반으로 도메인을 나누고
구조를 설계하고
확장성을 고려하는</p>
<p><strong>설계 중심의 단계</strong>였다.
<br></p>
<hr>
<br>

<h2 id="📌-마무리">📌 마무리</h2>
<p>이번 주차에서는
단순한 코드 작성이 아닌 구조 설계의 중요성을 배울 수 있었다.</p>
<p>다음 주차에서는
이 구조 위에 실제 API를 구현해보면서 더 깊이 이해해보고 싶다.</p>
<p><br><br></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UMC 10기 - Spring Boot 스터디 #3.]]></title>
            <link>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-3</link>
            <guid>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-3</guid>
            <pubDate>Thu, 02 Apr 2026 01:06:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yujindeang_/post/c05070d7-5eaa-4227-bb5b-058961c1f1d7/image.png" alt=""></p>
<br>

<p>이번 3주차에서는 스프링의 핵심 개념인 IoC와 DI를 시작으로,
객체 생성부터 HTTP 요청 처리까지의 전체 흐름을 학습했습니다.</p>
<p>특히 부록 1~4를 통해 Bean 생성 → Filter → DispatcherServlet → 응답 처리까지
실제 실행 흐름을 따라가며 스프링의 내부 구조를 이해하는 데 집중했습니다.</p>
<br>
<br>

<h1 id="3주차-워크북-정리">3주차 워크북 정리</h1>
<hr>
<h2 id="1-spring이-무엇인가-→-프레임워크">1. Spring이 무엇인가 → “프레임워크”</h2>
<blockquote>
<p><span style="color: #7Cb342"><strong>Spring은</strong></span>
 자바 애플리케이션을 쉽게 만들도록 도와주는 프레임워크</p>
</blockquote>
<p>핵심은 <strong>“뼈대 제공”</strong></p>
<p>자주 쓰는 기능 구조를 미리 만들어둠
개발자는 그 안에 코드만 채움</p>
<br>

<h2 id="2-프레임워크-vs-라이브러리-→-제어권-차이">2. 프레임워크 vs 라이브러리 → “제어권 차이”</h2>
<p><span style="background-color: #f2e2c6
">** ★ 라이브러리  **</span> : 내가 필요할 때 호출
제어권 → 개발자</p>
<p><span style="background-color: #f2e2c6
">*<em>★ 프레임워크  *</em></span>  : 내가 코드를 넣으면 알아서 실행
제어권 → 프레임워크</p>
<br>

<h2 id="3-ioc-→-제어권이-넘어간-상태">3. IoC → 제어권이 넘어간 상태</h2>
<p>프레임워크를 쓰면 발생하는 핵심 개념 <span style="color:#b4585a"><strong>제어의 역전 (IoC)</strong></span></p>
<p><strong>객체 생성 / 실행 흐름 / 생명주기</strong>
→ 개발자가 아니라 프레임워크가 관리</p>
<p>즉, <strong>“내가 실행하는 구조” → “프레임워크가 실행하는 구조”</strong></p>
<br>

<h2 id="4-di-→-ioc를-실제로-구현하는-방법">4. DI → IoC를 실제로 구현하는 방법</h2>
<p><strong>IoC만으로는 추상적</strong>이기 때문에 이를 <strong>구현하는 방법</strong>이 필요한데, 그게 <strong>DI</strong></p>
<p>객체가 필요한 의존성을 직접 생성 ❌
<strong>외부에서 주입받음 ⭕</strong></p>
<br>

<h2 id="5-왜-di가-필요한가-→-결합도-문제-해결">5. 왜 DI가 필요한가 → 결합도 문제 해결</h2>
<blockquote>
<p>문서에서 핵심 예시: 커피 머신</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/e8a8b8f3-db50-4a41-9759-2a55cc0578b3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/b0fd5eec-7232-4dde-bd13-4387a14e04bf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/ced537e7-7307-43b5-a0fb-ce1bbcd7b175/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/47702dcd-ef44-4c10-a2de-082cc162e137/image.png" alt=""></p>
<h4 id="기존-강한-결합">기존 (강한 결합)</h4>
<ul>
<li>CoffeeMachine 내부에서 직접 객체 생성</li>
<li>부품 바꾸려면 전체 코드 수정 필요</li>
</ul>
<h4 id="di-적용-느슨한-결합">DI 적용 (느슨한 결합)</h4>
<ul>
<li>인터페이스 + 외부 주입</li>
<li>Bean만 바꾸면 동작 변경 가능</li>
</ul>
<p><strong>결과</strong></p>
<ul>
<li>유지보수 쉬움</li>
<li>확장 쉬움</li>
</ul>
<br>

<h2 id="6-spring에서-ioc--di가-어떻게-구현되는가">6. Spring에서 IoC + DI가 어떻게 구현되는가</h2>
<p>★ <strong>“컨테이너” 개념</strong> ★</p>
<p><strong>Spring 컨테이너</strong></p>
<ul>
<li>객체(Bean)를 생성하고 관리</li>
<li>의존성 자동 주입</li>
</ul>
<p><strong>Bean</strong></p>
<ul>
<li>Spring이 관리하는 객체</li>
</ul>
<p><strong>동작 흐름</strong></p>
<ul>
<li>클래스 정의</li>
<li>어노테이션으로 관계 설정</li>
<li>컨테이너가 객체 생성</li>
<li>필요한 곳에 주입</li>
</ul>
<br>

<h2 id="7-di-방식-구체화-단계">7. DI 방식 (구체화 단계)</h2>
<p>DI를 실제 코드로 적용하는 방법</p>
<h4 id="①-생성자-주입-권장">① 생성자 주입 (권장)</h4>
<ul>
<li>필수 의존성 보장</li>
<li>객체 생성 시 완성됨<h4 id="②-setter-주입">② Setter 주입</h4>
</li>
<li>런타임 주입</li>
<li>Null 위험<h4 id="③-필드-주입">③ 필드 주입</h4>
</li>
<li>간단하지만 구조 파악 어려움</li>
<li>테스트 어려움</li>
</ul>
<br>

<h2 id="8-웹-구조로-확장-→-servlet-등장">8. 웹 구조로 확장 → Servlet 등장</h2>
<blockquote>
<p><strong>서블릿</strong>
HTTP 요청을 처리하는 자바 프로그램</p>
</blockquote>
<p><strong>흐름</strong></p>
<ol>
<li>요청 → Servlet Container</li>
<li>Servlet 실행</li>
<li>응답 반환</li>
</ol>
<br>

<h2 id="9-spring에서의-servlet-→-dispatcherservlet">9. Spring에서의 Servlet → DispatcherServlet</h2>
<p>Spring은 서블릿을 더 발전시킴
<span style="background-color: #f2e2c6
">** 핵심: DispatcherServlet **</span> </p>
<p>모든 요청을 받는 중앙 컨트롤러
요청 → Controller로 전달 → 응답 생성</p>
<br>
<br>

<h2 id="전체-흐름">전체 흐름</h2>
<blockquote>
<p>Spring (프레임워크) 
→ 제어권이 프레임워크로 이동 (<strong>IoC</strong>)
→ 의존성도 외부에서 관리 (<strong>DI</strong>)
→ 이를 <strong>컨테이너 + Bean</strong>으로 구현
→ 웹에서는 <strong>Servlet으로 요청</strong> 처리
→ Spring은 <strong>DispatcherServlet</strong>으로 통합 관리</p>
</blockquote>
<br>


<h2 id="개념-핵심-요약">개념 핵심 요약</h2>
<hr>
<ul>
<li>Spring은 프레임워크다</li>
<li>프레임워크는 제어권을 가져간다 → IoC</li>
<li>객체 관리도 외부에서 한다 → DI</li>
<li>이를 Spring 컨테이너 + Bean으로 구현</li>
<li>웹 요청은 Servlet이 처리</li>
<li>Spring에서는 DispatcherServlet이 전체 흐름 제어</li>
</ul>
<br>
<br>

<hr>
<h1 id="부록">[부록]</h1>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/8e63ea5a-def4-452b-82bd-95b300010a01/image.png" alt=""></p>
<p>이 포스팅을 시작하며 첨부한 이번주 워크북 자료 사진인데,
이번주는 부록도 있어서 부록에 해당하는 내용까지 정리해보겠습니다.</p>
<hr>
<h2 id="부록-①---스프링-해체-분석기---bean">부록 ① - 스프링 해체 분석기 - Bean</h2>
<h3 id="1-1-핵심-흐름">1-1 핵심 흐름</h3>
<pre><code>Application 실행
→ ComponentScan
→ Bean 정의 수집
→ Bean 생성 (싱글톤)
→ 의존성 분석
→ DI 수행
→ ApplicationContext에 저장</code></pre><br>

<h3 id="1-2-주요-개념">1-2 주요 개념</h3>
<p>** ApplicationContext**</p>
<ul>
<li>Bean을 생성 / 관리 / 조회하는 컨테이너</li>
</ul>
<p>** Bean 생성 과정**</p>
<ul>
<li>클래스 스캔</li>
<li>Bean 정의 등록</li>
<li>싱글톤 객체 생성</li>
</ul>
<p>** DI (의존성 주입)**</p>
<ul>
<li>생성 시점에 자동 주입</li>
<li>타입 기반으로 Bean 탐색 후 주입</li>
</ul>
<br>

<h3 id="1-3-내부-동작-핵심">1-3 내부 동작 핵심</h3>
<p>** AbstractAutowireCapableBeanFactory**
: Bean 생성 + DI 수행 핵심 엔진</p>
<p>** BeanPostProcessor**
: Bean 생성 후 개입하는 후처리 단계</p>
<p>** AutowiredAnnotationBeanPostProcessor**
 : @Autowired 찾아서 실제 주입 수행</p>
<br> 

<h3 id="1-4-핵심-정리">1-4 핵심 정리</h3>
<p>★ Spring은 실행 시점에 객체를 모두 생성하고, 의존성까지 연결해 둔다</p>
<br>

<hr>
<h2 id="부록-②---스프링-해체-분석기---전체-구조">부록 ② - 스프링 해체 분석기 - 전체 구조</h2>
<br>

<h3 id="2-1-전체-흐름">2-1 전체 흐름</h3>
<blockquote>
<p><strong>Filter → DispatcherServlet → Interceptor → Controller → 응답</strong></p>
</blockquote>
<br>

<h3 id="2-2-단계별-흐름">2-2 단계별 흐름</h3>
<ol>
<li>** Filter **</li>
</ol>
<ul>
<li>요청 전처리</li>
<li>서블릿 컨테이너에서 실행</li>
</ul>
<ol start="2">
<li><span style="background-color: #f2e2c6
">** DispatcherServlet **</span> </li>
</ol>
<ul>
<li>모든 요청을 받는 Front Controller</li>
<li>전체 흐름 제어</li>
</ul>
<p>3.** HandlerMapping **</p>
<ul>
<li>URL → Controller 매핑</li>
</ul>
<p>4.<span style="background-color: #f2e2c6
">** HandlerAdapter **</span></p>
<ul>
<li>Controller 실행 방식 결정</li>
</ul>
<p>5.<span style="background-color: #f2e2c6
">*<em>Interceptor *</em></span> </p>
<ul>
<li>Controller 전/후 처리</li>
<li>preHandle → 실행 → postHandle → afterCompletion</li>
</ul>
<p>6.<span style="background-color: #f2e2c6
">** Controller **</span> Controller</p>
<ul>
<li>비즈니스 로직 실행</li>
</ul>
<ol start="7">
<li><span style="background-color: #f2e2c6
">** 응답 처리 **</span></li>
</ol>
<p>1)** HTML (SSR)**
   : ViewResolver → View 렌더링
  2) <strong>JSON (REST)</strong>
   : HttpMessageConverter → JSON 변환</p>
<br>

<blockquote>
<h3 id="부록2-핵심-정리★★★">부록2 핵심 정리★★★</h3>
<p><strong>DispatcherServlet이 중심이 되어 요청을 적절한 Controller로 전달하고 응답을 생성한다</strong></p>
</blockquote>
<br>

<hr>
<br>

<h2 id="부록-③---스프링-해체-분석기---필터">부록 ③ - 스프링 해체 분석기 - 필터</h2>
<h3 id="3-1-filter-개념">3-1 Filter 개념</h3>
<p>Filter란? <strong>Servlet 전에 요청을 가로채는 객체</strong></p>
<h3 id="3-2-실행-구조">3-2 실행 구조</h3>
<pre><code class="language-java">ApplicationFilterChain</code></pre>
<p>→ Filter1 → Filter2 → Filter3 ...</p>
<p>*순차적으로 실행</p>
<br>

<h3 id="3-3-동작-방식">3-3 동작 방식</h3>
<pre><code class="language-java">doFilter(request, response, chain)</code></pre>
<p>→ 로직 수행
→ chain.doFilter()</p>
<p> 다음 필터 호출</p>
 <br>

<h3 id="3-4-핵심-구성">3-4 핵심 구성</h3>
<p><strong>FilterChain</strong></p>
<ul>
<li>필터들을 순서대로 실행하는 구조</li>
</ul>
<p><strong>ApplicationFilterChain</strong></p>
<ul>
<li>실제 필터 실행 담당</li>
<li>Tomcat 내부에서 관리</li>
</ul>
<p><strong>주요 메서드</strong></p>
<ul>
<li>init → 초기화</li>
<li>doFilter → 실행</li>
<li>destroy → 종료</li>
</ul>
<br>

<h3 id="3-5-확장-구조">3-5 확장 구조</h3>
<ul>
<li><strong>GenericFilterBean</strong> : 기본 필터 구현 클래스</li>
<li><strong>OncePerRequestFilter</strong> :요청당 1번만 실행 보장</li>
</ul>
<br>

<h3 id="3-6-핵심-흐름">3-6 핵심 흐름</h3>
<p>요청 → FilterChain 실행 → 모든 Filter 통과 → Servlet 전달</p>
<br>

<blockquote>
<h3 id="부록3-핵심-정리-★★★">부록3 핵심 정리 ★★★</h3>
<p><strong>Filter는 요청을 가장 먼저 처리하며, 여러 개가 체인 형태로 순차 실행된다</strong></p>
</blockquote>
<br>

<hr>
<br>

<h2 id="부록-④---스프링-해체-분석기---서블릿--핸들러">부록 ④ - 스프링 해체 분석기 - 서블릿 &amp; 핸들러</h2>
<br>

<h3 id="4-1-시작-지점">4-1 시작 지점</h3>
<pre><code class="language-java">ApplicationFilterChain
→ servlet.service(request, response)</code></pre>
<p>→ 필터를 모두 통과한 요청이 서블릿으로 진입</p>
<br>

<h3 id="4-2-어떤-서블릿이-처리하나">4-2 어떤 서블릿이 처리하나?</h3>
<p><strong>구조</strong>:</p>
<pre><code>HttpServlet → FrameworkServlet → DispatcherServlet</code></pre><ul>
<li>DispatcherServlet이 핵심이지만</li>
<li>실제 호출은 HttpServlet부터 시작됨</li>
</ul>
<br>

<h3 id="4-3-httpservlet-역할">4-3 HttpServlet 역할</h3>
<p><strong>기능</strong></p>
<ul>
<li>HTTP 메서드 판단</li>
<li>GET / POST / PUT / DELETE ...
: 요청 메서드에 따라 분기 처리</li>
</ul>
<p><strong>흐름</strong></p>
<pre><code class="language-java">service()
→ doGet / doPost ...</code></pre>
<p>: HTTP 요청 종류 결정</p>
<br>

<h3 id="4-4-frameworkservlet-역할">4-4 FrameworkServlet 역할</h3>
<p><strong>기능</strong></p>
<ul>
<li>ApplicationContext 기반 기능 제공</li>
<li>요청 처리 전 환경 세팅</li>
</ul>
<p><strong>하는 일</strong></p>
<ul>
<li>Context 세팅</li>
<li>인터셉터 준비</li>
<li>request/response 확장 처리</li>
</ul>
<p><strong>핵심 흐름</strong></p>
<pre><code class="language-java">HttpServlet.service()
→ FrameworkServlet.service()
→ super.service() (다시 HttpServlet)
→ doGet/doPost 실행
→ processRequest()</code></pre>
<p>: 구조상 “핑퐁” 형태로 실행됨</p>
<br>

<h3 id="4-5-dispatcherservlet-진입">4-5 DispatcherServlet 진입</h3>
<p><strong>시작</strong></p>
<pre><code class="language-java">FrameworkServlet
→ DispatcherServlet.doService()</code></pre>
<p><strong>doService 역할</strong></p>
<ul>
<li>요청 전처리</li>
<li>필요한 정보 request에 세팅<br>

</li>
</ul>
<p><strong>핵심 진입</strong>
doService()
→ doDispatch()</p>
<br> 

<h3 id="4-6-dodispatch-핵심-메서드">4-6 doDispatch (핵심 메서드)</h3>
<p>: DispatcherServlet의 핵심 로직</p>
<p><strong>전체 흐름</strong></p>
<ol>
<li>HandlerMapping → Handler 찾기</li>
<li>HandlerAdapter → 실행 방법 찾기</li>
<li>Interceptor preHandle</li>
<li>Handler 실행 (Controller)</li>
<li>응답 처리</li>
<li>Interceptor postHandle / afterCompletion</li>
</ol>
<br>

<h3 id="4-7-handlermapping">4-7 HandlerMapping</h3>
<p><strong>역할</strong> : 요청 → Controller 매핑</p>
<p><strong>내부 동작</strong></p>
<ul>
<li>MappingRegistry에서 정보 조회</li>
<li><code>@RequestMapping</code> 기반 매칭</li>
</ul>
<br>

<h3 id="4-8-handleradapter">4-8 HandlerAdapter</h3>
<p><strong>역할</strong> : 다양한 Controller 실행을 통일</p>
<p><strong>이유</strong></p>
<p>Controller 구현 방식이 다양하기 때문:</p>
<ul>
<li><code>@Controller</code></li>
<li>HttpServlet</li>
<li>인터페이스 기반 등</li>
</ul>
<p><strong>주요 Adapter</strong>
 : RequestMappingHandlerAdapter (가장 많이 사용)</p>
<p><strong>내부 동작</strong>
handle()
→ invokeHandlerMethod()
→ invokeAndHandle()</p>
<br>

<h3 id="4-9-controller-실행-과정">4-9 Controller 실행 과정</h3>
<p><strong>내부 처리</strong></p>
<ul>
<li>파라미터 바인딩</li>
<li>ModelAndView 준비</li>
<li>실제 메서드 호출</li>
</ul>
<p><strong>실행 방식</strong> :리플렉션 + 프록시 (AOP 기반)</p>
<br>

<h3 id="4-10-응답-처리">4-10 응답 처리</h3>
<p><strong>JSON 처리 흐름</strong></p>
<pre><code class="language-java">HandlerMethodReturnValueHandler
→ RequestResponseBodyMethodProcessor
→ HttpMessageConverter</code></pre>
<p><strong>실제 변환</strong>
MappingJackson2HttpMessageConverter
→ JSON 직렬화</p>
<p><strong>특징</strong></p>
<ul>
<li>JSON → ModelAndView 안 거침</li>
<li>바로 응답 반환</li>
</ul>
<br>

<h3 id="4-11-후처리">4-11 후처리</h3>
<p><strong>Interceptor</strong></p>
<pre><code class="language-java">postHandle
→ afterCompletion</code></pre>
<p><strong>마지막 단계</strong></p>
<ul>
<li>이벤트 발행</li>
<li>스레드 정리</li>
</ul>
<br>
<br>

<hr>
<br>

<p>3주차 워크북과 부록들은 단순히 개념을 외우는 것이 아니라,
Bean 생성부터 요청 처리까지의 흐름을 한 번에 연결해서 이해할 수 있었던 주차였습니다.</p>
<p>앞으로는 코드 작성뿐만 아니라,
내부에서 어떤 일이 일어나는지까지 함께 생각하며 학습해보려고 합니다.</p>
<br>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[UMC 10기 - Spring Boot 스터디 #2. API 설계와 API 명세서 작성]]></title>
            <link>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-2.-API-%EC%84%A4%EA%B3%84%EC%99%80-API-%EB%AA%85%EC%84%B8%EC%84%9C-%EC%9E%91%EC%84%B1</link>
            <guid>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-2.-API-%EC%84%A4%EA%B3%84%EC%99%80-API-%EB%AA%85%EC%84%B8%EC%84%9C-%EC%9E%91%EC%84%B1</guid>
            <pubDate>Tue, 24 Mar 2026 12:27:34 GMT</pubDate>
            <description><![CDATA[<br>

<h2 id="2주차-개념-요약">2주차 개념 요약</h2>
<hr>
<p>0주차와 1주차에서 데이터베이스 설계와 쿼리 작성을 진행했다면,
이번 2주차에서는 설계한 데이터를 실제 서비스 API로 연결하는 과정을 다루었다.</p>
<p>이전 주차에서 ERD를 고민하는 과정 자체가
결국 API를 큰 틀에서 설계하는 과정이었다는 점이 인상적이었다.</p>
<p>이번 주차의 핵심은 </p>
<blockquote>
<p><strong>API Endpoint 설계</strong>
<strong>요청(Request) / 응답(Response) 데이터 설계</strong></p>
</blockquote>
<p>그리고 이 모든 내용을 문서화한 것이 바로 <strong>API 명세서</strong>이다.</p>
<br>

<h3 id="api란">API란?</h3>
<p>API는 단순히 “클라이언트와 서버를 연결하는 것”이 아니라,
<strong>복잡한 내부 로직을 감추고, 외부에서 쉽게 사용할 수 있도록 만든 인터페이스</strong> 라는 개념으로 이해하는 것이 더 정확함.</p>
<br>

<h3 id="rest-api">REST API</h3>
<blockquote>
<p>REST는 HTTP 기반의 아키텍처로,
자원(Resource) + HTTP Method 로 API를 설계함.</p>
</blockquote>
<br>

<h3 id="http-method-핵심">HTTP Method 핵심</h3>
<table>
<thead>
<tr>
<th>Method</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>GET</td>
<td>조회 (변화 없음, 멱등성 O)</td>
</tr>
<tr>
<td>POST</td>
<td>생성 / 처리</td>
</tr>
<tr>
<td>PUT</td>
<td>전체 수정</td>
</tr>
<tr>
<td>PATCH</td>
<td>부분 수정</td>
</tr>
<tr>
<td>DELETE</td>
<td>삭제</td>
</tr>
</tbody></table>
<br>

<h3 id="멱등성-idempotency">멱등성 (Idempotency)</h3>
<p>→ 같은 요청을 여러 번 보내도 결과가 같아야 하는 성질</p>
<br>

<h3 id="restful-설계-핵심-규칙">RESTful 설계 핵심 규칙</h3>
<ul>
<li>URI에 동사 사용 ❌</li>
<li>자원은 복수형</li>
<li>계층 구조 표현 가능</li>
<li>필요 시 Path Variable 사용</li>
</ul>
<br>

<h3 id="api-구성-요소">API 구성 요소</h3>
<table>
<thead>
<tr>
<th>구성</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Path Variable</td>
<td>특정 대상 지정</td>
</tr>
<tr>
<td>Query Parameter</td>
<td>조건 조회</td>
</tr>
<tr>
<td>Request Body</td>
<td>데이터 전달</td>
</tr>
<tr>
<td>Request Header</td>
<td>인증 및 메타 정보</td>
</tr>
</tbody></table>
<p><br><br></p>
<h2 id="핵심-키워드">핵심 키워드</h2>
<hr>
<h3 id="1-restful-api란">#1. RESTful API란?</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/598a591a-9589-45e1-a88d-37e20043388f/image.png" alt=""></p>
<h3 id="2-멱등성이란">#2. 멱등성이란?</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/cf54a10a-4e0c-49ee-aca2-c61897c58f4d/image.png" alt=""></p>
<h3 id="3-http-메서드-종류">#3. HTTP 메서드 종류</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/8967473e-08f6-4843-83e4-36e45efcb4c0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/8f4edb38-2448-44b2-b5f8-da473fa5ecbe/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/bfe5fb28-0b59-4a24-84b5-9da25308c758/image.png" alt=""></p>
<p><br><br></p>
<h2 id="미션과제---api-명세서-작성">미션과제 - API 명세서 작성</h2>
<hr>
<p>이번 주차에서는
내가 설계한 ERD를 기반으로 실제 API 명세서를 작성해봤는데</p>
<br>

<h3 id="1-home-마이데이터-조회">1. Home 마이데이터 조회</h3>
<p><strong>Endpoint</strong>
<code>GET /api/home/my</code></p>
<p><strong>Header</strong>
Authorization: Bearer {accessToken}</p>
<p><strong>Response</strong></p>
<pre><code class="language-json">{
  &quot;nickname&quot;: &quot;yujin&quot;,
  &quot;email&quot;: &quot;test@example.com&quot;,
  &quot;phoneNumber&quot;: &quot;01012345678&quot;,
  &quot;phoneNumberStatus&quot;: 1,
  &quot;userPoint&quot;: 12000
}</code></pre>
<p><br><br></p>
<h3 id="2-home-지역-미션-조회">2. Home 지역 미션 조회</h3>
<p><strong>Endpoint</strong>
<code>GET /api/home</code></p>
<p><strong>Query</strong></p>
<pre><code>regionName=안암동&amp;page=0&amp;size=10</code></pre><p><strong>Response</strong></p>
<pre><code class="language-json">{
  &quot;content&quot;: [
    {
      &quot;missionId&quot;: 1,
      &quot;marketName&quot;: &quot;안암 분식&quot;,
      &quot;point&quot;: 500,
      &quot;content&quot;: &quot;떡볶이 먹기&quot;
    }
  ],
  &quot;page&quot;: 0,
  &quot;size&quot;: 10,
  &quot;hasNext&quot;: true
}</code></pre>
<p><br><br></p>
<h3 id="3-리뷰-작성">3. 리뷰 작성</h3>
<p><strong>Endpoint</strong>
<code>POST /api/review</code></p>
<p><strong>Request</strong></p>
<pre><code class="language-json">{
  &quot;marketId&quot;: 3,
  &quot;regionId&quot;: 1,
  &quot;stars&quot;: &quot;FIVE&quot;,
  &quot;content&quot;: &quot;맛있어요!&quot;
}</code></pre>
<p><strong>Response</strong></p>
<pre><code class="language-json">{
  &quot;reviewId&quot;: 10,
  &quot;message&quot;: &quot;리뷰가 작성되었습니다.&quot;
}</code></pre>
<p><br><br></p>
<h3 id="4-미션-목록-조회">4. 미션 목록 조회</h3>
<p><strong>Endpoint</strong>
<code>GET /api/mission</code></p>
<p><strong>Query</strong></p>
<pre><code>regionName=안암동&amp;page=0&amp;size=10
</code></pre><p><strong>Response</strong></p>
<pre><code class="language-json">{
  &quot;content&quot;: [
    {
      &quot;missionId&quot;: 3,
      &quot;marketName&quot;: &quot;치킨집&quot;,
      &quot;point&quot;: 1000,
      &quot;status&quot;: &quot;in_progress&quot;
    }
  ],
  &quot;page&quot;: 0,
  &quot;size&quot;: 10,
  &quot;hasNext&quot;: false
}
</code></pre>
<p><br><br></p>
<h3 id="5-미션-완료">5. 미션 완료</h3>
<p><strong>Endpoint</strong></p>
<p><code>POST /api/mission/completed</code></p>
<p><strong>Request</strong></p>
<pre><code class="language-json">{
  &quot;missionId&quot;: 12
}</code></pre>
<p><strong>Response</strong></p>
<pre><code class="language-json">{
  &quot;message&quot;: &quot;미션이 완료되었습니다.&quot;
}</code></pre>
<p><br><br></p>
<h3 id="6-회원가입">6. 회원가입</h3>
<p><strong>Endpoint</strong>
<code>POST /api/signup</code></p>
<p><strong>Request</strong></p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;홍길동&quot;,
  &quot;gender&quot;: &quot;FEMALE&quot;,
  &quot;birth&quot;: &quot;2002-05-10&quot;,
  &quot;addressLine1&quot;: &quot;서울&quot;,
  &quot;addressLine2&quot;: &quot;안암동&quot;,
  &quot;email&quot;: &quot;test@test.com&quot;,
  &quot;phoneNumber&quot;: &quot;01012345678&quot;,
  &quot;type&quot;: &quot;kakao&quot;,
  &quot;agreeId&quot;: 1
}</code></pre>
<p><strong>Response</strong></p>
<pre><code class="language-json">{
  &quot;userId&quot;: 1,
  &quot;message&quot;: &quot;회원가입이 완료되었습니다.&quot;
}</code></pre>
<p><br><br></p>
<h2 id="📌-마무리">📌 마무리</h2>
<hr>
<p>이번 2주차에서는
단순히 DB를 설계하는 것을 넘어,</p>
<blockquote>
<p><strong>★ 데이터를 어떻게 API로 노출할 것인지</strong>
<strong>★ 프론트와 어떻게 소통할 것인지</strong></p>
</blockquote>
<p>까지 고민해보는 과정이었습니다.</p>
<p>또한 API 명세서를 작성하면서 단순히 기능 구현이 아니라
<strong>“다른 개발자가 이해할 수 있는 구조”로 만드는 것이 중요하다는 점을 깨달았다.</strong></p>
<br>


<br>

<p>다음 3주차에서는</p>
<ul>
<li>Spring Boot에서 실제 API를 구현하고</li>
<li>컨트롤러와 서비스 계층을 통해</li>
<li>지금 설계한 API를 코드로 연결하는 과정을 학습하게 된다.</li>
</ul>
<p><br><br></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UMC 10기 - Spring Boot 스터디 #1.]]></title>
            <link>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-1</link>
            <guid>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-1</guid>
            <pubDate>Thu, 19 Mar 2026 06:38:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yujindeang_/post/0d59415c-3b1e-4136-b1be-ea1c35fa7e1e/image.png" alt=""></p>
<p>0주차에서 데이터베이스 구조를 설계했다면,
이번 1주차에서는 해당 ERD를 기반으로 실제 데이터를 조회하는 쿼리를 작성해보는 과정이다.</p>
<p>단순히 SQL 문법을 사용하는 것이 아니라,
요구사항을 어떤 쿼리로 풀어낼 수 있을지 고민하는 데에 초점을 두었다.</p>
<br> 
<br>

<h2 id="1주차-개념-요약">1주차 개념 요약</h2>
<hr>
<p>1주차에서는 0주차에 설계한 ERD를 바탕으로, 
요구사항을 실제 SQL 쿼리로 어떻게 풀어낼지를 학습했다.
즉, 이번 주차의 핵심은 단순히 테이블 구조를 이해하는 것을 넘어, <strong>필요한 데이터를 효율적으로 조회하는 방법</strong>을 익히는 데 있다.</p>
<p>먼저 <strong>요구사항을 보고 어떤 쿼리가 필요한지 판단하는 연습</strong>을 했다.
단순 조건 조회는 <code>WHERE 절</code>만으로 해결할 수 있지만, 여러 테이블의 정보가 함께 필요할 때는 <code>JOIN</code> 이 필요하다. 예를 들어 특정 지역의 미션을 조회하려면 미션, 가게, 지역 테이블을 연결해서 원하는 조건으로 필터링해야 한다. 이 과정을 통해 하나의 요구사항을 보고 어떤 테이블들이 연결되어야 하는지 생각하는 습관이 중요하다는 점을 배웠다.</p>
<p>또한 <code>JOIN</code>과 <code>서브쿼리(Subquery)</code> 의 차이도 다뤘다.
JOIN은 두 개 이상의 테이블을 결합해서 한 번에 결과를 조회하는 방식이고, 서브쿼리는 쿼리 안에 또 다른 쿼리를 넣어 조건이나 결과를 만드는 방식이다. 일반적으로는 JOIN이 더 효율적인 경우가 많지만, 상황에 따라 서브쿼리가 더 직관적이고 가독성이 좋은 경우도 있다. 따라서 중요한 것은 무조건 한 가지 방식만 고집하는 것이 아니라, 가독성과 성능을 함께 고려하여 선택하는 것이다.</p>
<p>이번 주차에서 특히 중요한 개념은 <code>페이징(Paging)</code> 이다.
목록 조회 시 모든 데이터를 한 번에 가져오면 성능 문제가 발생하기 때문에, 데이터베이스에서 필요한 만큼만 잘라서 조회해야 한다. 이를 위해 <code>LIMIT</code> 과 <code>OFFSET</code> 을 사용하는 <strong>Offset 기반 페이징을</strong> 먼저 학습했다. 이 방식은 구현이 단순하고 페이지 번호로 이동하기 쉽다는 장점이 있지만, 뒤로 갈수록 성능이 떨어지고 중간에 데이터가 추가되면 중복 조회나 누락이 발생할 수 있다는 단점이 있다.</p>
<p>이러한 문제를 보완하기 위해 <strong>Cursor 기반 페이징</strong>도 학습했다.
<code>Cursor Paging</code>은 마지막으로 조회한 데이터를 기준으로 다음 데이터를 가져오는 방식이다. 보통 PK처럼 고유한 값을 기준으로 삼으면 안정적으로 다음 페이지를 조회할 수 있다. 다만 별점처럼 중복 가능한 값만 커서로 사용하면 같은 결과가 반복될 수 있기 때문에, 정렬 기준 컬럼과 PK를 함께 사용해 고유한 커서를 만들어야 한다는 점이 중요하다.</p>
<p>정리하면 1주차는 요구사항을 SQL로 해석하는 방법, JOIN과 서브쿼리의 차이, 그리고 Offset / Cursor 기반 페이징의 원리와 차이점을 이해하는 주차였다. 결국 이번 주차를 통해, 좋은 쿼리란 단순히 동작하는 쿼리가 아니라 요구사항을 정확히 반영하면서도 성능과 확장성을 함께 고려한 쿼리라는 점을 배울 수 있었다.</p>
<br>
<br>


<h2 id="핵심-키워드">핵심 키워드</h2>
<hr>
<h3 id="1-db-join이란">#1. DB Join이란?</h3>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/e0ed83f2-1050-4f89-a31a-b7da4dfa2cb6/image.png" alt=""></p>
<br>

<h3 id="2-join-종류들">#2. Join 종류들</h3>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/e13827f8-ee44-49f7-91bc-11ec71c3577a/image.png" alt=""></p>
<h4 id="1-inner-join">1. INNER JOIN</h4>
<pre><code class="language-sql">SELECT *
FROM table1
INNERJOIN table2
ON table1.id= table2.id;</code></pre>
<ul>
<li>두 테이블에서 <strong>조건이 일치하는 행만 반환</strong></li>
<li>MySQL에서는 다음이 동일하게 취급된다</li>
</ul>
<pre><code class="language-sql">JOIN
INNER JOIN
CROSS JOIN</code></pre>
<h4 id="2-left-join-left-outer-join">2. LEFT JOIN (LEFT OUTER JOIN)</h4>
<pre><code class="language-sql">SELECT *
FROM table1
LEFTJOIN table2
ON table1.id= table2.id;</code></pre>
<ul>
<li><strong>왼쪽 테이블 기준</strong></li>
<li>오른쪽 테이블에 값이 없으면 <strong>NULL 반환</strong></li>
</ul>
<p>문서 예시도 존재</p>
<pre><code>If there is no matching row for the right table in a LEFT JOIN,
a row with all columns set to NULL is used for the right table.</code></pre><h4 id="3-right-join">3. RIGHT JOIN</h4>
<pre><code class="language-sql">SELECT *
FROM table1
RIGHTJOIN table2
ON table1.id= table2.id;</code></pre>
<ul>
<li><strong>오른쪽 테이블 기준</strong></li>
<li>MySQL 문서에서는 <strong>LEFT JOIN 사용을 권장</strong></li>
</ul>
<h4 id="4-cross-join">4. CROSS JOIN</h4>
<pre><code class="language-sql">SELECT *
FROM table1
CROSSJOIN table2;</code></pre>
<ul>
<li><strong>Cartesian Product</strong></li>
<li>모든 행 조합 생성</li>
</ul>
<p>문서에서도 설명</p>
<pre><code>INNER JOIN and , produce a Cartesian product if no condition exists</code></pre><h4 id="5-natural-join">5. NATURAL JOIN</h4>
<pre><code class="language-sql">SELECT *
FROM t1NATURALJOIN t2;</code></pre>
<ul>
<li>같은 이름의 컬럼을 자동으로 join</li>
</ul>
<p>문서 설명</p>
<pre><code>NATURAL JOIN uses all columns that exist in both tables</code></pre><br>

<h3 id="3-트랜잭션이란">#3. 트랜잭션이란?</h3>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/02187591-2879-4e38-9775-dc3f327a4f70/image.png" alt="">
위 처럼 공식 문서엔 나와있었고, </p>
<p>한마디로 <strong>트랜잭션이란? 데이터를 조작하기 위한 하나의 논리적인 작업 단위</strong>를 말한다. </p>
<p><strong>주요 명령어</strong></p>
<ul>
<li><strong>START TRANSACTION</strong> : 트랜잭션 시작</li>
<li><strong>COMMIT</strong> : 변경 내용 저장</li>
<li><strong>ROLLBACK</strong> : 작업 취소</li>
</ul>
<p>출처 : <a href="https://product.kyobobook.co.kr/detail/S000211514863"><strong>2024 유선배 SQL개발자 과외노트</strong></a></p>
<br>

<h3 id="4-join-on-과-where의-차이점">#4. Join on 과 where의 차이점</h3>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/2bc8c641-6e9a-4ad6-9b3a-882ed27ba38f/image.png" alt=""></p>
<br>
<br>

<h2 id="미션-과제">미션 과제</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/e17eaaa8-9ef8-4faa-bd95-898b13e921b7/image.png" alt=""></p>
<h3 id="1-리뷰-작성하는-쿼리">1) 리뷰 작성하는 쿼리</h3>
<p>0주차 과제에서 리뷰작성 관련한 table은 만들지 않았어서
<img src="https://velog.velcdn.com/images/yujindeang_/post/7996ca1f-8744-491c-9886-65c99917f24f/image.png" alt="">
해당 리뷰 작성 화면에서 필요한 data를 기준으로 review table 작성했습니다.</p>
<pre><code class="language-sql">INSERT INTO review (
    user_id,
    market_id,
    stars,
    content,
    created_at,
    updated_at,
    deleted_at
)
VALUES (
    &#39;닉네임1234&#39;,
    ?,
    &#39;FIVE&#39;,
    &#39;음 너무 맛있었어요 포인트도 얻고 맛있는 맛집도 알게 된것 같아 너무나도 행복한 식사였답니다. 다음에 또 올게요!!&#39;,
    NOW(),
    NOW(),
    NULL
);</code></pre>
<br>

<h3 id="2-내가-진행중-진행완료한-미션-모아보는-쿼리-페이징포함">2) 내가 진행중, 진행완료한 미션 모아보는 쿼리 (페이징포함)</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/77a84355-c177-4bd1-aa05-78a51417da32/image.png" alt="">
쿼리를 작성하려고 보니까, 테이블 정의상 status 는 mission 테이블에 있다.</p>
<pre><code>mission.status
-- in_progress / expired / challenging / participated</code></pre><p>그런데 의미를 보면</p>
<ul>
<li>in_progress : 미션 자체가 게시중</li>
<li>expired : 미션 자체가 만료됨</li>
<li>challenging : 내가 진행중</li>
<li>participated : 내가 완료함</li>
</ul>
<p>이 중 뒤의 두 개는 유저별 상태라서 
같은 미션이라도 어떤 유저는 아직 참여 안 했고, 어떤 유저는 진행 중이고, 어떤 유저는 완료했을 수 있기 때문에 participate 테이블에 있는 게 더 자연스러울거 같다는 생각이 들어서 수정했다.</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/3a73017b-d704-414f-a89c-18d355b2bc92/image.png" alt=""></p>
<pre><code class="language-sql">SELECT
    m.id AS mission_id,
    m.content,
    m.point,
    m.end_date,
    mk.id AS market_id,
    mk.name AS market_name,
    CASE
        WHEN p.status = &#39;challenging&#39; THEN &#39;진행중&#39;
        WHEN p.status = &#39;participated&#39; THEN &#39;진행완료&#39;
    END AS mission_status,
    p.created_at AS participate_created_at
FROM participate p
JOIN mission m ON p.mission_id = m.id
JOIN market mk ON m.market_id = mk.id
WHERE p.user_id = ?
  AND p.status IN (&#39;challenging&#39;, &#39;participated&#39;)
  AND p.deleted_at IS NULL
  AND m.deleted_at IS NULL
  AND mk.deleted_at IS NULL
ORDER BY p.created_at DESC
LIMIT 10 OFFSET 0;</code></pre>
<br>

<h3 id="3-마이-페이지-화면-쿼리">3) 마이 페이지 화면 쿼리</h3>
<pre><code class="language-sql">SELECT
    name AS nickname,
    email,
    phone_number,
    phone_number_status,
    user_point
FROM user
WHERE id = 1
  AND deleted_at IS NULL;</code></pre>
<br>

<h3 id="4-홈-화면-쿼리-현재-선택된-지역에서-도전이-가능한-미션-목록-페이징-포함">4) 홈 화면 쿼리 (현재 선택된 지역에서 도전이 가능한 미션 목록, 페이징 포함)</h3>
<pre><code class="language-sql">SELECT
    mk.id AS market_id,
    mk.name AS market_name,
    m.point,
    m.content,
    m.end_date
FROM mission m
JOIN market mk
    ON m.market_id = mk.id
JOIN region r
    ON mk.region_id = r.id
WHERE r.name = ?
  AND m.status = &#39;in_progress&#39;
  AND m.deleted_at IS NULL
  AND mk.deleted_at IS NULL
ORDER BY m.created_at DESC
LIMIT 10 OFFSET 0;</code></pre>
<br>

<h1 id="📌-마무리">📌 마무리</h1>
<hr>
<p>이번 1주차에서는 ERD를 기반으로 요구사항을 SQL 쿼리로 풀어내는 과정을 경험했다.</p>
<p>특히 JOIN을 활용한 다중 테이블 조회,
서브쿼리와의 차이,
그리고 페이징 처리 방식까지 다루면서
단순 조회를 넘어서 실제 서비스에서 필요한 형태의 쿼리를 구성하는 방법을 이해할 수 있었다.</p>
<p>다음 주차에서는 한 단계 더 나아가,
리소스 중심의 RESTful 설계가 무엇인지 이해하고,
API 명세서를 작성하는 과정을 통해
DB 설계와 쿼리를 실제 API로 연결하는 흐름을 학습할 예정이다.</p>
<br>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[UMC 10기 - Spring Boot 스터디 #0.]]></title>
            <link>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-0</link>
            <guid>https://velog.io/@yujindeang_/UMC-10%EA%B8%B0-Spring-Boot-%EC%8A%A4%ED%84%B0%EB%94%94-0</guid>
            <pubDate>Thu, 19 Mar 2026 06:09:05 GMT</pubDate>
            <description><![CDATA[<p>이 시리즈에서 정리하는 Spring Boot 스터디 내용은<br>UMC 10기 활동을 기반으로 진행된 학습과 과제를 바탕으로 작성되었습니다</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/cd374aeb-cbfe-4d6a-a568-f29af20bf466/image.png" alt=""></p>
<p>이전에 SWU UMC 7기에서는 Plan 파트로 참여했으며,<br>졸업 전 Spring Boot를 한 번 더 정리해보고자 UMC Spring Boot로 다시 참여하게 되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/a04917f2-e8b6-4c1e-b5a8-599372f2b064/image.png" alt=""></p>
<br>
<br>



<h1 id="개념-정리">개념 정리</h1>
<hr>
<h3 id="0주차-개념-요약">0주차 개념 요약</h3>
<p>0주차에서는 <strong>데이터베이스를 어떻게 설계해야 하는지를 중심으로</strong> 학습했다.
백엔드에서 DB 설계는 단순히 테이블을 만드는 작업이 아니라, 요구사항을 데이터 구조로 바꾸는 과정이라는 점이 핵심이다.</p>
<p>먼저 <code>DB</code>와 <code>DBMS</code>, 그리고 <code>RDB</code>와 <code>NoSQL</code>의 차이를 짚었다.
RDB는 테이블 간 관계를 기반으로 데이터를 관리하며, 중복을 줄이고 데이터 일관성과 정확성을 유지하는 데 강점이 있다. 반면 NoSQL은 대량 데이터 처리와 빠른 성능이 중요한 상황에서 주로 사용된다. 상황에 따라 SQL과 NoSQL을 함께 사용할 수도 있는데, 예를 들어 읽기 성능이 중요한 경우 Redis 같은 캐시를 추가로 도입할 수 있다.</p>
<p>이후 본격적으로 <code>ERD 설계</code>를 다루었다.
ERD는 테이블, 속성, 관계, 제약조건을 시각적으로 정리한 것으로, 프로젝트 초기에 와이어프레임이나 요구사항이 나오면 이를 바탕으로 설계를 시작하게 된다. 결국 DB를 설계한다는 것은 ERD를 작성한다는 것과 거의 같은 의미로 볼 수 있다.</p>
<p><strong>ERD를 작성할 때는 먼저 요구사항을 보고 어떤 데이터를 저장해야 할지 판단</strong>해야 한다.
이 과정에서 테이블명과 속성명 스타일을 통일하고, PK 이름 규칙도 일관되게 가져가는 것이 중요하다. 또 속성별로 NULL 허용 여부, 길이 제한, enum 사용 여부 같은 제약조건을 설정해야 한다. 이때 제약조건은 임의로 정하기보다 PM과 충분히 협의하며 결정하는 것이 중요하다.</p>
<p>추가로 <code>Soft Delete 개념</code>도 중요하게 다뤄졌다.
*<em>회원 탈퇴처럼 데이터를 바로 지우기보다 deleted_at 같은 값을 두어 삭제 여부만 표시하면, 복구 기능이나 탈퇴 후에도 남아야 하는 데이터 처리가 쉬워진다. *</em>실제 서비스에서는 이런 방식이 자주 사용된다.</p>
<p><code>관계 설정</code>에서는 특히 <code>다대다(N:N) 관계</code>를 어떻게 설계할지가 핵심이었다.
예를 들어 사용자-책 대여, 책-해시태그, 사용자-책 좋아요 관계는 모두 다대다 관계이므로, 이를 그대로 한 테이블에 담지 않고 중간 테이블(매핑 테이블) 을 두어 설계해야 한다. 이렇게 해야 데이터 중복을 줄이고 정규화된 구조를 유지할 수 있다.</p>
<p>마지막으로, ERD는 처음부터 완벽해야 하는 것이 아니라 구현 가능한 수준으로 빠르게 설계하고, 개발하면서 계속 수정해 나가는 것이 중요하다는 점도 강조되었다. <strong>결국 좋은 설계란 이론적으로만 완벽한 설계가 아니라, 현재 요구사항을 잘 반영하면서도 이후 변경에 대응할 수 있는 설계</strong>라고 정리할 수 있다.</p>
<br>
<br>


<h3 id="erd를-작성-예시-실습">ERD를 작성 (예시 실습)</h3>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/ddab4b8e-513f-41f4-9672-0d0c7f0d455c/image.png" alt=""></p>
<h3 id="각-테이블-작성">각 테이블 작성</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/dca1c3eb-efa7-46c8-9c49-31bcf7b0a631/image.png" alt=""></p>
<h3 id="설계-과정에서-고민한-부분">설계 과정에서 고민한 부분</h3>
<ul>
<li>소셜 로그인 시 카카오 uid를 어떻게 저장할 것인가</li>
<li>책 좋아요 기능을 별도 테이블로 분리할 필요성</li>
<li>알림은 단순 내용보다 type 기반 설계가 필요함</li>
<li>삭제 데이터를 관리하기 위한 Soft Delete 적용</li>
</ul>
<p>👉 특히 Soft Delete는 실제 서비스에서 매우 중요하며,
삭제 여부를 <code>deleted_at</code>으로 관리하는 방식이 일반적으로 사용된다.</p>
<br>


<h3 id="모법-답안">모법 답안</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/8eef4ed0-138f-473d-bd1c-a48126524492/image.png" alt=""></p>
<br>
<br>

<h1 id="핵심-키워드-정리">핵심 키워드 정리</h1>
<hr>
<blockquote>
<p>미션 과제 말고 직접 조사하는 과제가 있는데 </p>
</blockquote>
<pre><code>주요 내용들에 대해 조사해보고, 자신만의 생각을 통해 정리해보세요!
레퍼런스를 참고하여 정의, 속성, 장단점 등을 적어주셔도 됩니다.
조사는 공식 홈페이지 Best, 블로그(최신 날짜) Not Bad</code></pre><blockquote>
<p>라는 멘트를 보고, 공식 문서를 보고 최대한 찾고 싶었다</p>
</blockquote>
<h3 id="1-pk-fk-란">#1. PK, FK 란</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/ee02f72d-40de-4617-af49-49a161ce5c8c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/6db22eb4-db1a-4acb-ad35-617ab94ce519/image.png" alt=""></p>
<h3 id="2-erd란">#2. ERD란?</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/86a0c24e-e2cc-4378-a258-46f268cad519/image.png" alt=""></p>
<h3 id="3-연관관계란-그리고-연간관계를-설정하는-방법은">#3. 연관관계란? 그리고 연간관계를 설정하는 방법은?</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/979622d6-a1c9-4da7-a9ab-d75555117105/image.png" alt=""></p>
<blockquote>
<p>연관관계(Association)란 두 개 이상의 엔티티가 데이터베이스의 조인(join) 개념을 바탕으로 서로 연결되는 관계를 의미한다. 관계형 데이터베이스에서는 보통 외래 키(Foreign Key)를 통해 테이블 간 관계를 맺고, JPA에서는 이를 엔티티 간 관계로 매핑한다. 연관관계의 종류에는 <code>@ManyToOne</code>, <code>@OneToMany</code>, <code>@OneToOne</code>, <code>@ManyToMany</code>가 있다.                 </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/5b74d5e7-98e7-498a-9218-dc50dc0f224e/image.png" alt="">      </p>
<blockquote>
<p>연관관계는 JPA에서 어노테이션을 사용해 설정할 수 있다. 예를 들어 <code>@ManyToOne</code>은 여러 자식 엔티티가 하나의 부모 엔티티를 참조하는 관계를 나타내며, <code>@JoinColumn</code>을 사용해 외래 키 컬럼을 지정할 수 있다. 또한 연관관계는 한쪽만 참조하는 <strong>단방향 관계</strong>와 양쪽이 서로 참조하는 <strong>양방향 관계</strong>로 나뉜다. 양방향 관계에서는 외래 키를 실제로 관리하는 <strong>연관관계의 주인(owning side)</strong> 이 하나만 존재하며, 보통 자식 엔티티 쪽이 주인이 된다.</p>
</blockquote>
<h3 id="4-정규화란">#4. 정규화란?</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/c28902c0-91c5-44c2-8f6f-e3066eef107c/image.png" alt="">
위 처럼 공식 문서엔 나와있었고, </p>
<blockquote>
<p>한마디로 <strong>정규화란? 데이터 정합성(데이터의 정확성과 일관성을 유지하고 보장)을 위해 
 엔터티를 작은 단위로 분리하는 과정</strong>을 말한다. </p>
</blockquote>
<p>정규화를 할 경우 데이터 조회 성능은 처리조건에  따라 향상되는 경우도 있고 저하되는 경우도 있지만 입력, 수정, 삭제 성능은 일반적으로 향상된다고 볼 수 있다.</p>
<p>하지만 그렇다고 모든 엔터티를 무작정 분리하면 안 되기 때문에 
정규화를 하기 위한 일정한 룰이 존재함 !</p>
<p> <strong>정규화 종류 (대표단계)</strong>
    - <strong>제1정규형</strong> - 모든 속성은 반드시 하나의 값만 가져야 한다
    - <strong>제2정규형</strong> - 엔티티의 모든 일반 속성은 반드시 모든 주식별자에 종속되어야 한다.
    - <strong>제3정규형</strong> - 주식별자가 아닌 모든 속성 간에는 서로 종속될 수 없다.</p>
<p>출처 : <a href="https://product.kyobobook.co.kr/detail/S000211514863"><strong>2024 유선배 SQL개발자 과외노트</strong></a></p>
<h3 id="5-반-정규화란">#5. 반 정규화란?</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/18107995-5872-4693-b406-e1a9cc32b07c/image.png" alt="">
위 처럼 공식 문서엔 나와있었고, </p>
<blockquote>
<p>한마디로 <strong>반정규화란? 데이터의 조회 성능을 위해 
데이터의 중복을 허용하거나 데이터를 그룹핑하는 과정</strong>을 말한다. </p>
</blockquote>
<p>여기서 주의해야 할 점은 조회 성능은 향상될 수 있으나 입력, 수정, 삭제 성능은 저하될 수 있으며 데이터 정합성 이슈가 발생할 수 있다는 점이다. </p>
<p>반정규화의 과정은 정규화가 끝난 후 거치게 되며 정규화와 마찬가지로 일정한 룰이 존재한다</p>
<p><strong>반정규화 종류</strong>
    - 테이블 반정규화 - 테이블 변합, 분할, 추가
    - 컬럼 반정규화 - 중복 칼럼 추가, 파생 칼럼 추가, 이력 테이블 컬럼 추가
    - 관계 반정규화(중복 관계 추가) - 업무 프로세스상 JOIN이 필요한 경우가 맣아 중복 관계를 추가하는 것이 성능 측면에서 유리할 여우 고려</p>
<p>출처 : <a href="https://product.kyobobook.co.kr/detail/S000211514863"><strong>2024 유선배 SQL개발자 과외노트</strong></a></p>
<h3 id="6-db에서의-상속관계-표현은-어떻게-하는가">#6. DB에서의 상속관계 표현은 어떻게 하는가?</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/c143937d-7245-485f-a843-97c362c571db/image.png" alt=""></p>
<h3 id="7-인덱스란">#7. 인덱스란?</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/bc63eb20-a041-42bd-aaeb-601239133aee/image.png" alt="">
위 처럼 공식 문서엔 나와있었고, </p>
<p><strong>주요 특징</strong>으로는</p>
<ul>
<li>인덱스를 통하여 레코드를 빠르게 접근할 수 있다.</li>
<li>데이터 베이스의 물리적 구조와 밀접한 관계가 있다.</li>
<li>레코드의 삽입/삭제가 자주 발생 시 인덱스의 개수를 최소화하는 것이 효율적이다.</li>
<li>하지만 인덱스가 많아질수록 INSERT, UPDATE, DELETE 성능은 저하될 수 있다.</li>
</ul>
<ul>
<li>B 트리<ul>
<li>m차 B 트리는 근노드와 단말 노드를 제외한 모든 노드가 최소 m/2개, 최대 m개의 서브 트리를 가지는 구조이다.</li>
<li>한 노드에 있는 키 값은 오름차순을 유지한다.</li>
<li>근노드로부터 탐색 , 추가, 삭제가 이뤄진다.</li>
</ul>
</li>
</ul>
<p><strong>출처</strong> : <a href="https://product.kyobobook.co.kr/detail/S000217191918">절대족보 이기적 정보처리기사 필기 2026</a></p>
<hr>
<br>
<br>

<h1 id="week0-주차-워크북-미션">week0 주차 워크북 미션</h1>
<hr>
<blockquote>
<p><strong>주어진 IA(기획 플로우)와 와이어 프레임(디자인 프로토타입)을 보고 직접 데이터베이스를 설계하기</strong></p>
</blockquote>
<p>데이터베이스 설계 조건은 ?! 
<img src="https://velog.velcdn.com/images/yujindeang_/post/e591f31d-9a33-4eed-a378-577d4becc99f/image.png" alt=""></p>
<br>

<h2 id="1-로그인회원가입-파트">#1. 로그인/회원가입 파트</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/dde576ae-b1ff-4d59-a8f1-417c03ffeeab/image.png" alt=""></p>
<p>보이는 조건들을 정리해보면 </p>
<ol>
<li>로그인은 4가지 방법으로 가능함</li>
<li>이용 약관은 5개 - 필수도 있고 선택도 있음</li>
<li>개인정보는 이름, 성별, 생년월일, 주소1, 주소2를 받고</li>
<li>음식 종류를 선택가능 - 16개가 주어지고 선택은 다중선택 가능 </li>
</ol>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/e3aa8a13-0b0c-4df0-9e03-62ff4b264b5f/image.png" alt=""></p>
<pre><code class="language-sql">CREATE TABLE `user` (
    `id`    Long    NOT NULL,
    `name`    String(15)    NOT NULL,
    `gender`    enum    NOT NULL    COMMENT &#39;남 : MALE, 여 : FEMALE, 선택안함 : NONE&#39;,
    `birth`    LocalDate    NOT NULL,
    `address_line_1`    String(125)    NOT NULL,
    `address_line_2`    String(125)    NOT NULL,
    `created_at`    LocalDateTime    NOT NULL,
    `updated_at`    LocalDateTime    NOT NULL,
    `deleted_at`    LocalDateTime    NOT NULL,
    `agree_id`    Long    NOT NULL,
    `type`    enum    NOT NULL    COMMENT &#39;카카오 : kakao, 네이버 : naver,  애플 : apple, 구글 : google&#39;
);

CREATE TABLE `agree_conditions` (
    `id`    Long    NOT NULL,
    `agree_1`    int    NOT NULL,
    `agree_2`    int    NOT NULL,
    `agree_3`    int    NOT NULL,
    `agree_4`    int    NOT NULL,
    `agree_5`    int    NOT NULL,
    `created_at`    LocalDateTime    NOT NULL,
    `updated_at`    LocalDateTime    NOT NULL,
    `deleted_at`    LocalDateTime    NOT NULL
);

CREATE TABLE `food_cartegory` (
    `id`    Long    NOT NULL,
    `name`    String(15)    NULL
);

CREATE TABLE `preference` (
    `id`    Long    NOT NULL,
    `cartegory_id`    Long    NOT NULL,
    `user_id`    Long    NOT NULL
);
</code></pre>
<br>

<h2 id="2-홈-파트">#2. 홈 파트</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/3bd4643f-8a6a-43b9-b403-6f73c12991aa/image.png" alt=""></p>
<p>우선, 미션 관련 테이블을 만들기 전에 
상단에 보이는 포인트 내역, 알림여부에 대한 data 구조를 기존 table에서 추가했습니다.</p>
<pre><code class="language-sql">CREATE TABLE `user` (
    `id`    Long    NOT NULL,
     ... 생략

    `user_point`    String(20)    NOT NULL,
    `alram_status`    int    NOT NULL    COMMENT &#39;새로운 알림 있으면 : 1, 없으면 : 0&#39;
);</code></pre>
<pre><code class="language-sql">CREATE TABLE `alram` (
    `id`    Long    NOT NULL,
    `Field`    LocalDateTime    NOT NULL,
    `updated_at`    LocalDateTime    NOT NULL,
    `deleted_at`    LocalDateTime    NULL,
    `user_id`    Long    NOT NULL
);</code></pre>
<p>그런 다음 미션카드 데이터를 저장하기 위해 가게정보, 미션정보 그리고 기타 지역 정보 table, 참여 미션 테이블 등을 추가 했습니다.</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/b569121c-bc13-4f66-bc7e-677979cc7fc9/image.png" alt=""></p>
<br>


<h2 id="3-미션-파트">#3. 미션 파트</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/e89525be-9f26-42d1-a14d-89d706fce230/image.png" alt=""></p>
<p>다른 추가적인 데이터 입출력보다는, 3번째 page에 사장님 구분 번호 9자리에 대한걸 어떻게 처리할지 고민 해봤었다.</p>
<p><strong>방법1</strong>. 미션이 생성될 때 자동 증가되며 생성되는 미션 고유 id인 값을 보여주기
<strong>방법2</strong>. 미션 고유 id를 그럼 자동증가가 아닌, 9자리 랜덤생성으로 할지
<strong>방법3</strong>. 미션 성공요청시에 생성되는 인증번호를 따로 만들지 </p>
<p>일단 이에 대한 과제 제한 사항은 없었기 때문에, 
현재는 구현 단순성을 위해 1번 방식을 선택했지만,향후에는 인증번호 기반 구조(3번)도 고려할 수 있다.</p>
<br>


<h2 id="4-기타">#4. 기타</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/46009b19-41e9-4ea8-9790-78cd1162c9e1/image.png" alt=""></p>
<p>남은 페이지들을 확인해보니</p>
<p>기존 회원 table, 알림 table, 약관 동의 table, 참여 미션 table 에 조금씩 수정한다면, 다 보여줄 수 있는 데이터들 이였다</p>
<p><strong>01. 회원 table</strong></p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/5b44fd0d-fefa-4466-a8a7-6d85829e674c/image.png" alt=""></p>
<pre><code class="language-sql">CREATE TABLE `user` (

    ...

    // 추가 항목

    `email`    String(20)    NULL,
    `phone_number`    String(20)    NULL,
    `phone_number_status`    int    NULL    DEFAULT 0    COMMENT &#39;인증 전 : 0, 인증 후 : 1&#39;,

    ...

);</code></pre>
<p><strong>02. 알림 table</strong>
이전 화면에서는 알림이 어떻게 표시되는지 알 수 없어서 추가하지 않았었던, 알림 table을 완성시켜봐야할거같아요</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/1d17509c-6dcd-4620-bad9-c35149f78641/image.png" alt=""></p>
<pre><code class="language-sql">CREATE TABLE `alram` (
    `id`    Long    NOT NULL,
    `title`    String(50)    NOT NULL,
    `detail`    String(255)    NOT NULL,
    `created_at`    LocalDateTime    NOT NULL,
    `updated_at`    LocalDateTime    NOT NULL,
    `deleted_at`    LocalDateTime    NULL,
    `user_id`    Long    NOT NULL
);
</code></pre>
<br>

<p><strong>03. 약관 동의 table</strong>
새로운 이벤트 수신, 리뷰 답변 알림, 문의 내역 답변 알림 3개에 대한 알림 수신 설정을 할 수 있음
<img src="https://velog.velcdn.com/images/yujindeang_/post/bcc2e8f9-37e0-4ba0-906c-1d04b86fa575/image.png" alt=""></p>
<br>


<p><strong>04. 참여 미션 table</strong>
참여 미션 또한 생성시간이 있어야하고, ui에서도 참여 완료 시간이 찍혀야하기 때문에 꼭 필요한 것을 알 수 있음</p>
<pre><code class="language-sql">CREATE TABLE `participate` (
    ...

    // 추가 항목
    `created_at`    LocalDateTime    NOT NULL,
    `updated_at`    LocalDateTime    NOT NULL,
    `deleted_at`    LocalDateTime    NULL,
    ...
);</code></pre>
<br>


<h2 id="미션-최종-erd">미션 최종 ERD</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/63c9bfd0-03ed-4464-9540-bb7cc8e28306/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/3ada68d5-b051-4397-80f3-1d1dfc4eeac5/image.png" alt=""></p>
<br>
<br>

<h1 id="📌-마무리">📌 마무리</h1>
<hr>
<p>0주차에서는 단순한 DB 구조 설계가 아니라,<br>요구사항을 기반으로 데이터를 어떻게 구조화할 것인지에 대해 고민하는 과정이었습니다.</p>
<p>특히,</p>
<ul>
<li>N:N 관계를 중간 테이블로 분리하는 것</li>
<li>Soft Delete를 고려한 설계</li>
<li>확장성을 고려한 컬럼 설계</li>
</ul>
<p>이 세 가지가 핵심 포인트였다.</p>
<p>다음 주차에서는 이렇게 설계한 ERD를 기반으로<br>실제 데이터를 조회하는 쿼리를 작성해보며,<br>DB 설계가 어떻게 실제 기능으로 이어지는지 학습할 예정입니다.</p>
<br>
<br>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 스터디 기록 #5 - 조건부 렌더링 & 반복 렌더링]]></title>
            <link>https://velog.io/@yujindeang_/React-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B8%B0%EB%A1%9D-5-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B0%98%EB%B3%B5-%EB%A0%8C%EB%8D%94%EB%A7%81</link>
            <guid>https://velog.io/@yujindeang_/React-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B8%B0%EB%A1%9D-5-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B0%98%EB%B3%B5-%EB%A0%8C%EB%8D%94%EB%A7%81</guid>
            <pubDate>Mon, 23 Feb 2026 06:53:08 GMT</pubDate>
            <description><![CDATA[<p>저번 <code>4장 : 컴포넌트 상태</code> 개념 정리에 이어 
<code>7장 : 조건부 렌더링과 반복 렌더링</code> 개념정리 시작하겠습니다. </p>
<hr>
<h2 id="1-조건부-렌더링-conditional-rendering">1. 조건부 렌더링 (Conditional Rendering)</h2>
<p> 조건부 렌더링은 <strong>특정 조건에 따라 서로 다른 UI를 보여주는 방식</strong>이다.
React에서는 JSX 내부에서 조건을 활용해 화면을 제어한다.</p>
<br>


<h3 id="❶-if문을-사용한-조건부-렌더링">❶ if문을 사용한 조건부 렌더링</h3>
<p>: 가장 기본적인 방식은 <code>if문</code>을 사용해 return 값을 분기하는 것이다.</p>
<pre><code class="language-tsx">if (isLogin){
  return &lt;Logout /&gt;
}
return &lt;Login /&gt;</code></pre>
<ul>
<li>조건에 따라 완전히 다른 컴포넌트를 렌더링 할 때 적합</li>
<li>JSX 내부가 아니라 함수 상단에서 분기<br>

</li>
</ul>
<h3 id="❷-삼항-연산자--">❷ 삼항 연산자 (? :)</h3>
<p>: JSX 내부에서 가장 많이 사용하는 방식</p>
<pre><code class="language-tsx">{isLogin ? &lt;Logout /&gt; : &lt;Login /&gt;}</code></pre>
<h4 id="특징-">특징 :</h4>
<ul>
<li>JSX 안에서 바로 사용 가능</li>
<li>간결하지만 복잡해지면 가독성 저하<br>

</li>
</ul>
<h3 id="❸-and-연산자-">❸ AND 연산자 (&amp;&amp;)</h3>
<p>: 조건이 true일 때만 렌더링</p>
<pre><code class="language-tsx">{isLogin &amp;&amp; &lt;Logout /&gt;}</code></pre>
<h4 id="특징--1">특징 :</h4>
<ul>
<li>false일 경우 아무것도 렌더링되지 않음</li>
<li>&quot;조건이 참일 때만 보여주기&quot;에 적합<br>

</li>
</ul>
<h3 id="❹-변수에-조건-결과-저장">❹ 변수에 조건 결과 저장</h3>
<p>: 조건 결과를 변수에 저장 후 JSX에서 사용</p>
<pre><code class="language-tsx">let message
if (isLogin) {
  message = &quot;환영합니다&quot;
} else {
  message = &quot;로그인해주세요&quot;
}

return &lt;p&gt;{message}&lt;/p&gt;</code></pre>
<p>→ 복잡한 조건일 때 가독성 향상
<br></p>
<h3 id="❺-조건에-따른-클래스명-적용">❺ 조건에 따른 클래스명 적용</h3>
<pre><code class="language-tsx">&lt;div className={isActive ? &quot;active&quot; : &quot;inactive&quot;} /&gt;</code></pre>
<p>또는</p>
<pre><code class="language-tsx">&lt;div className={isActive &amp;&amp; &quot;active&quot;} /&gt;</code></pre>
<p>→ 스타일 제어에 매우 자주 사용됨</p>
<br>

<h3 id="⭐️-조건부-렌더링-핵심-정리">⭐️ 조건부 렌더링 핵심 정리</h3>
<ul>
<li>조건은 state 또는 props 기반으로 결정된다.</li>
<li>JSX 내부에서는 삼항, &amp;&amp; 연산자 사용.</li>
<li>복잡하면 변수 분리.</li>
<li>UI 제어는 결국 &quot;상태 기반 분기&quot; 이다.<br>

</li>
</ul>
<hr>
<br>

<h2 id="2-반복-렌더링-list-rendering">2. 반복 렌더링 (List Rendering)</h2>
<p>배열 데이터를 기반으로 여러 요소를 반복해서 출력하는 방식이다.
<br></p>
<h3 id="❶-map-메서드-사용">❶ map() 메서드 사용</h3>
<p> : React에서 가장 표준적인 반복 렌더링 방식</p>
<pre><code class="language-tsx">{items.map((item, index) =&gt; (
  &lt;li key={index}&gt;{item}&lt;/li&gt;
))}</code></pre>
<h4 id="특징">특징:</h4>
<ul>
<li>배열을 JSX 배열로 변환</li>
<li>가장 권장되는 방법<br>

</li>
</ul>
<h3 id="❷-key의-중요성">❷ key의 중요성</h3>
<p> : 반복 렌더링 시 반드시 key 속성 필요</p>
<pre><code class="language-tsx">&lt;li key={item.id}&gt;{item.name}&lt;/li&gt;</code></pre>
<h4 id="이유-">이유 :</h4>
<ul>
<li>React가 요소를 구분하기 위해 필요</li>
<li>효율적인 리렌더링을 위해 사용</li>
<li>index는 최후의 수단<br>

</li>
</ul>
<h3 id="❸-for문-사용">❸ for문 사용</h3>
<pre><code class="language-tsx">for (let i=0; i&lt;items.length; i++) {
  elements.push(&lt;li key={i}&gt;{items[i]}&lt;/li&gt;)
}</code></pre>
<ul>
<li>가능은 하지만 JSX 문법상 map보다 불편</li>
<li>실무에서는 거의 map 사용<br>

</li>
</ul>
<h3 id="❹-foreach는-사용하지-않음">❹ forEach는 사용하지 않음</h3>
<p>forEach는 값을 반환하지 않기 때문에 직접 JSX로 사용 불가
→ 별도 배열에 push 후 렌더링해야 함
<br></p>
<h3 id="❺-reduce-사용">❺ reduce 사용</h3>
<pre><code class="language-tsx">items.reduce((acc, item) =&gt; {
  acc.push(&lt;li key={item}&gt;{item}&lt;/li&gt;)
  return acc
}, [])</code></pre>
<ul>
<li>가능하지만 가독성 측면에서 map이 더 적합</li>
</ul>
<br>

<hr>
<br>

<h2 id="3-조건부--반복-렌더링-조합">3. 조건부 + 반복 렌더링 조합</h2>
<pre><code class="language-tsx">{itens.length &gt; 0 ? (
  items.map(item =&gt; &lt;li key={item.id}&gt;{item.name}&lt;/li&gt;)
) : (
  &lt;p&gt;데이터가 없습니다&lt;/p&gt;
)}</code></pre>
<p>또는</p>
<pre><code class="language-tsx">{items.length === 0 &amp;&amp; &lt;p&gt;데이터가 없습니다&lt;/p&gt;}</code></pre>
<p>→ 실제 프로젝트에서 매우 자주 사용</p>
<br>

<hr>
<br>

<h3 id="7장-전체-핵심-요약">7장 전체 핵심 요약</h3>
<ol>
<li><code>조건부 렌더링</code>은 상태 기반 UI 제어 방식이다.</li>
<li><code>삼항 연산자</code>와 <code>&amp;&amp; 연산자</code>가 가장 많이 사용된다.</li>
<li><code>반복 렌더링</code>은 <code>map</code>이 표준 방식이다.</li>
<li><code>key</code>는 <code>필수</code>이며, <code>고유값</code>을 사용하는 것이 가장 좋다.</li>
<li>조건과 반복은 항상 함께 사용된다.</li>
</ol>
<br>

<hr>
<br>

<p>4장에서 상태(state)의 개념을 이해했다면,
7장에서는 그 상태를 기반으로 화면을 어떻게 제어할 것인지를 배우는 단계였습니다.</p>
<p>이제 개념 정리는 마쳤으니,
다음 글에서는 이를 실제로 적용한 <code>3주차 과제</code>
<code>카운터 앱</code>과 <code>리스트 앱</code> 구현 과정을 정리해보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 스터디 기록 #4 - 컴포넌트 상태]]></title>
            <link>https://velog.io/@yujindeang_/React-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B8%B0%EB%A1%9D-4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%83%81%ED%83%9C</link>
            <guid>https://velog.io/@yujindeang_/React-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B8%B0%EB%A1%9D-4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%83%81%ED%83%9C</guid>
            <pubDate>Mon, 23 Feb 2026 05:48:27 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yujindeang_/post/69c66849-f868-4d2e-93b0-b78bb21dc416/image.png" alt=""></p>
<p>오늘은 3주차 스터디 범위인 4장과 7장 중,
먼저 <strong>4장 ‘컴포넌트 상태’</strong> 에 대한 개념 정리를 해보려고 합니다.</p>
<br>

<blockquote>
<p><strong>4장. 컴포넌트 상태</strong>
리액트 애플리케이션이 단순한 정적 페이지를 넘어 
동적으로 변화하는 UI를 제공 할 수 있는 이유는 바로 상태 관리 덕분입니다. </p>
</blockquote>
<p>이 장에서는 리액트에서 컴포넌트의 상태를 어떻게 정의하고,
어떻게 변경하며, 왜 상태로 관리해야 하는지를 살펴봅니다.</p>
<br>

<hr>
<br>

<h3 id="🅠-변수로-데이터를-관리하면-왜-문제가-생길까">🅠 변수로 데이터를 관리하면 왜 문제가 생길까?</h3>
<p>리액트 컴포넌트에서 데이터를 정의하는 가장 간단한 방법은 
자바스크립트의 <code>let</code>이나 <code>const</code> 키워드를 사용하는 것입니다.</p>
<pre><code class="language-tsx">export default function App(){
  let name = &#39;철수&#39;;
  const age = 20; 

  return (
    &lt;div&gt;
      &lt;p&gt;{name}&lt;/p&gt;
      &lt;p&gt;{age}&lt;/p&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>이 코드는 정상적으로 화면에 값을 출력합니다.</p>
<p>하지만 다음과 같이 값을 변경해보면 문제가 발생합니다.</p>
<pre><code class="language-tsx">export default function App(){
  let name = &#39;철수&#39;;
  const age = 20;

  const nameChange = () =&gt; {
    name = &#39;영희&#39;;
    console.log(name);
  };

  return (
    &lt;div&gt;
      &lt;p&gt;{name}&lt;/p&gt;
      &lt;p&gt;{age}&lt;/p&gt;
      &lt;button onClick={nameChange}&gt;이름 변경&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>버튼을 클릭하면 콘솔에는 &quot;영희&quot;가 출력됩니다. 하지만 화면에는 여전히 &quot;철수&quot;가 표시됩니다.
왜 이런 일이 발생할까요?</p>
<p>리액트는 일반 변수의 변경을 감지하지 못하기 때문입니다.</p>
<p>즉, let이나 const로 선언한 변수는 값이 바뀌어도컴포넌트가 다시 렌더링되지 않습니다.</p>
<br>

<h3 id="🅐-그래서-등장한-개념-상태state">🅐 그래서 등장한 개념, 상태(state)</h3>
<p>리액트에서는 화면에 영향을 주는 데이터는
단순한 변수가 아니라 상태(state) 로 관리해야 합니다.</p>
<p>컴포넌트의 상태란,</p>
<p>컴포넌트 내부에서 관리되는 데이터로,
사용자와의 상호작용에 따라 변경될 수 있는 값</p>
<p>을 의미합니다.</p>
<p>상태가 변경되면 리액트는 해당 컴포넌트를 자동으로 리렌더링합니다.
이 덕분에 UI가 실제 데이터 변화와 동기화됩니다.</p>
<br>

<hr>
<br>


<h2 id="1-컴포넌트의-상태란">1. 컴포넌트의 상태란?</h2>
<ul>
<li>리액트에서 상태(state)는 시간으니 흐름에 따라 변경되는 데이터이다.</li>
<li>일반 변수(let 등)는 값이 바뀌어도 리렌더링이 발생하지 않는다.</li>
<li>화면에 반영되어야 하는 값은 반드시 상태로 관리해야 한다.</li>
<li>상태가 변경되면 리액트는 해당 컴포넌트를 자동으로 다시 렌더링한다.</li>
<li>리렌더링은 변경된 부분만 효율적으로 반영된다.</li>
</ul>
<blockquote>
<p><strong>핵심</strong> : 화면에 영향을 주는 데이터는 상태로 관리한다.</p>
</blockquote>
<br>

<h2 id="2-userstate-훅-기본-상태-관리">2. userState 훅 (기본 상태 관리)</h2>
<h3 id="❶-userstate-기본-구조">❶ userState 기본 구조</h3>
<pre><code class="language-tsx">const [state, setState] = useState&lt;Type&gt;(initialState);</code></pre>
<ul>
<li><code>state</code> : 현재 상태 값</li>
<li><code>setState</code> : 상태 변경 함수</li>
<li><code>&lt;Type&gt;</code> : useState 훅으로 정의할 상태 값의 타입을 제네릭으로 지정 (생략가능)</li>
<li><code>initialState</code> : 초기값
특징 :</li>
<li>배열 구조 분해 할당 사용</li>
<li>setState 호출 시 리렌더링 발생</li>
</ul>
<br>

<h3 id="❷-상태-변경-방법">❷ 상태 변경 방법</h3>
<p><strong>(1) 값 직접 변경</strong></p>
<pre><code class="language-tsx">setCount(count + 1);</code></pre>
<p><strong>(2) 함수형 업데이트 (이전 값 기반)</strong></p>
<pre><code class="language-tsx">setCount(prev =&gt; prev + 1);</code></pre>
<p>→ 연속 호출, 비동기 업데이트 상황에서는 함수형 업데이트 권장</p>
<h3 id="❸-usestate-동작-흐름">❸ useState 동작 흐름</h3>
<ol>
<li>useState로 상태 선언</li>
<li>이벤트 발생</li>
<li>setState 호출</li>
<li>상태 변경</li>
<li>컴포넌트 재렌더링</li>
</ol>
<h3 id="❹-여러개의-상태-관리">❹ 여러개의 상태 관리</h3>
<pre><code class="language-tsx">const [name, setName] = useState(&#39;&#39;);
const [age, setAge] = useState(0);</code></pre>
<ul>
<li>상태는 여러 개 선언 가능</li>
<li>각 상태는 독립적으로 관리 됨</li>
</ul>
<h3 id="❺-초기값과-타입">❺ 초기값과 타입</h3>
<ul>
<li>초기값이 null 인 경우 타입 명시 필요 (TypeScript)</li>
<li>숫자, 문자열, 객체 등 다양한 타입 가능<pre><code class="language-tsx">const [user, setUser] = useState&lt;User | null&gt;(null);</code></pre>
</li>
</ul>
<br>

<h2 id="3-상태-관리-시-주의사항">3. 상태 관리 시 주의사항</h2>
<h3 id="❶-상태는-직접-변경하지-않는다">❶ 상태는 직접 변경하지 않는다</h3>
<pre><code class="language-tsx">count = count + 1  ❌
setCount(count + 1)  ✅</code></pre>
<h3 id="❷-여러번-setstate-호출-시-주의">❷ 여러번 setState 호출 시 주의</h3>
<pre><code class="language-tsx">setCount(count + 1);
setCount(count + 1);</code></pre>
<p>→ 기대값과 다를 수 있음
→ 함수형 업데이트 사용 권장</p>
<h3 id="❸-리액트-훅-사용-규칙">❸ 리액트 훅 사용 규칙</h3>
<ul>
<li>컴포넌트 최상단에서만 호출
조건문, 반복문 내부에서 호출 금지</li>
</ul>
<br>

<h2 id="4-상태-끌어올리기-state-lifting">4. 상태 끌어올리기 (State Lifting)</h2>
<ul>
<li>여러 컴포넌트가 동일 상태를 공유해야 할 경우</li>
<li>공통 부모 컴포넌트로 상태를 이동<pre><code>App
├─ CountDisplay
└─ CountButtons</code></pre></li>
<li>상태는 부모(App)에 위치</li>
<li>props로 하위 컴포넌트에 전달</li>
</ul>
<blockquote>
<p><strong>핵심</strong> : 상태는 가장 가까운 공통 부모에 둔다</p>
</blockquote>
<br>

<h2 id="5-상태-관리-패턴">5. 상태 관리 패턴</h2>
<h3 id="❶-상태와-변경-함수를-함께-props로-전달">❶ 상태와 변경 함수를 함께 props로 전달</h3>
<pre><code class="language-tsx">&lt;Child count={count} setCount={setCount} /&gt;</code></pre>
<h3 id="❷-변경-로직을-부모에서-정의-후-함수만-전달">❷ 변경 로직을 부모에서 정의 후 함수만 전달</h3>
<pre><code class="language-tsx">const increment = () =&gt; setCount(count + 1);
&lt;Child increment={increment} /&gt;</code></pre>
<p>→ 책임 분리 측면에서 더 권장되는 방식</p>
<br>

<h2 id="6-usereducer-상태-로직이-복잡할-때">6. useReducer (상태 로직이 복잡할 때)</h2>
<h3 id="기본-구조-"><strong>기본 구조</strong> :</h3>
<pre><code class="language-tsx">const [state, dispatch] = useReducer(reducer, initialState);</code></pre>
<h3 id="구성요소-"><strong>구성요소</strong> :</h3>
<ul>
<li>state : 현재 상태</li>
<li>dispatch : 액션 전달 함수</li>
<li>reducer : 상태 변경 로직 함수</li>
<li>initialState : 초기 상태</li>
</ul>
<h3 id="특징-"><strong>특징</strong> :</h3>
<ul>
<li>상태 변경 로직을 한 곳에 모아 관리</li>
<li>복잡한 상태 구조에 적합</li>
<li>액션 기반 업데이트</li>
</ul>
<br>

<h2 id="7-리액트-개발자-도구">7. 리액트 개발자 도구</h2>
<ul>
<li>Components 탭 → props, state 확인</li>
<li>Profiler → 렌더링 성능 분석</li>
<li>어떤 컴포넌트가 왜 렌더링 되는지 추적 가능</li>
</ul>
<br>


<hr>
<br>

<h3 id="4장-핵심-정리">4장 핵심 정리</h3>
<ul>
<li>화면에 영향을 주는 값은 상태로 관리한다.</li>
<li>상태 변경은 setState를 통해 이루어진다.</li>
<li>상태는 공통 부모에 위치시킨다.</li>
<li>복잡한 상태는 useReducer를 사용한다.</li>
<li>리렌더링은 상태 변경에 의해 발생한다</li>
</ul>
<br>

<hr>
<br>

<p>이번 글에서는 4장의 핵심 개념을 정리해보았습니다.
다음 글에서는 7장 조건부 렌더링과 반복 렌더링을 정리하고,
이후 3주차 과제 구현까지 이어서 기록해보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 8주 스터디 기록 #3 – 컴포넌트 정리 및 과제 구현]]></title>
            <link>https://velog.io/@yujindeang_/React-8%EC%A3%BC-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B8%B0%EB%A1%9D-3-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@yujindeang_/React-8%EC%A3%BC-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B8%B0%EB%A1%9D-3-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Sun, 15 Feb 2026 13:15:27 GMT</pubDate>
            <description><![CDATA[<p>오늘은 2주차 학습 범위인 3장. 컴포넌트 내용을 정리하고,
해당 개념을 바탕으로 과제를 구현해보았습니다.</p>
<hr>
<h3 id="컴포넌트">컴포넌트</h3>
<p>컴포넌트는 UI를 구성하는 독립적이고 재사용 가능한 단위입니다.
리액트는 이러한 컴포넌트를 조합하여 하나의 애플리케이션을 완성합니다.</p>
<p>예를 들어 하나의 웹 페이지는 다음과 같이 나눌 수 있습니다.</p>
<ul>
<li><code>header</code> : 로고, 네비게이션 바</li>
<li><code>nav</code> : 웹사이트 메뉴</li>
<li><code>article</code> : 주요 콘텐츠</li>
<li><code>section</code> : 세부 콘텐츠</li>
<li><code>aside</code> : 광고, 추가 정보</li>
<li><code>footer</code> : 저작권 정보, 연락처</li>
</ul>
<blockquote>
<h4 id="추상화abstraction"><strong>추상화(abstraction)</strong></h4>
<p>위 사진처럼 <strong>웹 페이지를 기능 단위로 나누는 과정</strong>을 추상화라고 합니다. 
추상화는 복잡한 구조를 단순하게 표현함으로써 
전체 시스템을 더 명확하게 이해하고 관리할 수 있도록 해줍니다</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/518902d2-219c-42dd-a6cf-b3cf3f93411c/image.png" alt=""></p>
<br>

<hr>
<br>

<h3 id="컴포넌트-예시">컴포넌트 예시</h3>
<p><strong>Header 컴포넌트</strong> </p>
<pre><code class="language-javascript">//Header 컴포넌트 : 로고와 내비게이션 바
function Header(){
    return(
        &lt;header&gt;
         &lt;h1&gt;My Website&lt;/h1&gt;
         &lt;Nav /&gt;
        &lt;/header&gt;

    );
}
</code></pre>
<p><strong>Navigation 컴포넌트</strong> </p>
<pre><code class="language-javascript">//Navigation 컴포넌트 : 웹 사이트 메뉴

function Nav(){
    return(
        &lt;nav&gt;Home | About | Services | Contact&lt;/Nav&gt;
    );
}
</code></pre>
<p><strong>Article 컴포넌트</strong></p>
<pre><code>// Article 컴포넌트: 주요 콘텐츠

function Article(){
  return( 
    &lt;article&gt;
      &lt;h2&gt; Main Article&lt;/h2)\&gt;
    &lt;/article&gt;
  );
}</code></pre><p><strong>Section 컴포넌트</strong></p>
<pre><code>// Section 컴포넌트: 세부 콘텐츠

function Article(){
  return( 
    &lt;section&gt;
      &lt;h3&gt;More Details(/h3)
    &lt;/section&gt;
);
}</code></pre><p><strong>Aside 컴포넌트</strong></p>
<pre><code>// Aside 컴포넌트: 광고, 추가 정보

function Aside() ({
    return( 
      &lt;aside&gt;
        &lt;h3&gt;Related Links&lt;/h3&gt;
      &lt;/aside&gt;
    );
}</code></pre><p><strong>Footer 컴포넌트</strong></p>
<pre><code>// Footer 컴포넌트: 저작권 정보, 연락처

function Footer() { 
    return ( 
      &lt;footer&gt;
        &lt;p&gt;©2025 My Website. All rights reserved.&lt;/p&gt; 
      &lt;/footer&gt;
    );
}</code></pre><p><strong>App 컴포넌트</strong></p>
<pre><code>// App 컴포넌트: 모든 컴포넌트를 조합해 전체 페이지 구성

function App() {
    return ( 
      &lt;div&gt;
        &lt;Header/&gt;
          &lt;Article/&gt;
          &lt;Aside/&gt;
        &lt;Footer/&gt;
      &lt;/div&gt;
    );
}</code></pre><hr>
<h3 id="컴포넌트-단위로-구성했을-때의-span-stylebackground-color-rgba24217918805이점span">컴포넌트 단위로 구성했을 때의 <span style="background-color: rgba(242,179,188,0.5)"><strong>이점</strong></span></h3>
<ol>
<li><span style="background-color: rgba(242,179,188,0.5)"><strong>재사용 가능</strong></span>
 한 번 만든 컴포넌트는 <strong>여러 곳에서 반복적으로 사용</strong>할 수 있습니다.
예를 들어 <strong>Header</strong>와 <strong>Footer</strong>는 대부분의 페이지에서 공통으로 사용됩니다.<br></li>
<li><span style="background-color: rgba(242,179,188,0.5)"><strong>유지보수 용이</strong></span>
 컴포넌트를 <strong>작은 단위로 나누면</strong>
문제가 발생했을 때 어디에서 오류가 발생했는지 빠르게 파악할 수 있습니다.
또한 수정 시 <strong>다른 부분에 영향을 최소화</strong>할 수 있습니다.<br></li>
<li><span style="background-color: rgba(242,179,188,0.5)"><strong>로직 분리 가능</strong></span>
 <strong>UI와 비즈니스 로직을 분리</strong>할 수 있습니다.
화면을 담당하는 컴포넌트와 데이터를 처리하는 로직을 구분하면</li>
</ol>
<p><strong>가독성</strong>과 <strong>유지보수성</strong>이 향상됩니다.
<br>
4. <span style="background-color: rgba(242,179,188,0.5)"><strong>복잡한 상태 관리 가능</strong></span>
    컴포넌트를 세분화하면 상태 변경 시 <strong>필요한 부분만 리렌더링</strong>되어
불필요한 렌더링을 줄일 수 있습니다.
<br></p>
<blockquote>
<h3 id="plus">plus</h3>
<p>컴포넌트는&#39;<strong>너무 세분화하면 관리가 어렵고, 너무 뭉뚱그리면 재사용성과 유지보수성이 떨어진다&#39;</strong> 는 점입니다. 따라서 <span style="background-color: #f2e2c6
"><strong>목적에 맞게 적절한 수준으로 나누는 것이 가장 중요</strong></span>합니다.</p>
</blockquote>
<br>


<p>여기까지가 컴포넌트에 대한 기초 이론 정리였고 </p>
<hr>
<br>

<h3 id="2주차-과제">2주차 과제</h3>
<blockquote>
<h4 id="✔-요구사항"><strong>✔ 요구사항</strong></h4>
<pre><code>1. 버튼 클릭 로직은 부모 컴포넌트에서 관리
2. 버튼 텍스트와 클릭 이벤트는 props로 전달
3. +1 → count 증가
4. -1 → count 감소
5. Reset → count 0 초기화
6. Alert → 현재 count 값 출력
7. 공통 Button 컴포넌트 생성</code></pre></blockquote>
<p>해당 과제 해결 과정을 보여드리려 합니다 </p>
<hr>
<br>

<h3 id="🛠-구현-과정">🛠 구현 과정</h3>
<h4 id="span-stylebackground-color-e0e0e0step-1-폴더-구조-설계span"><span style="background-color: #e0e0e0">Step 1. 폴더 구조 설계</span></h4>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/58d11fbd-e319-44b4-8564-81fe5f8f3311/image.png" alt=""></p>
<pre><code class="language-css">src
 ├── components
 │     └── Button.tsx
 │
 ├── App.tsx
 └── main.tsx</code></pre>
<ul>
<li>components/ : 재사용 가능한 UI 컴포넌트</li>
<li>App.tsx : 상태 및 로직 관리<br>

</li>
</ul>
<p>이처럼 파일 구성 했고,</p>
<h4 id="span-stylebackground-color-e0e0e0step-2-코드-작성span"><span style="background-color: #e0e0e0">Step 2. 코드 작성</span></h4>
<pre><code class="language-tsx">// components/Button.tsx 코드
type ButtonProps = {
    text: string
    onClick: () =&gt; void
  }

  function Button({ text, onClick }: ButtonProps) {
    return &lt;button onClick={onClick}&gt;{text}&lt;/button&gt;
  }

  export default Button
</code></pre>
<ul>
<li>버튼 UI 담당</li>
<li>props로 값과 함수 전달받음</li>
</ul>
<br>


<pre><code class="language-tsx">// app.tsx 코드
import { useState } from &quot;react&quot;
import Button from &quot;./components/Button&quot;

function App() {
  const [count, setCount] = useState(0)

  const handleIncrease = () =&gt; setCount(count + 1)
  const handleDecrease = () =&gt; setCount(count - 1)
  const handleReset = () =&gt; setCount(0)
  const handleAlert = () =&gt; alert(count)

  return (
    &lt;div&gt;
      &lt;h1&gt;Count: {count}&lt;/h1&gt;

      &lt;Button text=&quot;+1&quot; onClick={handleIncrease} /&gt;
      &lt;Button text=&quot;-1&quot; onClick={handleDecrease} /&gt;
      &lt;Button text=&quot;Reset&quot; onClick={handleReset} /&gt;
      &lt;Button text=&quot;Alert&quot; onClick={handleAlert} /&gt;
    &lt;/div&gt;
  )
}

export default App
</code></pre>
<p>코드는 이렇게 완성한 후 실행시켜보니</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/736ec495-73c9-4f0b-a938-fcb197919064/image.png" alt="">
<img src="https://velog.velcdn.com/images/yujindeang_/post/a2b12e68-0b44-4ed9-b0e1-ed120cb3e359/image.png" alt=""></p>
<p>과제 조건에 전부 만족하는 화면과 기능이 나왔습니다.
이번 과제를 통해 개념을 직접 코드로 구현해볼 수 있었습니다.</p>
<br>

<hr>
<br>

<p>컴포넌트는 단순히 화면을 나누는 것이 아니라,
역할을 나누는 설계 방식이라는 점을 이해하게 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 8주 스터디 기록 #2 - JSX 개요]]></title>
            <link>https://velog.io/@yujindeang_/React-8%EC%A3%BC-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B8%B0%EB%A1%9D-2-JSX-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@yujindeang_/React-8%EC%A3%BC-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B8%B0%EB%A1%9D-2-JSX-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Thu, 12 Feb 2026 07:03:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yujindeang_/post/b77617f9-7cbc-49f0-b9c6-087fe0cfd95b/image.png" alt=""></p>
<h3 id="jsx란">JSX란</h3>
<p><strong>JSX</strong>는 <strong>JavaScript XML의 약자</strong>로,
자바스크립트 코드 안에서 HTML과 유사한 문법으로 UI를 작성할 수 있게 해주는 <strong>문법 확장</strong>이다.</p>
<p>JSX는 브라우저가 직접 이해하는 문법이 아니라,
빌드 과정에서 일반 <strong>자바스크립트 코드(React.createElement)로 변환되어 실행</strong>된다.</p>
<br>

<p>예시:</p>
<pre><code class="language-javascript">const element = &lt;h1&gt;Hello, React!&lt;/h1&gt;</code></pre>
<br>
위 코드는 내부적으로 다음과 같이 변환된다.



<pre><code class="language-javascript">React.createElement(&#39;h1&#39;, null, &#39;Hello, React!&#39;)</code></pre>
<br>

<hr>
<br>

<h3 id="jsx의-문법적-특징">JSX의 문법적 특징</h3>
<p>JSX는 HTML과 문법이 매우 유사하기 때문에 처음 접하는 사람도 어렵지 않게 사용 할 수 있습니다. 
하지만 <strong>HTML과는 조금 다른 JSX만의 규칙</strong>이 몇 가지 존재합니다.</p>
<br>

<p><strong>[1] <span style="background-color: #f2e2c6">하나의 루트 요소로 반환하기</span></strong>
JSX에서 작성하는 컴포넌트는 반드시 하나의 루트 요소만 반환해야 합니다. 여러 요소를 반환하려면 <strong>하나의 부모 요소</strong>로 감싸거나 <strong>Fragment를 사용</strong>해야합니다
컴포넌트는 반드시 하나의 루트 요소만 반환해야 한다.</p>
<pre><code class="language-jsx">// 잘못된 예
return (
  &lt;h1&gt;Hello&lt;/h1&gt;
  &lt;p&gt;React&lt;/p&gt;
)


// 올바른 예시 1 - 부모 요소로 감싸기
return (
  &lt;div&gt;
    &lt;h1&gt;Hello&lt;/h1&gt;
    &lt;p&gt;React&lt;/p&gt;
  &lt;/div&gt;
)


// 올바른 예시 2- 부모 요소로 감싸기
return (
  &lt;&gt;
    &lt;h1&gt;Hello&lt;/h1&gt;
    &lt;p&gt;React&lt;/p&gt;
  &lt;/&gt;
)</code></pre>
<br>
<br>

<p><strong>[2] <span style="background-color: #f2e2c6">모든 태그 닫기</span></strong>
HTML에서는 빈 태그를 닫지 않아도 되지만, JSX에서는 모든 태그를 반드시 닫아야 합니다.</p>
<pre><code>// 잘못된 예시
&lt;img src=&quot;logo.png&quot;&gt;


// 올바른 예시
&lt;img src=&quot;logo.png&quot; /&gt;</code></pre><br>
<br>

<p><strong>[3] <span style="background-color: #f2e2c6">태그 속성은 카멜 케이스로 작성하기</span></strong>
JSX에서는 HTML 속성명을 카멜 케이스로 작성해야 합니다. class나 for처럼 자바스크립트 예약어와 충돌하는 속성은 className, htmlFor와 같이 다른 이름으로 대체합니다</p>
<pre><code>// 잘못된 예시
&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;
&lt;label for=&quot;name&quot;&gt;이름&lt;/label&gt;

// 올바른 예시
&lt;div className=&quot;box&quot;&gt;&lt;/div&gt;
&lt;label htmlFor=&quot;name&quot;&gt;이름&lt;/label&gt;</code></pre><p><strong>이유</strong>: class, for는 JavaScript 예약어이기 때문.</p>
<br>
<br>

<p><strong>[4]<span style="background-color: #f2e2c6">표현식은 중괄호 안에서 사용하기</span></strong>
JSX 내부에서는 <code>{}</code> 안에 자바스크립트 표현식을 사용할 수 있음</p>
<pre><code class="language-jsx">const name = &quot;Yujin&quot;
return &lt;h1&gt;Hello, {name}&lt;/h1&gt;</code></pre>
<p><em>*연산도 가능함</em></p>
<pre><code class="language-jsx">return &lt;p&gt;{1 + 2}&lt;/p&gt;</code></pre>
<p>단, if문이나 for문은 직접 사용할 수 없다.(표현식만 가능)</p>
<br>

<p><strong>[5] <span style="background-color: #f2e2c6">인라인 스타일은 객체로 지정하기</span></strong>
HTML에서는 style 속성을 문자열로 스타일을 지정하지만, JSX에서는 자바스크립트 객체로 지정합니다.</p>
<pre><code class="language-jsx">// html
&lt;div style=&quot;color: red;&quot;&gt;&lt;/div&gt;</code></pre>
<pre><code class="language-jsx">// JSX
&lt;div style={{ color: &#39;red&#39; }}&gt;&lt;/div&gt;

&lt;div style={{ backgroundColor: &#39;black&#39; }}&gt;&lt;/div&gt;
&lt;br&gt;</code></pre>
<p> 1) style 값은 객체, 2) 속성명은 카멜 케이스</p>
 <br>


<p><strong>[6] <span style="background-color: #f2e2c6">주석은 중괄호 안에 작성하기</span></strong>
JSX 내부에서는 중괄호 안에서 작성해야 함</p>
<pre><code class="language-jsx">return (
  &lt;div&gt;
    {/* 이 부분은 주석입니다 */}
    &lt;h1&gt;Hello&lt;/h1&gt;
  &lt;/div&gt;
)</code></pre>
<br>

<hr>
<br>

<p>JSX는 HTML과 매우 유사하지만,
실제로는<span style="color: #b4585a">** 자바스크립트 위에서 동작하는 문법 확장**</span> 이다.</p>
<p>HTML과의 차이점을 정확히 이해하는 것이
리액트 컴포넌트를 작성하는 첫 단계라고 느꼈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 8주 스터디 기록 #1 – 프로젝트 세팅과 기본 구조 이해]]></title>
            <link>https://velog.io/@yujindeang_/React-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%8F%84%EC%A0%84%EA%B8%B0-1</link>
            <guid>https://velog.io/@yujindeang_/React-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%8F%84%EC%A0%84%EA%B8%B0-1</guid>
            <pubDate>Thu, 12 Feb 2026 06:07:16 GMT</pubDate>
            <description><![CDATA[<p>백엔드(Spring) 중심으로 프로젝트를 진행해왔지만,
<span style="background-color: rgba(242,179,188,0.5)"><strong>막학기 졸업 프로젝트2를 개인 프로젝트로 진행</strong></span> 해야 한다.</p>
<p><strong>웹 서비스를 처음부터 끝까지 구현하려면</strong>
프론트엔드 역량도 필요하다고 느꼈고, 그 시작으로 <span style="background-color: rgba(242,179,188,0.5)"><strong>React를 공부하기로 결심</strong></span> 했다.</p>
<p>복학 전 한 학기 동안 집중적으로 학습하기 위해
<span style="background-color: rgba(242,179,188,0.5)"><strong>소학회 프론트엔드(React) 파트</strong></span>에 지원했다.</p>
<br>

<hr>
<br>

<p>소학회에서 시작부터 8주간은 스터디를 진행하는데
<img src="https://velog.velcdn.com/images/yujindeang_/post/b4b0a6b5-f41d-4d59-9948-d75e1685545a/image.png" alt="">이번 기수는 해당 교재를 기반으로 스터디를 진행하며,
8주간의 학습 내용을 기록해보려 한다.
<br>
<br></p>
<hr>
<br>
<br>


<h3 id="nodejs-설치-여부-확인">Node.js 설치 여부 확인</h3>
<blockquote>
<p>Vite를 사용하려면 <strong>Node.js</strong>가 필요하므로,
터미널에서 아래 명령어로 설치 여부를 확인했습니다.</p>
</blockquote>
<pre><code class="language-bash">% node -v
% npm -v</code></pre>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/5c2a2cee-dd61-44e4-a63d-58b9db57d77d/image.png" alt=""></p>
<ul>
<li><code>node -v</code> : Node.js 버전 확인</li>
<li><code>npm -v</code> : npm(Node Package Manager) 버전 확인</li>
</ul>
<br>
<br>

<hr>
<br>
<br>

<h3 id="vite로-리액트-프로젝트-생성">Vite로 리액트 프로젝트 생성</h3>
<blockquote>
<p>그럼 이제, 프로젝트를 생성해 보겠습니다</p>
</blockquote>
<pre><code class="language-bash">% npm create vite@latest</code></pre>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/9cb85905-9a7f-4675-9975-ecb1c9b556af/image.png" alt="">프로젝트 생성 시 교재와 동일하게 <code>React</code> + <code>TypeScript</code> 템플릿을 선택했다.</p>
<br>

<hr>
<br>

<h3 id="리액트-프로젝트-실행">리액트 프로젝트 실행</h3>
<blockquote>
<p><code>% npm run dev</code>로 프로젝트 실행하기</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/c17239fc-cae3-4953-8050-c1fe35941a0f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/5b6a96a6-800a-4b74-8707-71eb0750d909/image.png" alt="">
<span style="color: gray"><em>처음 실행하면, 기본 실행화면이 이렇게 뜰 겁니당</em></span></p>
<br>

<hr>
<br>

<h3 id="생성한-프로젝트의-구조">생성한 프로젝트의 구조</h3>
<blockquote>
<p>Vite로 프로젝트를 생성하면 아래 그림처럼 기본 폴더와 <strong>파일 구조가 자동으로 생성</strong>됩니다</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/28a819a2-9211-4147-b0fa-71a09237902a/image.png" alt=""></p>
<blockquote>
<h4 id="tip--scaffolding"><strong>Tip</strong> : scaffolding</h4>
<p>Vite와 같은 도구를 사용해 프로젝트의 기본 구조와 설정 파일을 자동으로 생성하는 
과정을 <strong>스캐폴딩(scaffolding)</strong> 이라고 합니다. 이를 통해 복잡한 환경 설정을 직접 하지 않고도 
명령어 한 줄로 리액트 애플리케이션의 기본 틀을 빠르게 만들 수 있습니다.</p>
</blockquote>
<br>

<hr>
<br>


<h3 id="test--hello-react-띄우기">test : Hello, React! 띄우기</h3>
<p><code>src/main.tsx</code>랑 <code>src/App.tsx</code>를 아래처럼 수정하면</p>
<pre><code class="language-javascript">// src/main.tsx

import { StrictMode } from &#39;react&#39;
import { createRoot } from &#39;react-dom/client&#39;
// import &#39;./index.css&#39; 
import App from &#39;./App.tsx&#39;

createRoot(document.getElementById(&#39;root&#39;)!).render(
  &lt;StrictMode&gt;
    &lt;App /&gt;
  &lt;/StrictMode&gt;,
)</code></pre>
<pre><code class="language-javascript">// src/App.tsx

export default function App(){
  return (
    &lt;div&gt; Hello, React!&lt;/div&gt;
  )
}</code></pre>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/522909e1-eb58-4645-b02b-e3282bc6dc07/image.png" alt=""></p>
<p>성공입니당 🎉</p>
<br>

<hr>
<br>


<h3 id="react-기본-실행-흐름">React 기본 실행 흐름</h3>
<ol>
<li><code>npm run dev</code> 실행  </li>
<li>Vite가 <code>index.html</code>을 로드  </li>
<li><code>index.html</code>이 <code>main.tsx</code>를 불러옴  </li>
<li><code>main.tsx</code>가 <code>App.tsx</code>를 렌더링  </li>
<li>화면에 React 컴포넌트가 출력</li>
</ol>
<br>

<hr>
<br>


<h3 id="react-기본-구조">React 기본 구조</h3>
<blockquote>
<p>프로젝트에 있는 여러 폴더와 파일 중에서 애플리케이션을 실행하는 데 특히 중요한 파일은
<strong>package.json</strong>, <strong>index.html</strong>, <strong>main.tsx</strong> , <strong>App.tsx</strong> </p>
</blockquote>
<br>


<p><strong><span style="background-color:#e2e2e2"> package.json</span></strong>
package.json 파일은 프로젝트의 핵심 설정파일
<br></p>
<p><strong><span style="background-color:#e2e2e2"> index.html</span></strong>
터미널에서 npm run dev 명령어를 입력해 개발 서버를 실행하면 
Vite 개발 서버는 내부적으로 index.html 파일을 자동으로 로드합니다.
이 파일 안에는 리액트 애플리케이션을 실행하는 데 필요한 최소한의 HTML 구조가 들어 있음
<br></p>
<p><strong><span style="background-color:#e2e2e2"> main.tsx</span></strong>
index.html 파일에서 <code>&lt;script src=&quot;/src/main.tsx&quot;&gt;</code>로 
main.tsx 파일을 불러오면 그때부터 리액트 코드가 실행됨
main.tsx 파일은 리액트 애플리케이션을 초기화하고 구성하는 역할을 함
<br></p>
<p><strong><span style="background-color:#e2e2e2">App.tsx</span></strong>
리액트 파일 중 첫 글자가 대문자로 시작하고 확장자가 .tsx나 .jsx인 파일을 컴포넌트라고 합니다.
일반적으로 확장자 앞에 붙은 이름을 사용해 해당 컴포넌트 파일을 저장합니다.
예를들어 App.tsx 파일은 App 컴포넌트라고 합니다
<br></p>
<br>

<hr>
<br>

<h3 id="소학회-1주차-과제">소학회 1주차 과제</h3>
<blockquote>
<ul>
<li>Vite로 리액트 프로젝트 생성,</li>
<li>JSX로 간단 자기소개 페이지 구현</li>
</ul>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/bd8533fa-1692-4346-bbcc-6c9bd270e5dc/image.png" alt=""></p>
<pre><code class="language-typescript">function Profile() {
  return (
    &lt;section&gt;
      &lt;h1&gt;자기소개&lt;/h1&gt;
      &lt;p&gt;안녕하세요 저는 소프트웨어융합학과 22학번 정유진입니다&lt;/p&gt;
    &lt;/section&gt;
  )
}

type InfoProps = {
  label: string
  value: string
}

function Info({ label, value }: InfoProps) {
  return (
    &lt;p&gt;
      &lt;strong&gt;{label}&lt;/strong&gt;: {value}
    &lt;/p&gt;
  )
}

function Skills() {
  const skills = [&#39;Java&#39;, &#39;Springboot&#39;,&#39;Python&#39;,&#39;React&#39;,&#39;Javascript&#39;, &#39;TypeScript&#39;]

  return (
    &lt;ul&gt;
      {skills.map(skill =&gt; (
        &lt;li key={skill}&gt;{skill}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}

export default function App() {

  return (
    &lt;main&gt;
      &lt;Profile /&gt;

      &lt;Info label=&quot;관심 분야&quot; value=&quot;Frontend / Backend&quot; /&gt;
      &lt;Info label=&quot;학습 도구&quot; value=&quot;Vite + React / springboot&quot; /&gt;

      &lt;h4&gt;기술 스택&lt;/h4&gt;
      &lt;Skills /&gt;

    &lt;/main&gt;
  )
}</code></pre>
<br>

<hr>
<br>

<h3 id="코드-설명">코드 설명</h3>
<p>App 컴포넌트는 최상위 컴포넌트이고,</p>
<blockquote>
<ul>
<li><strong>Profile 컴포넌트</strong>로 기본 소개를 보여주고</li>
<li><strong>Info 컴포넌트</strong>를 여러 번 사용해 정보 영역을 구성하고</li>
<li><strong>Skills 컴포넌트</strong>로 기술 스택을 출력했습니다.
→ 3개의 하위 컴포넌트</li>
</ul>
</blockquote>
<p><strong>[1]</strong> <span style="background-color:#E6E6E6"> <strong>Profile 컴포넌트</strong></span> - 정적인 UI
 : 자기소개 제목과 간단한 소개 문장을 담당합니다.</p>
<ul>
<li><code>section</code> 태그로 하나의 루트 요소를 유지했고</li>
<li>JSX 기본 규칙에 맞게 단일 루트로 반환</li>
</ul>
<br>

<p><strong>[2]</strong> <span style="background-color:#E6E6E6"> <strong>Info 컴포넌트</strong></span> <em>+ props 설명</em></p>
<p><strong>Info 컴포넌트는</strong></p>
<ul>
<li><code>label</code>과 <code>value</code>를 props로 받아서
같은 형태의 정보를 재사용할 수 있도록</li>
<li><code>InfoProps</code> 타입을 정의해서 
props의 타입을 명확히 지정</li>
</ul>
<p>→ 관심 분야, 학습 도구 같은 정보를 같은 컴포넌트로 반복해서 렌더링 가능</p>
<br>


<p><strong>[3]</strong> <span style="background-color:#E6E6E6"> <strong>Skills 컴포넌트</strong></span>
 : 배열 데이터를 JSX에서 렌더링하는 방식을 사용</p>
<ul>
<li><code>skills</code> 배열을 선언하고</li>
<li><code>map</code> 함수를 이용해 <code>&lt;li&gt;</code> 목록으로 변환</li>
<li>JSX 안에서 JavaScript 표현식을 사용하기 때문에 중괄호 <code>{}</code>를 사용했습니다.</li>
<li>또한 리스트 렌더링 시 <code>key</code> 속성을 지정해야 한다는 JSX 규칙에 따라 각 <code>li</code>에 <code>key</code>를 추가했습니다.</li>
</ul>
<p>*_ <span style="color:#6c6c6c"> <code>key</code>는 React가 변경된 요소를 효율적으로 추적하기 위해 사용하는 고유 식별자이다.</span>_</p>
<br>

<hr>
<br>

<p>여기까지 1장 끝났고, 
<code>2장.JSX개요</code> 까지 1주차 스터디라 다음 편에서 2장 개념 정리 진행하겠습니다 
<img src="https://velog.velcdn.com/images/yujindeang_/post/ec14c992-e879-485e-aafc-d4c28361a372/image.png" alt=""></p>
<br>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[[멋쟁이 사자처럼] 해커톤 준비(4) - 카카오 소셜로그인 추가 수정]]></title>
            <link>https://velog.io/@yujindeang_/%EB%A9%8B%EC%9F%81%EC%9D%B4-%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%ED%95%B4%EC%BB%A4%ED%86%A4-%EC%A4%80%EB%B9%844-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B6%94%EA%B0%80-%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@yujindeang_/%EB%A9%8B%EC%9F%81%EC%9D%B4-%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%ED%95%B4%EC%BB%A4%ED%86%A4-%EC%A4%80%EB%B9%844-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B6%94%EA%B0%80-%EC%88%98%EC%A0%95</guid>
            <pubDate>Sun, 03 Aug 2025 10:44:23 GMT</pubDate>
            <description><![CDATA[<p>카카오에서 넘어오는 토큰은... 
우리의 jwt로 권한 확인을 할 수 없는... 문제를 
내가 미쳐 보지 못했다 ㅎㅎ,,,</p>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/c00fa881-d332-43da-ab54-dbb428ed27a3/image.png" alt=""></p>
<p>그래서 끝나지 않은 카카오 소셜 로그인 개발 ㅜㅜ</p>
<blockquote>
<p>🔥 <strong>문제 상황</strong>
OAuth2 기반 소셜 로그인이라서,
카카오에서 로그인 성공하면 access token을 하나 주는데,
<code>Authorization: Bearer {카카오 access token}</code></p>
</blockquote>
<p>*<em>❌ 우리 서버는 이걸 이해 못 함.
*</em>
왜냐면 우리는 내부적으로 JWT 기반 인증 방식을 사용하고 있었고,
카카오 access token은 우리 시스템의 JWT가 아니기 때문!!....</p>
<h4 id="✨-해결-방법">✨ 해결 방법</h4>
<blockquote>
</blockquote>
<p><strong>1. kakaoLogin(code) 호출 후</strong>
<strong>2. 우리 JWT 발급 ⭐️ ⭐️ ⭐️</strong>
사용자 정보 기반으로
→ access token + refresh token 생성
→ LoginResponseDto에 담아서 클라이언트에 응답</p>
<p><strong>하나의 LoginResponseDto로 합치기</strong></p>
<pre><code class="language-java">@Data
@Getter
@AllArgsConstructor
public class LoginResponseDto {
    private String message;
    private String accessToken;
    private String refreshToken;
    private Long userId;
}</code></pre>
<p><strong>컨트롤러도 해당 Dto 사용하도록 변경</strong></p>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/oauth&quot;)
@RequiredArgsConstructor
public class OAuthController {

    private final KakaoOAuthService kakaoOAuthService;

    @GetMapping(&quot;/kakao/callback&quot;)
    public ResponseEntity&lt;LoginResponseDto&gt; kakaoCallback(@RequestParam String code) {
        LoginResponseDto loginResponse = kakaoOAuthService.kakaoLogin(code);
        return ResponseEntity.ok(loginResponse);
    }
}</code></pre>
<h4 id="kakaoauthservicejava-일부">KakaoAuthService.java 일부</h4>
<pre><code class="language-java">
public LoginResponseDto kakaoLogin(String code) {
        String kakaoAccessToken = getKakaoAccessToken(code);

        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(kakaoAccessToken);
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity&lt;?&gt; request = new HttpEntity&lt;&gt;(headers);

        ResponseEntity&lt;String&gt; response = restTemplate.exchange(
                &quot;https://kapi.kakao.com/v2/user/me&quot;,
                HttpMethod.GET,
                request,
                String.class
        );

        try {
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode root = objectMapper.readTree(response.getBody());

            String kakaoId = root.path(&quot;id&quot;).asText();
            String nickname = root.path(&quot;properties&quot;).path(&quot;nickname&quot;).asText();
            String email = root.path(&quot;kakao_account&quot;).path(&quot;email&quot;).asText(null);

            Optional&lt;User&gt; optionalUser = userRepository.findByUsername(kakaoId);

            User user = optionalUser.orElseGet(() -&gt; {
                User newUser = new User();
                newUser.setUsername(kakaoId);;
                newUser.setNickname(nickname);
                newUser.setPassword(&quot;&quot;);
                newUser.setType(UserType.PER);
                newUser.setProvider(&quot;kakao&quot;);
                return userRepository.save(newUser);
            });

            // JWT 토큰 생성
            String accessToken = jwtUtil.generateAccessToken(user);
            String refreshToken = jwtUtil.generateRefreshToken(user);

            String message = &quot;카카오 로그인 성공&quot;;
            return new LoginResponseDto(message, accessToken, refreshToken, user.getId());

        } catch (Exception e) {
            throw new RuntimeException(&quot;카카오 사용자 정보 파싱 실패&quot;, e);
        }
    }</code></pre>
<h3 id="결과를-보면">결과를 보면</h3>
<p><strong>일반 로그인</strong>
<img src="https://velog.velcdn.com/images/yujindeang_/post/7c795398-bf92-41b7-a277-c205d1d135e2/image.png" alt=""></p>
<p><strong>카카오 로그인 결과</strong>
<img src="https://velog.velcdn.com/images/yujindeang_/post/b66a980f-f54e-4ce7-921d-668425df0bef/image.png" alt=""></p>
<p>이제 소셜 로그인도 로컬 로그인과 똑같이 JWT 기반 인증으로 통일 성공 !
<del>다음엔 까먹지 않고 한번에 jwt까지 개발하도록 ^^</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋쟁이 사자처럼] 해커톤 준비(3) - 카카오 소셜로그인]]></title>
            <link>https://velog.io/@yujindeang_/%EB%A9%8B%EC%9F%81%EC%9D%B4-%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%ED%95%B4%EC%BB%A4%ED%86%A4-%EC%A4%80%EB%B9%843-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@yujindeang_/%EB%A9%8B%EC%9F%81%EC%9D%B4-%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%ED%95%B4%EC%BB%A4%ED%86%A4-%EC%A4%80%EB%B9%843-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Sun, 20 Jul 2025 11:04:05 GMT</pubDate>
            <description><![CDATA[<p>요즘 소셜로그인 없는 웹/앱 서비스가 없기 때문에
카카오 가능하면 구글,네이버까지 순서대로 해보려고 합니다 </p>
<blockquote>
<h3 id="카카오-소셜로그인-구현-진행-순서">카카오 소셜로그인 구현 진행 순서</h3>
</blockquote>
<h4 id="1-카카오-개발자-사이트-가입--앱-생성">1. 카카오 개발자 사이트 가입 &amp; 앱 생성</h4>
<p> : 내 앱 만들고, REST API 키랑 Redirect URI(로컬 주소 포함) 등록하기 
  (예: <a href="http://localhost:8080/oauth/kakao/callback">http://localhost:8080/oauth/kakao/callback</a>)</p>
<h4 id="2-spring-boot에-카카오-oauth-연동-라이브러리-세팅">2. Spring Boot에 카카오 OAuth 연동 라이브러리 세팅</h4>
<p> : 스프링 시큐리티 + OAuth2 클라이언트 의존성 추가
 : application.yml에 카카오 클라이언트 ID, 시크릿, 리다이렉트 URI 설정</p>
<h4 id="3-oauth2-로그인-설정-securityconfig에-등록">3. OAuth2 로그인 설정 (SecurityConfig에 등록)</h4>
<p> : 카카오 로그인 URL 및 콜백 처리 설정
 : 토큰 받아서 사용자 정보 조회, 회원가입 또는 로그인 처리</p>
<h4 id="4-테스트">4. 테스트</h4>
<p> : 로컬에서 <a href="http://localhost:8080/login-kakao">http://localhost:8080/login-kakao</a> 같은 URL 만들어서 테스트
 : 로그인이 성공하면 JWT 토큰 발급하거나 세션 생성</p>
<h2 id="🔹-카카오-개발자-사이트-가입--앱-생성">🔹 카카오 개발자 사이트 가입 &amp; 앱 생성</h2>
<hr>
<h3 id="1-httpsdeveloperskakaocom-이-사이트에서-앱-생성">1. <a href="https://developers.kakao.com/">https://developers.kakao.com/</a> 이 사이트에서 앱 생성</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/aea8c7f0-e74f-48eb-aa8b-ecf27bf24e42/image.png" alt=""></p>
<h3 id="2-기본으로-제공해주는-정보-동의-설정">2. 기본으로 제공해주는 정보 동의 설정</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/6be90a94-6897-4f47-a361-fbacd608e445/image.png" alt="">
*추가적인 정보도 받아올수있지만, 일단 가장 간단한 방법으로 구현해두려 함</p>
<h3 id="3-redirecturi-등록">3. RedirectURI 등록</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/aab9e096-4b2b-418f-9e89-2170aa47bf23/image.png" alt="">사용설정을 on 해주면
리다이렉트 URI를 등록할 수 있게 된다 
<img src="https://velog.velcdn.com/images/yujindeang_/post/59da315e-5a36-4d72-ac70-f58f41e172d8/image.png" alt=""> <code>http://localhost:8080/hackathon/api/oauth/kakao/callback</code> 로 설정</p>
<h3 id="4-의존성-확인">4. 의존성 확인</h3>
<pre><code class="language-java">implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;
implementation &#39;com.fasterxml.jackson.core:jackson-databind&#39;</code></pre>
<h3 id="5-rest-api-키-복사해두기">5. REST API 키 복사해두기</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/103b5c4e-9914-47b0-9516-6cc8a0129722/image.png" alt="">
이 경로에 있으니 번거롭지 않게 하려면 복사해두는걸 추천합니다 .! </p>
<h3 id="6-applicationyml에-추가">6. application.yml에 추가</h3>
<pre><code class="language-yml">kakao:
  client-id: ${OAUTH_KAKAO_CLIENT_ID}
  redirect-uri: ${OAUTH_KAKAO_REDIRECT_URI}</code></pre>
<h3 id="7-프론트-로그인-버튼에서-카카오-로그인-페이지로-리다이렉트">7. [프론트] 로그인 버튼에서 카카오 로그인 페이지로 리다이렉트</h3>
<p>예시 리다이렉트 주소
<code>https://kauth.kakao.com/oauth/authorize?client_id=7b56421a48b08f9dc4dd3e9f246b3a54&amp;redirect_uri=http://localhost:8080/hackathon/api/oauth/kakao/callback&amp;response_type=code</code></p>
<h3 id="8-백엔드-카카오에서-authorization-code-받기">8. [백엔드] 카카오에서 authorization code 받기</h3>
<pre><code class="language-java">@GetMapping(&quot;/kakao/callback&quot;)
public ResponseEntity&lt;?&gt; kakaoCallback(@RequestParam String code) {
    // code를 access token으로 바꾸고
    // 사용자 정보 요청해서 처리
}</code></pre>
<h3 id="9-카카오-accesstoken-요청--사용자-정보-요청">9. 카카오 AccessToken 요청 &amp; 사용자 정보 요청</h3>
<pre><code class="language-java">public String getKakaoAccessToken(String code) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap&lt;String, String&gt; params = new LinkedMultiValueMap&lt;&gt;();
        params.add(&quot;grant_type&quot;, &quot;authorization_code&quot;);
        params.add(&quot;client_id&quot;, clientId);
        params.add(&quot;redirect_uri&quot;, redirectUri);
        params.add(&quot;code&quot;, code);

        HttpEntity&lt;MultiValueMap&lt;String, String&gt;&gt; request = new HttpEntity&lt;&gt;(params, headers);

        ResponseEntity&lt;String&gt; response = restTemplate.postForEntity(tokenUri, request, String.class);

        try {
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode root = objectMapper.readTree(response.getBody());
            return root.path(&quot;access_token&quot;).asText();
        } catch (Exception e) {
            throw new RuntimeException(&quot;카카오 Access Token 파싱 실패&quot;, e);
        }
    }
</code></pre>
<h3 id="10-받은-토큰으로-사용자-정보-조회">10. 받은 토큰으로 사용자 정보 조회</h3>
<pre><code class="language-java">HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity&lt;?&gt; request = new HttpEntity&lt;&gt;(headers);

ResponseEntity&lt;String&gt; response = restTemplate.exchange(
        &quot;https://kapi.kakao.com/v2/user/me&quot;,
        HttpMethod.GET,
        request,
        String.class
);</code></pre>
<h3 id="11-사용자-정보json에서-id-or-email-추출-후-db에-회원이-있으면-로그인-없으면-자동-회원가입">11. 사용자 정보(json)에서 ID (or email) 추출 후 DB에 회원이 있으면 로그인, 없으면 자동 회원가입</h3>
<pre><code class="language-java">try {
   ObjectMapper objectMapper = new ObjectMapper();
   JsonNode root = objectMapper.readTree(response.getBody());

   String kakaoId = root.path(&quot;id&quot;).asText();
   String nickname = root.path(&quot;properties&quot;).path(&quot;nickname&quot;).asText();
   String email = root.path(&quot;kakao_account&quot;).path(&quot;email&quot;).asText(null);

   Optional&lt;User&gt; optionalUser = userRepository.findByUsername(kakaoId);

   User user = optionalUser.orElseGet(() -&gt; {
   User newUser = new User();
   newUser.setUsername(kakaoId); // ID를 username으로
   newUser.setPassword(&quot;&quot;);
   newUser.setProvider(&quot;kakao&quot;);
   newUser.setEmail(email); // 추가 가능
   return userRepository.save(newUser);
   });

   return new KakaoUserDto(email, nickname, accessToken);

} </code></pre>
<h3 id="12-jwt-발급해서-클라이언트에-전달">12. JWT 발급해서 클라이언트에 전달</h3>
<pre><code class="language-java">@Getter
@AllArgsConstructor
public class KakaoUserDto {
    private String email;
    private String nickname;
    private String accessToken;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋쟁이 사자처럼] 해커톤 준비(2) - swagger-ui 연결]]></title>
            <link>https://velog.io/@yujindeang_/%EB%A9%8B%EC%9F%81%EC%9D%B4-%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%ED%95%B4%EC%BB%A4%ED%86%A4-%EC%A4%80%EB%B9%842-swagger-ui-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@yujindeang_/%EB%A9%8B%EC%9F%81%EC%9D%B4-%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%ED%95%B4%EC%BB%A4%ED%86%A4-%EC%A4%80%EB%B9%842-swagger-ui-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Sun, 20 Jul 2025 10:45:26 GMT</pubDate>
            <description><![CDATA[<p>원랜 소셜로그인을 바로 쓸 예정이였는데,
막간을 이용해 swagger-ui 연결도 바로 적어보려고 합니다</p>
<h4 id="1-의존성-추가하기">1. 의존성 추가하기</h4>
<pre><code class="language-java">// swagger-ui
implementation &#39;org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0&#39;</code></pre>
<h4 id="2-applicationyml">2. <code>application.yml</code></h4>
<pre><code class="language-yml">springdoc:
  api-docs:
    path: /api-docs
  swagger-ui:
    path: /swagger-ui</code></pre>
<h4 id="3-접속해보기">3. 접속해보기</h4>
<p><code>http://localhost:8080/hackathon/api/swagger-ui/index.html#/</code> </p>
<blockquote>
<p><strong>참고</strong> 
저는 기본 url을 /hackathon/api/ 여기까지 해놨었습니다</p>
</blockquote>
<h4 id="4-필요시-controller에-어노테이션-추가">4. 필요시 Controller에 어노테이션 추가</h4>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/users&quot;)
public class UserController {

    @GetMapping(&quot;/login&quot;)
    @Operation(summary = &quot;로그인&quot;, description = &quot;로그인 성공시 토큰반환&quot;)
    public ResponseEntity&lt;LoginResponseDto&gt; login(@RequestBody LoginRequestDto request){
        // ...
    }
}</code></pre>
<blockquote>
<p>어노테이션 <code>@Operation</code>을 통해 
api 이름과 설명을 적어줄 수 있습니다</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/09c98594-3955-442d-bf25-49924f029a41/image.png" alt=""> 위와 같이 설정한 결과 예시</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋쟁이 사자처럼] 해커톤 준비(1) - local 회원가입&로그인 및 토큰 발급]]></title>
            <link>https://velog.io/@yujindeang_/%EB%A9%8B%EC%9F%81%EC%9D%B4-%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%ED%95%B4%EC%BB%A4%ED%86%A4-%EC%A4%80%EB%B9%841-local-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%B0%8F-%ED%86%A0%ED%81%B0-%EB%B0%9C%EA%B8%89</link>
            <guid>https://velog.io/@yujindeang_/%EB%A9%8B%EC%9F%81%EC%9D%B4-%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%ED%95%B4%EC%BB%A4%ED%86%A4-%EC%A4%80%EB%B9%841-local-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%B0%8F-%ED%86%A0%ED%81%B0-%EB%B0%9C%EA%B8%89</guid>
            <pubDate>Sun, 20 Jul 2025 06:17:28 GMT</pubDate>
            <description><![CDATA[<p>지금까지 해온 프로젝트에서 미루고 미뤄왔던 로그인 및 토큰 구현 맡기 
이번이 로그인/토큰발급 해볼 수 있는 마지막 기회라 생각하고
덜컥 맡아버렸습니다 ...... ㅎㅎ</p>
<hr>
<blockquote>
<p><strong>[진행할 순서는]</strong></p>
</blockquote>
<ul>
<li>User 엔티티 + DB 연결</li>
<li>일반 회원가입/로그인 (비밀번호 암호화)</li>
<li>JWT 발급 및 인증 흐름 적용</li>
<li>소셜 로그인 (구글 또는 카카오) + JWT 연동</li>
</ul>
<h2 id="1️⃣-user-엔티티--db-연결">1️⃣ User 엔티티 + DB 연결</h2>
<hr>
<h3 id="🔹-프로젝트-생성하기">🔹 프로젝트 생성하기</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/174565a4-e6e7-40f0-8cd8-a07d29f95495/image.png" alt="">
너무나 당연한 소리였죵 ?</p>
<h3 id="🔹-user-엔티티">🔹 user 엔티티</h3>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/ec4063fa-9315-4d40-97ef-1ebac161a568/image.png" alt=""></p>
<h3 id="🔹-db-생성-및-연결">🔹 DB 생성 및 연결</h3>
<pre><code class="language-sql">CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    provider VARCHAR(50) NOT NULL
);</code></pre>
<p>이렇게 DB를 만들고</p>
<pre><code class="language-yml">spring:
  datasource:
    url: ${DB_URL}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}

  jpva:
    hibernate:
      ddl-auto: update
    show-sql: true
    database-platform: org.hibernate.dialect.MySQLDialect</code></pre>
<p>연결을 위한 application.yml을 작성해줍니다</p>
<h2 id="2️⃣-일반-회원가입로그인-비밀번호-암호화">2️⃣ 일반 회원가입/로그인 (비밀번호 암호화)</h2>
<hr>
<h3 id="🔹-1-userrepository-생성">🔹 1. UserRepository 생성</h3>
<p>: JPA로 User 엔티티 저장/조회 처리</p>
<pre><code class="language-java">public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
    User findByUsername(String username);
}</code></pre>
<h3 id="🔹-2-usercontroller-생성">🔹 2. UserController 생성</h3>
<p>: 회원가입용 POST 엔드포인트 (/signup)
: 로그인용 POST 엔드포인트 (/login)</p>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/users&quot;)
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping(&quot;/signup&quot;)
    public String signup(@RequestParam String password, @RequestParam String provider) {
        userService.signup(password, provider);
        return &quot;회원가입 성공!&quot;;
    }

    @PostMapping(&quot;/login&quot;)
    public String login(@RequestParam Long id, @RequestParam String password) {
        boolean success = userService.login(id, password);
        return success ? &quot;로그인 성공!&quot; : &quot;로그인 실패!&quot;;
    }
}</code></pre>
<p>아 그리고 저는 api 기본 경로를 </p>
<pre><code class="language-yml">server:
  servlet:
    context-path: /hackathon/api</code></pre>
<p>로 미리 넣어둬서</p>
<blockquote>
<p>[실제 api 호출 경로는]
<strong>POST /hackathon/api/users/signup
POST /hackathon/api/users/login</strong>
이렇게 됩니다 </p>
</blockquote>
<h3 id="🔹-3-userservice-생성">🔹 3. UserService 생성</h3>
<p>: 비밀번호 암호화 (BCrypt 사용)
: 회원정보 저장 &amp; 검증</p>
<pre><code class="language-java">@Service
public class UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder passwordEncoder;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
        this.passwordEncoder = new BCryptPasswordEncoder();
    }

    public User signup(String rawPassword, String provider) {
        String encodedPassword = passwordEncoder.encode(rawPassword);

        User user = new User();
        user.setPassword(encodedPassword);
        user.setProvider(provider);

        return userRepository.save(user);
    }

    public boolean login(Long id, String rawPassword) {
        return userRepository.findById(id)
                .map(user -&gt; passwordEncoder.matches(rawPassword, user.getPassword()))
                .orElse(false);
    }
}</code></pre>
<p>이렇게 넣고,
<code>Cannot resolve method &#39;setPassword&#39; in &#39;User&#39;</code> 같은 오류가 난다면 <code>User.Entity</code>에서 @Setter @Getter 어노테이션을 넣었는지 확인하고 없다면, 추가해주세요 </p>
<pre><code class="language-java">@Getter @Setter
@Entity
public class User {
        ...
}</code></pre>
<h3 id="🔹-4-spring-security-설정">🔹 4. Spring Security 설정</h3>
<pre><code class="language-java">@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .cors(...)                    // 1.CORS 설정 (프론트 도메인 허용)
        .csrf(csrf -&gt; csrf.disable()) // 2.CSRF 비활성화 (JWT 사용 시 필수)
        .sessionManagement(...)       // 3.세션 안 씀 (stateless, JWT 기반 인증을 위해)
        .authorizeHttpRequests(...)   // 4.인증/비인증 경로 지정
        .addFilterBefore(...)         // 5.JWT 필터를 UsernamePasswordAuthenticationFilter 전에 끼워넣음
}</code></pre>
<p>이 로직으로 짜봤습니다 <code>5번 JWT 필터</code>를 넣으려고 </p>
<pre><code class="language-java">
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {

        // TODO: 여기에 JWT 인증 로직 구현 예정
        filterChain.doFilter(request, response);
    }
}</code></pre>
<p>이와 같이 <code>JwtAuthenticationFilter.java</code> 를 security 하위에 생성 해 두었고 
원래 작성하려했던 <code>SecurityConfig.java</code> 로 다시와서</p>
<pre><code class="language-java">import java.util.Arrays;
import java.util.List;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Value(&quot;${frontend.domain:localhost}&quot;)
    private String frontendDomain;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(org.springframework.security.config.annotation.web.builders.HttpSecurity http) throws Exception {
        http
                .cors(cors -&gt; cors.configurationSource(corsConfigurationSource())) // cors 설정
                .csrf(csrf -&gt; csrf.disable()) // CSRF 비활성화
                .sessionManagement(session -&gt; session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //세션 x

                // 임시 전체 허용
                .authorizeHttpRequests(auth -&gt; auth
                        .anyRequest().permitAll()
                )
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // JWT 필터

        return http.build();
    }

    private CorsConfigurationSource corsConfigurationSource() {
        return request -&gt; {
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowedOriginPatterns(Arrays.asList(
                    &quot;https://&quot; + frontendDomain,
                    &quot;http://localhost:3003&quot;,
                    &quot;https://localhost:3003&quot;,
                    &quot;https://localhost:8080&quot;
            ));
            config.setAllowedMethods(List.of(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;));
            config.setAllowedHeaders(List.of(&quot;*&quot;));
            config.setExposedHeaders(List.of(&quot;Authorization&quot;));
            config.setAllowCredentials(true);
            config.setMaxAge(3600L);
            return config;
        };
    }
}</code></pre>
<p>이렇게 완성했습니다.</p>
<h2 id="3️⃣-jwt-발급-및-인증-흐름-적용">3️⃣ JWT 발급 및 인증 흐름 적용</h2>
<hr>
<p>JWT 토큰을 발급하고 인증하는 로직을 적용하기 위해서 
<code>security&gt;jwt</code> 디렉토리를 만들었고 
<img src="https://velog.velcdn.com/images/yujindeang_/post/b9cfe29a-bd52-4038-b5a4-135096da7f5c/image.png" alt=""></p>
<p>이렇게 3개 클래스에 대해 만들어봤습니다 </p>
<blockquote>
<h4 id="jwtauthenticationfilterjava">JwtAuthenticationFilter.java</h4>
<p>HTTP 요청에 포함된 JWT 토큰을 검사하여 사용자의 인증 정보를 확인하고, 인증된 사용자만 서비스에 접근할 수 있도록 하는 필터 역할을 합니다.</p>
</blockquote>
<pre><code class="language-java">@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {

        // TODO: 여기에 JWT 인증 로직 구현 예정
        filterChain.doFilter(request, response);
    }
}</code></pre>
<p>로그인시 토큰 발급이 목적이기에, 아직 JWT 인증 로직은 구현 전입니다</p>
<blockquote>
<h4 id="jwtutiljava">JwtUtil.java</h4>
<p>JWT 토큰의 생성, 서명, 검증, 만료 시간 처리 등 JWT와 관련된 핵심 유틸리티 기능을 담당합니다.</p>
</blockquote>
<pre><code class="language-java">@Component
public class JwtUtil {

    @Value(&quot;${JWT_SECRET}&quot;)
    private String SECRET_KEY;

    private final long ACCESS_EXP_TIME = 1000L * 60 * 60; // 1시간

    private Key getSigningKey() {
        return Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8));
    }

    public String generateAccessToken(User user) {
        return Jwts.builder()
                .setSubject(user.getUsername()) // subject: username
                .claim(&quot;userId&quot;, user.getId())   // 추가 클레임
                .claim(&quot;type&quot;, &quot;access&quot;)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + ACCESS_EXP_TIME))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    public String generateRefreshToken(User user) {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(&quot;Asia/Seoul&quot;));
        calendar.set(Calendar.HOUR_OF_DAY, 9);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);

        if (calendar.getTimeInMillis() &lt;= System.currentTimeMillis()) {
            calendar.add(Calendar.DATE, 1); // 다음 날 9시
        }

        return Jwts.builder()
                .setSubject(user.getUsername())
                .claim(&quot;type&quot;, &quot;refresh&quot;)
                .setIssuedAt(new Date())
                .setExpiration(calendar.getTime())
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

}</code></pre>
<p>여기도 일단, 발급이 중요한 로직이기에, 발급 부분만 구현 해봤습니다 .!</p>
<blockquote>
<h4 id="tokenstatusjava">TokenStatus.java</h4>
<p>JWT 토큰의 유효성 상태(인증 성공, 만료, 무효 등)를 나타내는 상태 값을 정의하는 enum 클래스입니다.</p>
</blockquote>
<pre><code class="language-java">public enum TokenStatus {
    AUTHENTICATED,
    EXPIRED,
    INVALID
}</code></pre>
<h2 id="결과확인">결과확인</h2>
<hr>
<blockquote>
<h4 id="1-post-hackathonapiuserssignup">1. <code>POST</code> /hackathon/api/users/signup</h4>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/9cde20d5-e4af-49cf-9511-2d156183ba0d/image.png" alt=""> 회원가입 성공이라는 메시지와 함께, userId를 반환합니다</p>
</blockquote>
<blockquote>
<h4 id="2-user의-비밀번호도-db에-암호화되어-저장">2. user의 비밀번호도 DB에 암호화되어 저장</h4>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/d2e33267-6532-4bf5-a73b-887fddc4a05e/image.png" alt=""></p>
</blockquote>
<blockquote>
<h4 id="3-post-hackathonapiuserslogin">3. <code>POST</code> /hackathon/api/users/login</h4>
<p><img src="https://velog.velcdn.com/images/yujindeang_/post/f59c05e1-f368-4ac4-91c0-bd74855bb12d/image.png" alt=""> 해당 유저의 username과 password로 로그인시, 토큰 발급과 응답값 출력 성공 </p>
</blockquote>
<hr>
<pre><code>다음 편에서, 소셜 로그인 (구글 또는 카카오) + JWT 연동에 대해 써보겠습니다 </code></pre>]]></description>
        </item>
    </channel>
</rss>