<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Devlog U</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 05 Sep 2025 07:40:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Devlog U</title>
            <url>https://velog.velcdn.com/images/du-log/profile/83e5d0d8-cfa0-44e8-a387-d5d2bc462cad/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Devlog U. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/du-log" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[🔑 Spring Security - JWT 인증 필터 구현하기]]></title>
            <link>https://velog.io/@du-log/Spring-Security-JWT-%EC%9D%B8%EC%A6%9D-%ED%95%84%ED%84%B0-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@du-log/Spring-Security-JWT-%EC%9D%B8%EC%A6%9D-%ED%95%84%ED%84%B0-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 05 Sep 2025 07:40:23 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-jwt-인증-과정-개요">📌 JWT 인증 과정 개요</h2>
<p>Spring Security에서 <strong>JWT(JSON Web Token) 인증</strong>은 일반적으로 다음 단계를 거칩니다.</p>
<ol>
<li><strong>클라이언트 로그인 요청 → 서버에서 JWT 발급.</strong></li>
<li><strong>클라이언트 요청 시 헤더에 JWT 포함</strong> (<code>Authorization: Bearer &lt;토큰&gt;</code>).</li>
<li><strong>서버의 필터(<code>JwtAuthenticationFilter</code>)가 요청을 가로채서:</strong><ul>
<li>헤더에서 토큰 추출</li>
<li>유효성 검증</li>
<li>사용자 정보 로드</li>
<li><code>SecurityContext</code>에 인증 객체 저장</li>
</ul>
</li>
<li><strong>이후 컨트롤러에서 <code>@AuthenticationPrincipal</code> 등을 통해 인증된 사용자 정보를 활용.</strong></li>
</ol>
<p>이 필터는 모든 HTTP 요청을 가로채 JWT를 검증하고 인증 여부를 확인하는 핵심적인 역할을 합니다.</p>
<hr>
<h2 id="📝-코드-전체">📝 코드 전체</h2>
<pre><code class="language-java">package com.example.demo.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.*;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.*;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwt;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws ServletException, IOException {

        // 1. 요청 헤더에서 Authorization 값 가져오기
        String header = req.getHeader(&quot;Authorization&quot;);

        // 2. &quot;Bearer &quot;로 시작하는지 확인
        if (header != null &amp;&amp; header.startsWith(&quot;Bearer &quot;)) {
            String token = header.substring(7);

            // 3. 토큰 유효성 검증
            if (jwt.validate(token)) {
                // 4. 토큰에서 사용자 이름(username) 추출
                String username = jwt.getUsername(token);

                // 5. DB에서 사용자 정보 로드
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                // 6. 사용자 정보 기반 인증 객체 생성
                UsernamePasswordAuthenticationToken auth =
                        new UsernamePasswordAuthenticationToken(
                                userDetails, null, userDetails.getAuthorities());

                // 7. SecurityContext에 인증 객체 저장
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }

        // 8. 다음 필터/컨트롤러로 요청 전달
        chain.doFilter(req, res);
    }
}</code></pre>
<hr>
<h2 id="🔍-세부-설명">🔍 세부 설명</h2>
<h3 id="1-클래스-선언부">1. 클래스 선언부</h3>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter</code></pre>
<ul>
<li><code>@Component</code>: 이 클래스를 <strong>Spring Bean</strong>으로 등록하여 Spring IoC 컨테이너가 관리하게 합니다.</li>
<li><code>OncePerRequestFilter</code>: 한 번의 요청(<code>request</code>)에 대해 <strong>단 한 번만 실행되는 필터</strong>를 보장합니다.</li>
<li><code>@RequiredArgsConstructor</code>: <code>final</code> 필드(<code>jwt</code>, <code>userDetailsService</code>)를 인자로 받는 생성자를 자동으로 생성하여 **의존성 주입(DI)**을 처리합니다.</li>
</ul>
<h3 id="2-authorization-헤더-처리">2. <code>Authorization</code> 헤더 처리</h3>
<pre><code class="language-java">String header = req.getHeader(&quot;Authorization&quot;);
if (header != null &amp;&amp; header.startsWith(&quot;Bearer &quot;)) {
    String token = header.substring(7);
}</code></pre>
<p>클라이언트가 전송한 HTTP 요청의 <code>Authorization</code> 헤더에서 JWT를 추출합니다. 일반적으로 JWT는 <code>&quot;Bearer &lt;토큰값&gt;&quot;</code> 형식으로 전달되므로, <code>&quot;Bearer &quot;</code> 접두어를 제거하고 순수 토큰 값만 얻습니다.</p>
<h3 id="3-jwt-검증-및-사용자-이름-추출">3. JWT 검증 및 사용자 이름 추출</h3>
<pre><code class="language-java">if (jwt.validate(token)) {
    String username = jwt.getUsername(token);
}</code></pre>
<p>**<code>JwtTokenProvider</code>**를 사용하여 토큰의 유효성을 검사합니다. 토큰이 유효하다면, 토큰 내에 저장된 **사용자 이름(<code>Subject</code>)**을 추출합니다.</p>
<h3 id="4-사용자-정보-로드">4. 사용자 정보 로드</h3>
<pre><code class="language-java">UserDetails userDetails = userDetailsService.loadUserByUsername(username);</code></pre>
<p>**<code>UserDetailsService</code>**는 Spring Security에서 사용자 정보를 제공하는 핵심 인터페이스입니다. 이를 통해 추출한 사용자 이름으로 데이터베이스나 다른 저장소에서 사용자 계정 정보를 <code>UserDetails</code> 객체로 가져옵니다.</p>
<h3 id="5-인증-객체-생성-및-securitycontext-저장">5. 인증 객체 생성 및 <code>SecurityContext</code> 저장</h3>
<pre><code class="language-java">UsernamePasswordAuthenticationToken auth =
        new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities());

SecurityContextHolder.getContext().setAuthentication(auth);</code></pre>
<p>**인증 객체(<code>Authentication</code>)**를 생성하고 <code>SecurityContextHolder</code>에 저장합니다.</p>
<ul>
<li>첫 번째 인자: <strong>Principal (사용자 정보)</strong></li>
<li>두 번째 인자: <strong>Credentials (비밀번호)</strong> - 토큰 기반 인증이므로 <code>null</code>로 설정 가능.</li>
<li>세 번째 인자: <strong>Authorities (권한 목록)</strong></li>
</ul>
<p>이렇게 <code>SecurityContextHolder</code>에 저장된 인증 정보는 이후 컨트롤러나 서비스 계층에서 전역적으로 접근할 수 있습니다.</p>
<hr>
<h2 id="✅-정리--배운-점">✅ 정리 / 배운 점</h2>
<ul>
<li><code>JwtAuthenticationFilter</code>는 모든 요청을 가로채 JWT를 검증하는 <strong>핵심 필터</strong>입니다.</li>
<li>인증에 성공하면 <code>SecurityContext</code>에 인증 객체를 저장하여 Spring Security 전역에서 <strong>인증 상태를 유지</strong>합니다.</li>
<li>이 덕분에 컨트롤러 단에서는 별도의 인증 로직 없이 <code>@AuthenticationPrincipal</code> 등을 활용하여 <strong>인증된 사용자 정보에 손쉽게 접근</strong>할 수 있습니다.</li>
</ul>
<hr>
<h2 id="🚀-확장">🚀 확장</h2>
<ul>
<li><strong>토큰이 유효하지 않을 때</strong> <strong><code>401 Unauthorized</code></strong> 응답을 반환하도록 예외 처리를 추가하여 필터를 개선할 수 있습니다.</li>
<li><strong>Refresh Token과 Access Token</strong>을 함께 사용하는 방식으로 더 안전한 인증 구조를 설계할 수 있습니다.</li>
<li>로그아웃 시 토큰을 무효화하는 <strong>JWT 블랙리스트</strong> 기능을 추가하여 보안을 강화할 수 있습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔑 Spring Boot에서 JWT(Json Web Token) 인증 구현하기
]]></title>
            <link>https://velog.io/@du-log/Spring-Boot%EC%97%90%EC%84%9C-JWTJson-Web-Token-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@du-log/Spring-Boot%EC%97%90%EC%84%9C-JWTJson-Web-Token-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 05 Sep 2025 06:43:21 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-jwt란-무엇인가">📌 JWT란 무엇인가?</h2>
<p>JWT(Json Web Token)는 JSON 형식으로 사용자 정보를 안전하게 전달하기 위한 토큰 기반 인증 방식입니다. 사용자가 로그인하면 서버는 JWT를 발급하고, 이후 요청마다 클라이언트는 이 토큰을 헤더에 담아 보냅니다. 서버는 별도의 세션 저장소 없이도 토큰만으로 사용자를 인증할 수 있습니다.</p>
<hr>
<h2 id="📦-jwt의-구조">📦 JWT의 구조</h2>
<p>JWT는 <code>.</code>으로 구분된 세 부분으로 이루어집니다.</p>
<p><code>xxxxx.yyyyy.zzzzz</code></p>
<ul>
<li><p><strong>Header (헤더)</strong>
토큰의 타입(JWT)과 해싱 알고리즘(예: HS256) 정보를 담습니다.</p>
<pre><code class="language-json">{
  &quot;alg&quot;: &quot;HS256&quot;,
  &quot;typ&quot;: &quot;JWT&quot;
}</code></pre>
</li>
<li><p><strong>Payload (페이로드)</strong>
실제 담고 싶은 사용자 정보(Claims)입니다.
예: 사용자 이름, 권한, 만료 시간 등.</p>
<pre><code class="language-json">{
  &quot;sub&quot;: &quot;user123&quot;,
  &quot;iat&quot;: 1691234567,
  &quot;exp&quot;: 1691238167
}</code></pre>
</li>
<li><p><strong>Signature (서명)</strong>
Header와 Payload를 비밀 키(Secret Key)로 서명한 값입니다. 토큰 위·변조 여부를 검증할 때 사용됩니다.</p>
</li>
</ul>
<hr>
<h2 id="🛠-코드-소개">🛠 코드 소개</h2>
<p>이번에 작성한 <code>JwtTokenProvider</code> 클래스는 Spring Security에서 JWT를 생성하고 검증하는 핵심 역할을 담당합니다. 구체적으로는 다음과 같은 역할을 수행합니다.</p>
<ul>
<li><strong>토큰 생성 (<code>generateToken</code>)</strong></li>
<li><strong>토큰에서 사용자 이름 추출 (<code>getUsername</code>)</strong></li>
<li><strong>토큰 유효성 검증 (<code>validate</code>)</strong></li>
</ul>
<hr>
<h2 id="📝-코드-전체">📝 코드 전체</h2>
<pre><code class="language-java">package com.example.demo.security;

import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;

@Component
public class JwtTokenProvider {

    // HMAC-SHA 알고리즘을 위한 시크릿 키와 토큰 만료 시간
    private final SecretKey key;
    private final long expirationMs;

    // 생성자: application.yml에서 시크릿 키와 만료 시간 주입
    public JwtTokenProvider(
            @Value(&quot;${jwt.secret}&quot;) String secret,
            @Value(&quot;${jwt.expiration-ms}&quot;) long expirationMs
    ) {
        byte[] keyBytes = (secret.matches(&quot;^[A-Za-z0-9+/=]+$&quot;) ? Decoders.BASE64.decode(secret) : secret.getBytes());
        this.key = Keys.hmacShaKeyFor(keyBytes);
        this.expirationMs = expirationMs;
    }
    // 핵심: 토큰을 만들고 검증하는 데 필요한 시크릿 키와 만료 시간을 application.yml에서 가져와서 준비하는 과정

    // ✅ JWT 토큰 생성
    public String generateToken(UserDetails user) {
        Date now = new Date();
        Date exp = new Date(now.getTime() + expirationMs);

        return Jwts.builder()
                .setSubject(user.getUsername())   // 토큰의 주체(사용자 이름)
                .setIssuedAt(now)                 // 토큰 발행 시간
                .setExpiration(exp)               // 토큰 만료 시간
                .signWith(key, Jwts.SIG.HS256)    // 시크릿 키로 HS256 알고리즘을 사용하여 토큰에 서명
                .compact();                       // 최종적으로 문자열로 압축하여 반환
    }
    // 핵심: 토큰에 사용자 이름, 발행 시간, 만료 시간 등의 정보를 담고, 시크릿 키로 서명하여 안전하게 생성하고 문자열 형태로 변환하는 과정

    // ✅ 토큰에서 사용자 이름 추출
    public String getUsername(String token) {
        return Jwts.parser()
                .verifyWith(key)                 // 토큰의 서명을 시크릿 키로 검증
                .build()
                .parseSignedClaims(token)        // 토큰을 파싱하고 클레임(정보)을 가져옴
                .getPayload()
                .getSubject();                   // 토큰의 주체(사용자 이름) 반환
    }
    // 핵심: 토큰이 유효한지 먼저 확인한 다음, 그 안에 담긴 사용자 이름을 추출하는 과정

    // ✅ 토큰 유효성 검증
    public boolean validate(String token) {
        try {
            // 토큰을 파싱하고 서명을 검증
            Jwts.parser().verifyWith(key).build().parseSignedClaims(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            // 토큰이 잘못되었거나 만료된 경우 예외 발생
            return false;
        }
    }

    // 토큰 만료 시간 반환
    public long getExpirationMs() {
        return expirationMs;
    }
}</code></pre>
<hr>
<h2 id="🔍-세부-설명">🔍 세부 설명</h2>
<h3 id="1-생성자">1. 생성자</h3>
<pre><code class="language-java">public JwtTokenProvider(@Value(&quot;${jwt.secret}&quot;) String secret, @Value(&quot;${jwt.expiration-ms}&quot;) long expirationMs)</code></pre>
<p><code>application.yml</code>에서 <code>jwt.secret</code>과 <code>jwt.expiration-ms</code> 값을 읽어옵니다. 이 값들로 HMAC-SHA256 알고리즘에 맞는 <code>SecretKey</code> 객체를 생성하여 토큰 서명에 사용합니다.</p>
<h3 id="2-토큰-생성-generatetoken">2. 토큰 생성 (<code>generateToken</code>)</h3>
<pre><code class="language-java">public String generateToken(UserDetails user)</code></pre>
<p><code>user.getUsername()</code>을 토큰의 Subject로 설정하고, 발행 시간(<code>IssuedAt</code>)과 만료 시간(<code>Expiration</code>)을 지정합니다. <code>signWith(key, Jwts.SIG.HS256)</code>를 통해 시크릿 키 기반으로 서명하여 위변조를 방지합니다. 최종적으로 문자열 형태의 JWT를 반환합니다.</p>
<h3 id="3-토큰에서-사용자-이름-추출-getusername">3. 토큰에서 사용자 이름 추출 (<code>getUsername</code>)</h3>
<pre><code class="language-java">public String getUsername(String token)</code></pre>
<p>토큰을 파싱하고 서명을 검증한 뒤, Payload의 Subject(사용자 이름)를 꺼내옵니다.</p>
<h3 id="4-토큰-유효성-검증-validate">4. 토큰 유효성 검증 (<code>validate</code>)</h3>
<pre><code class="language-java">public boolean validate(String token)</code></pre>
<p><code>parseSignedClaims(token)</code> 실행 시 예외가 발생하지 않으면 <code>true</code>를 반환합니다. 만료되었거나 위조된 경우 <code>JwtException</code>이 발생하여 <code>false</code>를 반환합니다.</p>
<hr>
<h2 id="✅-정리-및-배운-점">✅ 정리 및 배운 점</h2>
<ul>
<li><strong>JWT</strong>는 세션 저장소 없이 인증 상태를 유지할 수 있는 강력한 방법입니다.</li>
<li><code>JwtTokenProvider</code>는 토큰 발급과 검증 로직을 담당하며 Spring Security와 자연스럽게 연동됩니다.</li>
<li>보안을 위해 시크릿 키는 반드시 <strong>환경 변수</strong>로 관리해야 하며, 외부에 노출되지 않도록 주의해야 합니다.</li>
</ul>
<hr>
<h2 id="🚀-확장">🚀 확장</h2>
<ul>
<li><strong>Refresh Token</strong> 발급 및 재발급 기능 추가: Access Token 만료 시 사용자 경험을 개선할 수 있습니다.</li>
<li><strong>권한(Role) 정보</strong>를 토큰에 함께 저장하여 Spring Security의 <code>Authentication</code> 객체로 변환할 수 있습니다.</li>
<li><strong>예외 처리 강화</strong>: 단순히 <code>true</code>/<code>false</code> 대신 &quot;만료됨 / 위조됨 / 형식 오류&quot; 등 구체적인 응답을 제공하여 클라이언트가 문제를 정확히 파악할 수 있도록 돕습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발 생산성을 10배 높여주는 Lombok 어노테이션 총정리]]></title>
            <link>https://velog.io/@du-log/%EA%B0%9C%EB%B0%9C-%EC%83%9D%EC%82%B0%EC%84%B1%EC%9D%84-10%EB%B0%B0-%EB%86%92%EC%97%AC%EC%A3%BC%EB%8A%94-Lombok-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%B4%9D%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@du-log/%EA%B0%9C%EB%B0%9C-%EC%83%9D%EC%82%B0%EC%84%B1%EC%9D%84-10%EB%B0%B0-%EB%86%92%EC%97%AC%EC%A3%BC%EB%8A%94-Lombok-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%B4%9D%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 23 Aug 2025 07:16:09 GMT</pubDate>
            <description><![CDATA[<p> 이번 포스팅에서는 <strong>Lombok</strong> 라이브러리가 제공하는 어노테이션들을 예제 코드와 함께 자세히 알아보겠습니다.</p>
<hr>
<h3 id="1-생성자-어노테이션-객체-생성-방식을-자유롭게"><strong>1. 생성자 어노테이션: 객체 생성 방식을 자유롭게</strong></h3>
<p>객체를 생성하는 방법은 여러 가지가 있으며, Lombok은 각 상황에 맞는 생성자를 자동으로 만들어줍니다.</p>
<h4 id="noargsconstructor-기본-생성자"><strong><code>@NoArgsConstructor</code> (기본 생성자)</strong></h4>
<p>인자가 없는 기본 생성자를 만듭니다.</p>
<ul>
<li><p><strong>사용 목적</strong>:</p>
<ul>
<li><strong>JPA(Java Persistence API)</strong>: JPA는 데이터베이스에서 엔티티를 조회할 때 리플렉션(Reflection)을 통해 객체를 생성하는데, 이때 기본 생성자가 반드시 필요합니다.</li>
<li><strong>JSON 직렬화/역직렬화</strong>: Jackson이나 Gson 같은 라이브러리가 JSON 데이터를 객체로 변환할 때도 기본 생성자를 사용합니다.</li>
</ul>
</li>
<li><p><strong>예제</strong>:</p>
</li>
</ul>
<!-- end list -->

<pre><code class="language-java">@NoArgsConstructor
public class Book {
    private String title;
    private String author;
}

// Lombok이 생성하는 코드:
// public Book() {}</code></pre>
<h4 id="allargsconstructor-모든-필드-생성자"><strong><code>@AllArgsConstructor</code> (모든 필드 생성자)</strong></h4>
<p>클래스의 <strong>모든 필드를 인자로 받는 생성자</strong>를 만듭니다.</p>
<ul>
<li><strong>사용 목적</strong>:<ul>
<li>객체를 생성하는 시점에 모든 필드를 한 번에 초기화할 때 유용합니다.</li>
</ul>
</li>
<li><strong>예제</strong>:</li>
</ul>
<!-- end list -->

<pre><code class="language-java">@AllArgsConstructor
public class Book {
    private String title;
    private String author;
}

// Lombok이 생성하는 코드:
// public Book(String title, String author) {
//     this.title = title;
//     this.author = author;
// }</code></pre>
<h4 id="requiredargsconstructor-필수-필드-생성자"><strong><code>@RequiredArgsConstructor</code> (필수 필드 생성자)</strong></h4>
<p><code>final</code>로 선언되었거나 <code>@NonNull</code> 어노테이션이 붙은 <strong>필수 필드만을 인자로 받는 생성자</strong>를 만듭니다.</p>
<ul>
<li><p><strong>사용 목적</strong>:</p>
<ul>
<li><strong>의존성 주입(Dependency Injection)</strong>: 스프링(Spring) 프레임워크에서 가장 흔히 사용되는 방식으로, 반드시 필요한 의존성만 모아 생성자를 만들 수 있어 안정적입니다.</li>
<li><strong>불변 객체</strong>: <code>final</code> 필드를 사용해 불변 객체를 생성할 때 매우 유용합니다.</li>
</ul>
</li>
<li><p><strong>예제</strong>:</p>
</li>
</ul>
<!-- end list -->

<pre><code class="language-java">@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;

    @NonNull
    private String version;
}

// Lombok이 생성하는 코드:
// public UserService(UserRepository userRepository, String version) {
//     this.userRepository = userRepository;
//     this.version = version;
// }</code></pre>
<h4 id="builder-빌더-패턴"><strong><code>@Builder</code> (빌더 패턴)</strong></h4>
<p>가장 강력하고 유연한 생성 방식으로, <strong>빌더 패턴</strong>을 자동으로 구현해 줍니다.</p>
<ul>
<li><p><strong>사용 목적</strong>:</p>
<ul>
<li><strong>필드가 많은 경우</strong>: 필드가 5개 이상일 때 <code>@AllArgsConstructor</code>보다 훨씬 명확하고 안전합니다.</li>
<li><strong>선택적 필드</strong>: 모든 필드가 필수적이지 않고, 일부 필드만 설정하고 싶을 때 사용하면 코드가 깔끔해집니다.</li>
</ul>
</li>
<li><p><strong>예제</strong>:</p>
</li>
</ul>
<!-- end list -->

<pre><code class="language-java">@Builder
public class UserProfile {
    private String name;
    private int age;
    private String email;
    private boolean isPublic;
}

// 사용 방법:
UserProfile profile = UserProfile.builder()
                        .name(&quot;Alice&quot;)
                        .age(30)
                        .isPublic(true) // 이메일은 선택적으로 생략 가능
                        .build();</code></pre>
<hr>
<h3 id="2-보일러플레이트boilerplate-코드-제거"><strong>2. 보일러플레이트(Boilerplate) 코드 제거</strong></h3>
<p>매일 반복해서 작성하는 Getter, Setter, <code>toString()</code> 같은 메서드들도 Lombok을 사용하면 한 줄로 해결할 수 있습니다.</p>
<h4 id="getter--setter"><strong><code>@Getter</code> &amp; <code>@Setter</code></strong></h4>
<p>필드에 대한 <strong>Getter와 Setter 메서드</strong>를 자동으로 생성해줍니다.</p>
<ul>
<li><p><strong>사용 목적</strong>:</p>
<ul>
<li><strong><code>@Getter</code></strong>: 객체의 데이터를 읽어와야 할 때 주로 사용합니다.</li>
<li><strong><code>@Setter</code></strong>: 외부에서 필드의 값을 변경해야 할 때 사용합니다. 불변 객체를 만들 때는 <code>@Setter</code>를 사용하지 않는 것이 좋습니다.</li>
</ul>
</li>
<li><p><strong>예제</strong>:</p>
</li>
</ul>
<!-- end list -->

<pre><code class="language-java">public class Product {
    @Getter @Setter private String name;
    @Getter private int price; // price는 변경 불가능하게 설정
}

// Lombok이 생성하는 코드:
// public String getName() { return name; }
// public void setName(String name) { this.name = name; }
// public int getPrice() { return price; }</code></pre>
<h4 id="tostring"><strong><code>@ToString</code></strong></h4>
<p>객체의 필드 값들을 포함하는 <strong><code>toString()</code> 메서드</strong>를 자동으로 만들어줍니다.</p>
<ul>
<li><p><strong>사용 목적</strong>:</p>
<ul>
<li><strong>디버깅</strong>: 객체의 현재 상태를 로그로 출력하거나 디버깅 툴에서 확인할 때 매우 편리합니다.</li>
<li><strong>예외 처리</strong>: 예외 로그에 객체 상태를 포함시켜 문제를 빠르게 파악할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>예제</strong>:</p>
</li>
</ul>
<!-- end list -->

<pre><code class="language-java">@ToString(exclude = &quot;password&quot;) // password 필드는 제외
public class User {
    private String username;
    private String password;
    private String email;
}

// User 객체 출력 시:
// User(username=johndoe, email=john@example.com)</code></pre>
<h4 id="equalsandhashcode"><strong><code>@EqualsAndHashCode</code></strong></h4>
<p><code>equals()</code>와 <code>hashCode()</code> 메서드를 자동으로 오버라이딩합니다.</p>
<ul>
<li><strong>사용 목적</strong>:<ul>
<li><strong>객체 비교</strong>: 두 객체가 논리적으로 동일한지 비교해야 할 때 사용합니다.</li>
<li><strong>컬렉션 사용</strong>: <code>HashSet</code>이나 <code>HashMap</code> 같은 컬렉션에 객체를 저장할 때, <code>hashCode()</code>를 이용해 성능을 높일 수 있습니다.</li>
</ul>
</li>
<li><strong>예제</strong>:</li>
</ul>
<!-- end list -->

<pre><code class="language-java">@EqualsAndHashCode
public class Point {
    private int x;
    private int y;
}

// 두 Point 객체 비교
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
p1.equals(p2); // true</code></pre>
<h4 id="data-최강의-종합-어노테이션"><strong><code>@Data</code> (최강의 종합 어노테이션)</strong></h4>
<p><code>@Data</code>는 <code>@Getter</code>, <code>@Setter</code>, <code>@ToString</code>, <code>@EqualsAndHashCode</code>, <code>@RequiredArgsConstructor</code>를 <strong>모두 포함</strong>하는 종합 선물 세트입니다.</p>
<ul>
<li><p><strong>사용 목적</strong>:</p>
<ul>
<li><strong>DTO(Data Transfer Object)</strong>: 데이터를 담아 계층 간에 전달하는 역할을 하는 DTO 클래스에 주로 사용됩니다.</li>
</ul>
</li>
<li><p><strong>주의점</strong>: 엔티티 클래스에서는 데이터베이스 테이블의 컬럼을 직접 변경하는 <code>@Setter</code>를 함부로 사용하는 것을 지양해야 합니다.</p>
</li>
<li><p><strong>예제</strong>:</p>
</li>
</ul>
<!-- end list -->

<pre><code class="language-java">@Data
public class PostDto {
    private String title;
    private String content;
}</code></pre>
<hr>
<h3 id="마치며"><strong>마치며</strong></h3>
<p>이러한 Lombok 어노테이션들은 개발자가 <strong>비즈니스 로직</strong>에 더 집중할 수 있게 도와줍니다.  코드를 간결하게 만들고 유지보수를 쉽게 하는 어노테이션을 잘 활용하여 생산성을 높여 보세요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 302 리다이렉트부터 403 에러까지]]></title>
            <link>https://velog.io/@du-log/Spring-Security-302-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8%EB%B6%80%ED%84%B0-403-%EC%97%90%EB%9F%AC%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@du-log/Spring-Security-302-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8%EB%B6%80%ED%84%B0-403-%EC%97%90%EB%9F%AC%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Fri, 08 Aug 2025 01:37:20 GMT</pubDate>
            <description><![CDATA[<p>스프링 시큐리티(Spring Security)는 자바 개발자에게 필수적인 보안 프레임워크입니다. 하지만 처음 접하면 설정의 복잡성 때문에 <code>302 Found</code>, <code>403 Forbidden</code> 같은 이해하기 어려운 오류들과 자주 마주하게 되죠. 제가 직접 겪었던 디버깅 과정을 작성하고자 합니다.</p>
<h3 id="1-첫-번째-난관-api-요청에-뜨는-302-found-그-정체는">1. 첫 번째 난관: API 요청에 뜨는 <code>302 Found</code>, 그 정체는?</h3>
<p>저는 <code>/api/posts</code>라는 새로운 REST API를 만들고 Postman으로 <code>POST</code> 요청을 보냈습니다. 그런데 예상과 달리 <code>200 OK</code> 대신 <code>302 Found</code> 오류가 발생했고, 응답 헤더의 <code>Location</code> 필드는 저를 <code>/login</code> 페이지로 안내하고 있었습니다.</p>
<hr>
<h4 id="📌-문제-분석-formlogin의-함정">📌 문제 분석: <code>formLogin</code>의 함정</h4>
<p>스프링 시큐리티는 기본적으로 웹 애플리케이션에 최적화되어 있습니다. 따라서 **<code>formLogin</code>**이라는 필터가 자동으로 활성화되는데, 이 필터의 주된 역할은 사용자가 인증되지 않았을 때 로그인 페이지로 리다이렉트시키는 것입니다.</p>
<p>포스트맨으로 보낸 API 요청은 당연히 로그인 정보를 담고 있지 않았습니다. 따라서 <code>formLogin</code> 필터는 요청을 차단하고 &quot;로그인하세요!&quot;라는 의미로 저를 <code>/login</code> 페이지로 보내버린 것이죠. API는 HTML 페이지를 받아 리다이렉트하는 것이 아닌, 상태 코드와 데이터만 주고받아야 하는데 말이죠.</p>
<ul>
<li><strong>해결책의 시작</strong>: API와 웹 애플리케이션의 보안 설정을 분리해야 한다는 것을 깨달았습니다. 스프링 시큐리티는 **여러 개의 <code>SecurityFilterChain</code> 빈(Bean)**을 허용하므로, API만을 위한 필터 체인과 웹을 위한 필터 체인을 따로 만들기로 했습니다. API용 필터 체인에는 <code>formLogin</code>을 비활성화하는 <code>formLogin(form -&gt; form.disable())</code> 설정을 추가했습니다.</li>
</ul>
<hr>
<h3 id="2-두-번째-난관-302가-사라지니-403-forbidden이-나타났다">2. 두 번째 난관: <code>302</code>가 사라지니 <code>403 Forbidden</code>이 나타났다</h3>
<p><code>formLogin</code> 필터를 비활성화하자 마침내 <code>302</code> 오류가 사라졌습니다. 하지만 이번에는 <code>403 Forbidden</code> 오류가 저를 기다리고 있었습니다. <code>SecurityConfig</code>에 <code>/api/**</code> 경로를 <code>permitAll()</code>로 설정했음에도 말이죠.</p>
<hr>
<h4 id="📌-문제-분석-authenticationprincipal과-permitall의-충돌">📌 문제 분석: <code>@AuthenticationPrincipal</code>과 <code>permitAll()</code>의 충돌</h4>
<p><code>403 Forbidden</code>은 &quot;인증은 되었지만, 해당 리소스에 접근할 권한이 없다&quot;는 의미의 오류입니다. 그런데 저는 로그인한 적이 없으니 인증이 될 리가 없는데, 왜 이 오류가 발생했을까요? 원인은 바로 <strong>API 컨트롤러 메서드</strong>에 있었습니다.</p>
<pre><code class="language-java">// PostRestController.java
@PostMapping
public String createPost(@RequestBody PostCreateRequestDto dto,
                         @AuthenticationPrincipal UserDetails userDetails) {
    // ... userDetails.getUsername() 사용 ...
}</code></pre>
<ul>
<li><p><code>permitAll()</code>은 <strong>&#39;로그인 여부와 관계없이&#39;</strong> 접근을 허용합니다. 따라서 요청은 컨트롤러 메서드까지 도달했습니다.</p>
</li>
<li><p>하지만 메서드의 <strong><code>@AuthenticationPrincipal</code></strong> 파라미터는 **&#39;로그인한 사용자의 정보&#39;**를 요구합니다.</p>
</li>
<li><p>익명 사용자가 접근했기 때문에 <code>userDetails</code>가 <code>null</code>이 되었고, <code>userDetails.getUsername()</code> 같은 코드를 실행하는 순간 <code>NullPointerException</code>이 발생합니다.</p>
</li>
<li><p>스프링 시큐리티는 이 예외를 가로채서 <code>403</code> 상태 코드로 변환한 것입니다.</p>
</li>
<li><p><strong>해결책</strong>: <code>createPost</code> 메서드에서 <strong><code>@AuthenticationPrincipal</code> 파라미터를 제거</strong>하고, 게시글 작성자를 임시로 데이터베이스에 있는 특정 사용자(<code>userRepository.findById(1L)</code>)로 지정했습니다. 이 API는 익명 사용자의 글쓰기를 허용하는 API였으므로, 로그인 정보를 요구하는 로직 자체가 잘못된 것이었죠.</p>
</li>
</ul>
<hr>
<h3 id="3-세-번째-난관-돌아온-302와-필터-체인-우선순위">3. 세 번째 난관: 돌아온 <code>302</code>와 필터 체인 우선순위</h3>
<p>위의 두 가지 문제를 해결하고 다시 실행했을 때, <code>302 Found</code> 오류가 또다시 저를 괴롭혔습니다. <code>SecurityFilterChain</code>을 분리하고 <code>@Order</code>를 부여했음에도 말이죠.</p>
<hr>
<h4 id="📌-문제-분석-매칭-순서의-함정">📌 문제 분석: 매칭 순서의 함정</h4>
<p>스프링 시큐리티는 여러 개의 <code>SecurityFilterChain</code> 빈이 있을 때, 요청 URL에 맞는 <strong>가장 구체적인 필터</strong>를 먼저 적용합니다. 하지만 제 코드에서는 이 순서가 예상과 다르게 작동하고 있었습니다.</p>
<pre><code class="language-java">// apiFilterChain
http.securityMatcher(&quot;/api/**&quot;) // API 전용 필터
// ...
// webFilterChain
http.securityMatcher(&quot;/**&quot;) // 모든 경로에 대한 필터
// ...</code></pre>
<p>이 두 필터는 <code>POST /api/posts</code> 요청에 모두 매칭될 수 있습니다. Spring Boot는 <code>apiFilterChain</code>이 더 구체적이므로 먼저 적용해야 하지만, 내부적인 로딩 순서나 설정 미스로 인해 <code>webFilterChain</code>이 먼저 동작하는 경우가 발생할 수 있습니다. <code>webFilterChain</code>은 <code>formLogin</code> 필터를 포함하고 있으므로, API 요청을 가로채 다시 <code>302</code> 리다이렉트를 일으킨 것입니다.</p>
<ul>
<li><strong>최종 해결책</strong>: <code>apiFilterChain</code>을 <strong>가장 단순하고 명확하게</strong> 만들었습니다. 다른 설정들과 충돌할 여지가 있는 <code>anyRequest().authenticated()</code>를 제거하고, <code>anyRequest().permitAll()</code>만 남겼습니다. 또한, <code>apiFilterChain</code>에 <code>formLogin(form -&gt; form.disable())</code>을 명시적으로 추가하여, 설령 요청이 잘못 매칭되더라도 로그인 페이지로 리다이렉트되는 것을 원천적으로 차단했습니다.</li>
</ul>
<!-- end list -->

<pre><code class="language-java">@Configuration
@EnableWebSecurity
public class SecurityConfig {
    // ...
    @Bean
    @Order(1) // 우선순위 1
    public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
        http
                .securityMatcher(&quot;/api/**&quot;)
                .csrf(csrf -&gt; csrf.disable())
                .authorizeHttpRequests(auth -&gt; auth.anyRequest().permitAll()) // ✅ API 요청은 무조건 허용
                .formLogin(form -&gt; form.disable()) // ✅ 로그인 폼 비활성화
                .httpBasic(basic -&gt; basic.disable());
        return http.build();
    }
    // ...
}</code></pre>
<hr>
<h3 id="최종-결론-200-ok를-만나다-🎉">최종 결론: <code>200 OK</code>를 만나다! 🎉</h3>
<p>수많은 오류와 디버깅 끝에 마침내 <code>200 OK</code> 응답을 받았습니다. 이 경험을 통해 스프링 시큐리티 디버깅의 핵심 원칙을 깨달았습니다.</p>
<ul>
<li><code>302 Found</code> 오류는 단순한 리다이렉트 문제가 아니라, <strong>내부 로직의 충돌이나 예외가 겉으로 드러난 현상</strong>일 수 있습니다.</li>
<li>API와 웹 애플리케이션을 함께 개발할 때는 반드시 <strong><code>SecurityFilterChain</code>을 분리</strong>하고 <code>@Order</code>를 사용해 우선순위를 명확히 해야 합니다.</li>
<li>API용 필터 체인은 <code>formLogin</code>, <code>httpBasic</code> 등 인증 필터를 <strong>명시적으로 비활성화</strong>하는 것이 가장 안전합니다.</li>
</ul>
<p>이 글이 여러분의 <code>302 Found</code> 디버깅에 큰 도움이 되기를 바랍니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JDBC] 기본 구성 요소 및 문법]]></title>
            <link>https://velog.io/@du-log/JDBC-%EA%B8%B0%EB%B3%B8-%EA%B5%AC%EC%84%B1-%EC%9A%94%EC%86%8C-%EB%B0%8F-%EB%AC%B8%EB%B2%95</link>
            <guid>https://velog.io/@du-log/JDBC-%EA%B8%B0%EB%B3%B8-%EA%B5%AC%EC%84%B1-%EC%9A%94%EC%86%8C-%EB%B0%8F-%EB%AC%B8%EB%B2%95</guid>
            <pubDate>Fri, 06 Jun 2025 00:55:50 GMT</pubDate>
            <description><![CDATA[<p><strong>JDBC (Java Database Connectivity)</strong> API를 사용하는 방법이 궁금하여 정리해보고자 합니다. JDBC는 Java 애플리케이션이 다양한 관계형 데이터베이스와 상호작용할 수 있도록 표준화된 방법을 제공합니다.</p>
<hr>
<h3 id="jdbc-기본-구성-요소-및-문법">JDBC 기본 구성 요소 및 문법</h3>
<p>JDBC를 사용하여 SQL 쿼리를 실행하려면 다음과 같은 단계를 거칩니다.</p>
<ol>
<li><strong>JDBC 드라이버 로드</strong></li>
<li><strong>데이터베이스 연결 설정</strong></li>
<li><strong>Statement 또는 PreparedStatement 객체 생성</strong></li>
<li><strong>SQL 쿼리 실행</strong></li>
<li><strong>결과 처리 (SELECT 문인 경우)</strong></li>
<li><strong>자원 해제 (Connection, Statement, ResultSet 닫기)</strong></li>
</ol>
<h3 id="1-jdbc-드라이버-로드-classforname">1. JDBC 드라이버 로드 (<code>Class.forName()</code>)</h3>
<p>데이터베이스와 통신하려면 해당 데이터베이스 벤더가 제공하는 JDBC 드라이버를 JVM에 로드해야 합니다.</p>
<pre><code class="language-java">try {
    Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;); // MySQL 드라이버
    // Class.forName(&quot;oracle.jdbc.driver.OracleDriver&quot;); // Oracle 드라이버
    // Class.forName(&quot;org.postgresql.Driver&quot;); // PostgreSQL 드라이버
    System.out.println(&quot;JDBC 드라이버 로드 성공!&quot;);
} catch (ClassNotFoundException e) {
    System.err.println(&quot;JDBC 드라이버를 찾을 수 없습니다: &quot; + e.getMessage());
    e.printStackTrace();
}</code></pre>
<ul>
<li><strong>설명:</strong> <code>Class.forName()</code> 메소드를 사용하여 드라이버 클래스를 메모리에 로드합니다. 이렇게 하면 드라이버가 스스로 등록됩니다. (<code>JDBC 4.0</code> 이상에서는 이 단계가 필수는 아니지만, 명시적으로 로드하는 것이 명확합니다.)</li>
</ul>
<h3 id="2-데이터베이스-연결-설정-drivermanagergetconnection">2. 데이터베이스 연결 설정 (<code>DriverManager.getConnection()</code>)</h3>
<p>로드된 드라이버를 사용하여 특정 데이터베이스에 연결합니다.</p>
<pre><code class="language-java">String url = &quot;jdbc:mysql://localhost:3306/userdb?useSSL=false&amp;serverTimezone=UTC&quot;; // JDBC URL
String user = &quot;root&quot;; // 데이터베이스 사용자 이름
String password = &quot;1234&quot;; // 데이터베이스 비밀번호

Connection conn = null; // Connection 객체 선언

try {
    conn = DriverManager.getConnection(url, user, password);
    System.out.println(&quot;데이터베이스 연결 성공!&quot;);
} catch (SQLException e) {
    System.err.println(&quot;데이터베이스 연결 실패: &quot; + e.getMessage());
    e.printStackTrace();
}</code></pre>
<ul>
<li><strong><code>url</code> (JDBC URL):</strong> 연결할 데이터베이스의 위치와 속성을 지정합니다. <code>jdbc:mysql://</code> 부분은 MySQL 드라이버를 사용한다는 의미이고, <code>localhost:3306</code>은 데이터베이스 서버의 주소와 포트, <code>/userdb</code>는 데이터베이스 이름입니다. <code>?</code> 이후는 추가 옵션입니다.</li>
<li><strong><code>user</code>, <code>password</code>:</strong> 데이터베이스 접속에 필요한 사용자 이름과 비밀번호입니다.</li>
<li><strong><code>Connection</code> 객체:</strong> 데이터베이스 세션을 나타냅니다. 모든 SQL 작업은 이 연결을 통해 이루어집니다.</li>
</ul>
<h3 id="3-statement-또는-preparedstatement-객체-생성">3. Statement 또는 PreparedStatement 객체 생성</h3>
<p>SQL 쿼리를 실행하기 위한 객체를 생성합니다.</p>
<ul>
<li><p><strong><code>Statement</code> (정적 SQL 쿼리용):</strong></p>
<pre><code class="language-java">  Statement stmt = null;
  try {
      stmt = conn.createStatement();
      System.out.println(&quot;Statement 객체 생성 성공!&quot;);
  } catch (SQLException e) {
      e.printStackTrace();
  }</code></pre>
<ul>
<li><strong>용도:</strong> SQL 쿼리 문자열이 실행 시점에 변하지 않는 (매개변수가 없는) 간단한 쿼리에 사용됩니다.</li>
<li><strong>단점:</strong> SQL 인젝션 공격에 취약할 수 있으며, 반복 실행 시 성능 저하가 발생할 수 있습니다.</li>
</ul>
</li>
<li><p><strong><code>PreparedStatement</code> (매개변수 있는 동적 SQL 쿼리용 - 권장):</strong></p>
<pre><code class="language-java">  String sqlInsert = &quot;INSERT INTO users (name, email) VALUES (?, ?)&quot;; // &#39;?&#39; 자리표시자
  PreparedStatement pstmt = null;
  try {
      pstmt = conn.prepareStatement(sqlInsert);
      System.out.println(&quot;PreparedStatement 객체 생성 성공!&quot;);
  } catch (SQLException e) {
      e.printStackTrace();
  }</code></pre>
<ul>
<li><strong>용도:</strong> SQL 쿼리 문자열에 물음표(<code>?</code>)와 같은 자리표시자가 포함되어 실행 시점에 값을 바인딩해야 하는 쿼리에 사용됩니다. <strong>보안(SQL 인젝션 방지) 및 성능 향상</strong> 측면에서 <code>Statement</code>보다 훨씬 권장됩니다.</li>
</ul>
</li>
</ul>
<h3 id="4-sql-쿼리-실행">4. SQL 쿼리 실행</h3>
<p>생성된 <code>Statement</code> 또는 <code>PreparedStatement</code> 객체를 사용하여 SQL 쿼리를 실행합니다.</p>
<ul>
<li><p><strong>INSERT, UPDATE, DELETE (데이터 변경):</strong>
  <code>Statement</code> 사용 시:</p>
<pre><code class="language-java">  String sqlUpdate = &quot;UPDATE users SET email = &#39;new_email@example.com&#39; WHERE name = &#39;Alice&#39;&quot;;
  try {
      int rowsAffected = stmt.executeUpdate(sqlUpdate);
      System.out.println(rowsAffected + &quot; 개의 행이 업데이트되었습니다.&quot;);
  } catch (SQLException e) {
      e.printStackTrace();
  }</code></pre>
<p>  <code>PreparedStatement</code> 사용 시:</p>
<pre><code class="language-java">  String sqlUpdate = &quot;UPDATE users SET email = ? WHERE name = ?&quot;;
  try {
      pstmt = conn.prepareStatement(sqlUpdate);
      pstmt.setString(1, &quot;new_email@example.com&quot;); // 첫 번째 &#39;?&#39;
      pstmt.setString(2, &quot;Alice&quot;); // 두 번째 &#39;?&#39;
      int rowsAffected = pstmt.executeUpdate();
      System.out.println(rowsAffected + &quot; 개의 행이 업데이트되었습니다.&quot;);
  } catch (SQLException e) {
      e.printStackTrace();
  }</code></pre>
<ul>
<li><strong><code>executeUpdate()</code>:</strong> <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>와 같이 데이터베이스의 데이터를 변경하는 SQL 쿼리를 실행할 때 사용합니다. 실행된 행의 수를 <code>int</code> 타입으로 반환합니다.</li>
</ul>
</li>
<li><p><strong>SELECT (데이터 조회):</strong>
  <code>Statement</code> 사용 시:</p>
<pre><code class="language-java">  String sqlSelect = &quot;SELECT id, name, email FROM users WHERE id &gt; 10&quot;;
  ResultSet rs = null;
  try {
      rs = stmt.executeQuery(sqlSelect);
      // 5. 결과 처리 부분으로 이어짐
  } catch (SQLException e) {
      e.printStackTrace();
  }</code></pre>
<p>  <code>PreparedStatement</code> 사용 시:</p>
<pre><code class="language-java">  String sqlSelect = &quot;SELECT id, name, email FROM users WHERE id &gt; ?&quot;;
  ResultSet rs = null;
  try {
      pstmt = conn.prepareStatement(sqlSelect);
      pstmt.setInt(1, 10); // 첫 번째 &#39;?&#39;
      rs = pstmt.executeQuery();
      // 5. 결과 처리 부분으로 이어짐
  } catch (SQLException e) {
      e.printStackTrace();
  }</code></pre>
<ul>
<li><strong><code>executeQuery()</code>:</strong> <code>SELECT</code> 쿼리를 실행하여 데이터를 조회할 때 사용합니다. 쿼리 결과를 <code>ResultSet</code> 객체로 반환합니다.</li>
</ul>
</li>
</ul>
<h3 id="5-결과-처리-resultset---select-문인-경우">5. 결과 처리 (<code>ResultSet</code> - SELECT 문인 경우)</h3>
<p><code>SELECT</code> 쿼리의 결과를 <code>ResultSet</code> 객체로부터 읽어들입니다.</p>
<pre><code class="language-java">// 위에서 얻은 ResultSet rs 객체를 사용
if (rs != null) { // rs가 null이 아닌지 확인 (쿼리 실패 방지)
    try {
        while (rs.next()) { // 다음 행이 존재하는 동안 반복
            int id = rs.getInt(&quot;id&quot;); // &quot;id&quot; 컬럼의 정수 값 가져오기
            String name = rs.getString(&quot;name&quot;); // &quot;name&quot; 컬럼의 문자열 값 가져오기
            String email = rs.getString(&quot;email&quot;); // &quot;email&quot; 컬럼의 문자열 값 가져오기

            // 컬럼 인덱스로도 가져올 수 있습니다. (성능은 약간 좋지만 가독성은 떨어짐)
            // int id = rs.getInt(1); // 첫 번째 컬럼
            // String name = rs.getString(2); // 두 번째 컬럼

            System.out.printf(&quot;ID: %d, 이름: %s, 이메일: %s\n&quot;, id, name, email);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}</code></pre>
<ul>
<li><strong><code>rs.next()</code>:</strong> <code>ResultSet</code>의 커서를 다음 행으로 이동시키고, 다음 행이 존재하면 <code>true</code>, 없으면 <code>false</code>를 반환합니다. <code>while</code> 루프와 함께 사용하여 모든 행을 순회합니다.</li>
<li><strong><code>rs.get데이터타입(&quot;컬럼이름&quot;)</code> 또는 <code>rs.get데이터타입(컬럼인덱스)</code>:</strong> 현재 커서가 가리키는 행에서 지정된 컬럼의 값을 Java 타입으로 가져옵니다.</li>
</ul>
<h3 id="6-자원-해제-connection-statement-resultset-닫기">6. 자원 해제 (Connection, Statement, ResultSet 닫기)</h3>
<p>데이터베이스 자원은 사용 후 반드시 닫아주어 누수를 방지해야 합니다. <code>try-with-resources</code> 문을 사용하는 것이 가장 권장되는 방법입니다.</p>
<pre><code class="language-java">// try-with-resources 사용 (Java 7 이상)
// 괄호 안에 선언된 자원들은 블록이 끝나면 자동으로 close() 메소드가 호출됩니다.
try (Connection conn = DriverManager.getConnection(url, user, password);
     Statement stmt = conn.createStatement(); // 또는 PreparedStatement
     ResultSet rs = stmt.executeQuery(&quot;SELECT * FROM users&quot;)) { // SELECT 쿼리인 경우

    // SQL 쿼리 실행 및 결과 처리 코드

} catch (SQLException e) {
    System.err.println(&quot;SQL 에러 발생: &quot; + e.getMessage());
    e.printStackTrace();
} catch (ClassNotFoundException e) { // Class.forName() 에서 발생 가능
    System.err.println(&quot;드라이버 로드 에러: &quot; + e.getMessage());
    e.printStackTrace();
}</code></pre>
<ul>
<li><strong><code>try-with-resources</code>:</strong> <code>AutoCloseable</code> 인터페이스를 구현하는 객체(여기서는 <code>Connection</code>, <code>Statement</code>, <code>ResultSet</code>)를 <code>try</code> 문 괄호 안에 선언하면, <code>try</code> 블록이 정상적으로 종료되거나 예외가 발생하더라도 해당 객체들의 <code>close()</code> 메소드가 자동으로 호출됩니다. 이 방법이 자원 관리의 가장 안전하고 효율적인 방법입니다.</li>
</ul>
<hr>
<h3 id="전체-예시-코드-select-문">전체 예시 코드 (SELECT 문):</h3>
<pre><code class="language-java">import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement; // 또는 java.sql.Statement
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcSelectExample {

    public static void main(String[] args) {
        String url = &quot;jdbc:mysql://localhost:3306/userdb?useSSL=false&amp;serverTimezone=UTC&quot;;
        String user = &quot;root&quot;;
        String password = &quot;1234&quot;;

        // try-with-resources를 사용하여 자원 자동 해제
        try (Connection conn = DriverManager.getConnection(url, user, password);
             // PreparedStatement 사용 예시 (더 권장)
             PreparedStatement pstmt = conn.prepareStatement(&quot;SELECT id, name, email FROM users WHERE id &gt; ?&quot;)) {

            // 드라이버 로드는 JDBC 4.0+ 에서는 필수가 아니지만, 명시적으로 넣어줄 수 있습니다.
            // Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;); 

            System.out.println(&quot;데이터베이스 연결 성공!&quot;);

            // PreparedStatement에 값 바인딩
            pstmt.setInt(1, 0); // id가 0보다 큰 모든 사용자 (WHERE id &gt; 0)

            // 쿼리 실행 및 결과 받기
            ResultSet rs = pstmt.executeQuery();

            System.out.println(&quot;📝 저장된 사용자 목록:&quot;);
            // ResultSet에서 데이터 한 행씩 읽기
            while (rs.next()) {
                int id = rs.getInt(&quot;id&quot;);
                String name = rs.getString(&quot;name&quot;);
                String email = rs.getString(&quot;email&quot;);
                System.out.printf(&quot;👉 [%d] %s / %s\n&quot;, id, name, email);
            }

        } catch (SQLException e) {
            System.err.println(&quot;SQL 에러 발생: &quot; + e.getMessage());
            e.printStackTrace();
        } 
        // ClassNotFoundException은 DriverManager.getConnection() 호출시 드라이버 로드가
        // 실패했을 때 발생할 수 있지만, 요즘은 대부분의 경우 자동으로 로드되므로 생략하기도 합니다.
        // 하지만 Class.forName()을 명시적으로 사용한다면 필요합니다.
        /* catch (ClassNotFoundException e) {
            System.err.println(&quot;JDBC 드라이버를 찾을 수 없습니다: &quot; + e.getMessage());
            e.printStackTrace();
        }*/
    }
}</code></pre>
<p>이것이 SQL과 관련된 Java (JDBC) 코드의 기본적인 문법과 흐름입니다. 실제 프로젝트에서는 Connection Pool, ORM (JPA/Hibernate, MyBatis) 등을 사용하여 JDBC 코드를 직접 작성하는 것을 최소화하기도 하지만, JDBC의 기본 원리를 이해하는 것은 매우 중요합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux 개념]]></title>
            <link>https://velog.io/@du-log/Redux-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@du-log/Redux-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Tue, 06 May 2025 07:42:51 GMT</pubDate>
            <description><![CDATA[<h2 id="🧠-redux의-핵심-개념">🧠 Redux의 핵심 개념</h2>
<h3 id="🔁-1-single-source-of-truth-단일-상태-저장소">🔁 1. <strong>Single Source of Truth (단일 상태 저장소)</strong></h3>
<p>Redux는 애플리케이션의 모든 상태를 하나의 객체 트리로 만들고, 이를 하나의 <code>store</code>에 저장합니다. 즉, 상태는 중앙 집중식으로 관리됩니다.</p>
<pre><code class="language-js">const store = createStore(reducer);</code></pre>
<hr>
<h3 id="🔄-2-state는-읽기-전용-read-only">🔄 2. <strong>State는 읽기 전용 (Read-Only)</strong></h3>
<p>Redux에서는 상태(state)를 직접 수정하지 않습니다. 오직 <strong>action</strong>을 통해서만 상태를 변경할 수 있으며, 실제 변경은 <strong>reducer</strong>에서 처리합니다.</p>
<hr>
<h3 id="🧬-3-상태는-순수-함수인-reducer를-통해-변경된다">🧬 3. <strong>상태는 순수 함수인 reducer를 통해 변경된다</strong></h3>
<p><code>reducer</code>는 이전 상태와 <code>action</code>을 입력받아 <strong>새로운 상태</strong>를 반환하는 <strong>순수 함수</strong>입니다.</p>
<pre><code class="language-js">function reducer(state, action) {
  switch(action.type) {
    case &#39;INCREMENT&#39;:
      return { count: state.count + 1 };
    default:
      return state;
  }
}</code></pre>
<hr>
<h2 id="🧩-redux-용어-정리">🧩 Redux 용어 정리</h2>
<hr>
<h3 id="1-📦-store">1. 📦 <strong>Store</strong></h3>
<ul>
<li>Redux의 중앙 상태 저장소입니다.</li>
<li><code>createStore()</code>로 생성하며, 현재 상태(state), reducer, dispatch 기능을 포함합니다.</li>
</ul>
<hr>
<h3 id="2-⚡-action">2. ⚡ <strong>Action</strong></h3>
<ul>
<li>상태 변경을 요청하는 <strong>일반 자바스크립트 객체</strong>입니다.</li>
<li>필수 속성: <code>type</code> (변경의 종류를 나타냄)</li>
<li>선택 속성: <code>payload</code> (데이터 전달용)</li>
</ul>
<pre><code class="language-js">const incrementAction = {
  type: &#39;INCREMENT&#39;,
  payload: 1 // 선택적
};</code></pre>
<hr>
<h3 id="3-🖱️-event">3. 🖱️ <strong>Event</strong></h3>
<ul>
<li>일반적으로 <strong>사용자의 입력(클릭, 키보드 입력 등)</strong> 또는 네트워크 응답 등을 의미합니다.</li>
<li>Redux 자체 용어는 아니며, 보통 이벤트가 발생하면 그에 따라 <strong>action을 생성</strong>합니다.</li>
</ul>
<pre><code class="language-js">// 버튼 클릭 이벤트 → 액션 생성 → 디스패치
&lt;button onClick={() =&gt; dispatch({ type: &#39;INCREMENT&#39; })}&gt;+&lt;/button&gt;</code></pre>
<hr>
<h3 id="4-📤-dispatch">4. 📤 <strong>Dispatch</strong></h3>
<ul>
<li><code>store.dispatch(action)</code> 형태로 사용.</li>
<li>특정 액션을 스토어에 전달해서 상태 업데이트를 <strong>트리거</strong>합니다.</li>
</ul>
<pre><code class="language-js">dispatch({ type: &#39;INCREMENT&#39; });</code></pre>
<ul>
<li>디스패치된 액션은 reducer로 전달되어 상태를 변경하게 됩니다.</li>
</ul>
<hr>
<h3 id="5-🔄-reducer">5. 🔄 <strong>Reducer</strong></h3>
<ul>
<li>액션을 받아서, 이전 상태를 기반으로 <strong>새로운 상태 객체를 반환</strong>하는 순수 함수.</li>
</ul>
<pre><code class="language-js">const initialState = { count: 0 };

function counterReducer(state = initialState, action) {
  switch(action.type) {
    case &#39;INCREMENT&#39;:
      return { count: state.count + 1 };
    case &#39;DECREMENT&#39;:
      return { count: state.count - 1 };
    default:
      return state;
  }
}</code></pre>
<hr>
<h3 id="6-🛠️-middleware-선택-사항">6. 🛠️ Middleware (선택 사항)</h3>
<ul>
<li>액션이 리듀서에 도달하기 전에 가로채서 작업을 수행할 수 있는 중간 단계입니다.</li>
<li>예: <code>redux-thunk</code>, <code>redux-saga</code> (비동기 처리에 자주 사용)</li>
</ul>
<hr>
<h2 id="🔁-redux-데이터-흐름-정리-one-way-data-flow">🔁 Redux 데이터 흐름 정리 (One-way data flow)</h2>
<ol>
<li><strong>Event 발생</strong> (예: 버튼 클릭)</li>
<li>→ <strong>Action 생성</strong></li>
<li>→ <strong>Dispatch로 액션 전달</strong></li>
<li>→ <strong>Reducer가 상태를 변경</strong></li>
<li>→ <strong>Store의 새로운 상태가 컴포넌트에 반영됨</strong></li>
</ol>
<hr>
<h2 id="🔍-예제-카운터">🔍 예제 (카운터)</h2>
<pre><code class="language-js">// 1. 액션 생성자
const increment = () =&gt; ({ type: &#39;INCREMENT&#39; });

// 2. 리듀서
function counter(state = { count: 0 }, action) {
  switch (action.type) {
    case &#39;INCREMENT&#39;:
      return { count: state.count + 1 };
    default:
      return state;
  }
}

// 3. 스토어 생성
const store = createStore(counter);

// 4. 디스패치 (예: 버튼 클릭 시)
store.dispatch(increment());</code></pre>
<hr>
<h2 id="📝-정리">📝 정리</h2>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Store</td>
<td>애플리케이션의 전체 상태를 보관하는 저장소</td>
</tr>
<tr>
<td>Action</td>
<td>상태 변경 요청을 나타내는 객체</td>
</tr>
<tr>
<td>Event</td>
<td>사용자의 입력이나 외부 자극, 액션을 발생시키는 트리거</td>
</tr>
<tr>
<td>Dispatch</td>
<td>액션을 스토어에 보내는 함수</td>
</tr>
<tr>
<td>Reducer</td>
<td>액션을 처리해 새 상태를 반환하는 순수 함수</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[상태 관리 라이브러리 
]]></title>
            <link>https://velog.io/@du-log/%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@du-log/%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Sat, 19 Apr 2025 07:42:16 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="🌟-react-상태-관리-완벽-가이드">🌟 React 상태 관리 완벽 가이드</h2>
<p>리액트(React)는 컴포넌트 기반 UI 라이브러리로, <strong>컴포넌트 내부 상태(state)를 다루는 기능</strong>(<code>useState</code>, <code>useReducer</code> 등)을 기본 제공합니다. 하지만 애플리케이션 규모가 커질수록, 컴포넌트 간 데이터를 효율적으로 공유하고 복잡도를 관리하기 위해 <strong>별도의 상태 관리 라이브러리</strong>를 도입하는 것이 일반적입니다.<br>이 글에서는 상태 관리의 <strong>기초 개념</strong>부터 <strong>언제</strong>, <strong>왜</strong>, <strong>어떻게</strong> 라이브러리를 쓰는지, 주요 <strong>원리</strong>와 <strong>종류</strong>, 마지막으로 <strong>Redux를 활용한 실전 예제</strong>까지 하나씩 살펴보겠습니다.</p>
<hr>
<h3 id="목차">목차</h3>
<ol>
<li>상태(state)란 무엇인가?  </li>
<li>상태 관리가 필요한 이유 &amp; 언제 사용하는가?  </li>
<li>상태 관리 라이브러리를 왜 사용하는가?  </li>
<li>주요 상태 관리 라이브러리 종류 비교  </li>
<li>상태 관리의 기본 원리  </li>
<li>Redux 심화: 구조와 사용법  <ul>
<li>6.1. 단방향 데이터 흐름(Flux 아키텍처)  </li>
<li>6.2. <code>store</code>: 중앙 저장소  </li>
<li>6.3. <code>slice</code>/<code>reducer</code>: 상태 모듈화  </li>
<li>6.4. <code>Provider</code>: React와 연결  </li>
<li>6.5. Hooks(<code>useSelector</code>, <code>useDispatch</code>)  </li>
<li>6.6. 비동기 처리(<code>createAsyncThunk</code>)  </li>
</ul>
</li>
<li>마무리 및 추천 학습 자료</li>
</ol>
<hr>
<h2 id="1️⃣-상태state란-무엇인가">1️⃣ 상태(state)란 무엇인가?</h2>
<ul>
<li><strong>정의</strong>: 컴포넌트 내부에서 변할 수 있는 <strong>데이터</strong>, 즉 UI와 로직을 연결하는 값  </li>
<li><strong>예시</strong>  <pre><code class="language-jsx">const [count, setCount] = useState(0);
const [text, setText] = useState(&quot;&quot;);</code></pre>
<ul>
<li>버튼 클릭 시 숫자(count)가 오르내리고  </li>
<li>사용자 입력(input) 값을 관리  </li>
</ul>
</li>
</ul>
<hr>
<h2 id="2️⃣-상태-관리가-필요한-이유--언제-사용하는가">2️⃣ 상태 관리가 필요한 이유 &amp; 언제 사용하는가?</h2>
<ol>
<li><p><strong>컴포넌트 간 데이터 공유가 필요할 때</strong>  </p>
<ul>
<li>로그인 상태, 장바구니 아이템, 테마 설정 등  </li>
<li><code>props</code>로 매번 전달하면 깊이가 깊어질수록 코드 복잡도↑ (prop drilling 문제)</li>
</ul>
</li>
<li><p><strong>전역 또는 공통으로 사용되어야 할 데이터가 있을 때</strong>  </p>
<ul>
<li>사이트 전체에서 사용하는 유저 정보, 알림 토큰, 다크 모드, 언어 설정 등</li>
</ul>
</li>
<li><p><strong>복잡한 상태 로직을 모듈화/테스트하기 위해</strong>  </p>
<ul>
<li>단순 <code>useState</code>만으로는 액션 흐름을 추적하기 어려움  </li>
<li>상태 업데이트 로직을 분리해 유지보수성↑</li>
</ul>
</li>
<li><p><strong>비동기 작업 관리</strong>  </p>
<ul>
<li>서버 API 호출, 로딩/에러 상태 관리  </li>
<li>복잡한 비동기 로직을 체계적으로 다루고 싶을 때</li>
</ul>
</li>
</ol>
<hr>
<h2 id="3️⃣-상태-관리-라이브러리를-왜-사용하는가">3️⃣ 상태 관리 라이브러리를 왜 사용하는가?</h2>
<ul>
<li><strong>규모 확장성</strong>: 프로젝트가 커질수록 상태와 액션이 늘어나고, 이를 통합 관리하는 구조가 필요  </li>
<li><strong>예측 가능성</strong>: 상태 변경이 모두 “액션 → 리듀서” 흐름을 거치므로, 로그나 디버깅이 쉬움  </li>
<li><strong>공동 작업 편의</strong>: 팀원 간 역할 분리(프론트엔드 작업, 상태 로직 작성 등)가 수월  </li>
<li><strong>클린 아키텍처</strong>: UI, 로직, 데이터 계층을 명확히 분리</li>
</ul>
<hr>
<h2 id="4️⃣-주요-상태-관리-라이브러리-종류-비교">4️⃣ 주요 상태 관리 라이브러리 종류 비교</h2>
<table>
<thead>
<tr>
<th>라이브러리</th>
<th>특징</th>
<th>장단점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Redux</strong></td>
<td>Flux 패턴 기반, 큰 커뮤니티, 강력한 에코시스템</td>
<td>✅ 예측 가능성, DevTools <br> ❌ 보일러플레이트 코드</td>
</tr>
<tr>
<td><strong>Zustand</strong></td>
<td>간결한 API, 작은 번들 크기</td>
<td>✅ 직관적, 러닝 커브 낮음 <br> ❌ 커뮤니티·에코시스템 상대적 작음</td>
</tr>
<tr>
<td><strong>Recoil</strong></td>
<td>Facebook 개발, atom/selector 개념</td>
<td>✅ React 전용, 성능 최적화 <br> ❌ 아직 발전 중</td>
</tr>
<tr>
<td><strong>Jotai</strong></td>
<td>단일 원자(atom) 모델, 동시성 지원</td>
<td>✅ 간단, 모던 API <br> ❌ 러닝 커브 약간 있음</td>
</tr>
<tr>
<td><strong>MobX</strong></td>
<td>관찰(observable) 기반, 자동 리렌더링</td>
<td>✅ 코드 간결, 반응형 <br> ❌ 복잡한 디버깅</td>
</tr>
</tbody></table>
<hr>
<h2 id="5️⃣-상태-관리의-기본-원리">5️⃣ 상태 관리의 기본 원리</h2>
<ol>
<li><strong>단방향 데이터 흐름</strong>  <ul>
<li>UI → Action → Reducer → New State → UI  </li>
</ul>
</li>
<li><strong>불변성(Immutable State)</strong>  <ul>
<li>상태 객체를 직접 수정하지 않고, 항상 새 복사본을 반환  </li>
</ul>
</li>
<li><strong>순수 함수로서의 리듀서</strong>  <ul>
<li>같은 입력(state, action)이면 항상 같은 출력 반환  </li>
</ul>
</li>
<li><strong>모듈화</strong>  <ul>
<li>기능별로 slice/reducer 분리 → 유지보수 용이</li>
</ul>
</li>
</ol>
<hr>
<h2 id="6️⃣-redux-심화-구조와-사용법">6️⃣ Redux 심화: 구조와 사용법</h2>
<h3 id="61-단방향-데이터-흐름-flux-아키텍처">6.1 단방향 데이터 흐름 (Flux 아키텍처)</h3>
<pre><code>UI ──▶ Action ──▶ Reducer ──▶ Store ──▶ UI</code></pre><ul>
<li><strong>Action</strong>: 상태 변경 의도를 나타내는 <strong>객체</strong>  </li>
<li><strong>Reducer</strong>: action에 따라 state를 반환하는 <strong>순수 함수</strong>  </li>
<li><strong>Store</strong>: 애플리케이션 전체 상태를 보관하는 <strong>중앙 저장소</strong></li>
</ul>
<hr>
<h3 id="62-store-중앙-저장소">6.2 store: 중앙 저장소</h3>
<pre><code class="language-js">// src/app/store.js
import { configureStore } from &#39;@reduxjs/toolkit&#39;;
import counterReducer from &#39;../features/counter/counterSlice&#39;;

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    // user: userReducer,
    // todo: todoReducer,
  },
});</code></pre>
<ul>
<li><strong>왜?</strong> 모든 상태를 한 곳에 모아두면, 어디서나 일관되게 꺼내 쓰고, 변경 이력을 추적할 수 있음</li>
</ul>
<hr>
<h3 id="63-slicereducer-상태-조각-모듈화">6.3 slice/reducer: 상태 조각 모듈화</h3>
<pre><code class="language-js">// src/features/counter/counterSlice.js
import { createSlice } from &#39;@reduxjs/toolkit&#39;;

const initialState = { value: 0 };

const counterSlice = createSlice({
  name: &#39;counter&#39;,
  initialState,
  reducers: {
    increment: (state) =&gt; { state.value += 1; },  // Immer 덕분에 직접 수정처럼 보이지만, 불변성 유지
    decrement: (state) =&gt; { state.value -= 1; },
    incrementByAmount: (state, action) =&gt; { state.value += action.payload; },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;</code></pre>
<ul>
<li><strong>왜?</strong> 액션 생성자와 리듀서를 한 곳에 묶어 가독성과 유지보수성을 높임  </li>
</ul>
<hr>
<h3 id="64-provider-react와-redux-연결">6.4 Provider: React와 Redux 연결</h3>
<pre><code class="language-js">// src/index.js
import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom/client&#39;;
import App from &#39;./App&#39;;
import { Provider } from &#39;react-redux&#39;;
import { store } from &#39;./app/store&#39;;

const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;Provider store={store}&gt;
    &lt;App /&gt;
  &lt;/Provider&gt;
);</code></pre>
<ul>
<li><strong>왜?</strong> React 트리 전체에 store를 “주입”하여, 하위 컴포넌트 어디서든 접근 가능하게 함  </li>
</ul>
<hr>
<h3 id="65-hooks-useselector--usedispatch">6.5 Hooks: useSelector &amp; useDispatch</h3>
<ul>
<li><strong>useSelector</strong>: store에서 원하는 상태를 추출  <pre><code class="language-js">const count = useSelector((state) =&gt; state.counter.value);</code></pre>
</li>
<li><strong>useDispatch</strong>: 액션을 디스패치(dispatch)하여 상태 변경  <pre><code class="language-js">const dispatch = useDispatch();
dispatch(increment());
dispatch(incrementByAmount(5));</code></pre>
</li>
</ul>
<hr>
<h3 id="66-비동기-처리-createasyncthunk">6.6 비동기 처리: createAsyncThunk</h3>
<pre><code class="language-js">// features/todo/todoSlice.js
import { createSlice, createAsyncThunk } from &#39;@reduxjs/toolkit&#39;;

export const fetchTodos = createAsyncThunk(
  &#39;todos/fetchTodos&#39;,
  async () =&gt; {
    const res = await fetch(&#39;/api/todos&#39;);
    return res.json();
  }
);

const todoSlice = createSlice({
  name: &#39;todos&#39;,
  initialState: { items: [], status: &#39;idle&#39;, error: null },
  reducers: { /* 동기 액션 */ },
  extraReducers: (builder) =&gt; {
    builder
      .addCase(fetchTodos.pending, (state) =&gt; { state.status = &#39;loading&#39;; })
      .addCase(fetchTodos.fulfilled, (state, action) =&gt; {
        state.status = &#39;succeeded&#39;;
        state.items = action.payload;
      })
      .addCase(fetchTodos.rejected, (state, action) =&gt; {
        state.status = &#39;failed&#39;;
        state.error = action.error.message;
      });
  },
});
export default todoSlice.reducer;</code></pre>
<ul>
<li><strong>왜?</strong> API 호출 로직과 상태(loading, success, error)를 간결하게 관리</li>
</ul>
<hr>
<h2 id="7️⃣-마무리-및-추천-학습-자료">7️⃣ 마무리 및 추천 학습 자료</h2>
<ul>
<li><strong>공식 문서</strong>  <ul>
<li>Redux Toolkit: <a href="https://redux-toolkit.js.org/">https://redux-toolkit.js.org/</a>  </li>
<li>React Redux: <a href="https://react-redux.js.org/">https://react-redux.js.org/</a>  </li>
</ul>
</li>
<li><strong>튜토리얼</strong>  <ul>
<li>Redux Essentials: <a href="https://redux.js.org/tutorials/essentials/part-1-overview-concepts">https://redux.js.org/tutorials/essentials/part-1-overview-concepts</a>  </li>
</ul>
</li>
<li><strong>다른 라이브러리 탐색</strong>  <ul>
<li>Zustand: <a href="https://github.com/pmndrs/zustand">https://github.com/pmndrs/zustand</a>  </li>
<li>Recoil: <a href="https://recoiljs.org/">https://recoiljs.org/</a>  </li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JSP]Request와 Response 객체: 요청과 응답의 흐름 이해하기]]></title>
            <link>https://velog.io/@du-log/JSP-Request%EC%99%80-Response-%EA%B0%9D%EC%B2%B4-%EC%9A%94%EC%B2%AD%EA%B3%BC-%EC%9D%91%EB%8B%B5%EC%9D%98-%ED%9D%90%EB%A6%84-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@du-log/JSP-Request%EC%99%80-Response-%EA%B0%9D%EC%B2%B4-%EC%9A%94%EC%B2%AD%EA%B3%BC-%EC%9D%91%EB%8B%B5%EC%9D%98-%ED%9D%90%EB%A6%84-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 16 Jan 2025 08:02:24 GMT</pubDate>
            <description><![CDATA[<p>웹 애플리케이션은 클라이언트(사용자)의 요청(request)을 받아 서버가 응답(response)을 보내는 방식으로 작동합니다. JSP는 이러한 요청과 응답을 처리하기 위해 <code>HttpServletRequest</code>와 <code>HttpServletResponse</code> 객체를 제공합니다. 이번 글에서는 <code>request</code>와 <code>response</code> 객체를 활용하는 방법과 이를 사용하는 코드를 분석해보겠습니다.</p>
<hr>
<h2 id="1-request-객체"><strong>1. Request 객체</strong></h2>
<p><code>HttpServletRequest</code> 객체는 클라이언트가 보낸 데이터를 서버에서 처리하기 위한 정보를 제공합니다. 주로 사용자의 입력 데이터를 처리하는 데 사용됩니다.</p>
<h3 id="예제-1-사용자-데이터를-서버로-전송"><strong>예제 1: 사용자 데이터를 서버로 전송</strong></h3>
<h4 id="formhtml-코드"><strong><code>form.html</code> 코드</strong></h4>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;title&gt;Insert title here&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;form action=&quot;mSignUp.jsp&quot; method=&quot;get&quot;&gt;
        name: &lt;input type=&quot;text&quot; name=&quot;m_name&quot;&gt;&lt;br&gt; 
        password: &lt;input type=&quot;password&quot; name=&quot;m_pass&quot;&gt;&lt;br&gt; 
        hobby: 
        sport &lt;input type=&quot;checkbox&quot; name=&quot;m_hobby&quot; value=&quot;sport&quot;&gt; 
        cooking &lt;input type=&quot;checkbox&quot; name=&quot;m_hobby&quot; value=&quot;cooking&quot;&gt; 
        travel &lt;input type=&quot;checkbox&quot; name=&quot;m_hobby&quot; value=&quot;travel&quot;&gt;&lt;br&gt; 
        &lt;input type=&quot;submit&quot; value=&quot;sign up&quot;&gt;
    &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h4 id="설명"><strong>설명</strong></h4>
<ul>
<li><strong><code>&lt;form&gt;</code> 태그:</strong><ul>
<li><code>action=&quot;mSignUp.jsp&quot;</code>: 데이터를 처리할 JSP 페이지를 지정.</li>
<li><code>method=&quot;get&quot;</code>: GET 방식으로 데이터를 전송.</li>
</ul>
</li>
<li>사용자가 입력한 데이터를 <code>mSignUp.jsp</code> 페이지로 전송합니다.<h4 id="결과"><strong>결과</strong></h4>
<img src="https://velog.velcdn.com/images/du-log/post/607e228e-74cc-4297-bf05-8477ad640c51/image.png" alt=""></li>
</ul>
<hr>
<h4 id="msignupjsp-코드"><strong><code>mSignUp.jsp</code> 코드</strong></h4>
<pre><code class="language-jsp">&lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot; pageEncoding=&quot;UTF-8&quot;%&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;title&gt;Insert title here&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;%!String m_name;
    String m_pass;
    String[] m_hobby;%&gt;

    &lt;%
    // request 객체를 이용해 form 데이터 가져오기
    m_name = request.getParameter(&quot;m_name&quot;); // 단일 값
    m_pass = request.getParameter(&quot;m_pass&quot;); // 단일 값
    m_hobby = request.getParameterValues(&quot;m_hobby&quot;); // 다중 값
    %&gt;

    &lt;p&gt;m_name: &lt;%= m_name %&gt;&lt;/p&gt;
    &lt;p&gt;m_pass: &lt;%= m_pass %&gt;&lt;/p&gt;
    &lt;p&gt;m_hobby:&lt;/p&gt;
    &lt;ul&gt;
    &lt;%
        for (int i = 0; i &lt; m_hobby.length; i++) {
    %&gt;
        &lt;li&gt;&lt;%= m_hobby[i] %&gt;&lt;/li&gt;
    &lt;%
        }
    %&gt;
    &lt;/ul&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h4 id="설명-1"><strong>설명</strong></h4>
<ul>
<li><strong><code>request.getParameter(String name)</code></strong><ul>
<li>클라이언트가 전송한 단일 값을 가져옵니다. (<code>name</code>, <code>password</code> 등)</li>
</ul>
</li>
<li><strong><code>request.getParameterValues(String name)</code></strong><ul>
<li>다중 선택 항목(예: 체크박스) 값을 배열로 가져옵니다.</li>
</ul>
</li>
<li>입력된 데이터는 화면에 출력되며, <code>m_name</code>, <code>m_pass</code>, <code>m_hobby</code> 변수에 저장됩니다.<h4 id="결과-1"><strong>결과</strong></h4>
<img src="https://velog.velcdn.com/images/du-log/post/121dd2e4-d3b5-4b9b-b9b2-3556b14fd104/image.png" alt=""></li>
</ul>
<hr>
<h2 id="2-response-객체"><strong>2. Response 객체</strong></h2>
<p><code>HttpServletResponse</code> 객체는 서버에서 클라이언트로 데이터를 전송하거나 다른 페이지로 리다이렉트할 때 사용됩니다.</p>
<h3 id="예제-2-페이지-리다이렉트"><strong>예제 2: 페이지 리다이렉트</strong></h3>
<h4 id="firstpagejsp-코드"><strong><code>firstPage.jsp</code> 코드</strong></h4>
<pre><code class="language-jsp">&lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot; pageEncoding=&quot;UTF-8&quot;%&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;title&gt;Insert title here&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;p&gt;First Page!!&lt;/p&gt;
    &lt;%
        // response 객체를 사용하여 페이지 리다이렉트
        response.sendRedirect(&quot;secondPage.jsp&quot;);
    %&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h4 id="설명-2"><strong>설명</strong></h4>
<ul>
<li><strong><code>response.sendRedirect(String location)</code></strong><ul>
<li>클라이언트를 지정된 URL로 리다이렉트합니다.</li>
<li><code>secondPage.jsp</code>로 페이지 이동이 이루어지며, 클라이언트는 해당 URL로 새로운 요청을 보내게 됩니다.</li>
</ul>
</li>
</ul>
<hr>
<h4 id="secondpagejsp-코드"><strong><code>secondPage.jsp</code> 코드</strong></h4>
<pre><code class="language-jsp">&lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot; pageEncoding=&quot;UTF-8&quot;%&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;title&gt;Insert title here&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;p&gt;Second Page!!&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h4 id="설명-3"><strong>설명</strong></h4>
<ul>
<li>리다이렉트된 페이지에서는 클라이언트가 새로운 요청을 받습니다.</li>
<li>첫 번째 페이지에서 처리된 정보는 사라지며, 두 번째 페이지가 새롭게 로드됩니다.<h4 id="결과-2"><strong>결과</strong></h4>
<img src="https://velog.velcdn.com/images/du-log/post/de1c4587-17de-4550-8a2c-39f24372bc4c/image.png" alt=""></li>
</ul>
<hr>
<h2 id="request와-response-객체의-주요-메서드"><strong>Request와 Response 객체의 주요 메서드</strong></h2>
<h3 id="request-객체"><strong>Request 객체</strong></h3>
<ol>
<li><p><strong><code>getParameter(String name)</code></strong></p>
<ul>
<li>단일 값 가져오기.</li>
<li>예: <code>request.getParameter(&quot;m_name&quot;);</code></li>
</ul>
</li>
<li><p><strong><code>getParameterValues(String name)</code></strong></p>
<ul>
<li>다중 값 가져오기.</li>
<li>예: <code>request.getParameterValues(&quot;m_hobby&quot;);</code></li>
</ul>
</li>
<li><p><strong><code>getAttribute(String name)</code></strong></p>
<ul>
<li>서버 내에서 속성을 가져옵니다.</li>
</ul>
</li>
</ol>
<hr>
<h3 id="response-객체"><strong>Response 객체</strong></h3>
<ol>
<li><p><strong><code>sendRedirect(String location)</code></strong></p>
<ul>
<li>지정된 URL로 클라이언트를 리다이렉트.</li>
<li>예: <code>response.sendRedirect(&quot;secondPage.jsp&quot;);</code></li>
</ul>
</li>
<li><p><strong><code>setContentType(String type)</code></strong></p>
<ul>
<li>응답 데이터의 MIME 유형을 설정.</li>
<li>예: <code>response.setContentType(&quot;text/html&quot;);</code></li>
</ul>
</li>
<li><p><strong><code>getWriter()</code></strong></p>
<ul>
<li>출력 스트림을 반환하여 클라이언트에 데이터를 보냅니다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="결론"><strong>결론</strong></h2>
<p><code>HttpServletRequest</code>와 <code>HttpServletResponse</code>는 웹 애플리케이션에서 클라이언트와 서버 간의 데이터 흐름을 처리하는 데 필수적인 객체입니다.  </p>
<ul>
<li><strong>Request 객체</strong>는 클라이언트 데이터를 서버로 전달하며, 폼 입력 처리, 데이터 검증 등에 사용됩니다.</li>
<li><strong>Response 객체</strong>는 서버의 처리 결과를 클라이언트로 전달하거나 다른 페이지로 리다이렉트할 때 사용됩니다.</li>
</ul>
<p>이 두 객체를 잘 활용하면 클라이언트-서버 간의 효율적인 데이터 처리가 가능합니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JSP]JSP 스크립트: HTML과 Java의 조화로 동적 웹페이지 구현하기]]></title>
            <link>https://velog.io/@du-log/JSP-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-HTML%EA%B3%BC-Java%EC%9D%98-%EC%A1%B0%ED%99%94%EB%A1%9C-%EB%8F%99%EC%A0%81-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-wwx50vae</link>
            <guid>https://velog.io/@du-log/JSP-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-HTML%EA%B3%BC-Java%EC%9D%98-%EC%A1%B0%ED%99%94%EB%A1%9C-%EB%8F%99%EC%A0%81-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-wwx50vae</guid>
            <pubDate>Thu, 16 Jan 2025 01:56:55 GMT</pubDate>
            <description><![CDATA[<p>JSP(JavaServer Pages)는 웹 애플리케이션 개발에서 HTML과 Java를 결합하여 동적인 웹페이지를 생성할 수 있는 강력한 도구입니다. 이번 글에서는 JSP 스크립트를 이해하고, HTML에 Java 코드를 삽입하여 JSP 파일을 작성하는 방법을 단계적으로 살펴보겠습니다.</p>
<hr>
<h2 id="jsp와-servlet의-차이">JSP와 Servlet의 차이</h2>
<p><img src="https://velog.velcdn.com/images/du-log/post/1f44a713-a610-45be-94f5-1dc628461c79/image.png" alt=""></p>
<ul>
<li><p><strong>Servlet</strong>: Java 코드로만 이루어져 있으며, <code>.java</code> 파일로 작성된 후 <code>.class</code> 파일로 컴파일됩니다.</p>
<ul>
<li>예: <code>MyServlet.java -&gt; MyServlet.class</code></li>
</ul>
</li>
<li><p><strong>JSP</strong>: HTML 문서 안에 Java 코드를 포함하는 형태로, JSP 파일은 서버에서 처리되며 최종적으로 Servlet으로 변환됩니다.</p>
<ul>
<li>예: <code>example.jsp -&gt; example_jsp.java -&gt; example_jsp.class</code></li>
</ul>
</li>
</ul>
<p>이 변환 과정 덕분에 JSP는 Java의 동적 처리 능력과 HTML의 간단한 문서 작성 능력을 결합합니다.</p>
<hr>
<h2 id="jsp-주요-스크립트-태그">JSP 주요 스크립트 태그</h2>
<p>JSP 스크립트 태그는 HTML 문서 내에서 Java 코드를 삽입하거나 실행하기 위해 사용됩니다. 주요 태그는 다음과 같습니다:</p>
<h3 id="1-선언-태그---">1. <strong>선언 태그 (<code>&lt;%! ... %&gt;</code>):</strong></h3>
<ul>
<li><strong>용도:</strong> JSP 페이지에서 Java 멤버 변수나 메서드를 선언합니다.</li>
<li><strong>특징:</strong> 선언된 변수와 메서드는 JSP 파일이 서블릿으로 변환된 후 클래스의 멤버로 정의됩니다.</li>
</ul>
<pre><code class="language-jsp">&lt;%!
    int num = 10;
    String str = &quot;jsp&quot;;
    ArrayList&lt;String&gt; list = new ArrayList&lt;String&gt;();

    public void jspMethod() {
        System.out.println(&quot;--jspMethod()--&quot;);
    }
%&gt;</code></pre>
<h3 id="2-주석-태그-------">2. <strong>주석 태그 (<code>&lt;%-- ... --%&gt;</code>):</strong></h3>
<ul>
<li><strong>용도:</strong> JSP에서 주석을 작성합니다.</li>
<li><strong>특징:</strong> 주석 내용은 JSP가 서블릿으로 변환될 때 제외되므로 클라이언트에게 전송되지 않습니다.</li>
</ul>
<pre><code class="language-jsp">&lt;%-- This is a JSP comment. It will not appear in the client-side HTML. --%&gt;</code></pre>
<h3 id="3-스크립트릿-태그---">3. <strong>스크립트릿 태그 (<code>&lt;% ... %&gt;</code>):</strong></h3>
<ul>
<li><strong>용도:</strong> JSP 페이지 내에서 Java 코드를 삽입하고 실행합니다.</li>
<li><strong>특징:</strong> HTML과 Java 코드를 혼합하여 동적 로직을 처리할 수 있습니다.</li>
</ul>
<pre><code class="language-jsp">&lt;%
    if (num &gt; 0) {
%&gt;
&lt;p&gt;num is greater than 0&lt;/p&gt;
&lt;%
    } else {
%&gt;
&lt;p&gt;num is less than or equal to 0&lt;/p&gt;
&lt;%
    }
%&gt;</code></pre>
<h3 id="4-표현식-태그---">4. <strong>표현식 태그 (<code>&lt;%= ... %&gt;</code>):</strong></h3>
<ul>
<li><strong>용도:</strong> Java 변수나 메서드의 반환값을 출력합니다.</li>
<li><strong>특징:</strong> 태그 내부의 코드는 자동으로 <code>out.print()</code> 메서드로 처리됩니다.</li>
</ul>
<pre><code class="language-jsp">&lt;p&gt;num is &lt;%= num %&gt;&lt;/p&gt;</code></pre>
<h3 id="5-지시어---">5. <strong>지시어 (<code>&lt;%@ ... %&gt;</code>):</strong></h3>
<ul>
<li><strong>용도:</strong> JSP 페이지의 설정 및 외부 파일 또는 라이브러리 포함 등을 정의합니다.</li>
<li><strong>주요 지시어:</strong><ol>
<li><strong>page 지시어:</strong> JSP 페이지의 전역 설정을 정의합니다.<pre><code class="language-jsp">&lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot; %&gt;</code></pre>
</li>
<li><strong>include 지시어:</strong> 다른 JSP 파일을 포함합니다.<pre><code class="language-jsp">&lt;%@ include file=&quot;header.jsp&quot; %&gt;</code></pre>
</li>
<li><strong>taglib 지시어:</strong> 커스텀 태그 라이브러리를 사용할 수 있도록 정의합니다.<pre><code class="language-jsp">&lt;%@ taglib uri=&quot;http://java.sun.com/jsp/jstl/core&quot; prefix=&quot;c&quot; %&gt;</code></pre>
</li>
</ol>
</li>
</ul>
<hr>
<h2 id="jsp-예제-코드">JSP 예제 코드</h2>
<p>다음은 위에서 설명한 주요 태그를 활용한 JSP 예제입니다.</p>
<pre><code class="language-jsp">&lt;%@page import=&quot;java.util.ArrayList&quot;%&gt;
&lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot; pageEncoding=&quot;UTF-8&quot;%&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;title&gt;JSP Example&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;%@include file=&quot;header.jsp&quot; %&gt;

&lt;!-- 선언태그 --&gt;
&lt;%!
    int num = 10;
    String str = &quot;jsp&quot;;
    ArrayList&lt;String&gt; list = new ArrayList&lt;String&gt;();

    public void jspMethod() {
        System.out.println(&quot;--jspMethod()--&quot;);
    }
%&gt;

&lt;!-- 주석태그 --&gt;
&lt;%-- This is a JSP comment. It will not appear in the client-side HTML. --%&gt;

&lt;!-- 스크립트릿 태그 --&gt;
&lt;%
    if (num &gt; 0) {
%&gt;
&lt;p&gt;num is greater than 0&lt;/p&gt;
&lt;%
    } else {
%&gt;
&lt;p&gt;num is less than or equal to 0&lt;/p&gt;
&lt;%
    }
%&gt;

&lt;!-- 표현식 태그 --&gt;
&lt;p&gt;num is &lt;%= num %&gt;&lt;/p&gt;

&lt;%@include file=&quot;footer.jsp&quot; %&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
<h2 id="java-코드가-브라우저에-표시되지-않는-이유">Java 코드가 브라우저에 표시되지 않는 이유</h2>
<p>JSP 파일은 서버에서 처리되며, HTML은 클라이언트(브라우저)로 전송됩니다. Java 코드는 서버에서 실행된 후 결과만 HTML로 변환되기 때문에 클라이언트는 실행된 결과만 확인할 수 있습니다.</p>
<ul>
<li><strong>변환 과정:</strong><ol>
<li><code>example.jsp</code> → <code>example_jsp.java</code>: JSP 파일이 Servlet으로 변환.</li>
<li><code>example_jsp.java</code> → <code>example_jsp.class</code>: Servlet 클래스가 컴파일.</li>
<li><code>example_jsp.class</code>: 서버에서 실행되고, 결과 HTML이 클라이언트로 전송.</li>
</ol>
</li>
</ul>
<hr>
<h2 id="추가적으로-알아두면-좋은-사항">추가적으로 알아두면 좋은 사항</h2>
<ol>
<li><p><strong>JSP의 장점:</strong></p>
<ul>
<li>HTML과 Java의 자연스러운 통합.</li>
<li>빠른 프로토타이핑이 가능.</li>
<li>Java의 강력한 기능 활용.</li>
</ul>
</li>
<li><p><strong>JSP의 단점:</strong></p>
<ul>
<li>복잡한 비즈니스 로직 포함 시 가독성 저하.</li>
<li>MVC 패턴에서 View에 해당하며, 비즈니스 로직은 별도의 Servlet 또는 Java 클래스에서 처리하는 것이 바람직.</li>
</ul>
</li>
<li><p><strong>JSP와 MVC 패턴:</strong></p>
<ul>
<li>JSP는 View 역할로 사용되고, Servlet은 Controller 역할을 담당합니다. 이를 통해 코드의 재사용성과 유지보수를 높일 수 있습니다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="결론">결론</h2>
<p>JSP 스크립트는 동적인 웹 애플리케이션 개발에서 HTML과 Java를 유연하게 결합할 수 있는 강력한 도구입니다. 선언 태그, 주석 태그, 스크립트릿 태그, 표현식 태그, 지시어 등을 적절히 활용하면 동적이고 효율적인 웹페이지를 쉽게 구현할 수 있습니다.  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JSP] form 데이터 처리]]></title>
            <link>https://velog.io/@du-log/JSP-form-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@du-log/JSP-form-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Tue, 14 Jan 2025 08:20:37 GMT</pubDate>
            <description><![CDATA[<h1 id="servlet을-활용한-form-데이터-처리-get과-post의-차이와-실전-예제">Servlet을 활용한 Form 데이터 처리: GET과 POST의 차이와 실전 예제</h1>
<p>웹 애플리케이션에서 사용자의 <strong>Form 데이터</strong>를 처리하는 것은 매우 중요한 작업입니다. 이번 글에서는 Form 데이터를 처리하는 Servlet의 활용 방법과 <code>doGet()</code> 및 <code>doPost()</code> 메서드의 차이를 학습한 내용을 바탕으로 설명하겠습니다.</p>
<hr>
<h2 id="form-데이터-처리의-기본-흐름">Form 데이터 처리의 기본 흐름</h2>
<ol>
<li><strong>사용자가 Form을 제출</strong>하면 브라우저는 데이터를 HTTP 요청(Request)으로 웹 컨테이너(Servlet)로 전송합니다.</li>
<li>Servlet에서 <code>HttpServletRequest</code> 객체를 통해 데이터를 수신하고 처리합니다.</li>
<li>응답은 <code>HttpServletResponse</code> 객체를 통해 브라우저로 전달됩니다.</li>
</ol>
<hr>
<h2 id="get과-post의-차이점">GET과 POST의 차이점</h2>
<table>
<thead>
<tr>
<th>특징</th>
<th>GET</th>
<th>POST</th>
</tr>
</thead>
<tbody><tr>
<td><strong>데이터 전송 방식</strong></td>
<td>URL에 쿼리 스트링으로 포함</td>
<td>HTTP Request의 본문(Body)에 포함</td>
</tr>
<tr>
<td><strong>URL 예시</strong></td>
<td><code>http://localhost:8090/mSignUp?m_name=John</code></td>
<td><code>http://localhost:8090/mSignUp</code></td>
</tr>
<tr>
<td><strong>보안</strong></td>
<td>데이터가 URL에 노출 (덜 안전)</td>
<td>데이터가 숨겨짐 (더 안전)</td>
</tr>
<tr>
<td><strong>용도</strong></td>
<td>간단한 데이터 전송, 조회 목적</td>
<td>민감한 데이터 전송, 저장 및 수정 목적</td>
</tr>
<tr>
<td><strong>데이터 길이 제한</strong></td>
<td>제한 있음</td>
<td>제한 없음</td>
</tr>
</tbody></table>
<hr>
<h2 id="html-form-코드">HTML Form 코드</h2>
<p>아래의 Form 코드는 사용자 정보를 입력받아 POST 방식으로 Servlet에 데이터를 전송합니다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;Form Example&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;

    &lt;form action=&quot;mSignUp&quot; method=&quot;post&quot;&gt;
        Name: &lt;input type=&quot;text&quot; name=&quot;m_name&quot;&gt;&lt;br&gt;
        Password: &lt;input type=&quot;password&quot; name=&quot;m_pass&quot;&gt;&lt;br&gt;
        Gender: 
        &lt;input type=&quot;radio&quot; name=&quot;m_gender&quot; value=&quot;M&quot; checked&gt; Man
        &lt;input type=&quot;radio&quot; name=&quot;m_gender&quot; value=&quot;W&quot;&gt; Woman&lt;br&gt;
        Hobby: 
        &lt;input type=&quot;checkbox&quot; name=&quot;m_hobby&quot; value=&quot;sport&quot;&gt; Sport
        &lt;input type=&quot;checkbox&quot; name=&quot;m_hobby&quot; value=&quot;cooking&quot;&gt; Cooking
        &lt;input type=&quot;checkbox&quot; name=&quot;m_hobby&quot; value=&quot;reading&quot;&gt; Reading
        &lt;input type=&quot;checkbox&quot; name=&quot;m_hobby&quot; value=&quot;travel&quot;&gt; Travel&lt;br&gt;
        Residence: 
        &lt;select name=&quot;m_residence&quot;&gt;
            &lt;option value=&quot;seoul&quot; selected&gt;Seoul&lt;/option&gt;
            &lt;option value=&quot;jeju&quot;&gt;Jeju&lt;/option&gt;
            &lt;option value=&quot;busan&quot;&gt;Busan&lt;/option&gt;
        &lt;/select&gt;&lt;br&gt;
        &lt;input type=&quot;submit&quot; value=&quot;Sign Up&quot;&gt;
    &lt;/form&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
<h2 id="servlet-코드-memsignupjava">Servlet 코드: <code>MemSignUp.java</code></h2>
<h3 id="핵심-기능">핵심 기능</h3>
<ul>
<li><strong>GET 요청 처리:</strong> <code>doGet()</code> 메서드를 통해 데이터를 URL 쿼리에서 읽어옵니다.</li>
<li><strong>POST 요청 처리:</strong> <code>doPost()</code> 메서드는 <code>doGet()</code>을 호출하여 동일한 방식으로 데이터를 처리합니다.</li>
<li><strong>Form 데이터 추출:</strong> <code>request.getParameter()</code>, <code>request.getParameterValues()</code>, <code>request.getParameterNames()</code>를 사용합니다.</li>
</ul>
<h3 id="servlet-코드-설명">Servlet 코드 설명</h3>
<pre><code class="language-java">@WebServlet(&quot;/mSignUp&quot;)
public class MemSignUp extends HttpServlet {

    // GET 요청 처리
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println(&quot; -- doGet() -- &quot;);

        // Form 데이터 수신
        String m_name = request.getParameter(&quot;m_name&quot;);
        String m_pass = request.getParameter(&quot;m_pass&quot;);
        String m_gender = request.getParameter(&quot;m_gender&quot;);
        String[] m_hobbys = request.getParameterValues(&quot;m_hobby&quot;);
        String m_residence = request.getParameter(&quot;m_residence&quot;);

        // 데이터 출력
        System.out.println(&quot;Name: &quot; + m_name);
        System.out.println(&quot;Password: &quot; + m_pass);
        System.out.println(&quot;Gender: &quot; + m_gender);
        System.out.println(&quot;Hobbies: &quot; + Arrays.toString(m_hobbys));
        System.out.println(&quot;Residence: &quot; + m_residence);

        // 모든 파라미터 이름 출력
        Enumeration&lt;String&gt; names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            System.out.println(&quot;Parameter Name: &quot; + name);
        }
    }

    // POST 요청 처리
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println(&quot; -- doPost() -- &quot;);
        // POST 요청도 GET 방식으로 처리
        doGet(request, response);
    }
}</code></pre>
<hr>
<h2 id="주요-메서드-설명">주요 메서드 설명</h2>
<ol>
<li><p><strong><code>request.getParameter(String name)</code></strong>  </p>
<ul>
<li>단일 값 데이터를 반환합니다.  </li>
<li>예: <code>request.getParameter(&quot;m_name&quot;)</code> → 사용자의 이름 반환.</li>
</ul>
</li>
<li><p><strong><code>request.getParameterValues(String name)</code></strong>  </p>
<ul>
<li>다중 값 데이터를 배열로 반환합니다.  </li>
<li>예: <code>request.getParameterValues(&quot;m_hobby&quot;)</code> → 선택한 취미 반환.</li>
</ul>
</li>
<li><p><strong><code>request.getParameterNames()</code></strong>  </p>
<ul>
<li>모든 요청 파라미터 이름을 <code>Enumeration</code>으로 반환합니다.  </li>
<li>예: 모든 Form 필드 이름을 순회하며 출력.</li>
</ul>
</li>
<li><p><strong><code>response.getWriter()</code></strong>  </p>
<ul>
<li>응답으로 데이터를 출력하는 스트림을 반환합니다.  </li>
<li>예: <code>response.getWriter().println(&quot;Hello User!&quot;)</code>.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="실행-결과">실행 결과</h2>
<h3 id="get-요청">GET 요청</h3>
<ul>
<li>URL:  <pre><code>http://localhost:8090/mSignUp?m_name=John&amp;m_pass=1234&amp;m_gender=M&amp;m_hobby=sport&amp;m_hobby=reading&amp;m_residence=seoul</code></pre></li>
<li>Console 출력:  <pre><code>-- doGet() --
Name: John
Password: 1234
Gender: M
Hobbies: [sport, reading]
Residence: seoul
Parameter Name: m_name
Parameter Name: m_pass
Parameter Name: m_gender
Parameter Name: m_hobby
Parameter Name: m_residence</code></pre></li>
</ul>
<h3 id="post-요청">POST 요청</h3>
<ul>
<li>URL:  <pre><code>http://localhost:8090/mSignUp</code></pre></li>
<li>Console 출력: 위와 동일 (Form 데이터는 요청 본문에 포함).</li>
</ul>
<hr>
<h2 id="결론">결론</h2>
<ul>
<li>GET과 POST 요청은 각각 목적에 맞게 사용해야 합니다.  </li>
<li>GET: 데이터 조회용으로 사용하며, 데이터가 URL에 노출됩니다.  </li>
<li>POST: 민감한 데이터 전송이나 데이터 수정/생성에 적합합니다.  </li>
<li>Form 데이터를 Servlet에서 처리하려면 <code>HttpServletRequest</code>의 다양한 메서드를 활용하면 됩니다.  </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JSP]Servlet life-cycle]]></title>
            <link>https://velog.io/@du-log/JSPServlet-life-cycle</link>
            <guid>https://velog.io/@du-log/JSPServlet-life-cycle</guid>
            <pubDate>Tue, 14 Jan 2025 07:18:29 GMT</pubDate>
            <description><![CDATA[<h3 id="servlet-life-cycle-생성부터-종료까지의-흐름-완벽-이해">Servlet Life Cycle: 생성부터 종료까지의 흐름 완벽 이해</h3>
<p>Servlet은 클라이언트의 요청을 처리하고 서버에서 동적 콘텐츠를 생성하는 Java 기반의 기술입니다. 이번 글에서는 Servlet이 동작하는 생명 주기(Life Cycle)를 단계별로 살펴보고, 각 단계에서 호출되는 메서드의 역할과 중요성을 이해하겠습니다.  </p>
<hr>
<h2 id="servlet-life-cycle의-주요-단계">Servlet Life Cycle의 주요 단계  <img src="https://velog.velcdn.com/images/du-log/post/f16951c3-7e73-4a7f-aa1f-7a8efdce3017/image.png" alt=""></h2>
<p>Servlet의 생명 주기는 <strong>Servlet 컨테이너</strong>에 의해 관리되며, 아래와 같은 단계를 거칩니다:  </p>
<ol>
<li><strong>객체 생성</strong>: Servlet 인스턴스가 메모리에 로드됩니다.  </li>
<li><strong>초기화(init)</strong>: 초기 설정을 수행합니다.  </li>
<li><strong>요청 처리(service)</strong>: 클라이언트의 요청을 처리하고 응답을 반환합니다.  </li>
<li><strong>종료(destroy)</strong>: 자원을 정리하고 Servlet을 메모리에서 해제합니다.  </li>
</ol>
<hr>
<h3 id="servlet-life-cycle의-실행-흐름">Servlet Life Cycle의 실행 흐름</h3>
<h4 id="1-servlet-객체-생성-전후-postconstruct">1. <strong>Servlet 객체 생성 전후: @PostConstruct</strong></h4>
<p><code>@PostConstruct</code>는 Servlet 객체가 생성된 후, <code>init()</code> 메서드가 호출되기 전에 실행됩니다. 이 어노테이션은 초기화 작업이 필요한 경우에 유용합니다.  </p>
<pre><code class="language-java">@PostConstruct
public void postConstruct() {
    System.out.println(&quot; -- postConstruct() --&quot;);
}</code></pre>
<p><strong>주요 포인트</strong>  </p>
<ul>
<li>Servlet 생성 이후 바로 실행됩니다.  </li>
<li>초기화 메서드(<code>init()</code>) 호출 전에 사용자 정의 작업을 수행할 수 있습니다.  </li>
</ul>
<hr>
<h4 id="2-초기화-init">2. <strong>초기화: init()</strong></h4>
<p><code>init()</code> 메서드는 Servlet이 처음 초기화될 때 한 번 호출됩니다. 주로 설정 파일 읽기, 리소스 초기화 등의 작업을 수행합니다.  </p>
<pre><code class="language-java">@Override
public void init() throws ServletException {
    System.out.println(&quot; -- init() --&quot;);
}</code></pre>
<p><strong>주요 포인트</strong>  </p>
<ul>
<li>Servlet이 처음 로드될 때 호출됩니다.  </li>
<li><code>init()</code> 메서드는 초기화 실패 시 예외를 던질 수 있습니다.  </li>
</ul>
<hr>
<h4 id="3-요청-처리-service와-dogetdopost">3. <strong>요청 처리: service()와 doGet()/doPost()</strong></h4>
<p><code>service()</code> 메서드는 클라이언트의 HTTP 요청을 처리하며, 요청 방식(GET, POST 등)에 따라 <code>doGet()</code> 또는 <code>doPost()</code> 메서드를 호출합니다.  </p>
<pre><code class="language-java">@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println(&quot; -- doGet() --&quot;);
}</code></pre>
<p><strong>주요 포인트</strong>  </p>
<ul>
<li><code>service()</code>는 HTTP 요청을 분류하며, 기본적으로 재정의하지 않습니다.  </li>
<li><code>doGet()</code>은 GET 요청, <code>doPost()</code>는 POST 요청을 처리합니다.  </li>
</ul>
<hr>
<h4 id="4-종료-전후-predestroy">4. <strong>종료 전후: @PreDestroy</strong></h4>
<p><code>@PreDestroy</code>는 Servlet이 제거되기 전에 호출됩니다. 주로 종료 작업을 수행하는 데 사용됩니다.  </p>
<pre><code class="language-java">@PreDestroy
public void preDestroy() {
    System.out.println(&quot; -- preDestroy() --&quot;);
}</code></pre>
<p><strong>주요 포인트</strong>  </p>
<ul>
<li><code>destroy()</code> 메서드 호출 전 실행됩니다.  </li>
<li>외부 리소스 정리 또는 로그 저장 등에 활용됩니다.  </li>
</ul>
<hr>
<h4 id="5-종료-destroy">5. <strong>종료: destroy()</strong></h4>
<p><code>destroy()</code> 메서드는 Servlet이 메모리에서 해제될 때 호출됩니다. 종료 작업을 수행합니다.  </p>
<pre><code class="language-java">@Override
public void destroy() {
    System.out.println(&quot; -- destroy() --&quot;);
}</code></pre>
<p><strong>주요 포인트</strong>  </p>
<ul>
<li>메모리 누수를 방지하기 위해 리소스를 해제합니다.  </li>
<li>주로 데이터베이스 연결 해제, 파일 스트림 닫기 등을 수행합니다.  </li>
</ul>
<hr>
<h3 id="servlet-life-cycle-코드-예제">Servlet Life Cycle 코드 예제</h3>
<pre><code class="language-java">import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LifeCycleServlet extends HttpServlet {

    @PostConstruct
    public void postConstruct() {
        System.out.println(&quot; -- postConstruct() -- &quot;);
    }

    @Override
    public void init() throws ServletException {
        System.out.println(&quot; -- init() -- &quot;);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println(&quot; -- doGet() -- &quot;);
        response.getWriter().println(&quot;Hello from doGet()&quot;);
    }

    @Override
    public void destroy() {
        System.out.println(&quot; -- destroy() -- &quot;);
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println(&quot; -- preDestroy() -- &quot;);
    }
}</code></pre>
<hr>
<h2 id="servlet-life-cycle의-흐름-정리">Servlet Life Cycle의 흐름 정리</h2>
<ol>
<li><strong>객체 생성:</strong> Servlet 컨테이너가 Servlet 클래스를 메모리에 로드합니다.  </li>
<li><strong>초기화:</strong> <code>@PostConstruct</code> → <code>init()</code> 실행. 초기 설정 작업 수행.  </li>
<li><strong>요청 처리:</strong> <code>service()</code> → 요청 방식에 따라 <code>doGet()</code> 또는 <code>doPost()</code> 호출.  </li>
<li><strong>종료 전후:</strong> <code>@PreDestroy</code> → 종료 작업 수행.  </li>
<li><strong>종료:</strong> <code>destroy()</code> → 리소스 정리 후 Servlet 객체 메모리 해제.  </li>
</ol>
<hr>
<h2 id="servlet-life-cycle을-활용하는-팁">Servlet Life Cycle을 활용하는 팁</h2>
<ol>
<li><p><strong>리소스 효율 관리</strong>  </p>
<ul>
<li><code>init()</code>과 <code>destroy()</code>를 활용해 데이터베이스 연결과 같은 리소스를 효율적으로 관리하세요.  </li>
</ul>
</li>
<li><p><strong>로그 남기기</strong>  </p>
<ul>
<li>각 단계에서 로그를 기록하여 Servlet의 동작 흐름을 추적할 수 있습니다.  </li>
</ul>
</li>
<li><p><strong>테스트와 디버깅</strong>  </p>
<ul>
<li><code>@PostConstruct</code>와 <code>@PreDestroy</code>를 활용하면 초기화와 종료 작업의 디버깅이 쉬워집니다.  </li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JSP]Servlet request & response]]></title>
            <link>https://velog.io/@du-log/JSPServletServlet-request-response</link>
            <guid>https://velog.io/@du-log/JSPServletServlet-request-response</guid>
            <pubDate>Tue, 14 Jan 2025 04:40:03 GMT</pubDate>
            <description><![CDATA[<h3 id="servlet과-httpservletrequest-httpservletresponse의-역할">Servlet과 HttpServletRequest, HttpServletResponse의 역할</h3>
<p>Servlet은 클라이언트의 요청을 처리하고 응답을 생성하는 <strong>Java 기반의 서버 측 프로그램</strong>입니다. 이 과정에서 <code>HttpServletRequest</code>와 <code>HttpServletResponse</code>는 각각 클라이언트와 서버 간의 데이터 흐름을 담당하며 핵심 역할을 합니다.</p>
<hr>
<h2 id="httpservletrequest와-httpservletresponse의-역할과-사용-시점">HttpServletRequest와 HttpServletResponse의 역할과 사용 시점</h2>
<h3 id="1-httpservletrequest-클라이언트-요청-처리"><strong>1. HttpServletRequest: 클라이언트 요청 처리</strong></h3>
<p><code>HttpServletRequest</code>는 클라이언트가 서버로 보낸 요청 정보를 담고 있으며, 서버에서 이를 활용해 요청을 처리합니다.  </p>
<h4 id="사용-시점"><strong>사용 시점</strong></h4>
<ul>
<li><strong>폼 데이터 처리:</strong> 클라이언트가 HTML 폼을 통해 보낸 데이터(예: 로그인 정보, 검색 키워드)를 읽어올 때.  </li>
<li><strong>헤더 정보 분석:</strong> 요청 헤더에서 클라이언트의 정보(예: User-Agent, Accept-Language)를 분석할 때.  </li>
<li><strong>세션 관리:</strong> 현재 사용자의 세션 정보를 가져오거나 새로운 세션을 생성할 때.  </li>
<li><strong>쿠키 관리:</strong> 클라이언트가 보낸 쿠키 정보를 가져와 사용자의 상태를 파악할 때.  </li>
<li><strong>파일 업로드:</strong> 멀티파트 요청에서 파일 데이터를 처리할 때.  </li>
</ul>
<h4 id="예시-코드"><strong>예시 코드</strong></h4>
<pre><code class="language-java">protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 폼 데이터 읽기
    String username = request.getParameter(&quot;username&quot;);
    String password = request.getParameter(&quot;password&quot;);

    // 세션 생성 및 설정
    HttpSession session = request.getSession();
    session.setAttribute(&quot;user&quot;, username);

    // 응답 처리
    response.getWriter().println(&quot;Welcome, &quot; + username);
}</code></pre>
<hr>
<h3 id="2-httpservletresponse-서버-응답-생성"><strong>2. HttpServletResponse: 서버 응답 생성</strong></h3>
<p><code>HttpServletResponse</code>는 서버에서 생성한 응답을 클라이언트로 전송하는 데 사용됩니다.  </p>
<h4 id="사용-시점-1"><strong>사용 시점</strong></h4>
<ul>
<li><strong>HTML 출력:</strong> 동적으로 생성한 HTML 페이지를 클라이언트에 전달할 때.  </li>
<li><strong>리다이렉션:</strong> 클라이언트를 다른 URL로 이동시킬 때.  </li>
<li><strong>상태 코드 설정:</strong> 응답 상태 코드(예: 200, 404, 500)를 설정할 때.  </li>
<li><strong>파일 다운로드:</strong> 바이너리 데이터를 응답으로 전송해 파일 다운로드를 제공할 때.  </li>
<li><strong>쿠키 전송:</strong> 서버가 클라이언트에 새 쿠키를 설정할 때.  </li>
</ul>
<h4 id="예시-코드-1"><strong>예시 코드</strong></h4>
<pre><code class="language-java">protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 쿠키 설정
    Cookie cookie = new Cookie(&quot;sessionId&quot;, &quot;12345&quot;);
    cookie.setMaxAge(3600); // 1시간 유지
    response.addCookie(cookie);

    // 상태 코드 설정
    response.setStatus(HttpServletResponse.SC_OK);

    // HTML 응답 출력
    response.setContentType(&quot;text/html;charset=UTF-8&quot;);
    PrintWriter out = response.getWriter();
    out.println(&quot;&lt;html&gt;&lt;body&gt;&lt;h1&gt;Response Example&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;&quot;);
}</code></pre>
<hr>
<h2 id="httpservletrequest의-상세-사용-예와-메서드"><strong>HttpServletRequest의 상세 사용 예와 메서드</strong></h2>
<h3 id="클라이언트의-요청-데이터를-처리하는-주요-메서드">클라이언트의 요청 데이터를 처리하는 주요 메서드</h3>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>getParameter(String name)</code></td>
<td>단일 요청 파라미터 값을 가져옵니다.</td>
</tr>
<tr>
<td><code>getParameterNames()</code></td>
<td>요청 파라미터 이름들의 열거형을 반환합니다.</td>
</tr>
<tr>
<td><code>getParameterValues(String name)</code></td>
<td>동일한 이름의 다중 요청 파라미터 값을 배열로 반환합니다.</td>
</tr>
<tr>
<td><code>getCookies()</code></td>
<td>클라이언트가 전송한 쿠키 배열을 반환합니다.</td>
</tr>
<tr>
<td><code>getSession()</code></td>
<td>현재 세션을 반환하며 없으면 새로 생성합니다.</td>
</tr>
<tr>
<td><code>getHeader(String name)</code></td>
<td>요청 헤더 값을 반환합니다.</td>
</tr>
<tr>
<td><code>getRequestURI()</code></td>
<td>요청된 URI를 반환합니다.</td>
</tr>
<tr>
<td><code>getMethod()</code></td>
<td>요청 방식(GET, POST 등)을 반환합니다.</td>
</tr>
</tbody></table>
<hr>
<h2 id="httpservletresponse의-상세-사용-예와-메서드"><strong>HttpServletResponse의 상세 사용 예와 메서드</strong></h2>
<h3 id="서버가-응답을-생성하는-주요-메서드">서버가 응답을 생성하는 주요 메서드</h3>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>setContentType(String type)</code></td>
<td>응답의 MIME 타입을 설정합니다.</td>
</tr>
<tr>
<td><code>setStatus(int sc)</code></td>
<td>응답 상태 코드를 설정합니다.</td>
</tr>
<tr>
<td><code>addCookie(Cookie cookie)</code></td>
<td>응답에 쿠키를 추가합니다.</td>
</tr>
<tr>
<td><code>sendRedirect(String location)</code></td>
<td>클라이언트를 지정된 URL로 리다이렉트합니다.</td>
</tr>
<tr>
<td><code>getWriter()</code></td>
<td>클라이언트에 텍스트 데이터를 출력하기 위한 PrintWriter를 반환합니다.</td>
</tr>
<tr>
<td><code>getOutputStream()</code></td>
<td>바이너리 데이터를 출력하기 위한 OutputStream을 반환합니다.</td>
</tr>
</tbody></table>
<hr>
<h2 id="servlet-httpservletrequest-httpservletresponse의-상호작용">Servlet, HttpServletRequest, HttpServletResponse의 상호작용</h2>
<ol>
<li><strong>클라이언트 요청:</strong> 사용자가 브라우저를 통해 URL을 요청하거나 폼을 제출하면, 해당 요청은 HTTP 요청 형태로 서버로 전송됩니다.  </li>
<li><strong>HttpServletRequest 활용:</strong> 서버는 요청 데이터를 <code>HttpServletRequest</code> 객체를 통해 분석하고 필요한 정보를 추출합니다.  </li>
<li><strong>서버 처리:</strong> 요청을 처리한 결과를 생성하며, 필요 시 세션, 쿠키 등을 설정합니다.  </li>
<li><strong>HttpServletResponse 활용:</strong> 처리 결과를 <code>HttpServletResponse</code> 객체를 통해 클라이언트로 응답합니다.  </li>
<li><strong>클라이언트 응답:</strong> 클라이언트는 서버에서 전달된 응답 데이터를 화면에 렌더링하거나 추가 요청을 수행합니다.  </li>
</ol>
<hr>
<h2 id="servlet-사용의-주요-이점"><strong>Servlet 사용의 주요 이점</strong></h2>
<ol>
<li><strong>동적 콘텐츠 생성:</strong> 클라이언트 요청에 따라 맞춤형 데이터를 제공합니다.  </li>
<li><strong>HTTP 프로토콜 지원:</strong> GET, POST, PUT 등 다양한 요청 방식을 처리합니다.  </li>
<li><strong>확장성:</strong> 요청당 스레드를 생성하여 대규모 트래픽도 안정적으로 처리할 수 있습니다.  </li>
<li><strong>다양한 API 지원:</strong> 세션 관리, 쿠키 처리 등 HTTP 기반의 다양한 기능을 제공합니다.  </li>
</ol>
<hr>
<h3 id="결론">결론</h3>
<p><code>HttpServletRequest</code>와 <code>HttpServletResponse</code>는 웹 애플리케이션에서 클라이언트와 서버 간 데이터 흐름을 관리하는 핵심 도구입니다. 이를 효율적으로 사용하면 동적 웹 애플리케이션을 개발할 수 있으며, 사용자의 요구에 맞춘 동작 구현도 가능합니다🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Data Analysis] IMDB]]></title>
            <link>https://velog.io/@du-log/Data-Analysis-IMDB</link>
            <guid>https://velog.io/@du-log/Data-Analysis-IMDB</guid>
            <pubDate>Sun, 01 Dec 2024 10:32:38 GMT</pubDate>
            <description><![CDATA[<h1 id="imdb-데이터-분석-및-주요-인사이트"><strong>IMDB 데이터 분석 및 주요 인사이트</strong></h1>
<p>영화 산업은 수십 년간 놀라운 성장을 이루어왔으며, 다양한 장르와 콘텐츠 유형을 통해 전 세계 관객들에게 즐거움을 선사하고 있습니다. 이번 프로젝트에서는 IMDB 데이터를 분석하여 영화와 TV 콘텐츠의 장르별, 연도별, 그리고 평점과 투표 수 간의 관계를 시각화한 대시보드를 제작했습니다. 이를 통해 얻은 주요 인사이트를 공유합니다.</p>
<hr>
<h2 id="1-데이터-요약"><strong>1. 데이터 요약</strong></h2>
<h3 id="주요-지표">주요 지표</h3>
<ul>
<li><strong>전체 평균 평점</strong>: 7.259점  </li>
<li><strong>가장 많은 투표를 받은 영화</strong>: <em>The Shawshank Redemption</em> (296만 7,074표)  </li>
<li><strong>최다 투표 상위 10개 작품</strong>: 영화와 TV 시리즈가 혼합된 형태로, <em>The Dark Knight*와 *Inception</em> 등 잘 알려진 작품들이 포함.</li>
</ul>
<hr>
<h2 id="2-분석-인사이트"><strong>2. 분석 인사이트</strong></h2>
<h3 id="1-장르별-데이터-분석"><strong>1) 장르별 데이터 분석</strong></h3>
<ul>
<li><strong>장르별 평균 평점</strong>:<ul>
<li><code>Documentary</code>(다큐멘터리)가 평균 평점 7.259로 가장 높은 평가를 받았습니다.</li>
<li>반면, <code>Horror</code>(호러)는 평균 4.877점으로 가장 낮은 평점을 기록.</li>
<li><em>코미디와 드라마</em> 장르가 다양한 하위 조합(<code>Comedy, Drama</code>, <code>Drama, Romance</code>)에서 꾸준히 높은 점수를 유지.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>인사이트</strong>:  
  다큐멘터리는 관객층이 한정적이지만, 높은 품질의 콘텐츠를 제공해 평점이 우수. 반면, 호러 장르는 대중적이지만 작품 간 품질 차이가 커 평점이 낮은 경향을 보입니다.</p>
</blockquote>
<hr>
<h3 id="2-출시-연도별-영화-수와-평균-평점"><strong>2) 출시 연도별 영화 수와 평균 평점</strong></h3>
<ul>
<li><p><strong>연도별 영화 수 증가</strong>:</p>
<ul>
<li>1970년대 이후 영화 제작 수가 급격히 증가, 2000년대 이후 정점에 도달.</li>
<li>특히 2010년대에는 스트리밍 서비스의 발전과 함께 콘텐츠 양이 폭발적으로 증가.</li>
</ul>
</li>
<li><p><strong>평균 평점의 변화</strong>:</p>
<ul>
<li>연도별로 큰 변동은 없으나, 최신 영화의 평점 분포는 다소 낮은 경향을 보임.</li>
<li>이는 최근 수많은 콘텐츠 출시로 인해 관객의 평가 기준이 더 엄격해졌음을 시사.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>인사이트</strong>:  
  영화 제작 기술과 플랫폼의 발전이 양적 성장을 이끌었지만, 질적 기준은 다양화됨. 이는 관객과 제작사 간의 기대치 조정이 필요함을 보여줍니다.</p>
</blockquote>
<hr>
<h3 id="3-평점과-투표-수의-관계"><strong>3) 평점과 투표 수의 관계</strong></h3>
<ul>
<li>평점이 <strong>8.0 이상</strong>인 경우, 투표 수가 대체로 많아지는 경향을 보임.</li>
<li>반면, 평점이 낮은 작품(5.0 미만)은 투표 수가 적어 관객의 관심이 부족.</li>
</ul>
<blockquote>
<p><strong>인사이트</strong>:  
  높은 평점 작품은 바이럴 효과로 인해 더 많은 관객과 상호작용을 끌어내는 반면, 저평점 콘텐츠는 주목받기 어려운 경향이 있습니다. 제작사는 평점이 높은 콘텐츠 제작에 집중해야 할 필요가 있습니다.</p>
</blockquote>
<hr>
<h3 id="4-타입별-콘텐츠-비율"><strong>4) 타입별 콘텐츠 비율</strong></h3>
<ul>
<li><strong>Documentary</strong>와 <strong>Comedy</strong> 콘텐츠의 비율이 가장 높음.</li>
<li>TV 시리즈와 영화가 고르게 분포되어 있으며, 다양한 장르가 폭넓게 제작되고 있음.</li>
</ul>
<blockquote>
<p><strong>인사이트</strong>:  
  최근 관객의 관심은 TV 시리즈와 다큐멘터리로 확대되고 있으며, 플랫폼별 특화 콘텐츠 전략이 중요해지고 있습니다.</p>
</blockquote>
<hr>
<h3 id="5-상위-10개-작품의-특징"><strong>5) 상위 10개 작품의 특징</strong></h3>
<ul>
<li>최다 투표 상위 10개 작품은 대부분 <strong>Action</strong>, <strong>Drama</strong>, <strong>Adventure</strong> 장르에 속함.</li>
<li><em>The Shawshank Redemption*과 *The Dark Knight</em> 등은 스토리와 연출의 완성도로 관객에게 깊은 인상을 남김.</li>
</ul>
<blockquote>
<p><strong>인사이트</strong>:  
  대중적 사랑을 받는 작품은 강렬한 스토리와 독창성이 주요 요소로 작용합니다. 제작사는 이러한 트렌드를 분석하여 성공적인 콘텐츠를 기획해야 할 것입니다.</p>
</blockquote>
<hr>
<h2 id="3-결론-및-제안"><strong>3. 결론 및 제안</strong></h2>
<p>이번 분석을 통해 IMDB 데이터를 바탕으로 영화 및 TV 콘텐츠의 주요 트렌드와 성과를 확인할 수 있었습니다. 아래는 추가적인 제안 사항입니다:</p>
<ul>
<li><p><strong>콘텐츠 제작</strong>:</p>
<ul>
<li>다큐멘터리와 드라마/코미디 장르의 품질 높은 콘텐츠에 투자.</li>
<li>관객의 관심을 끌 수 있는 새로운 장르와 형식의 실험 필요.</li>
</ul>
</li>
<li><p><strong>데이터 기반 마케팅</strong>:</p>
<ul>
<li>높은 평점을 받은 작품을 중심으로 마케팅 캠페인 강화.</li>
<li>투표 수가 적은 콘텐츠에 대한 개선 및 리마케팅 전략 수립.</li>
</ul>
</li>
<li><p><strong>플랫폼별 차별화</strong>:</p>
<ul>
<li>스트리밍 서비스와 전통적 극장 배급 간의 전략적 차별화가 중요.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="대시보드-활용"><strong>대시보드 활용</strong></h2>
<p>이번 대시보드는 데이터를 시각적으로 효과적으로 전달하여 비즈니스 의사결정에 활용 가능하도록 설계되었습니다. 장르, 연도별 트렌드, 그리고 평점과 투표 수 관계를 통해 콘텐츠 제작 및 유통의 새로운 방향성을 제시할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ToyProject]검색창 구현]]></title>
            <link>https://velog.io/@du-log/ToyProject%EA%B2%80%EC%83%89%EC%B0%BD-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@du-log/ToyProject%EA%B2%80%EC%83%89%EC%B0%BD-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Tue, 26 Nov 2024 00:03:04 GMT</pubDate>
            <description><![CDATA[<h2 id="검색창searchbox-구현-코드-리뷰-🔍">검색창(SearchBox) 구현 코드 리뷰 🔍</h2>
<p>이번 포스팅에서는 <strong>노벨상 탐색기 프로젝트</strong>의 핵심 기능 중 하나인 <strong>검색창</strong>과 관련된 코드를 리뷰합니다. 검색창은 사용자로부터 입력을 받아 API를 호출하고, 결과를 필터링하여 사용자에게 표시하는 중요한 UI/UX 요소입니다. 리뷰 대상은 다음과 같습니다:</p>
<ul>
<li><strong>SearchBox.js</strong>: 검색창 컴포넌트</li>
<li><strong>SearchBox.css</strong>: 검색창 스타일링</li>
<li><strong>SearchResults.js</strong>: 검색 결과를 표시하는 컴포넌트</li>
<li><strong>SearchResults.css</strong>: 결과 목록 스타일링</li>
</ul>
<hr>
<h3 id="1️⃣-searchboxjs---검색창-컴포넌트">1️⃣ <strong>SearchBox.js</strong> - 검색창 컴포넌트</h3>
<pre><code class="language-javascript">import React, { useState } from &quot;react&quot;;
import { useNavigate } from &quot;react-router-dom&quot;;
import &quot;./SearchBox.css&quot;;

function SearchBox({ onSearch }) {
  const [query, setQuery] = useState(&quot;&quot;);
  const [year, setYear] = useState(&quot;&quot;);
  const [prizeCategory, setPrizeCategory] = useState(&quot;&quot;);
  const navigate = useNavigate();

  const handleSubmit = async (e) =&gt; {
    e.preventDefault();
    try {
      let apiUrl = `https://api.nobelprize.org/2.1/laureates?`;
      if (query) apiUrl += `name=${query}&amp;`;
      if (year) apiUrl += `nobelPrizeYear=${year}&amp;`;
      if (prizeCategory) apiUrl += `nobelPrizeCategory=${prizeCategory}`;

      const response = await fetch(apiUrl);
      const data = await response.json();

      const filteredData = data.laureates.filter((laureate) =&gt;
        laureate.fullName.en.toLowerCase().startsWith(query.toLowerCase())
      );

      onSearch(filteredData || []);
      navigate(&quot;/search-results&quot;);
    } catch (error) {
      console.error(&quot;Error fetching data:&quot;, error);
    }
  };

  return (
    &lt;div className=&quot;search-container&quot;&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;input
          type=&quot;text&quot;
          value={query}
          onChange={(e) =&gt; setQuery(e.target.value)}
          placeholder=&quot;이름 검색&quot;
        /&gt;
        &lt;input
          type=&quot;text&quot;
          value={year}
          onChange={(e) =&gt; setYear(e.target.value)}
          placeholder=&quot;수상 연도&quot;
        /&gt;
        &lt;input
          type=&quot;text&quot;
          value={prizeCategory}
          onChange={(e) =&gt; setPrizeCategory(e.target.value)}
          placeholder=&quot;수상 종류&quot;
        /&gt;
        &lt;button type=&quot;submit&quot;&gt;검색&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}

export default SearchBox;</code></pre>
<h4 id="📌-리뷰-포인트">📌 <strong>리뷰 포인트</strong></h4>
<ol>
<li><p><strong>구조적 완성도</strong></p>
<ul>
<li><code>useState</code>를 활용해 검색 필드의 상태를 관리하며, 입력값에 실시간으로 반응합니다.</li>
<li><code>useNavigate</code>를 사용해 검색 결과 페이지로 이동을 구현한 점이 효율적입니다.</li>
</ul>
</li>
<li><p><strong>API 호출 및 데이터 처리</strong></p>
<ul>
<li>조건에 따라 API URL을 동적으로 생성하며, 사용자 입력값에 맞는 데이터를 필터링하는 로직이 명확합니다.</li>
<li><code>try-catch</code> 블록을 사용해 에러를 처리하고 있어 안정성이 돋보입니다.</li>
</ul>
</li>
<li><p><strong>코드 개선 제안</strong></p>
<ul>
<li><strong>Validation 추가</strong>: 연도나 상 종류가 유효하지 않은 경우 적절한 경고 메시지를 표시하는 로직을 추가하면 사용자 경험이 개선됩니다.</li>
<li><strong>필드 초기화</strong>: 검색이 완료된 후 입력값을 초기화하는 기능을 고려해보세요.<pre><code class="language-javascript">setQuery(&quot;&quot;);
setYear(&quot;&quot;);
setPrizeCategory(&quot;&quot;);</code></pre>
</li>
</ul>
</li>
</ol>
<hr>
<h3 id="2️⃣-searchboxcss---검색창-스타일링">2️⃣ <strong>SearchBox.css</strong> - 검색창 스타일링</h3>
<pre><code class="language-css">.search-container {
  width: 100%;
  max-width: 800px;
  margin: 50px auto;
  padding: 20px;
  background-color: #f9f9f9;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

form {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
}

input {
  width: 30%;
  padding: 12px;
  border: 1px solid #ccc;
  border-radius: 5px;
  font-size: 1rem;
  box-sizing: border-box;
}

input:focus {
  border-color: #007bff;
  outline: none;
  box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
}

button {
  width: 20%;
  padding: 12px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
  font-size: 1.1rem;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

button:hover {
  background-color: #0056b3;
}</code></pre>
<h4 id="📌-리뷰-포인트-1">📌 <strong>리뷰 포인트</strong></h4>
<ol>
<li><p><strong>레이아웃 및 스타일링</strong></p>
<ul>
<li><code>flexbox</code>를 사용하여 각 필드를 깔끔하게 정렬하고 간격을 유지하였습니다.</li>
<li><code>box-shadow</code>와 <code>border-radius</code>로 검색창에 부드러운 느낌을 더했습니다.</li>
</ul>
</li>
<li><p><strong>디자인 개선 제안</strong></p>
<ul>
<li><strong>반응형 디자인</strong>: 모바일 화면에서 입력 필드와 버튼이 너무 좁아 보일 수 있으므로, 미디어 쿼리를 통해 필드의 너비를 100%로 조정하는 것이 좋습니다.<pre><code class="language-css">@media (max-width: 768px) {
  input, button {
    width: 100%;
    margin-bottom: 10px;
  }
}</code></pre>
</li>
</ul>
</li>
</ol>
<hr>
<h3 id="3️⃣-searchresultsjs---검색-결과-표시">3️⃣ <strong>SearchResults.js</strong> - 검색 결과 표시</h3>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import &quot;./SearchResults.css&quot;;

function SearchResults({ searchResults }) {
  if (!searchResults || searchResults.length === 0) {
    return &lt;div&gt;검색 결과가 없습니다.&lt;/div&gt;;
  }

  return (
    &lt;div className=&quot;search-results&quot;&gt;
      &lt;h2&gt;검색 결과&lt;/h2&gt;
      &lt;ul&gt;
        {searchResults.map((result, index) =&gt; (
          &lt;li key={index}&gt;
            &lt;h3&gt;{result.knownName?.en || result.fullName?.en || &quot;이름 없음&quot;}&lt;/h3&gt;
            &lt;p&gt;수상 연도: {result.nobelPrizes[0]?.awardYear}&lt;/p&gt;
            &lt;p&gt;수상 종류: {result.nobelPrizes[0]?.category?.en}&lt;/p&gt;
          &lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}

export default SearchResults;</code></pre>
<h4 id="📌-리뷰-포인트-2">📌 <strong>리뷰 포인트</strong></h4>
<ol>
<li><p><strong>구조적 간결성</strong></p>
<ul>
<li>조건부 렌더링을 통해 결과가 없을 경우 메시지를 출력하는 구조가 명확합니다.</li>
<li>API 응답 데이터를 적절히 매핑하여 사용자에게 필요한 정보를 제공합니다.</li>
</ul>
</li>
<li><p><strong>코드 개선 제안</strong></p>
<ul>
<li><strong>로딩 상태 추가</strong>: API 호출 중 로딩 스피너를 표시하면 사용자 경험이 향상됩니다.<pre><code class="language-javascript">const [isLoading, setIsLoading] = useState(false);</code></pre>
</li>
</ul>
</li>
</ol>
<hr>
<h3 id="4️⃣-searchresultscss---결과-스타일링">4️⃣ <strong>SearchResults.css</strong> - 결과 스타일링</h3>
<pre><code class="language-css">.search-results {
  margin: 20px;
  padding: 20px;
  background-color: #f9f9f9;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.search-results h2 {
  font-size: 24px;
  margin-bottom: 16px;
}

.search-results li {
  padding: 15px;
  background-color: #fff;
  border: 1px solid #ddd;
  border-radius: 6px;
  margin-bottom: 16px;
}</code></pre>
<h4 id="📌-리뷰-포인트-3">📌 <strong>리뷰 포인트</strong></h4>
<ul>
<li>결과 목록을 깔끔하게 정리한 스타일링이 돋보입니다.</li>
<li>호버 효과로 시각적 피드백을 제공하는 점이 유용합니다.
<img src="https://velog.velcdn.com/images/du-log/post/5b2e0d03-545a-4abf-96b5-5f981667a158/image.png" alt=""></li>
</ul>
<hr>
<h3 id="마무리-✨">마무리 ✨</h3>
<p>이 코드는 검색창 구현과 관련된 <strong>구조적 완성도와 디자인의 균형</strong>이 돋보입니다. 다만, 추가적인 UX 개선 사항(로딩 상태, 에러 처리 등)을 반영하면 더 나은 사용자 경험을 제공할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ToyProject] 내비게이션 바]]></title>
            <link>https://velog.io/@du-log/ToyProject-%EB%82%B4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98-%EB%B0%94</link>
            <guid>https://velog.io/@du-log/ToyProject-%EB%82%B4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98-%EB%B0%94</guid>
            <pubDate>Mon, 25 Nov 2024 01:16:32 GMT</pubDate>
            <description><![CDATA[<h2 id="화면-상단-내비게이션-바-코드-리뷰-🚀">화면 상단 내비게이션 바 코드 리뷰 🚀</h2>
<p>이번 포스팅에서는 노벨상 탐색기 프로젝트의 <strong>내비게이션 바</strong> 구현과 관련된 주요 파일들의 코드 리뷰를 할 것입니다.내비게이션 바는 사용자 경험을 높이고 페이지 간의 이동을 직관적으로 만들어주는 중요한 UI 요소입니다. 리뷰 대상은 다음과 같습니다:</p>
<ul>
<li><strong>Nav.js</strong>: 내비게이션 바의 구조를 정의한 컴포넌트</li>
<li><strong>Nav.css</strong>: 내비게이션 바의 스타일링</li>
<li><strong>About.js</strong>: &quot;About&quot; 페이지의 콘텐츠를 제공하는 컴포넌트</li>
<li><strong>Contact.js</strong>: &quot;Contact&quot; 페이지의 콘텐츠를 제공하는 컴포넌트</li>
<li><strong>App.js</strong>: 내비게이션 바 및 라우팅 설정 적용</li>
</ul>
<hr>
<h3 id="1️⃣-navjs---내비게이션-바-구조">1️⃣ <strong>Nav.js</strong> - 내비게이션 바 구조</h3>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { Link } from &quot;react-router-dom&quot;;
import &quot;./Nav.css&quot;;

function Nav() {
  return (
    &lt;div className=&quot;navbar&quot;&gt;
      &lt;Link to=&quot;/&quot; className=&quot;navbarTitle&quot;&gt;
        &lt;h1&gt;Nobel Winners&lt;/h1&gt;
      &lt;/Link&gt;
      &lt;Link className=&quot;navbarMenu&quot; to=&quot;/about&quot;&gt;
        About
      &lt;/Link&gt;
      &lt;Link className=&quot;navbarMenu&quot; to=&quot;/contact&quot;&gt;
        Contact
      &lt;/Link&gt;
    &lt;/div&gt;
  );
}

export default Nav;</code></pre>
<h4 id="📌-리뷰-포인트">📌 <strong>리뷰 포인트</strong></h4>
<ol>
<li><p><strong>컴포넌트 구조</strong>  </p>
<ul>
<li><strong><code>&lt;Link&gt;</code></strong>를 사용하여 React Router와의 통합을 매끄럽게 구현하였습니다.  </li>
<li>각 메뉴는 의미에 맞는 클래스 이름을 부여받아 스타일링 및 식별이 용이합니다.  </li>
</ul>
</li>
<li><p><strong>UI/UX 고려</strong>  </p>
<ul>
<li><strong><code>Nobel Winners</code></strong>는 h1 태그로 정의되어 화면의 중심 요소로 자리 잡습니다.  </li>
<li>&quot;About&quot;와 &quot;Contact&quot; 링크는 적절한 텍스트로 표현되어 내비게이션 용도를 명확히 합니다.  </li>
</ul>
</li>
<li><p><strong>코드 개선 제안</strong>  </p>
<ul>
<li><p><strong>활성화된 메뉴 표시</strong>: <code>NavLink</code>를 사용해 현재 활성화된 메뉴를 강조하면 UX가 더욱 향상됩니다.  </p>
<pre><code class="language-javascript">import { NavLink } from &quot;react-router-dom&quot;;

&lt;NavLink 
  to=&quot;/about&quot; 
  className={({ isActive }) =&gt; isActive ? &quot;navbarMenu active&quot; : &quot;navbarMenu&quot;}
&gt;
  About
&lt;/NavLink&gt;</code></pre>
<p>위와 같이 활성 상태에 따라 추가 클래스(<code>active</code>)를 적용하면 현재 페이지를 직관적으로 표시할 수 있습니다.  </p>
</li>
</ul>
</li>
</ol>
<hr>
<h3 id="2️⃣-navcss---내비게이션-바-스타일링">2️⃣ <strong>Nav.css</strong> - 내비게이션 바 스타일링</h3>
<pre><code class="language-css">.navbar {
  width: 100%;
  background-color: white;
  padding: 15px 0;
  display: flex;
  align-items: center; /* 수직 중앙 정렬 */
  text-align: left;
}

.navbarTitle {
  text-decoration: none; /* Link 태그의 기본 밑줄 제거 */
}

.navbarTitle h1 {
  font-size: 36px; /* Nobel Winners의 크기를 더 크게 설정 */
  font-weight: bold; /* 글자 굵게 설정 */
  margin-left: 20px;
  color: black;
  text-decoration: none; /* h1 태그의 밑줄도 제거 */
}

.navbarTitle h1:hover {
  text-decoration: underline; /* 마우스를 올리면 밑줄 표시 */
}

.navbarMenu {
  color: black;
  margin-left: 20px;
  font-size: 18px; /* 나머지 메뉴의 크기는 기본보다 작게 설정 */
  text-decoration: none;
}

.navbarMenu:hover {
  text-decoration: underline;
}</code></pre>
<h4 id="📌-리뷰-포인트-1">📌 <strong>리뷰 포인트</strong></h4>
<ol>
<li><p><strong>레이아웃 설계</strong>  </p>
<ul>
<li><code>flexbox</code>를 사용해 메뉴를 수평 정렬하고, 텍스트가 가운데 정렬되도록 구현했습니다.  </li>
</ul>
</li>
<li><p><strong>디자인 세부사항</strong>  </p>
<ul>
<li><code>Nobel Winners</code>는 더 크고 굵은 글씨로 설정하여 헤더 역할을 강조했습니다.  </li>
<li>메뉴 항목(<code>About</code>, <code>Contact</code>)에는 호버 시 밑줄 효과를 추가하여 시각적인 피드백을 제공합니다.  </li>
</ul>
</li>
<li><p><strong>코드 개선 제안</strong>  </p>
<ul>
<li><strong>반응형 고려</strong>: 모바일 화면에서는 메뉴가 좁게 보일 수 있으므로 미디어 쿼리를 추가해 메뉴를 드롭다운 형식으로 전환하거나 폰트를 조정하는 것이 좋습니다.  <pre><code class="language-css">@media (max-width: 768px) {
  .navbar {
    flex-direction: column;
    align-items: flex-start;
    padding: 10px;
  }
  .navbarMenu {
    font-size: 16px;
    margin-left: 0;
    margin-top: 10px;
  }
}</code></pre>
</li>
</ul>
</li>
</ol>
<hr>
<h3 id="3️⃣-aboutjs---about-페이지-구현">3️⃣ <strong>About.js</strong> - About 페이지 구현</h3>
<pre><code class="language-javascript">function About() {
  return (
    &lt;div&gt;
      &lt;h1&gt;About&lt;/h1&gt;
      &lt;p&gt;
        The Nobel Prize is a set of prestigious international awards given
        annually in several categories. The prizes were established by the 1895
        will of Alfred Nobel, the inventor of dynamite. The prizes in Physics,
        Chemistry, Physiology or Medicine, Literature, and Peace were first
        awarded in 1901.
      &lt;/p&gt;
    &lt;/div&gt;
  );
}
export default About;</code></pre>
<h4 id="📌-리뷰-포인트-2">📌 <strong>리뷰 포인트</strong></h4>
<ol>
<li><p><strong>구조적 완성도</strong>  </p>
<ul>
<li>간결하고 명확한 구조로 About 페이지를 설명합니다.  </li>
</ul>
</li>
<li><p><strong>코드 개선 제안</strong>  </p>
<ul>
<li>스타일링 추가: <code>About.css</code> 파일을 만들어 텍스트 정렬, 폰트 스타일 등을 세부적으로 지정하면 더욱 완성도를 높일 수 있습니다.  </li>
<li>예시:<pre><code class="language-css">.aboutContainer {
  padding: 20px;
  font-family: Arial, sans-serif;
}</code></pre>
</li>
</ul>
</li>
</ol>
<hr>
<h3 id="4️⃣-contactjs---contact-페이지-구현">4️⃣ <strong>Contact.js</strong> - Contact 페이지 구현</h3>
<pre><code class="language-javascript">function Contact() {
  return (
    &lt;div&gt;
      &lt;h1&gt;Contact&lt;/h1&gt;
    &lt;/div&gt;
  );
}
export default Contact;</code></pre>
<h4 id="📌-리뷰-포인트-3">📌 <strong>리뷰 포인트</strong></h4>
<ol>
<li><p><strong>구조의 간소화</strong>  </p>
<ul>
<li>Contact 페이지는 기본적인 틀만 구현되어 있습니다.  </li>
</ul>
</li>
<li><p><strong>코드 개선 제안</strong>  </p>
<ul>
<li>폼 추가: 사용자가 연락처나 이메일을 입력할 수 있는 폼을 추가하면 실용성이 향상됩니다.  <pre><code class="language-javascript">function Contact() {
  return (
    &lt;div&gt;
      &lt;h1&gt;Contact&lt;/h1&gt;
      &lt;form&gt;
        &lt;label htmlFor=&quot;email&quot;&gt;Email:&lt;/label&gt;
        &lt;input type=&quot;email&quot; id=&quot;email&quot; name=&quot;email&quot; /&gt;
        &lt;label htmlFor=&quot;message&quot;&gt;Message:&lt;/label&gt;
        &lt;textarea id=&quot;message&quot; name=&quot;message&quot;&gt;&lt;/textarea&gt;
        &lt;button type=&quot;submit&quot;&gt;Send&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}</code></pre>
</li>
</ul>
</li>
</ol>
<hr>
<h3 id="5️⃣-appjs---내비게이션-바-적용">5️⃣ <strong>App.js</strong> - 내비게이션 바 적용</h3>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { BrowserRouter as Router, Route, Routes } from &quot;react-router-dom&quot;;
import Nav from &quot;./Nav&quot;;
import About from &quot;./About&quot;;
import Contact from &quot;./Contact&quot;;

function App() {
  return (
    &lt;Router&gt;
      &lt;Nav /&gt;
      &lt;Routes&gt;
        &lt;Route path=&quot;/about&quot; element={&lt;About /&gt;} /&gt;
        &lt;Route path=&quot;/contact&quot; element={&lt;Contact /&gt;} /&gt;
      &lt;/Routes&gt;
    &lt;/Router&gt;
  );
}

export default App;</code></pre>
<h4 id="📌-리뷰-포인트-4">📌 <strong>리뷰 포인트</strong></h4>
<ol>
<li><p><strong>라우팅 통합</strong>  </p>
<ul>
<li><code>React Router</code>의 <code>&lt;Routes&gt;</code>와 <code>&lt;Route&gt;</code>를 활용해 페이지 간 이동을 간단히 구성했습니다.  </li>
</ul>
</li>
<li><p><strong>구성의 명확성</strong>  </p>
<ul>
<li><code>Nav</code> 컴포넌트를 모든 페이지에서 공유하도록 설정하여 일관된 내비게이션 경험을 제공합니다.  </li>
</ul>
</li>
</ol>
<hr>
<h3 id="완성본">완성본</h3>
<p><img src="https://velog.velcdn.com/images/du-log/post/b0705d3c-d661-44f4-8245-613afcc13388/image.png" alt=""></p>
<p>이제 이 코드를 기반으로 내비게이션 바와 페이지 전환의 세부 구현을 학습해 보세요! 😊 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ToyProject]Nobel-prize 소개]]></title>
            <link>https://velog.io/@du-log/ToyProjectNobel-prize-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@du-log/ToyProjectNobel-prize-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Sun, 24 Nov 2024 01:46:20 GMT</pubDate>
            <description><![CDATA[<h2 id="노벨상-탐색기-nobel-prize-explorer---프로젝트-소개-🎓✨">노벨상 탐색기 (Nobel Prize Explorer) - 프로젝트 소개 🎓✨</h2>
<h3 id="🌟-프로젝트-개요">🌟 프로젝트 개요</h3>
<p><strong>&quot;노벨상 탐색기&quot;</strong>는 노벨상 수상자 데이터를 쉽게 탐색할 수 있도록 설계된 React 기반의 웹 애플리케이션입니다.<br>학생, 연구자, 일반 사용자들이 카테고리별로 노벨상 수상자 정보를 확인할 수 있는 인터랙티브한 환경을 제공합니다.<br><strong>React Router</strong>와 <strong>CSS Grid</strong>를 활용하여 사용성과 시각적인 매력을 동시에 고려하였으며 API 연동을 통해 실시간 데이터를 제공합니다.  </p>
<hr>
<h3 id="🎯-프로젝트-목표">🎯 프로젝트 목표</h3>
<ol>
<li><p><strong>정보 제공 플랫폼</strong>  </p>
<ul>
<li>노벨상 수상 데이터를 기반으로 누구나 쉽게 원하는 정보를 얻을 수 있는 사용자 친화적인 플랫폼을 구축합니다.  </li>
</ul>
</li>
<li><p><strong>데이터 활용 및 시각화</strong>  </p>
<ul>
<li><a href="https://nobelprize.readme.io/">Nobel Prize API</a>를 활용하여 실시간 데이터를 제공하며 향후 데이터 시각화를 도입할 계획입니다.  </li>
</ul>
</li>
<li><p><strong>UI/UX 최적화</strong>  </p>
<ul>
<li>반응형 디자인을 적용하여 데스크탑, 태블릿, 모바일에서도 최적의 경험을 제공합니다.  </li>
</ul>
</li>
<li><p><strong>코드의 확장성 및 재사용성 확보</strong>  </p>
<ul>
<li>모듈화된 컴포넌트 구조를 통해 유지보수와 확장이 용이한 프로젝트를 구현하였습니다.  </li>
</ul>
</li>
</ol>
<hr>
<h3 id="✨-주요-기능">✨ 주요 기능</h3>
<ol>
<li><p><strong>카테고리별 탐색</strong>  </p>
<ul>
<li>Medicine, Physics, Chemistry 등 다양한 노벨상 카테고리를 선택해 각 카테고리에 대한 수상 정보를 확인할 수 있습니다.  </li>
</ul>
</li>
<li><p><strong>이미지 기반 인터페이스</strong>  </p>
<ul>
<li>시각적 매력을 더하기 위해 카테고리마다 관련 이미지를 배치하였으며 사용자가 직관적으로 탐색할 수 있도록 구성했습니다.  </li>
</ul>
</li>
<li><p><strong>에러 처리</strong>  </p>
<ul>
<li>API 호출 중 발생할 수 있는 오류를 감지하고 사용자에게 알림을 표시하여 안정성을 높였습니다.  </li>
</ul>
</li>
<li><p><strong>라우팅 시스템</strong>  </p>
<ul>
<li><strong>React Router</strong>를 사용해 부드러운 페이지 전환을 구현하고 사용자의 탐색 경험을 향상시켰습니다.  </li>
</ul>
</li>
</ol>
<hr>
<h3 id="🛠️-기술-스택">🛠️ 기술 스택</h3>
<ol>
<li><strong>React</strong>: 모던 UI 라이브러리로, 컴포넌트 기반 개발을 통해 효율적인 UI 구현.  </li>
<li><strong>React Router</strong>: 페이지 전환 및 라우팅 기능을 제공.  </li>
<li><strong>CSS Grid</strong>: 카테고리 선택 화면에 적용하여 반응형 레이아웃 구현.  </li>
<li><strong>노벨상 API</strong>: 동적 데이터 로드에 사용.  </li>
<li><strong>Node.js</strong> (옵션): 추가적인 서버 로직 처리 가능.  </li>
</ol>
<hr>
<h3 id="🗂️-프로젝트-특징">🗂️ 프로젝트 특징</h3>
<h4 id="💡-컴포넌트-중심의-구조">💡 컴포넌트 중심의 구조</h4>
<p>모든 UI 요소를 재사용 가능한 React 컴포넌트로 구현하여 유지보수와 확장성을 극대화했습니다.  </p>
<h4 id="🌍-반응형-설계">🌍 반응형 설계</h4>
<p>CSS Grid와 미디어 쿼리를 활용하여 다양한 디바이스에서 최적의 화면을 제공합니다.  </p>
<h4 id="📊-데이터-기반-애플리케이션">📊 데이터 기반 애플리케이션</h4>
<p>실시간 데이터 로드 기능을 통해 동적이고 최신 정보를 제공합니다.  </p>
<hr>
<h3 id="🚀-앞으로의-계획">🚀 앞으로의 계획</h3>
<ol>
<li><p><strong>검색 기능 추가</strong>  </p>
<ul>
<li>특정 키워드나 수상자 이름으로 정보를 탐색할 수 있는 글로벌 검색창을 도입할 예정입니다.  </li>
</ul>
</li>
<li><p><strong>시각화 도구 통합</strong>  </p>
<ul>
<li>Chart.js 또는 D3.js를 활용하여 수상자 데이터의 트렌드 및 국가별 통계를 시각화합니다.  </li>
</ul>
</li>
<li><p><strong>다크 모드 구현</strong>  </p>
<ul>
<li>라이트 모드와 다크 모드 간 전환을 지원하는 사용자 설정 기능 추가.  </li>
</ul>
</li>
<li><p><strong>테스트 자동화</strong>  </p>
<ul>
<li><strong>Cypress</strong> 및 <strong>React Testing Library</strong>를 활용한 자동화 테스트 도입.  </li>
</ul>
</li>
</ol>
<hr>
<p><strong>노벨상 탐색기 프로젝트</strong>는 데이터 중심의 웹 애플리케이션 개발에 관심 있는 개발자들에게 좋은 학습 자료가 될 것이라고 생각합니다.<br>사용자 친화적인 UI/UX와 동적 데이터 처리에 대한 도전을 통해 한 단계 더 성장할 수 있었던 프로젝트였습니다.<br>이 블로그 시리즈를 통해 여러분과 함께 프로젝트 코드를 상세히 리뷰하고 개선 아이디어를 나누고자 합니다. 😊  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] 특정 파일만 커밋하기]]></title>
            <link>https://velog.io/@du-log/Git-%ED%8A%B9%EC%A0%95-%ED%8C%8C%EC%9D%BC%EB%A7%8C-%EC%BB%A4%EB%B0%8B%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@du-log/Git-%ED%8A%B9%EC%A0%95-%ED%8C%8C%EC%9D%BC%EB%A7%8C-%EC%BB%A4%EB%B0%8B%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 23 Oct 2024 05:52:26 GMT</pubDate>
            <description><![CDATA[<h1 id="git에서-특정-파일만-add-하는-방법">Git에서 특정 파일만 <code>add</code> 하는 방법</h1>
<p>Git을 사용하다 보면 한 번에 여러 파일을 수정하지만, 특정 파일만 커밋하고 싶을 때가 있습니다. 이럴 때 유용한 명령어가 <code>git add</code> 입니다. 이번 포스팅에서는 Git에서 특정 파일만 <code>add</code>하는 방법을 알아보겠습니다.</p>
<h2 id="1-git-상태-확인-git-status">1. Git 상태 확인 (<code>git status</code>)</h2>
<p>먼저, 현재 작업 중인 디렉토리의 변경 사항을 확인하려면 <code>git status</code> 명령어를 사용합니다. 이 명령어를 통해 어떤 파일이 수정되었는지, 추가해야 할 파일이 무엇인지 확인할 수 있습니다.</p>
<pre><code class="language-bash">git status</code></pre>
<p>이 명령어를 입력하면 다음과 같이 출력됩니다.</p>
<pre><code>On branch main
Changes not staged for commit:
  (use &quot;git add &lt;file&gt;...&quot; to update what will be committed)
  (use &quot;git restore &lt;file&gt;...&quot; to discard changes in working directory)
    modified:   file1.txt
    modified:   file2.txt

Untracked files:
  (use &quot;git add &lt;file&gt;...&quot; to include in what will be committed)
    file3.txt</code></pre><p>위의 결과에서 <code>file1.txt</code>와 <code>file2.txt</code>는 수정되었고, <code>file3.txt</code>는 새로 추가된 파일임을 알 수 있습니다.</p>
<h2 id="2-특정-파일만-git-add-하기">2. 특정 파일만 <code>git add</code> 하기</h2>
<p>여러 파일이 변경되었을 때, 그 중 하나만 <code>add</code>하려면 <code>git add</code> 명령어 뒤에 파일 경로를 지정해줍니다. 예를 들어, <code>file1.txt</code>만 추가하고 싶다면 다음과 같이 입력합니다.</p>
<pre><code class="language-bash">git add file1.txt</code></pre>
<p>이렇게 하면 <code>file1.txt</code>만 스테이징 영역에 추가됩니다. 나머지 파일들은 아직 스테이징 되지 않은 상태로 남아 있습니다.</p>
<h2 id="3-여러-파일을-개별적으로-add-하기">3. 여러 파일을 개별적으로 <code>add</code> 하기</h2>
<p>만약 여러 파일을 선택적으로 추가하고 싶다면, 파일을 하나씩 선택하여 <code>git add</code> 명령어를 반복 실행하면 됩니다.</p>
<pre><code class="language-bash">git add file1.txt
git add file3.txt</code></pre>
<p>이렇게 하면 <code>file1.txt</code>와 <code>file3.txt</code>만 스테이징 영역에 추가되고, 나머지 파일들은 스테이징되지 않은 상태로 남습니다.</p>
<h2 id="4-특정-디렉토리의-파일만-add-하기">4. 특정 디렉토리의 파일만 <code>add</code> 하기</h2>
<p>특정 디렉토리 내의 파일을 한꺼번에 추가하고 싶을 때는 해당 디렉토리 경로를 명시해주면 됩니다. 예를 들어, <code>src</code> 디렉토리 내의 모든 파일을 추가하려면 아래와 같이 입력합니다.</p>
<pre><code class="language-bash">git add src/</code></pre>
<p>이 명령어는 <code>src</code> 디렉토리 내부의 모든 파일을 스테이징 영역에 추가합니다.</p>
<h2 id="5-변경된-모든-파일을-add-하기">5. 변경된 모든 파일을 <code>add</code> 하기</h2>
<p>만약 변경된 모든 파일을 한 번에 스테이징하고 싶다면, <code>.</code>(점)을 사용하면 됩니다. 다음 명령어를 실행하면 현재 디렉토리와 그 하위 디렉토리 내의 모든 변경 사항이 스테이징 영역에 추가됩니다.</p>
<pre><code class="language-bash">git add .</code></pre>
<hr>
<h2 id="결론">결론</h2>
<p>Git을 사용할 때, 특정 파일만 선택적으로 <code>add</code>하는 방법은 협업 과정에서 실수를 줄이고 원하는 변경 사항만 커밋하는 데 큰 도움이 됩니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Pandas] 데이터 프레임]]></title>
            <link>https://velog.io/@du-log/Pandas-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%94%84%EB%A0%88%EC%9E%84</link>
            <guid>https://velog.io/@du-log/Pandas-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%94%84%EB%A0%88%EC%9E%84</guid>
            <pubDate>Fri, 27 Sep 2024 02:20:40 GMT</pubDate>
            <description><![CDATA[<p>Pandas의 <code>DataFrame</code>은 2차원 데이터 구조로, 엑셀 스프레드시트처럼 행과 열로 구성된 데이터를 다룰 수 있는 강력한 도구입니다. <code>DataFrame</code>은 다양한 데이터 소스를 불러오거나 생성한 후, 데이터를 분석하고 조작하는 데 유용합니다. 여기서는 <code>DataFrame</code>에 대해 자세히 설명하고, 이를 활용하는 여러 방법을 예제와 함께 소개하겠습니다.</p>
<h2 id="1-dataframe이란">1. DataFrame이란?</h2>
<p><code>DataFrame</code>은 Pandas의 핵심 데이터 구조 중 하나로, 2차원 테이블(표) 형식으로 데이터를 저장합니다. 각 열은 데이터의 특징을 나타내고, 각 행은 개별 데이터를 나타냅니다. <code>DataFrame</code>은 여러 타입의 데이터를 함께 저장할 수 있으며, 인덱스를 통해 행과 열을 참조할 수 있습니다.</p>
<h3 id="예시">예시</h3>
<pre><code class="language-python">import pandas as pd

# 딕셔너리를 사용한 DataFrame 생성
data = {&#39;Name&#39;: [&#39;John&#39;, &#39;Anna&#39;, &#39;Peter&#39;, &#39;Linda&#39;],
        &#39;Age&#39;: [28, 24, 35, 32],
        &#39;City&#39;: [&#39;New York&#39;, &#39;Paris&#39;, &#39;Berlin&#39;, &#39;London&#39;]}

df = pd.DataFrame(data)

# DataFrame 출력
print(df)</code></pre>
<p>결과:</p>
<pre><code>    Name  Age      City
0   John   28  New York
1   Anna   24     Paris
2  Peter   35    Berlin
3  Linda   32    London</code></pre><h2 id="2-dataframe-생성-방법">2. DataFrame 생성 방법</h2>
<h3 id="21-딕셔너리를-사용한-생성">2.1. 딕셔너리를 사용한 생성</h3>
<p>딕셔너리로 <code>DataFrame</code>을 생성할 때, 키는 열 이름이 되고, 값은 데이터가 됩니다.</p>
<pre><code class="language-python"># 딕셔너리로 생성
data = {&#39;Product&#39;: [&#39;Phone&#39;, &#39;Tablet&#39;, &#39;Laptop&#39;],
        &#39;Price&#39;: [500, 800, 1200],
        &#39;Stock&#39;: [50, 30, 20]}

df = pd.DataFrame(data)
print(df)</code></pre>
<h3 id="22-리스트를-사용한-생성">2.2. 리스트를 사용한 생성</h3>
<p>리스트의 리스트를 사용하여 <code>DataFrame</code>을 생성할 수 있으며, 열 이름을 <code>columns</code> 파라미터로 지정합니다.</p>
<pre><code class="language-python"># 리스트로 생성
data = [[&#39;Alice&#39;, 25], [&#39;Bob&#39;, 30], [&#39;Charlie&#39;, 35]]
df = pd.DataFrame(data, columns=[&#39;Name&#39;, &#39;Age&#39;])
print(df)</code></pre>
<h3 id="23-numpy-배열로-생성">2.3. Numpy 배열로 생성</h3>
<p>Pandas는 Numpy와 호환되므로, Numpy 배열을 이용하여 <code>DataFrame</code>을 만들 수 있습니다.</p>
<pre><code class="language-python">import numpy as np

# Numpy 배열로 생성
data = np.array([[1, 2, 3], [4, 5, 6]])
df = pd.DataFrame(data, columns=[&#39;A&#39;, &#39;B&#39;, &#39;C&#39;])
print(df)</code></pre>
<h2 id="3-dataframe의-주요-속성">3. DataFrame의 주요 속성</h2>
<p><code>DataFrame</code>은 여러 유용한 속성을 제공하여 데이터에 대한 정보를 쉽게 확인할 수 있습니다.</p>
<ul>
<li><code>df.shape</code>: DataFrame의 행과 열 개수를 튜플 형태로 반환합니다.</li>
<li><code>df.columns</code>: 열 이름을 반환합니다.</li>
<li><code>df.index</code>: 행 인덱스를 반환합니다.</li>
<li><code>df.dtypes</code>: 각 열의 데이터 타입을 반환합니다.</li>
<li><code>df.head()</code>: 처음 5개 행을 반환합니다.</li>
<li><code>df.tail()</code>: 마지막 5개 행을 반환합니다.</li>
</ul>
<h3 id="예시-1">예시</h3>
<pre><code class="language-python">print(df.shape)     # (4, 3)
print(df.columns)   # Index([&#39;Name&#39;, &#39;Age&#39;, &#39;City&#39;], dtype=&#39;object&#39;)
print(df.dtypes)    # 각 열의 데이터 타입 출력</code></pre>
<h2 id="4-dataframe-데이터-접근-및-조작">4. DataFrame 데이터 접근 및 조작</h2>
<h3 id="41-열-단위-접근">4.1. 열 단위 접근</h3>
<p>열을 선택할 때는 열 이름을 대괄호 <code>[]</code> 안에 입력하여 접근할 수 있습니다.</p>
<pre><code class="language-python"># &#39;Name&#39; 열 선택
print(df[&#39;Name&#39;])</code></pre>
<h3 id="42-행-단위-접근">4.2. 행 단위 접근</h3>
<p>행을 선택할 때는 <code>iloc[]</code>나 <code>loc[]</code>를 사용합니다.</p>
<ul>
<li><code>iloc[]</code>: 정수 기반 인덱싱</li>
<li><code>loc[]</code>: 라벨 기반 인덱싱</li>
</ul>
<pre><code class="language-python"># 첫 번째 행 선택 (정수 인덱스)
print(df.iloc[0])

# &#39;John&#39;이 있는 행 선택 (라벨 인덱스)
print(df.loc[df[&#39;Name&#39;] == &#39;John&#39;])</code></pre>
<h3 id="43-새로운-열-추가">4.3. 새로운 열 추가</h3>
<p>새로운 열을 쉽게 추가할 수 있습니다.</p>
<pre><code class="language-python"># &#39;Country&#39;라는 새 열 추가
df[&#39;Country&#39;] = [&#39;USA&#39;, &#39;France&#39;, &#39;Germany&#39;, &#39;UK&#39;]
print(df)</code></pre>
<h3 id="44-데이터-필터링">4.4. 데이터 필터링</h3>
<p>조건을 이용해 데이터를 필터링할 수 있습니다.</p>
<pre><code class="language-python"># 나이가 30 이상인 사람들만 필터링
filtered_df = df[df[&#39;Age&#39;] &gt;= 30]
print(filtered_df)</code></pre>
<h3 id="45-데이터-정렬">4.5. 데이터 정렬</h3>
<p>특정 열을 기준으로 데이터를 정렬할 수 있습니다.</p>
<pre><code class="language-python"># 나이를 기준으로 오름차순 정렬
sorted_df = df.sort_values(by=&#39;Age&#39;)
print(sorted_df)</code></pre>
<h2 id="5-데이터-조작-기본-연산">5. 데이터 조작 (기본 연산)</h2>
<p>Pandas는 데이터를 조작하는 여러 기본 기능을 제공합니다.</p>
<h3 id="51-통계-연산">5.1. 통계 연산</h3>
<p>Pandas는 기본적인 통계 기능을 제공하며, 각 열에 대해 계산할 수 있습니다.</p>
<pre><code class="language-python"># 기본 통계 정보
print(df.describe())

# 특정 열의 평균
print(df[&#39;Age&#39;].mean())</code></pre>
<h3 id="52-결측치-처리">5.2. 결측치 처리</h3>
<p>결측치(NaN)를 처리하는 방법도 제공합니다.</p>
<pre><code class="language-python"># 결측치가 있는 행 삭제
df_cleaned = df.dropna()

# 결측치를 특정 값으로 대체
df_filled = df.fillna(0)</code></pre>
<h2 id="6-dataframe-병합-및-연결">6. DataFrame 병합 및 연결</h2>
<h3 id="61-행열-연결">6.1. 행/열 연결</h3>
<p><code>concat()</code> 함수를 사용하여 여러 DataFrame을 연결할 수 있습니다.</p>
<pre><code class="language-python"># 두 DataFrame을 행 단위로 연결
df1 = pd.DataFrame({&#39;A&#39;: [1, 2], &#39;B&#39;: [3, 4]})
df2 = pd.DataFrame({&#39;A&#39;: [5, 6], &#39;B&#39;: [7, 8]})
df_concat = pd.concat([df1, df2])
print(df_concat)</code></pre>
<h3 id="62-병합join">6.2. 병합(Join)</h3>
<p>두 개 이상의 DataFrame을 특정 키를 기준으로 병합할 수 있습니다.</p>
<pre><code class="language-python"># 공통된 &#39;ID&#39;를 기준으로 병합
df1 = pd.DataFrame({&#39;ID&#39;: [1, 2, 3], &#39;Name&#39;: [&#39;John&#39;, &#39;Anna&#39;, &#39;Peter&#39;]})
df2 = pd.DataFrame({&#39;ID&#39;: [1, 2, 4], &#39;Age&#39;: [28, 24, 35]})
df_merged = pd.merge(df1, df2, on=&#39;ID&#39;)
print(df_merged)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Pandas] 데이터 불러오기]]></title>
            <link>https://velog.io/@du-log/Pandas-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@du-log/Pandas-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Thu, 26 Sep 2024 04:41:46 GMT</pubDate>
            <description><![CDATA[<p>Pandas에서 데이터를 불러오는 방법에 대해 설명드리겠습니다. Pandas는 CSV, Excel, SQL, JSON 등 다양한 형식의 데이터를 불러올 수 있는 강력한 기능을 제공합니다. 아래는 데이터 파일을 불러오는 여러 가지 방법과 그 사용 예시입니다.</p>
<h2 id="1-csv-파일-불러오기">1. CSV 파일 불러오기</h2>
<p>가장 일반적으로 사용하는 데이터 형식인 CSV 파일은 <code>read_csv()</code> 함수로 불러올 수 있습니다.</p>
<pre><code class="language-python">import pandas as pd

# CSV 파일 불러오기
df = pd.read_csv(&#39;data.csv&#39;)

# 데이터 출력
print(df.head())  # 처음 5개의 행 출력</code></pre>
<h3 id="주요-옵션">주요 옵션</h3>
<ul>
<li><code>index_col</code>: 특정 열을 인덱스로 설정할 수 있습니다.</li>
<li><code>usecols</code>: 특정 열만 선택하여 불러올 수 있습니다.</li>
<li><code>encoding</code>: CSV 파일의 인코딩을 지정할 수 있습니다.</li>
</ul>
<pre><code class="language-python"># 인덱스 열 설정 및 특정 열만 불러오기
df = pd.read_csv(&#39;data.csv&#39;, index_col=&#39;ID&#39;, usecols=[&#39;ID&#39;, &#39;Name&#39;, &#39;Age&#39;], encoding=&#39;utf-8&#39;)
print(df)</code></pre>
<h2 id="2-excel-파일-불러오기">2. Excel 파일 불러오기</h2>
<p>Excel 파일은 <code>read_excel()</code> 함수를 사용하여 불러올 수 있습니다. </p>
<pre><code class="language-python"># Excel 파일 불러오기
df = pd.read_excel(&#39;data.xlsx&#39;)

# 데이터 출력
print(df.head())</code></pre>
<h3 id="여러-시트-불러오기">여러 시트 불러오기</h3>
<p>Excel 파일에서 특정 시트를 불러오거나, 여러 시트를 불러와 딕셔너리로 저장할 수 있습니다.</p>
<pre><code class="language-python"># 특정 시트 불러오기
df = pd.read_excel(&#39;data.xlsx&#39;, sheet_name=&#39;Sheet1&#39;)

# 여러 시트 불러오기
dfs = pd.read_excel(&#39;data.xlsx&#39;, sheet_name=None)  # 딕셔너리로 모든 시트를 불러옴
print(dfs[&#39;Sheet1&#39;].head())</code></pre>
<h2 id="3-sql-데이터베이스에서-불러오기">3. SQL 데이터베이스에서 불러오기</h2>
<p>Pandas는 SQL 데이터베이스에서 데이터를 쉽게 불러올 수 있습니다. 이를 위해 <code>read_sql()</code> 함수를 사용하며, SQLAlchemy 등의 데이터베이스 커넥터가 필요합니다.</p>
<pre><code class="language-python">from sqlalchemy import create_engine

# SQLite 엔진 생성
engine = create_engine(&#39;sqlite:///data.db&#39;)

# SQL 쿼리를 통해 데이터 불러오기
df = pd.read_sql(&#39;SELECT * FROM people&#39;, engine)

# 데이터 출력
print(df.head())</code></pre>
<h2 id="4-json-파일-불러오기">4. JSON 파일 불러오기</h2>
<p>JSON 형식의 데이터를 불러올 때는 <code>read_json()</code> 함수를 사용합니다.</p>
<pre><code class="language-python"># JSON 파일 불러오기
df = pd.read_json(&#39;data.json&#39;)

# 데이터 출력
print(df.head())</code></pre>
<h3 id="다양한-json-형식-불러오기">다양한 JSON 형식 불러오기</h3>
<p><code>orient</code> 옵션을 사용하여 JSON 데이터의 구조를 지정할 수 있습니다.</p>
<pre><code class="language-python"># &#39;records&#39; 구조의 JSON 불러오기
df = pd.read_json(&#39;data_records.json&#39;, orient=&#39;records&#39;)
print(df)</code></pre>
<h2 id="5-html-파일에서-테이블-불러오기">5. HTML 파일에서 테이블 불러오기</h2>
<p>HTML 파일에서 테이블을 불러올 때는 <code>read_html()</code> 함수를 사용합니다. 이는 웹 페이지에 있는 테이블 데이터를 가져오는 데 유용합니다.</p>
<pre><code class="language-python"># HTML 파일에서 테이블 불러오기
dfs = pd.read_html(&#39;https://example.com/data.html&#39;)

# 첫 번째 테이블 출력
print(dfs[0].head())</code></pre>
<h2 id="6-압축된-파일-불러오기">6. 압축된 파일 불러오기</h2>
<p>Pandas는 압축된 파일에서 데이터를 불러오는 것도 지원합니다. CSV 파일을 gzip, zip, bz2, xz 형식으로 압축한 후 <code>read_csv()</code> 함수에서 <code>compression</code> 옵션을 사용하여 불러올 수 있습니다.</p>
<pre><code class="language-python"># Gzip 압축된 CSV 파일 불러오기
df = pd.read_csv(&#39;data.csv.gz&#39;, compression=&#39;gzip&#39;)
print(df.head())</code></pre>
<h2 id="7-클립보드에서-데이터-불러오기">7. 클립보드에서 데이터 불러오기</h2>
<p>데이터를 클립보드에서 불러올 수도 있습니다. 예를 들어, 엑셀이나 구글 스프레드시트에서 데이터를 복사한 후 <code>read_clipboard()</code>를 사용하면 쉽게 DataFrame으로 변환됩니다.</p>
<pre><code class="language-python"># 클립보드에서 데이터 불러오기
df = pd.read_clipboard()

# 데이터 출력
print(df.head())</code></pre>
<h2 id="8-특정-파일-형식-감지-및-불러오기">8. 특정 파일 형식 감지 및 불러오기</h2>
<p><code>read_*()</code> 함수 외에도 파일의 형식을 자동으로 감지하여 불러오는 경우도 있습니다. 예를 들어, <code>.csv</code>, <code>.xlsx</code>와 같은 파일 확장자를 기반으로 파일을 불러오는 도구도 사용할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Pandas] 데이터 저장]]></title>
            <link>https://velog.io/@du-log/Pandas-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%80%EC%9E%A5</link>
            <guid>https://velog.io/@du-log/Pandas-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%80%EC%9E%A5</guid>
            <pubDate>Wed, 25 Sep 2024 07:44:14 GMT</pubDate>
            <description><![CDATA[<p>Pandas에서 데이터를 저장하는 방법은 다양합니다. CSV, Excel, SQL 등 여러 형식으로 데이터를 저장할 수 있는데, 이를 각각 예제로 설명하겠습니다.</p>
<h2 id="1-csv-파일로-저장">1. CSV 파일로 저장</h2>
<h3 id="기본적인-csv-저장">기본적인 CSV 저장</h3>
<p>DataFrame을 CSV 파일로 저장할 때 <code>to_csv()</code> 메서드를 사용합니다.</p>
<pre><code class="language-python">import pandas as pd

# 예제 데이터프레임 생성
data = {&#39;Name&#39;: [&#39;John&#39;, &#39;Anna&#39;, &#39;Peter&#39;, &#39;Linda&#39;],
        &#39;Age&#39;: [28, 24, 35, 32],
        &#39;City&#39;: [&#39;New York&#39;, &#39;Paris&#39;, &#39;Berlin&#39;, &#39;London&#39;]}
df = pd.DataFrame(data)

# CSV 파일로 저장
df.to_csv(&#39;data.csv&#39;, index=False)</code></pre>
<ul>
<li><code>index=False</code>는 인덱스 열을 CSV에 저장하지 않도록 합니다.</li>
</ul>
<h3 id="csv-파일에-특정-인코딩-적용">CSV 파일에 특정 인코딩 적용</h3>
<p>특정 인코딩으로 저장하고 싶을 때는 <code>encoding</code> 파라미터를 사용할 수 있습니다.</p>
<pre><code class="language-python"># UTF-8 인코딩으로 저장
df.to_csv(&#39;data_utf8.csv&#39;, index=False, encoding=&#39;utf-8&#39;)</code></pre>
<h2 id="2-excel-파일로-저장">2. Excel 파일로 저장</h2>
<h3 id="기본적인-excel-저장">기본적인 Excel 저장</h3>
<p>DataFrame을 Excel 파일로 저장할 때는 <code>to_excel()</code> 메서드를 사용합니다.</p>
<pre><code class="language-python"># Excel 파일로 저장
df.to_excel(&#39;data.xlsx&#39;, index=False)</code></pre>
<h3 id="여러-시트에-저장">여러 시트에 저장</h3>
<p>여러 DataFrame을 하나의 Excel 파일의 다른 시트에 저장하려면 <code>ExcelWriter</code>를 사용합니다.</p>
<pre><code class="language-python"># 여러 시트에 저장
with pd.ExcelWriter(&#39;multi_sheet_data.xlsx&#39;) as writer:
    df.to_excel(writer, sheet_name=&#39;Sheet1&#39;)
    df.to_excel(writer, sheet_name=&#39;Sheet2&#39;)</code></pre>
<h2 id="3-sql-데이터베이스에-저장">3. SQL 데이터베이스에 저장</h2>
<h3 id="sqlite에-저장">SQLite에 저장</h3>
<p>SQLite 데이터베이스에 저장할 때는 <code>to_sql()</code> 메서드를 사용합니다. 이때 <code>sqlalchemy</code> 또는 <code>sqlite3</code> 라이브러리가 필요합니다.</p>
<pre><code class="language-python">from sqlalchemy import create_engine

# SQLite 엔진 생성
engine = create_engine(&#39;sqlite:///data.db&#39;)

# 데이터베이스에 저장 (테이블 이름: people)
df.to_sql(&#39;people&#39;, engine, index=False, if_exists=&#39;replace&#39;)</code></pre>
<ul>
<li><code>if_exists=&#39;replace&#39;</code>는 테이블이 이미 존재하면 덮어쓰기를 수행합니다. 데이터베이스 테이블을 업데이트하려면 <code>append</code>로 설정할 수 있습니다.</li>
</ul>
<h2 id="4-json-파일로-저장">4. JSON 파일로 저장</h2>
<h3 id="기본적인-json-저장">기본적인 JSON 저장</h3>
<p>DataFrame을 JSON 형식으로 저장할 때는 <code>to_json()</code> 메서드를 사용합니다.</p>
<pre><code class="language-python"># JSON 파일로 저장
df.to_json(&#39;data.json&#39;)</code></pre>
<h3 id="포맷-옵션-설정">포맷 옵션 설정</h3>
<p><code>orient</code> 옵션을 사용하여 JSON 형식의 구조를 지정할 수 있습니다. 예를 들어, <code>records</code>로 설정하면 각 행이 JSON 객체로 저장됩니다.</p>
<pre><code class="language-python"># JSON 포맷을 &#39;records&#39;로 저장
df.to_json(&#39;data_records.json&#39;, orient=&#39;records&#39;)</code></pre>
<h2 id="5-html-파일로-저장">5. HTML 파일로 저장</h2>
<p>DataFrame을 HTML 파일로 저장할 때는 <code>to_html()</code> 메서드를 사용합니다.</p>
<pre><code class="language-python"># HTML 파일로 저장
df.to_html(&#39;data.html&#39;)</code></pre>
<h2 id="6-압축된-파일로-저장">6. 압축된 파일로 저장</h2>
<p>CSV나 Excel 파일을 압축하여 저장할 수 있습니다. <code>compression</code> 파라미터를 이용하여 여러 압축 방식을 설정할 수 있습니다.</p>
<pre><code class="language-python"># Gzip 압축을 사용하여 CSV 저장
df.to_csv(&#39;data.csv.gz&#39;, index=False, compression=&#39;gzip&#39;)

# ZIP 압축을 사용하여 Excel 저장
df.to_excel(&#39;data.zip&#39;, index=False, engine=&#39;xlsxwriter&#39;, compression={&#39;method&#39;: &#39;zip&#39;, &#39;archive_name&#39;: &#39;data.xlsx&#39;})</code></pre>
<h2 id="7-클립보드에-저장">7. 클립보드에 저장</h2>
<p>데이터를 클립보드에 복사하여 쉽게 붙여넣을 수 있습니다. 예를 들어, 엑셀이나 구글 스프레드시트에 데이터를 빠르게 붙여넣을 때 유용합니다.</p>
<pre><code class="language-python"># 클립보드에 저장
df.to_clipboard(index=False)</code></pre>
]]></description>
        </item>
    </channel>
</rss>