<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sujung_shin.log</title>
        <link>https://velog.io/</link>
        <description>안녕하세요:)</description>
        <lastBuildDate>Mon, 27 May 2024 03:50:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sujung_shin.log</title>
            <url>https://velog.velcdn.com/images/sujung_shin/profile/4d9b8aca-bc9b-4f51-b2f1-f689ec015543/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sujung_shin.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sujung_shin" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[넥사크로플랫폼]]></title>
            <link>https://velog.io/@sujung_shin/%EB%84%A5%EC%82%AC%ED%81%AC%EB%A1%9C%ED%94%8C%EB%9E%AB%ED%8F%BC</link>
            <guid>https://velog.io/@sujung_shin/%EB%84%A5%EC%82%AC%ED%81%AC%EB%A1%9C%ED%94%8C%EB%9E%AB%ED%8F%BC</guid>
            <pubDate>Mon, 27 May 2024 03:50:41 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 여러분! 오늘은 여러분께 <strong>넥사크로플랫폼(Nexacro Platform)</strong>에 대해 소개해드리려고 합니다. 넥사크로플랫폼은 웹 애플리케이션 개발을 효율적으로 도와주는 강력한 도구로, 다양한 디바이스와 브라우저에서 일관된 사용자 경험을 제공하는 특징을 가지고 있습니다. </p>
<h2 id="넥사크로플랫폼이란">넥사크로플랫폼이란?</h2>
<p>넥사크로플랫폼은 한국의 소프트웨어 회사인 투비소프트에서 개발한 통합 개발 환경입니다. 이 플랫폼은 다양한 운영체제와 디바이스에서 동작하는 웹 애플리케이션을 쉽게 개발할 수 있도록 돕습니다. HTML5 기반의 UI 컴포넌트와 강력한 데이터 처리 기능을 제공하여, 효율적이고 직관적인 개발 환경을 제공합니다.</p>
<h2 id="언어적-기반">언어적 기반</h2>
<p>넥사크로플랫폼은 주로 HTML5, JavaScript, CSS를 기반으로 하고 있습니다. 이를 통해 다양한 웹 브라우저와 호환되며, 최신 웹 기술을 활용하여 고성능의 웹 애플리케이션을 개발할 수 있습니다.</p>
<ul>
<li><strong>HTML5</strong>: 웹 페이지의 구조를 정의하고 다양한 멀티미디어 요소를 통합할 수 있게 해줍니다.</li>
<li><strong>JavaScript</strong>: 동적이고 인터랙티브한 웹 페이지를 만들기 위한 프로그래밍 언어입니다.</li>
<li><strong>CSS</strong>: 웹 페이지의 스타일과 레이아웃을 정의하는 언어입니다.</li>
</ul>
<h2 id="기본-개념">기본 개념</h2>
<h3 id="1-멀티-디바이스-지원">1. 멀티 디바이스 지원</h3>
<p>넥사크로플랫폼은 PC, 태블릿, 스마트폰 등 다양한 디바이스에서 일관된 사용자 경험을 제공합니다. 이를 통해 개발자는 하나의 코드로 여러 플랫폼을 지원할 수 있어 개발 효율성이 크게 향상됩니다.</p>
<h3 id="2-강력한-ui-컴포넌트">2. 강력한 UI 컴포넌트</h3>
<p>넥사크로플랫폼은 다양한 UI 컴포넌트를 제공하여, 복잡한 사용자 인터페이스도 쉽게 구현할 수 있습니다. 드래그 앤 드롭 방식의 편리한 UI 디자이너를 통해 개발자는 직관적으로 인터페이스를 설계할 수 있습니다.</p>
<h3 id="3-높은-생산성">3. 높은 생산성</h3>
<p>직관적인 개발 환경과 다양한 도구들로 인해 개발 생산성이 크게 향상됩니다. 코드 작성부터 디버깅, 배포까지 모든 과정을 한 곳에서 관리할 수 있어, 개발 시간과 비용을 절감할 수 있습니다.</p>
<h3 id="4-데이터-처리-능력">4. 데이터 처리 능력</h3>
<p>넥사크로플랫폼은 강력한 데이터 처리 능력을 갖추고 있어 대규모 데이터 애플리케이션에서도 안정적으로 동작합니다. 다양한 데이터베이스와의 연동이 용이하며, 실시간 데이터 처리도 가능합니다.</p>
<h2 id="사용-사례">사용 사례</h2>
<p>넥사크로플랫폼은 다양한 산업 분야에서 활용되고 있습니다. 예를 들어 금융, 제조, 유통 등 여러 분야에서 넥사크로플랫폼을 이용한 웹 애플리케이션이 사용되고 있습니다. 특히, 복잡한 비즈니스 로직과 대규모 데이터를 처리해야 하는 애플리케이션에서 그 진가를 발휘하고 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타임리프(Thymeleaf)에서 '|' 사용하기
]]></title>
            <link>https://velog.io/@sujung_shin/%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84Thymeleaf%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sujung_shin/%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84Thymeleaf%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 26 May 2024 04:37:59 GMT</pubDate>
            <description><![CDATA[<p>오늘은 타임리프(Thymeleaf)에서 유용하게 사용되는 연산자인 &#39;|&#39;에 대해 알아보겠습니다. 타임리프는 HTML 템플릿 엔진으로, Spring Framework와 함께 자주 사용됩니다. 타임리프의 다양한 기능 중에서도, &#39;|&#39; 연산자는 텍스트 치환과 표현식 평가에 있어 중요한 역할을 합니다.</p>
<h2 id="-연산자의-기본-사용법">&#39;|&#39; 연산자의 기본 사용법</h2>
<p>타임리프에서 &#39;|&#39; 연산자는 주로 텍스트 치환에서 사용됩니다. 이는 템플릿 내에서 특정 표현식을 평가하고, 그 결과를 텍스트로 대체하는 기능을 합니다. 다음은 간단한 예제입니다.</p>
<pre><code class="language-html">&lt;p th:text=&quot;&#39;Hello, &#39; + ${user.name} + &#39;!&#39;&quot;&gt;Hello, Guest!&lt;/p&gt;</code></pre>
<p>위의 코드는 사용자의 이름을 포함한 인사말을 출력합니다. 만약 <code>user.name</code>이 &#39;John&#39;이라면, 결과는 &#39;Hello, John!&#39;이 됩니다. 그러나 이는 문자열 연결을 사용한 예제입니다. &#39;|&#39; 연산자는 이를 좀 더 간단하게 만들어줍니다.</p>
<pre><code class="language-html">&lt;p th:text=&quot;|Hello, ${user.name}!|&quot;&gt;Hello, Guest!&lt;/p&gt;</code></pre>
<p>위 코드에서 &#39;|&#39; 연산자는 문자열 내에 변수 표현식을 쉽게 포함할 수 있도록 도와줍니다. 이 방식은 코드의 가독성을 높여줍니다.</p>
<h2 id="복잡한-표현식에서의--사용">복잡한 표현식에서의 &#39;|&#39; 사용</h2>
<p>타임리프는 단순한 텍스트 치환 외에도 복잡한 표현식을 처리할 수 있습니다. 예를 들어, 조건부 표현식을 사용할 때 &#39;|&#39; 연산자를 활용할 수 있습니다.</p>
<pre><code class="language-html">&lt;p th:text=&quot;${user.age &gt; 18 ? &#39;Adult&#39; : &#39;Minor&#39;}&quot;&gt;Unknown&lt;/p&gt;</code></pre>
<p>위의 코드는 사용자의 나이에 따라 &#39;Adult&#39; 또는 &#39;Minor&#39;를 출력합니다. &#39;|&#39; 연산자를 사용하면 더욱 직관적인 문법으로 표현할 수 있습니다.</p>
<pre><code class="language-html">&lt;p th:text=&quot;|${user.age &gt; 18 ? &#39;Adult&#39; : &#39;Minor&#39;}|&quot;&gt;Unknown&lt;/p&gt;</code></pre>
<p>이처럼 &#39;|&#39; 연산자는 텍스트 내에 표현식을 포함시켜 가독성을 높여줍니다.</p>
<h2 id="다국어-지원-및-메시지-번들">다국어 지원 및 메시지 번들</h2>
<p>타임리프는 다국어 지원을 위한 메시지 번들 기능을 제공합니다. 이 때 &#39;|&#39; 연산자를 사용하여 메시지를 쉽게 삽입할 수 있습니다.</p>
<pre><code class="language-html">&lt;p th:text=&quot;#{welcome.message}&quot;&gt;Welcome!&lt;/p&gt;</code></pre>
<p>메시지 번들 파일(<code>messages.properties</code>)에 다음과 같은 내용이 있다고 가정해봅시다.</p>
<pre><code>welcome.message=Hello, ${name}!</code></pre><p>이를 &#39;|&#39; 연산자와 함께 사용하면, 코드가 훨씬 더 직관적이고 간결해집니다.</p>
<pre><code class="language-html">&lt;p th:text=&quot;|#{welcome.message}|&quot;&gt;Welcome!&lt;/p&gt;</code></pre>
<p>타임리프는 메시지 번들에서 표현식을 지원하기 때문에, 사용자의 이름을 포함한 메시지를 쉽게 출력할 수 있습니다.</p>
<h2 id="결론">결론</h2>
<p>타임리프에서 &#39;|&#39; 연산자는 텍스트 치환과 표현식 평가를 더욱 간편하고 직관적으로 만들어줍니다. 이를 활용하면 템플릿 코드의 가독성이 향상되고, 유지보수가 쉬워집니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Lombok 설정 문제 해결하기]]></title>
            <link>https://velog.io/@sujung_shin/Lombok-%EC%84%A4%EC%A0%95-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sujung_shin/Lombok-%EC%84%A4%EC%A0%95-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 25 May 2024 07:38:19 GMT</pubDate>
            <description><![CDATA[<p>Spring Boot 프로젝트에서 Lombok을 사용하여 의존성 주입을 하려고 했으나, 의외로 간단한 설정 문제로 인해 시간이 꽤 소요되었습니다. </p>
<h4 id="문제-상황">문제 상황</h4>
<p>Spring Boot 프로젝트에서 Lombok의 <code>@RequiredArgsConstructor</code>를 사용하여 <code>final</code> 필드를 초기화하려고 했습니다. 하지만 다음과 같은 오류가 발생했습니다:</p>
<pre><code>variable boardService not initialized in the default constructor</code></pre><p>분명히 Lombok 설정이 되어 있는데도 불구하고 의존성 주입이 제대로 이루어지지 않는 상황이었습니다. Lombok 어노테이션을 사용했지만, 오류는 여전히 존재했습니다.</p>
<h4 id="해결-방법">해결 방법</h4>
<p>여러 가지 방법을 시도해 보았으나, 결국 build.gradle 파일에 Lombok 의존성을 추가하는 것이 문제를 해결해 주었습니다. 아래에 그 과정을 상세히 설명하겠습니다.</p>
<ol>
<li><p><strong>Lombok 의존성 추가</strong></p>
<p>먼저 <code>build.gradle</code> 파일에 Lombok 의존성을 추가합니다. <code>compileOnly</code>와 <code>annotationProcessor</code> 두 가지 의존성을 모두 추가해야 합니다.</p>
<pre><code class="language-groovy">dependencies {
    // Lombok 의존성 추가
    compileOnly &#39;org.projectlombok:lombok:1.18.20&#39; // 원하는 Lombok 버전
    annotationProcessor &#39;org.projectlombok:lombok:1.18.20&#39;

    // 기타 의존성
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    runtimeOnly &#39;mysql:mysql-connector-java&#39;
}</code></pre>
</li>
<li><p><strong>IDE 설정 확인</strong></p>
<p>Lombok 플러그인이 설치되어 있는지 확인합니다. IntelliJ IDEA와 Eclipse에서 Lombok 플러그인을 설치하고, annotation processing이 활성화되어 있는지 확인합니다.</p>
<ul>
<li><p><strong>IntelliJ IDEA</strong>:</p>
<ul>
<li><code>File</code> &gt; <code>Settings</code> &gt; <code>Plugins</code> &gt; <code>Marketplace</code>에서 <code>Lombok</code> 검색 후 설치</li>
<li><code>File</code> &gt; <code>Settings</code> &gt; <code>Build, Execution, Deployment</code> &gt; <code>Compiler</code> &gt; <code>Annotation Processors</code>에서 <code>Enable annotation processing</code> 체크</li>
</ul>
</li>
<li><p><strong>Eclipse</strong>:</p>
<ul>
<li><code>Help</code> &gt; <code>Eclipse Marketplace</code>에서 <code>Lombok</code> 검색 후 설치</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>프로젝트 클린 및 재빌드</strong></p>
<p>프로젝트를 클린하고 다시 빌드합니다.</p>
<pre><code class="language-sh">./gradlew clean build</code></pre>
</li>
<li><p><strong>코드 수정</strong></p>
<p>Lombok 어노테이션을 사용하여 의존성 주입을 설정합니다.</p>
<pre><code class="language-java">package com.board.controller;

import com.board.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping(&quot;/board&quot;)
@RequiredArgsConstructor
public class BoardController {

    private final BoardService boardService;

    // 기타 메서드 추가
}</code></pre>
</li>
</ol>
<p>이제 프로젝트를 실행하면, <code>@RequiredArgsConstructor</code>를 사용한 의존성 주입이 정상적으로 작동합니다.</p>
<h4 id="결론">결론</h4>
<p>Lombok을 사용하는 데 있어 작은 설정 하나가 큰 문제를 일으킬 수 있습니다. <code>compileOnly</code>와 <code>annotationProcessor</code> 의존성을 모두 추가하는 것이 중요하다는 것을 이번 기회를 통해 배웠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RestTemplate을 이용한 Spring에서의 간편한 HTTP 통신
]]></title>
            <link>https://velog.io/@sujung_shin/RestTemplate%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-Spring%EC%97%90%EC%84%9C%EC%9D%98-%EA%B0%84%ED%8E%B8%ED%95%9C-HTTP-%ED%86%B5%EC%8B%A0</link>
            <guid>https://velog.io/@sujung_shin/RestTemplate%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-Spring%EC%97%90%EC%84%9C%EC%9D%98-%EA%B0%84%ED%8E%B8%ED%95%9C-HTTP-%ED%86%B5%EC%8B%A0</guid>
            <pubDate>Thu, 23 May 2024 14:02:43 GMT</pubDate>
            <description><![CDATA[<p>Spring 프레임워크를 사용하여 애플리케이션을 개발하다 보면 외부 API와 통신해야 할 필요가 자주 생깁니다. 이때 가장 간편하게 사용할 수 있는 도구 중 하나가 바로 <code>RestTemplate</code>입니다. 이번 글에서는 RestTemplate의 기본 사용법과 주요 기능들에 대해 알아보겠습니다.</p>
<h2 id="resttemplate이란">RestTemplate이란?</h2>
<p>RestTemplate은 Spring에서 제공하는 HTTP 클라이언트로, RESTful 웹 서비스를 호출하기 위해 설계되었습니다. 간단한 HTTP 요청부터 복잡한 인증까지 다양한 기능을 손쉽게 사용할 수 있도록 도와줍니다.</p>
<h2 id="resttemplate-설정하기">RestTemplate 설정하기</h2>
<p>먼저 Spring 애플리케이션에 RestTemplate을 설정하는 방법을 살펴보겠습니다. Spring Boot를 사용하는 경우, RestTemplate 빈을 쉽게 생성할 수 있습니다.</p>
<pre><code class="language-java">import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class AppConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}</code></pre>
<p>이제 RestTemplate 빈을 주입 받아 사용할 준비가 되었습니다.</p>
<h2 id="기본적인-사용법">기본적인 사용법</h2>
<h3 id="get-요청">GET 요청</h3>
<p>RestTemplate을 이용하여 외부 API로부터 데이터를 가져오는 예제를 살펴보겠습니다. 예를 들어, JSONPlaceholder의 사용자 목록을 가져오는 코드는 다음과 같습니다.</p>
<pre><code class="language-java">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    public String getUsers() {
        String url = &quot;https://jsonplaceholder.typicode.com/users&quot;;
        return restTemplate.getForObject(url, String.class);
    }
}</code></pre>
<p><code>getForObject</code> 메서드는 지정된 URL로 GET 요청을 보내고, 응답을 원하는 타입으로 변환합니다. 위 예제에서는 JSON 응답을 문자열로 반환합니다.</p>
<h3 id="post-요청">POST 요청</h3>
<p>POST 요청을 보내는 방법도 매우 간단합니다. 다음은 사용자 정보를 POST 요청으로 보내는 예제입니다.</p>
<pre><code class="language-java">import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    public ResponseEntity&lt;String&gt; createUser(User user) {
        String url = &quot;https://jsonplaceholder.typicode.com/users&quot;;
        return restTemplate.postForEntity(url, user, String.class);
    }
}</code></pre>
<p><code>postForEntity</code> 메서드는 지정된 URL로 POST 요청을 보내고, 응답을 <code>ResponseEntity</code> 객체로 반환합니다.</p>
<h3 id="예외-처리">예외 처리</h3>
<p>RestTemplate을 사용하다 보면 다양한 예외 상황을 만날 수 있습니다. 이를 처리하기 위해서는 <code>RestClientException</code>을 사용하면 됩니다.</p>
<pre><code class="language-java">import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    public String getUsers() {
        String url = &quot;https://jsonplaceholder.typicode.com/users&quot;;
        try {
            return restTemplate.getForObject(url, String.class);
        } catch (HttpClientErrorException e) {
            // HTTP 4xx 에러 처리
            return &quot;Client error: &quot; + e.getStatusCode();
        } catch (RestClientException e) {
            // 그 외 에러 처리
            return &quot;Error: &quot; + e.getMessage();
        }
    }
}</code></pre>
<h2 id="결론">결론</h2>
<p>RestTemplate은 Spring 애플리케이션에서 RESTful 웹 서비스를 호출할 때 매우 유용한 도구입니다. 간단한 설정과 사용법으로 HTTP 요청을 쉽게 처리할 수 있으며, 다양한 예외 처리 방법을 통해 안정성을 높일 수 있습니다. 더 나아가, RestTemplate의 다양한 기능들을 활용하여 더욱 효율적인 HTTP 통신을 구현할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MySQL에서 스키마와 사용자 이해하기]]></title>
            <link>https://velog.io/@sujung_shin/MySQL%EC%97%90%EC%84%9C-%EC%8A%A4%ED%82%A4%EB%A7%88%EC%99%80-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sujung_shin/MySQL%EC%97%90%EC%84%9C-%EC%8A%A4%ED%82%A4%EB%A7%88%EC%99%80-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 22 May 2024 07:23:46 GMT</pubDate>
            <description><![CDATA[<p>MySQL 데이터베이스의 세계에서는 스키마와 사용자 개념을 이해하는 것이 기본입니다. 이 두 구성 요소는 데이터가 어떻게 조직되고, 관리되며, 접근되는지에 중요한 역할을 합니다. 이 블로그 포스트에서는 스키마와 사용자가 무엇인지, 그 중요성, 그리고 MySQL 데이터베이스 환경 내에서 어떻게 작동하는지에 대해 자세히 설명하겠습니다.</p>
<h3 id="스키마란-무엇인가">스키마란 무엇인가?</h3>
<p>MySQL에서 스키마는 데이터베이스 구조를 포함하는 청사진 또는 컨테이너입니다. 여기에는 테이블, 뷰, 저장 프로시저, 트리거 및 기타 데이터베이스 객체가 포함됩니다. 스키마는 이러한 객체들을 논리적으로 그룹화하여 데이터베이스를 관리하고 조직하기 쉽게 만듭니다.</p>
<h4 id="스키마에-대한-주요-포인트">스키마에 대한 주요 포인트:</h4>
<ol>
<li><strong>조직화</strong>: 스키마는 관련 데이터베이스 객체를 논리적으로 그룹화하는 데 도움이 됩니다. 예를 들어, 고객 정보와 관련된 모든 테이블과 프로시저는 하나의 스키마로 그룹화될 수 있습니다.</li>
<li><strong>네임스페이스</strong>: 스키마는 이름 충돌을 피하기 위한 네임스페이스를 제공합니다. 동일한 이름을 가진 두 개의 테이블이 다른 스키마에 존재할 수 있습니다.</li>
<li><strong>권한 관리</strong>: 스키마는 권한을 관리하는 데 사용될 수 있습니다. 사용자에게 특정 스키마에 대한 접근 권한을 부여함으로써 데이터베이스의 특정 부분에 대한 접근을 제한하거나 허용할 수 있습니다.</li>
</ol>
<h3 id="스키마-생성하기">스키마 생성하기</h3>
<p>MySQL에서 스키마를 생성하는 것은 간단합니다. 다음은 간단한 예입니다:</p>
<pre><code class="language-sql">CREATE SCHEMA my_database;</code></pre>
<p>이 명령은 <code>my_database</code>라는 새로운 스키마를 생성합니다. 이 스키마 내에서 필요한 테이블 및 기타 객체를 생성할 수 있습니다.</p>
<h3 id="사용자란-무엇인가">사용자란 무엇인가?</h3>
<p>MySQL에서 사용자는 데이터베이스와 상호 작용할 수 있는 계정입니다. 사용자는 데이터 읽기에서부터 데이터베이스 구조 변경에 이르기까지 다양한 수준의 접근 권한을 가질 수 있습니다.</p>
<h4 id="사용자에-대한-주요-포인트">사용자에 대한 주요 포인트:</h4>
<ol>
<li><strong>인증</strong>: 사용자는 일반적으로 사용자 이름과 비밀번호를 통해 인증을 거쳐 데이터베이스에 접근할 수 있습니다.</li>
<li><strong>권한</strong>: 사용자에게는 다양한 수준의 권한이 부여될 수 있습니다. 예를 들어, 사용자는 읽기 전용 접근 권한을 가질 수도 있고, 전체 관리자 권한을 가질 수도 있습니다.</li>
<li><strong>보안</strong>: 사용자와 그들의 권한을 적절히 관리하는 것은 데이터베이스 보안에 중요합니다. 권한이 없는 사용자는 민감한 데이터와 관리자 기능에 접근할 수 없도록 해야 합니다.</li>
</ol>
<h3 id="사용자-생성하기">사용자 생성하기</h3>
<p>MySQL에서 사용자를 생성하려면 사용자 이름과 비밀번호를 지정해야 합니다. 다음은 예입니다:</p>
<pre><code class="language-sql">CREATE USER &#39;username&#39;@&#39;host&#39; IDENTIFIED BY &#39;password&#39;;</code></pre>
<p>이 명령은 지정된 <code>host</code>에서 <code>username</code>이라는 이름을 가진 새 사용자를 주어진 <code>password</code>로 생성합니다.</p>
<h3 id="사용자에게-권한-부여하기">사용자에게 권한 부여하기</h3>
<p>사용자가 생성된 후, 데이터베이스와 상호 작용할 수 있도록 필요한 권한을 부여해야 합니다. 특정 스키마에 대한 모든 권한을 부여하는 예는 다음과 같습니다:</p>
<pre><code class="language-sql">GRANT ALL PRIVILEGES ON my_database.* TO &#39;username&#39;@&#39;host&#39;;</code></pre>
<p>이 명령은 <code>my_database</code> 스키마의 모든 권한을 <code>host</code>에서 <code>username</code>에게 부여합니다. 권한 변경 사항을 적용하려면 다음 명령을 실행해야 합니다:</p>
<pre><code class="language-sql">FLUSH PRIVILEGES;</code></pre>
<p>이로써 사용자는 지정된 스키마 내에서 모든 데이터베이스 작업을 수행할 수 있게 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Filter와 Interceptor: 개념과 사용 예시]]></title>
            <link>https://velog.io/@sujung_shin/Spring-Filter%EC%99%80-Interceptor-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%82%AC%EC%9A%A9-%EC%98%88%EC%8B%9C</link>
            <guid>https://velog.io/@sujung_shin/Spring-Filter%EC%99%80-Interceptor-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%82%AC%EC%9A%A9-%EC%98%88%EC%8B%9C</guid>
            <pubDate>Tue, 21 May 2024 03:54:42 GMT</pubDate>
            <description><![CDATA[<p>Spring Framework는 웹 애플리케이션 개발에 있어서 다양한 기능을 제공합니다. 그 중에서도 요청과 응답을 가로채고 처리할 수 있는 두 가지 주요 기능이 바로 Filter와 Interceptor입니다. 이 글에서는 Filter와 Interceptor의 개념, 차이점, 그리고 사용 예시에 대해 알아보겠습니다.</p>
<h3 id="1-filter">1. Filter</h3>
<h4 id="개념">개념</h4>
<p>Filter는 Servlet의 스펙 중 하나로, HTTP 요청 및 응답을 전처리하거나 후처리할 수 있는 기능을 제공합니다. 필터는 웹 애플리케이션 전역에서 동작하며, 주로 보안, 로깅, 인코딩, 인증 등의 작업을 수행하는 데 사용됩니다.</p>
<h4 id="사용-예시">사용 예시</h4>
<p>Filter를 사용하여 요청의 인코딩을 설정하는 예제를 살펴보겠습니다.</p>
<p><strong>예제 코드:</strong></p>
<pre><code class="language-java">import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class EncodingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 초기화 작업
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        request.setCharacterEncoding(&quot;UTF-8&quot;);
        response.setCharacterEncoding(&quot;UTF-8&quot;);
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 종료 작업
    }
}</code></pre>
<p><strong>설정:</strong></p>
<p>이 필터를 Spring Boot 애플리케이션에 설정하려면 <code>@Component</code> 애노테이션을 사용하거나 <code>FilterRegistrationBean</code>을 통해 등록할 수 있습니다.</p>
<pre><code class="language-java">import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean&lt;EncodingFilter&gt; encodingFilter() {
        FilterRegistrationBean&lt;EncodingFilter&gt; registrationBean = new FilterRegistrationBean&lt;&gt;();
        registrationBean.setFilter(new EncodingFilter());
        registrationBean.addUrlPatterns(&quot;/*&quot;);
        return registrationBean;
    }
}</code></pre>
<h3 id="2-interceptor">2. Interceptor</h3>
<h4 id="개념-1">개념</h4>
<p>Interceptor는 Spring MVC에서 제공하는 기능으로, HTTP 요청을 가로채서 전처리와 후처리를 할 수 있습니다. 인터셉터는 컨트롤러와 관련된 작업을 수행할 때 주로 사용되며, 요청을 가로채고 컨트롤러의 메서드가 실행되기 전후에 작업을 수행할 수 있습니다.</p>
<h4 id="사용-예시-1">사용 예시</h4>
<p>Interceptor를 사용하여 요청이 컨트롤러에 도달하기 전에 인증을 체크하는 예제를 살펴보겠습니다.</p>
<p><strong>예제 코드:</strong></p>
<pre><code class="language-java">import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AuthenticationInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        // 인증 로직
        String authToken = request.getHeader(&quot;Authorization&quot;);
        if (authToken == null || !isValidToken(authToken)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        return true;
    }

    private boolean isValidToken(String authToken) {
        // 토큰 검증 로직 (예: JWT 검증)
        return &quot;valid-token&quot;.equals(authToken);
    }
}</code></pre>
<p><strong>설정:</strong></p>
<p>이 인터셉터를 Spring Boot 애플리케이션에 설정하려면 <code>WebMvcConfigurer</code>를 구현한 클래스에서 등록할 수 있습니다.</p>
<pre><code class="language-java">import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthenticationInterceptor()).addPathPatterns(&quot;/api/**&quot;);
    }
}</code></pre>
<h3 id="filter와-interceptor의-차이점">Filter와 Interceptor의 차이점</h3>
<ul>
<li><strong>동작 위치:</strong> Filter는 Servlet 컨테이너 수준에서 동작하며, Interceptor는 Spring MVC 컨텍스트 내에서 동작합니다.</li>
<li><strong>적용 범위:</strong> Filter는 모든 요청에 대해 적용될 수 있으며, Interceptor는 특정 컨트롤러와 관련된 요청에 대해서만 적용됩니다.</li>
<li><strong>기능:</strong> Filter는 주로 인코딩 설정, 보안, 로깅 등에 사용되며, Interceptor는 인증, 권한 검사, 로깅 등에 사용됩니다.</li>
</ul>
<h3 id="결론">결론</h3>
<p>Filter와 Interceptor는 Spring Framework에서 매우 유용하게 사용될 수 있는 기능입니다. 각각의 장단점을 이해하고 적절하게 사용함으로써, 더 효율적이고 관리하기 쉬운 코드를 작성할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SOLID 원칙이란?]]></title>
            <link>https://velog.io/@sujung_shin/SOLID-%EC%9B%90%EC%B9%99%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@sujung_shin/SOLID-%EC%9B%90%EC%B9%99%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Mon, 20 May 2024 01:58:22 GMT</pubDate>
            <description><![CDATA[<p>오늘은 소프트웨어 개발에서 중요한 원칙 중 하나인 SOLID 원칙에 대해 알아보겠습니다. SOLID는 객체 지향 프로그래밍에서 코드의 유지보수성과 확장성을 높이기 위해 고안된 다섯 가지 설계 원칙을 의미합니다. 이 글에서는 각 원칙의 개념과 중요성을 예제와 함께 설명하겠습니다.</p>
<h2 id="solid-원칙이란">SOLID 원칙이란?</h2>
<p>SOLID는 다섯 가지 설계 원칙의 앞글자를 딴 약어입니다. 이 원칙들은 소프트웨어 디자인에서 흔히 발생하는 문제를 피하고, 코드의 품질을 향상시키는 데 도움을 줍니다. SOLID 원칙은 다음과 같습니다:</p>
<ol>
<li><strong>단일 책임 원칙 (Single Responsibility Principle, SRP)</strong></li>
<li><strong>개방-폐쇄 원칙 (Open/Closed Principle, OCP)</strong></li>
<li><strong>리스코프 치환 원칙 (Liskov Substitution Principle, LSP)</strong></li>
<li><strong>인터페이스 분리 원칙 (Interface Segregation Principle, ISP)</strong></li>
<li><strong>의존 역전 원칙 (Dependency Inversion Principle, DIP)</strong></li>
</ol>
<p>이제 각 원칙에 대해 자세히 알아보겠습니다.</p>
<h2 id="단일-책임-원칙-single-responsibility-principle-srp">단일 책임 원칙 (Single Responsibility Principle, SRP)</h2>
<p>SRP는 클래스는 하나의 책임만 가져야 한다는 원칙입니다. 즉, 클래스는 하나의 기능만을 가져야 하며, 그 기능을 변경해야 할 이유는 하나뿐이어야 합니다. 이렇게 하면 코드의 모듈성이 높아지고, 유지보수가 쉬워집니다.</p>
<h3 id="예제">예제</h3>
<pre><code class="language-java">class ReportGenerator {
    public void generateReport() {
        // 보고서 생성 로직
    }
}

class EmailSender {
    public void sendEmail() {
        // 이메일 전송 로직
    }
}</code></pre>
<p>위 예제에서 <code>ReportGenerator</code> 클래스는 보고서 생성 책임만 가지고, <code>EmailSender</code> 클래스는 이메일 전송 책임만 가집니다. 이렇게 하면 각 클래스가 단일 책임을 가지게 되어 코드의 변경이 필요할 때 영향을 최소화할 수 있습니다.</p>
<h2 id="개방-폐쇄-원칙-openclosed-principle-ocp">개방-폐쇄 원칙 (Open/Closed Principle, OCP)</h2>
<p>OCP는 소프트웨어 개체는 확장에 열려 있어야 하고, 수정에는 닫혀 있어야 한다는 원칙입니다. 즉, 새로운 기능을 추가할 때 기존 코드를 수정하지 않고 확장할 수 있어야 합니다.</p>
<h3 id="예제-1">예제</h3>
<pre><code class="language-java">interface Shape {
    double area();
}

class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double area() {
        return Math.PI * radius * radius;
    }
}

class Rectangle implements Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double area() {
        return width * height;
    }
}</code></pre>
<p>위 예제에서는 <code>Shape</code> 인터페이스를 구현하는 <code>Circle</code>과 <code>Rectangle</code> 클래스가 있습니다. 새로운 도형을 추가하려면 <code>Shape</code> 인터페이스를 구현하는 새로운 클래스를 추가하면 되며, 기존 코드를 수정할 필요가 없습니다.</p>
<h2 id="리스코프-치환-원칙-liskov-substitution-principle-lsp">리스코프 치환 원칙 (Liskov Substitution Principle, LSP)</h2>
<p>LSP는 자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있어야 한다는 원칙입니다. 즉, 자식 클래스는 부모 클래스의 기능을 모두 수행할 수 있어야 합니다.</p>
<h3 id="예제-2">예제</h3>
<pre><code class="language-java">class Bird {
    public void fly() {
        System.out.println(&quot;Flying&quot;);
    }
}

class Sparrow extends Bird {
    // Sparrow는 Bird의 fly 메소드를 제대로 구현합니다.
}</code></pre>
<p>위 예제에서 <code>Sparrow</code> 클래스는 <code>Bird</code> 클래스의 기능을 그대로 상속받아 사용할 수 있습니다. 따라서 <code>Sparrow</code> 객체는 <code>Bird</code> 객체가 사용되는 곳에서 대체될 수 있습니다.</p>
<h2 id="인터페이스-분리-원칙-interface-segregation-principle-isp">인터페이스 분리 원칙 (Interface Segregation Principle, ISP)</h2>
<p>ISP는 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다는 원칙입니다. 즉, 클라이언트는 자신이 사용하지 않는 메소드에 의존하지 않아야 합니다.</p>
<h3 id="예제-3">예제</h3>
<pre><code class="language-java">interface Printer {
    void print();
}

interface Scanner {
    void scan();
}

class MultifunctionDevice implements Printer, Scanner {
    public void print() {
        // 인쇄 로직
    }

    public void scan() {
        // 스캔 로직
    }
}</code></pre>
<p>위 예제에서는 <code>Printer</code>와 <code>Scanner</code> 인터페이스를 분리하여 <code>MultifunctionDevice</code> 클래스가 각각의 기능을 구현하도록 합니다. 이렇게 하면 각 기능을 별도로 관리할 수 있어 유지보수가 용이해집니다.</p>
<h2 id="의존-역전-원칙-dependency-inversion-principle-dip">의존 역전 원칙 (Dependency Inversion Principle, DIP)</h2>
<p>DIP는 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다는 원칙입니다. 즉, 구체적인 클래스보다는 인터페이스나 추상 클래스에 의존해야 합니다.</p>
<h3 id="예제-4">예제</h3>
<pre><code class="language-java">interface Keyboard {
    void type();
}

class MechanicalKeyboard implements Keyboard {
    public void type() {
        // 기계식 키보드 타이핑 로직
    }
}

class Computer {
    private Keyboard keyboard;

    public Computer(Keyboard keyboard) {
        this.keyboard = keyboard;
    }

    public void type() {
        keyboard.type();
    }
}</code></pre>
<p>위 예제에서 <code>Computer</code> 클래스는 <code>Keyboard</code> 인터페이스에 의존하며, <code>MechanicalKeyboard</code> 클래스는 <code>Keyboard</code> 인터페이스를 구현합니다. 이렇게 하면 <code>Computer</code> 클래스는 구체적인 <code>MechanicalKeyboard</code> 클래스에 의존하지 않게 되어 유연성이 높아집니다.</p>
<h2 id="결론">결론</h2>
<p>SOLID 원칙은 소프트웨어 설계의 기본이 되는 다섯 가지 원칙을 제시합니다. 이 원칙들을 잘 준수하면 코드의 유지보수성과 확장성이 크게 향상됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring MVC: 웹 애플리케이션 개발의 핵심 프레임워크]]></title>
            <link>https://velog.io/@sujung_shin/Spring-MVC-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B0%9C%EB%B0%9C%EC%9D%98-%ED%95%B5%EC%8B%AC-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@sujung_shin/Spring-MVC-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B0%9C%EB%B0%9C%EC%9D%98-%ED%95%B5%EC%8B%AC-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Sun, 19 May 2024 03:52:16 GMT</pubDate>
            <description><![CDATA[<p>Spring MVC의 개념, 아키텍처, 그리고 기본적인 사용법에 대해 알아보겠습니다.</p>
<h2 id="spring-mvc란">Spring MVC란?</h2>
<p>Spring MVC는 스프링 프레임워크의 일부로, 모델-뷰-컨트롤러(Model-View-Controller) 패턴을 기반으로 하는 웹 애플리케이션 프레임워크입니다. 이 패턴을 통해 애플리케이션의 비즈니스 로직과 사용자 인터페이스를 분리하여 유지보수성과 확장성을 높일 수 있습니다.</p>
<h2 id="spring-mvc-아키텍처">Spring MVC 아키텍처</h2>
<p>Spring MVC의 아키텍처는 주로 다음과 같은 주요 구성 요소로 이루어져 있습니다:</p>
<ol>
<li><strong>디스패처 서블릿(DispatcherServlet)</strong>: 모든 HTTP 요청을 받아서 적절한 컨트롤러로 전달하는 프론트 컨트롤러 역할을 합니다.</li>
<li><strong>컨트롤러(Controller)</strong>: 사용자의 요청을 처리하고, 필요한 데이터를 모델에 담아 뷰에 전달합니다.</li>
<li><strong>모델(Model)</strong>: 애플리케이션의 데이터와 비즈니스 로직을 포함합니다. 데이터베이스와의 상호 작용도 포함됩니다.</li>
<li><strong>뷰(View)</strong>: 모델의 데이터를 사용자가 볼 수 있는 형태로 변환하여 출력합니다. 주로 JSP, Thymeleaf 등이 사용됩니다.</li>
</ol>
<h2 id="spring-mvc의-동작-흐름">Spring MVC의 동작 흐름</h2>
<ol>
<li><strong>사용자 요청</strong>: 사용자가 웹 브라우저를 통해 특정 URL로 요청을 보냅니다.</li>
<li><strong>디스패처 서블릿</strong>: 이 요청을 받아 적절한 컨트롤러로 전달합니다.</li>
<li><strong>컨트롤러</strong>: 요청을 처리하고 필요한 데이터를 모델에 담습니다.</li>
<li><strong>뷰 리졸버(View Resolver)</strong>: 컨트롤러가 반환한 뷰 이름을 실제 뷰(JSP 파일 등)로 변환합니다.</li>
<li><strong>뷰</strong>: 모델 데이터를 사용하여 사용자에게 응답을 생성하고 반환합니다.</li>
</ol>
<h2 id="spring-mvc-설정">Spring MVC 설정</h2>
<p>Spring MVC를 설정하기 위해서는 다음과 같은 단계가 필요합니다:</p>
<ol>
<li><strong>스프링 프로젝트 생성</strong>: 스프링 부트를 사용하여 빠르게 시작할 수 있습니다.</li>
<li><strong>디스패처 서블릿 설정</strong>: <code>web.xml</code> 파일이나 Java 설정 파일에서 디스패처 서블릿을 정의합니다.</li>
<li><strong>컨트롤러 작성</strong>: <code>@Controller</code> 어노테이션을 사용하여 컨트롤러 클래스를 정의하고, 요청 매핑을 설정합니다.</li>
<li><strong>뷰 설정</strong>: JSP나 Thymeleaf 같은 뷰 템플릿을 설정합니다.</li>
</ol>
<h2 id="간단한-예제">간단한 예제</h2>
<pre><code class="language-java">@Controller
public class HomeController {

    @GetMapping(&quot;/home&quot;)
    public String home(Model model) {
        model.addAttribute(&quot;message&quot;, &quot;Welcome to Spring MVC!&quot;);
        return &quot;home&quot;;
    }
}</code></pre>
<p>위 예제는 <code>/home</code> URL로 요청이 들어올 때 <code>home</code> 메서드가 호출되고, <code>home.jsp</code> 뷰를 반환하는 간단한 컨트롤러입니다. 모델에 &quot;Welcome to Spring MVC!&quot;라는 메시지를 담아 뷰로 전달합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 라이프 사이클에 대한 이해]]></title>
            <link>https://velog.io/@sujung_shin/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@sujung_shin/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Fri, 17 May 2024 14:14:23 GMT</pubDate>
            <description><![CDATA[<p>스프링 프레임워크(Spring Framework)는 자바 플랫폼을 위한 애플리케이션 프레임워크로, 의존성 주입(Dependency Injection)과 제어의 역전(Inversion of Control, IoC)을 통해 애플리케이션 개발을 단순화하고 유연성을 높입니다. 스프링의 핵심 기능 중 하나는 빈(Bean) 라이프 사이클을 관리하는 것입니다. 이번 포스트에서는 스프링 빈의 라이프 사이클을 단계별로 살펴보겠습니다.</p>
<h4 id="1-빈-인스턴스화-instantiation">1. 빈 인스턴스화 (Instantiation)</h4>
<p>스프링 컨테이너는 애플리케이션 컨텍스트(ApplicationContext)를 로드하면서 정의된 빈을 인스턴스화합니다. 빈은 XML 설정 파일이나 자바 설정 클래스에서 정의될 수 있습니다. 빈 인스턴스화 단계에서는 빈의 생성자가 호출됩니다.</p>
<pre><code class="language-java">public class MyBean {
    public MyBean() {
        System.out.println(&quot;MyBean 인스턴스화&quot;);
    }
}</code></pre>
<h4 id="2-의존성-주입-dependency-injection">2. 의존성 주입 (Dependency Injection)</h4>
<p>빈이 인스턴스화된 후, 스프링 컨테이너는 빈의 프로퍼티나 생성자를 통해 의존성을 주입합니다. 이는 빈 정의에서 설정된 의존성에 따라 수행됩니다.</p>
<pre><code class="language-java">public class MyBean {
    private DependencyBean dependency;

    public void setDependency(DependencyBean dependency) {
        this.dependency = dependency;
    }
}</code></pre>
<h4 id="3-빈-이름-설정-setting-bean-name">3. 빈 이름 설정 (Setting Bean Name)</h4>
<p>빈 이름이 설정됩니다. 빈 이름은 스프링 컨테이너에 의해 설정되며, 빈 이름을 설정하기 위해 <code>BeanNameAware</code> 인터페이스를 구현할 수 있습니다.</p>
<pre><code class="language-java">public class MyBean implements BeanNameAware {
    @Override
    public void setBeanName(String name) {
        System.out.println(&quot;빈 이름 설정: &quot; + name);
    }
}</code></pre>
<h4 id="4-빈-팩토리-설정-setting-bean-factory">4. 빈 팩토리 설정 (Setting Bean Factory)</h4>
<p>빈 팩토리가 설정됩니다. 빈 팩토리를 설정하기 위해 <code>BeanFactoryAware</code> 인터페이스를 구현할 수 있습니다.</p>
<pre><code class="language-java">public class MyBean implements BeanFactoryAware {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        System.out.println(&quot;빈 팩토리 설정&quot;);
    }
}</code></pre>
<h4 id="5-어플리케이션-컨텍스트-설정-setting-application-context">5. 어플리케이션 컨텍스트 설정 (Setting Application Context)</h4>
<p>애플리케이션 컨텍스트가 설정됩니다. 애플리케이션 컨텍스트를 설정하기 위해 <code>ApplicationContextAware</code> 인터페이스를 구현할 수 있습니다.</p>
<pre><code class="language-java">public class MyBean implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        System.out.println(&quot;애플리케이션 컨텍스트 설정&quot;);
    }
}</code></pre>
<h4 id="6-초기화-전-콜백-pre-initialization-callback">6. 초기화 전 콜백 (Pre-Initialization Callback)</h4>
<p>스프링 컨테이너는 빈이 초기화되기 전에 빈 후처리기(BeanPostProcessor)를 통해 초기화 전 콜백을 호출합니다. 이를 통해 개발자는 초기화 전에 특정 작업을 수행할 수 있습니다.</p>
<pre><code class="language-java">public class CustomBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(&quot;초기화 전 콜백: &quot; + beanName);
        return bean;
    }
}</code></pre>
<h4 id="7-초기화-initialization">7. 초기화 (Initialization)</h4>
<p>빈의 초기화 메서드가 호출됩니다. 이는 빈 정의에서 명시적으로 설정된 초기화 메서드이거나 <code>InitializingBean</code> 인터페이스의 <code>afterPropertiesSet</code> 메서드입니다.</p>
<pre><code class="language-java">public class MyBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println(&quot;초기화 메서드 호출&quot;);
    }
}</code></pre>
<h4 id="8-초기화-후-콜백-post-initialization-callback">8. 초기화 후 콜백 (Post-Initialization Callback)</h4>
<p>빈이 초기화된 후, 빈 후처리기(BeanPostProcessor)를 통해 초기화 후 콜백이 호출됩니다.</p>
<pre><code class="language-java">public class CustomBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(&quot;초기화 후 콜백: &quot; + beanName);
        return bean;
    }
}</code></pre>
<h4 id="9-빈-사용-bean-in-use">9. 빈 사용 (Bean in Use)</h4>
<p>빈이 초기화된 후, 애플리케이션 로직에서 빈을 사용합니다. 이 단계에서는 빈의 비즈니스 로직이 수행됩니다.</p>
<pre><code class="language-java">public class MyBean {
    public void performTask() {
        System.out.println(&quot;빈 사용: 작업 수행&quot;);
    }
}</code></pre>
<h4 id="10-소멸-전-콜백-pre-destroy-callback">10. 소멸 전 콜백 (Pre-Destroy Callback)</h4>
<p>빈이 소멸되기 전에 소멸 전 콜백이 호출됩니다. 이는 <code>DisposableBean</code> 인터페이스의 <code>destroy</code> 메서드이거나 빈 정의에서 명시적으로 설정된 소멸 메서드입니다.</p>
<pre><code class="language-java">public class MyBean implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println(&quot;소멸 전 콜백 호출&quot;);
    }
}</code></pre>
<h4 id="11-빈-소멸-bean-destruction">11. 빈 소멸 (Bean Destruction)</h4>
<p>빈이 소멸됩니다. 이 단계에서 스프링 컨테이너는 빈을 메모리에서 제거하고 모든 자원을 해제합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[쇼핑몰의 주문 결제 로직 이해하기]]></title>
            <link>https://velog.io/@sujung_shin/%EC%87%BC%ED%95%91%EB%AA%B0%EC%9D%98-%EC%A3%BC%EB%AC%B8-%EA%B2%B0%EC%A0%9C-%EB%A1%9C%EC%A7%81-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-0uvngo8u</link>
            <guid>https://velog.io/@sujung_shin/%EC%87%BC%ED%95%91%EB%AA%B0%EC%9D%98-%EC%A3%BC%EB%AC%B8-%EA%B2%B0%EC%A0%9C-%EB%A1%9C%EC%A7%81-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-0uvngo8u</guid>
            <pubDate>Thu, 16 May 2024 00:30:04 GMT</pubDate>
            <description><![CDATA[<h3 id="쇼핑몰에서-주문-결제가-이뤄지는-과정">쇼핑몰에서 주문 결제가 이뤄지는 과정</h3>
<p>쇼핑몰에서 상품을 주문하고 결제하는 과정은 우리가 실제 매장에서 쇼핑하는 것과 비슷합니다. 어떤 식으로 주문과 결제가 진행되는지 단계별로 알아볼까요?</p>
<ol>
<li><p><strong>상품을 고르고 장바구니에 담기</strong></p>
<ul>
<li>원하는 상품을 선택하고, 색상이나 사이즈 같은 옵션을 고른 뒤 장바구니에 담습니다.</li>
<li>이때 <strong>장바구니</strong>는 상품을 임시로 모아두는 곳이죠.</li>
<li>상품을 장바구니에 담으면 쇼핑몰 서버가 <strong>&quot;이 상품은 사용자의 장바구니에 있다&quot;</strong>고 기록해 둡니다.</li>
</ul>
</li>
<li><p><strong>장바구니 확인 및 주문자 정보 입력</strong></p>
<ul>
<li>장바구니에 담긴 상품들을 모두 확인하고, 불필요한 상품은 삭제할 수 있습니다.</li>
<li>주문을 진행하기 전에 <strong>주문자 정보</strong>(이름, 연락처, 주소 등)와 <strong>배송받을 수령인 정보</strong>를 입력합니다.</li>
</ul>
</li>
<li><p><strong>결제 방식 선택 및 결제 진행</strong></p>
<ul>
<li><strong>신용카드, 카카오페이, 무통장입금</strong> 등 원하는 결제 방식을 선택합니다.</li>
<li>결제 버튼을 누르면 쇼핑몰 서버가 <strong>결제 정보</strong>를 받게 됩니다.</li>
<li>예를 들어, 페이팔로 결제를 진행한다면 <strong>페이팔 서버</strong>에서 결제 승인이 완료됩니다.</li>
</ul>
</li>
<li><p><strong>주문 생성 및 결제 처리</strong></p>
<ul>
<li><strong>주문 정보</strong>가 서버로 전달되고, <strong>새로운 주문</strong>이 생성됩니다.</li>
<li>장바구니에 담긴 상품을 주문 상품으로 변환하여 데이터베이스에 기록합니다.</li>
<li>결제도 완료되면 서버는 이를 데이터베이스에 저장합니다.</li>
<li>이때 <strong>결제 내역서</strong>가 생성됩니다.</li>
</ul>
</li>
<li><p><strong>장바구니 비우기</strong></p>
<ul>
<li>주문이 완료되면 장바구니에 담겨 있던 상품을 모두 비웁니다.</li>
</ul>
</li>
</ol>
<h3 id="예시를-통해-주문-로직을-쉽게-이해하기">예시를 통해 주문 로직을 쉽게 이해하기</h3>
<p>이 과정을 예시로 한 번 설명해볼게요.</p>
<ol>
<li><p><strong>장바구니에 상품 추가</strong></p>
<ul>
<li><strong>사용자</strong>는 온라인 쇼핑몰에서 신발을 찾고, 색상과 사이즈를 고른 뒤 <strong>&#39;장바구니에 담기&#39;</strong> 버튼을 누릅니다.</li>
<li>쇼핑몰 서버는 이 신발을 사용자 장바구니에 담습니다.</li>
</ul>
</li>
<li><p><strong>장바구니 확인 및 주문자 정보 입력</strong></p>
<ul>
<li><strong>사용자</strong>는 장바구니 페이지에 가서 담긴 상품을 확인합니다.</li>
<li>주문하기 위해 <strong>주문자 정보</strong>(이름, 연락처, 주소 등)를 입력합니다.</li>
</ul>
</li>
<li><p><strong>결제 진행</strong></p>
<ul>
<li>결제 페이지에서 결제 방식을 선택합니다.</li>
<li><strong>사용자</strong>가 신용카드를 선택했다면 카드 정보를 입력하고 <strong>&#39;결제하기&#39;</strong> 버튼을 누릅니다.</li>
<li>결제 승인이 완료되면 <strong>결제 성공</strong> 메시지를 받게 됩니다.</li>
</ul>
</li>
<li><p><strong>주문 생성 및 결제 완료</strong></p>
<ul>
<li>쇼핑몰 서버는 새로운 주문을 생성합니다.</li>
<li>장바구니에 있던 신발을 주문 상품으로 변환하여 데이터베이스에 기록합니다.</li>
<li><strong>결제 정보</strong>도 함께 저장합니다.</li>
</ul>
</li>
<li><p><strong>장바구니 비우기</strong></p>
<ul>
<li>주문이 완료된 상품을 장바구니에서 제거합니다.</li>
</ul>
</li>
</ol>
<h3 id="정리">정리</h3>
<p>온라인 쇼핑몰에서 상품을 주문하고 결제하는 과정은 <strong>주문자 정보 입력</strong> → <strong>결제 방식 선택</strong> → <strong>결제 진행</strong> → <strong>주문 생성</strong> → <strong>장바구니 비우기</strong>로 요약할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[쇼핑몰 장바구니 로직에 대한 설명]]></title>
            <link>https://velog.io/@sujung_shin/%EC%87%BC%ED%95%91%EB%AA%B0-%EC%9E%A5%EB%B0%94%EA%B5%AC%EB%8B%88-%EB%A1%9C%EC%A7%81%EC%97%90-%EB%8C%80%ED%95%9C-%EC%84%A4%EB%AA%85-e93fkprj</link>
            <guid>https://velog.io/@sujung_shin/%EC%87%BC%ED%95%91%EB%AA%B0-%EC%9E%A5%EB%B0%94%EA%B5%AC%EB%8B%88-%EB%A1%9C%EC%A7%81%EC%97%90-%EB%8C%80%ED%95%9C-%EC%84%A4%EB%AA%85-e93fkprj</guid>
            <pubDate>Wed, 15 May 2024 00:18:28 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 쇼핑몰에서 <strong>장바구니 로직</strong>이 어떻게 작동하는지 쉽게 이해할 수 있도록 비유와 함께 설명해 보겠습니다.</p>
<h3 id="쇼핑몰-장바구니란">쇼핑몰 장바구니란?</h3>
<p>우선 쇼핑몰에서 장바구니는 실제로 매장에서 물건을 고르기 전에 임시로 담아두는 카트와 비슷합니다. 여러 가지 상품을 담고 마지막에 결제를 하기 전에 필요한지 다시 한번 확인할 수 있도록 도와주는 역할을 합니다.</p>
<h3 id="장바구니-기능-요약">장바구니 기능 요약</h3>
<p>장바구니에서 주로 제공하는 기능은 다음과 같습니다.</p>
<ol>
<li><p><strong>장바구니에 상품 추가</strong>:</p>
<ul>
<li>쇼핑몰에서 원하는 상품을 고르고 &#39;장바구니에 담기&#39; 버튼을 눌러 해당 상품을 장바구니에 추가합니다.</li>
</ul>
</li>
<li><p><strong>장바구니 목록 보기</strong>:</p>
<ul>
<li>장바구니에 담긴 모든 상품을 확인할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>장바구니 상품 삭제</strong>:</p>
<ul>
<li>장바구니에 담긴 특정 상품을 삭제할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>장바구니 비우기</strong>:</p>
<ul>
<li>장바구니에 담긴 모든 상품을 한 번에 삭제할 수 있습니다.</li>
</ul>
</li>
</ol>
<h3 id="장바구니-로직-작동-방식">장바구니 로직 작동 방식</h3>
<p>그렇다면 이러한 기능들이 어떻게 구현되고 작동하는지 알아보겠습니다.</p>
<h4 id="1-상품을-장바구니에-추가하는-로직">1. 상품을 장바구니에 추가하는 로직</h4>
<ul>
<li><strong>사용자가 상품을 선택</strong>하고 <strong>&#39;장바구니에 담기&#39; 버튼</strong>을 클릭하면, 상품의 정보(예: 상품 이름, 가격, 색상, 사이즈 등)가 서버로 전송됩니다.</li>
<li><strong>서버</strong>는 사용자가 장바구니에 담은 상품을 <strong>기록하고 저장</strong>합니다.<ul>
<li>예를 들어, 쇼핑몰의 데이터베이스라는 곳에 상품 정보를 저장하여 &quot;A 사용자가 상품 B를 장바구니에 담았다&quot;고 기록하는 것입니다.</li>
</ul>
</li>
</ul>
<h4 id="2-장바구니-목록을-보여주는-로직">2. 장바구니 목록을 보여주는 로직</h4>
<ul>
<li><strong>사용자</strong>가 장바구니 페이지에 접근하면, 서버에 <strong>&quot;내 장바구니 목록을 보여줘!&quot;</strong>라고 요청합니다.</li>
<li>서버는 데이터베이스에서 해당 사용자가 장바구니에 담아둔 모든 상품을 찾아 목록으로 반환합니다.</li>
<li>이를 통해 사용자는 현재 담긴 상품 목록을 확인할 수 있습니다.</li>
</ul>
<h4 id="3-장바구니-상품-삭제-로직">3. 장바구니 상품 삭제 로직</h4>
<ul>
<li>장바구니에 담긴 상품을 삭제하고자 할 때 사용자가 <strong>&#39;삭제&#39; 버튼</strong>을 누르면 서버에 어떤 상품을 삭제할지 정보를 보냅니다.</li>
<li><strong>서버</strong>는 데이터베이스에서 해당 상품을 <strong>삭제</strong>하여 사용자의 장바구니에서 빠지도록 합니다.</li>
</ul>
<h4 id="4-장바구니-비우기-로직">4. 장바구니 비우기 로직</h4>
<ul>
<li>사용자가 장바구니를 한 번에 비우고 싶을 때 <strong>&#39;모두 비우기&#39; 버튼</strong>을 누르면, 서버에 비우기 요청을 보냅니다.</li>
<li><strong>서버</strong>는 데이터베이스에서 해당 사용자의 장바구니에 담긴 모든 상품을 삭제합니다.</li>
</ul>
<p>쇼핑몰 시스템의 장바구니 기능은 실제 매장에서 카트를 사용하는 것과 비슷한 방식으로 상품을 고르고 결제하기 전에 확인할 수 있도록 도와주는 역할을 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[테스트 코드 작성의 중요성: JUnit과 MockMVC를 활용한 테스트 코드 작성 경험]]></title>
            <link>https://velog.io/@sujung_shin/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1-JUnit%EA%B3%BC-MockMVC%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1-%EA%B2%BD%ED%97%98-d7ir0woe</link>
            <guid>https://velog.io/@sujung_shin/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1-JUnit%EA%B3%BC-MockMVC%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1-%EA%B2%BD%ED%97%98-d7ir0woe</guid>
            <pubDate>Tue, 14 May 2024 03:49:48 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 <strong>JUnit과 MockMVC를 활용한 테스트 코드 작성의 중요성</strong>에 대해 이야기해 보려고 합니다. 실제로 구현한 테스트 코드를 기반으로 기능 구현 전에 테스트를 계획하고 진행하는 것이 얼마나 중요한지 알아보겠습니다.</p>
<h4 id="테스트-코드-작성의-중요성">테스트 코드 작성의 중요성</h4>
<p>테스트 코드를 작성하는 이유와 그 중요성은 다음과 같습니다.</p>
<ol>
<li><p><strong>기능 구현 전 테스트 계획</strong>:</p>
<ul>
<li>기능 구현 전에 테스트 시나리오를 계획하여 올바른 동작을 보장합니다.</li>
<li>테스트 주도 개발(TDD, Test-Driven Development) 접근 방식.</li>
</ul>
</li>
<li><p><strong>오류 사전 발견</strong>:</p>
<ul>
<li>개발 초기 단계에서 오류를 발견하고 해결하는 데 큰 도움을 줍니다.</li>
<li>사소한 문제부터 중대한 버그까지 조기에 발견하여 안정성을 높입니다.</li>
</ul>
</li>
<li><p><strong>안정적인 소프트웨어 개발</strong>:</p>
<ul>
<li>전체 기능을 테스트하여 안정적인 소프트웨어 개발을 위한 기반을 마련합니다.</li>
<li>기능 추가 및 변경 시에도 기존 기능의 안정성을 유지할 수 있습니다.</li>
</ul>
</li>
</ol>
<h4 id="실제-프로젝트에서-junit과-mockmvc를-활용한-테스트-코드-작성">실제 프로젝트에서 JUnit과 MockMVC를 활용한 테스트 코드 작성</h4>
<p>다음은 <code>BoardAPIController</code>에 대한 테스트 코드를 작성한 사례입니다.</p>
<h5 id="boardapicontroller">BoardAPIController</h5>
<p><code>BoardAPIController</code>는 게시글 작성, 수정, 삭제 등의 기능을 제공하는 컨트롤러입니다.</p>
<pre><code class="language-java">package com.moonBam.controller.board;

import com.moonBam.dto.MemberDTO;
import com.moonBam.dto.board.PostDTO;
import com.moonBam.dto.board.PostUpdateRequestDTO;
import com.moonBam.service.PostService;
import com.moonBam.service.ScrapService;
import com.moonBam.service.member.MemberLoginService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/api/post&quot;)
public class BoardAPIController {

    private final PostService postService;
    private final MemberLoginService memberLoginService;
    private final ScrapService scrapService;

    @PostMapping
    public ResponseEntity&lt;?&gt; create(@RequestBody @Validated PostDTO postDTO,
                                    BindingResult bindingResult, Principal principal) {

        if (bindingResult.hasErrors()) {
            Map&lt;String, String&gt; errors = getErrors(bindingResult);
            String s = errors.values().stream().toList().get(0);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(Map.of(&quot;message&quot;, s, &quot;errors&quot;, errors));
        }

        String sanitizedPostText = sanitizeHtml(postDTO.getPostText());
        String sanitizedPostTitle = sanitizeHtml(postDTO.getPostTitle());
        postDTO.setPostText(sanitizedPostText);
        postDTO.setPostTitle(sanitizedPostTitle);

        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);

        if (loginUser == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;로그인이 필요한 서비스입니다.&quot;));
        }

        postDTO.setUserId(loginUser.getUserId());
        postDTO.setNickname(loginUser.getNickname());
        Long postID = postService.save(postDTO);
        return ResponseEntity.ok(Map.of(&quot;postID&quot;, postID));
    }

    @PatchMapping(&quot;/{postId}&quot;)
    public ResponseEntity&lt;?&gt; update(@RequestBody @Validated PostUpdateRequestDTO postUpdateRequestDTO, @PathVariable(&quot;postId&quot;) Long postId,
                                    BindingResult bindingResult, Principal principal) {

        PostDTO postDTO = postService.findById(postId);

        if (bindingResult.hasErrors()) {
            Map&lt;String, String&gt; errors = getErrors(bindingResult);
            String s = errors.values().stream().toList().get(0);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(Map.of(&quot;message&quot;, s, &quot;errors&quot;, errors));
        }

        String sanitizedPostText = sanitizeHtml(postUpdateRequestDTO.getPostText());
        postUpdateRequestDTO.setPostText(sanitizedPostText);
        postUpdateRequestDTO.setPostTitle(sanitizeHtml(postUpdateRequestDTO.getPostTitle()));

        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);

        if (loginUser == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;로그인이 필요한 서비스입니다.&quot;));
        }

        if (!loginUser.getUserId().equals(postDTO.getUserId())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;글을 수정할 권한이 없습니다.&quot;));
        }

        postService.update(postUpdateRequestDTO);

        return ResponseEntity.ok(Map.of(&quot;postID&quot;, postId));
    }

    private Map&lt;String, String&gt; getErrors(BindingResult bindingResult) {
        return bindingResult.getFieldErrors().stream()
                .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
    }

    @DeleteMapping(&quot;{postId}&quot;)
    public ResponseEntity&lt;?&gt; delete(@PathVariable(&quot;postId&quot;) Long postId, Principal principal) {
        PostDTO postDTO = postService.findById(postId);
        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);

        if (loginUser == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;로그인이 필요한 서비스입니다.&quot;));
        }

        if (postDTO == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of(&quot;message&quot;, &quot;글이 존재하지 않습니다.&quot;));
        }

        if (!loginUser.getUserId().equals(postDTO.getUserId())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;글을 삭제할 권한이 없습니다.&quot;));
        }

        scrapService.findAllByPostId(postId).forEach(scrapDTO -&gt; scrapService.delete(scrapDTO.getScrapId()));
        postService.delete(postId);

        return ResponseEntity.ok(Map.of(&quot;message&quot;, &quot;삭제가 완료되었습니다.&quot;));
    }

    private String sanitizeHtml(String input) {
        return input.replaceAll(&quot;(?i)&lt;script.*?&gt;.*?&lt;/script&gt;&quot;, &quot;&quot;) // 스크립트 태그 제거
                .replaceAll(&quot;(?i)&lt;.*?javascript:.*?&gt;.*?&lt;/.*?&gt;&quot;, &quot;&quot;) // &quot;javascript:&quot; URI 사용 제거
                .replaceAll(&quot;(?i)&lt;.*?\\bon.*?&gt;.*?&lt;/.*?&gt;&quot;, &quot;&quot;); // 이벤트 핸들러 제거 (예: onclick)
    }
}</code></pre>
<h5 id="boardapicontroller-테스트-코드">BoardAPIController 테스트 코드</h5>
<p><code>JUnit</code>과 <code>MockMVC</code>를 사용해 <code>BoardAPIController</code>에 대한 테스트 코드를 작성합니다.</p>
<pre><code class="language-java">package com.moonBam.controller.board;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.moonBam.dto.MemberDTO;
import com.moonBam.dto.board.PostDTO;
import com.moonBam.service.PostService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class BoardAPIControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private PostService postService;
    private ObjectMapper objectMapper;
    private MockHttpSession mockSession;

    @BeforeEach
    void setup() {
        objectMapper = new ObjectMapper();
        mockSession = new MockHttpSession();
    }

    @Test
    void createPostWhenNotLoggedIn() throws Exception {
        PostDTO post = getValidPostDTO();
        String content = objectMapper.writeValueAsString(post);

        mockMvc.perform(post(&quot;/api/post&quot;)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(content))
                .andExpect(status().isUnauthorized())
                .andExpect(jsonPath(&quot;$.message&quot;).value(&quot;로그인이 필요한 서비스입니다.&quot;));
    }

    @Test
    void createPostSuccessfully() throws Exception {
        MemberDTO loginUser = new MemberDTO();
        loginUser.setUserId(&quot;testUser&quot;);
        mockSession.setAttribute(&quot;loginUser&quot;, loginUser);

        PostDTO post = getValidPostDTO();
        String content = objectMapper.writeValueAsString(post);

        given(postService.save(any(PostDTO.class))).willReturn(1L);

        mockMvc.perform(MockMvcRequestBuilders.post(&quot;/api/post&quot;)
                        .session(mockSession)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(content))
                .andExpect(status().isOk())
                .andExpect(jsonPath(&quot;$.postID&quot;).value(1L));
    }

    @Test
    void createPostWithValidationErrors() throws Exception {
        PostDTO postDTO = new PostDTO();
        String content = objectMapper.writeValueAsString(postDTO);

        mockMvc.perform(post(&quot;/api/post&quot;)
                        .contentType(MediaType.APPLICATION_JSON)


 .content(content))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath(&quot;$.message&quot;).value(&quot;입력 값에 오류가 있습니다.&quot;));
    }

    private PostDTO getValidPostDTO() {
        return new PostDTO(
                1L,
                &quot;BoardName&quot;,
                &quot;UserId123&quot;,
                1L,
                &quot;Post Title Example&quot;,
                new Date(),
                null,
                &quot;This is the content of the post.&quot;,
                &quot;Nickname&quot;,
                1L
        );
    }

    @Test
    void deletePostSuccessfully() throws Exception {
        PostDTO existingPost = new PostDTO();
        Long existingPostId = 1L;
        existingPost.setPostId(existingPostId);
        existingPost.setUserId(&quot;test-user&quot;);
        given(postService.findById(existingPostId)).willReturn(existingPost);

        MemberDTO memberDTO = new MemberDTO();
        memberDTO.setUserId(&quot;test-user&quot;);

        mockMvc.perform(MockMvcRequestBuilders.delete(&quot;/api/post/&quot; + existingPostId)
                        .sessionAttr(&quot;loginUser&quot;, memberDTO))
                .andExpect(status().isOk())
                .andExpect(jsonPath(&quot;$.message&quot;).value(&quot;삭제가 완료되었습니다.&quot;));
    }

    @Test
    void deletePostWhenNotLoggedIn() throws Exception {
        PostDTO existingPost = new PostDTO();
        Long existingPostId = 1L;
        existingPost.setPostId(existingPostId);
        given(postService.findById(existingPostId)).willReturn(existingPost);

        MemberDTO memberDTO = new MemberDTO();
        memberDTO.setUserId(&quot;test-user&quot;);

        mockMvc.perform(MockMvcRequestBuilders.delete(&quot;/api/post/&quot; + existingPostId))
                .andExpect(status().isUnauthorized())
                .andExpect(jsonPath(&quot;$.message&quot;).value(&quot;로그인이 필요한 서비스입니다.&quot;));
    }

    @Test
    void deletePostWhenNotFound() throws Exception {
        Long nonExistingPostId = 9999L;

        MemberDTO memberDTO = new MemberDTO();
        memberDTO.setUserId(&quot;test-user&quot;);

        mockMvc.perform(MockMvcRequestBuilders.delete(&quot;/api/post/&quot; + nonExistingPostId)
                        .sessionAttr(&quot;loginUser&quot;, memberDTO))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath(&quot;$.message&quot;).value(&quot;글이 존재하지 않습니다.&quot;));
    }

    @Test
    void deletePostWhenUserHasNoPermission() throws Exception {
        PostDTO existingPost = new PostDTO();
        Long existingPostId = 1L;
        existingPost.setPostId(existingPostId);
        existingPost.setUserId(&quot;test-user&quot;);

        given(postService.findById(existingPostId)).willReturn(existingPost);

        MemberDTO memberDTO = new MemberDTO();
        memberDTO.setUserId(&quot;another-user&quot;);

        mockMvc.perform(MockMvcRequestBuilders.delete(&quot;/api/post/&quot; + existingPostId)
                        .sessionAttr(&quot;loginUser&quot;, memberDTO))
                .andExpect(status().isUnauthorized())
                .andExpect(jsonPath(&quot;$.message&quot;).value(&quot;글을 삭제할 권한이 없습니다.&quot;));
    }

}</code></pre>
<p>이번 포스팅에서는 <strong>JUnit과 MockMVC를 활용한 테스트 코드 작성</strong>을 통해 개발 초기 단계에서 오류를 발견하고 해결하는 데 큰 도움을 주며, 테스트 코드 작성의 중요성을 직접 경험한 사례를 공유했습니다.</p>
<p>테스트 코드를 작성함으로써 기능 구현 전에 테스트를 계획하고 안정적인 소프트웨어 개발을 위한 기반을 마련하는 것이 얼마나 중요한지 다시 한번 깨달았습니다.</p>
<p><strong>참고 자료</strong></p>
<ul>
<li><a href="https://junit.org/junit5/docs/current/user-guide/">JUnit 5 User Guide</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/MockMvc.html">Spring Framework MockMvc Documentation</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[클린 코드와 예외 처리의 중요성: 사용자 권한 검증 및 중복 코드의 리팩토링]]></title>
            <link>https://velog.io/@sujung_shin/%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C%EC%99%80-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B6%8C%ED%95%9C-%EA%B2%80%EC%A6%9D-%EB%B0%8F-%EC%A4%91%EB%B3%B5-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-jmmbrpsg</link>
            <guid>https://velog.io/@sujung_shin/%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C%EC%99%80-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B6%8C%ED%95%9C-%EA%B2%80%EC%A6%9D-%EB%B0%8F-%EC%A4%91%EB%B3%B5-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-jmmbrpsg</guid>
            <pubDate>Mon, 13 May 2024 00:58:29 GMT</pubDate>
            <description><![CDATA[<p>오늘은 클린 코드와 예외 처리의 중요성을 주제로 이야기하려고 합니다. 특히 <strong>중복 코드의 리팩토링</strong>과 <strong>사용자 권한 검증</strong>을 통해 코드의 가독성과 유지보수성을 높이는 방법에 대해 소개하겠습니다.</p>
<h4 id="프로젝트-배경">프로젝트 배경</h4>
<p>이번 프로젝트는 RESTful API를 사용해 게시판 서비스를 구현하는 것이 목적이었습니다. 게시글 작성, 수정, 삭제와 같은 기능을 구현하면서, 아래와 같은 문제점을 발견하게 되었습니다.</p>
<ol>
<li><p><strong>중복 코드의 반복 사용</strong>:</p>
<ul>
<li>로그인한 사용자를 찾는 로직이 여러 곳에서 반복되고 있었음.</li>
<li>이를 해결하기 위해 로그인된 사용자를 가져오는 서비스 로직을 별도로 분리하여 재사용성을 높임.</li>
</ul>
</li>
<li><p><strong>예외 처리의 일관성 부족</strong>:</p>
<ul>
<li>권한 검증 및 요청 유효성 검사 과정에서 예외 처리의 중요성을 깨달음.</li>
<li><code>ResponseStatusException</code>을 통해 명확한 에러 메시지와 상태 코드를 반환하도록 개선함.</li>
</ul>
</li>
</ol>
<h4 id="memberloginservice-클래스"><code>MemberLoginService</code> 클래스</h4>
<p>중복된 로그인 유저 조회 로직을 <code>MemberLoginService</code> 클래스에 통합하여 각 컨트롤러에서 재사용할 수 있게 했습니다.</p>
<pre><code class="language-java">package com.moonBam.service.member;

import com.moonBam.dto.MemberDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import java.security.Principal;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class MemberLoginService {

    private final MemberService memberService;

    public MemberDTO findByPrincipal(Principal principal) {
        if (principal == null) {
            return null;
        }

        return Optional.ofNullable(memberService.findByUserId(principal.getName()))
                .orElseThrow(() -&gt; new ResponseStatusException(HttpStatus.UNAUTHORIZED, principal.getName() + &quot; not found&quot;));
    }
}</code></pre>
<ul>
<li><strong><code>findByPrincipal</code> 메서드</strong>:<ul>
<li>로그인된 사용자의 <code>Principal</code>을 통해 <code>MemberDTO</code>를 찾음.</li>
<li>찾지 못한 경우 <code>ResponseStatusException</code>을 발생시켜 <code>UNAUTHORIZED</code> 상태 코드를 반환.</li>
</ul>
</li>
</ul>
<h4 id="boardapicontroller-클래스"><code>BoardAPIController</code> 클래스</h4>
<p>권한 검증과 유효성 검사를 개선한 <code>BoardAPIController</code> 클래스입니다.</p>
<pre><code class="language-java">package com.moonBam.controller.board;

import com.moonBam.dto.MemberDTO;
import com.moonBam.dto.board.PostDTO;
import com.moonBam.dto.board.PostPageDTO;
import com.moonBam.dto.board.PostUpdateRequestDTO;
import com.moonBam.service.PostService;
import com.moonBam.service.ScrapService;
import com.moonBam.service.member.MemberLoginService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/api/post&quot;)
public class BoardAPIController {

    private final PostService postService;
    private final MemberLoginService memberLoginService;
    private final ScrapService scrapService;

    @PostMapping
    public ResponseEntity&lt;?&gt; create(@RequestBody @Validated PostDTO postDTO,
                                    BindingResult bindingResult, Principal principal) {

        if (bindingResult.hasErrors()) {
            Map&lt;String, String&gt; errors = getErrors(bindingResult);
            String s = errors.values().stream().toList().get(0);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(Map.of(&quot;message&quot;, s, &quot;errors&quot;, errors));
        }

        // XSS 방지를 위해 입력값에서 스크립트 태그를 제거하는 로직 추가
        String sanitizedPostText = sanitizeHtml(postDTO.getPostText());
        String sanitizedPostTitle = sanitizeHtml(postDTO.getPostTitle());
        postDTO.setPostText(sanitizedPostText);
        postDTO.setPostTitle(sanitizedPostTitle);

        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);

        postDTO.setUserId(loginUser.getUserId());
        postDTO.setNickname(loginUser.getNickname());
        Long postID = postService.save(postDTO);
        return ResponseEntity.ok(Map.of(&quot;postID&quot;, postID)); // 동적으로 ajax success function에서 redirect 시킬 용도
    }

    @PatchMapping(&quot;/{postId}&quot;)
    public ResponseEntity&lt;?&gt; update(@RequestBody @Validated PostUpdateRequestDTO postUpdateRequestDTO, @PathVariable(&quot;postId&quot;) Long postId,
                                    BindingResult bindingResult, Principal principal) {

        PostDTO postDTO = postService.findById(postId);

        if (bindingResult.hasErrors()) {
            Map&lt;String, String&gt; errors = getErrors(bindingResult);
            String s = errors.values().stream().toList().get(0);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(Map.of(&quot;message&quot;, s, &quot;errors&quot;, errors));
        }

        // XSS 방지를 위해 입력값에서 스크립트 태그를 제거하는 로직 추가
        String sanitizedPostText = sanitizeHtml(postUpdateRequestDTO.getPostText());
        postUpdateRequestDTO.setPostText(sanitizedPostText);
        postUpdateRequestDTO.setPostTitle(sanitizeHtml(postUpdateRequestDTO.getPostTitle()));

        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);

        if (!loginUser.getUserId().equals(postDTO.getUserId())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;글을 수정할 권한이 없습니다.&quot;));
        }

        postService.update(postUpdateRequestDTO);

        return ResponseEntity.ok(Map.of(&quot;postID&quot;, postId));
    }

    @DeleteMapping(&quot;{postId}&quot;)
    public ResponseEntity&lt;?&gt; delete(@PathVariable(&quot;postId&quot;) Long postId, Principal principal) {
        PostDTO postDTO = postService.findById(postId);
        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);

        if (postDTO == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of(&quot;message&quot;, &quot;글이 존재하지 않습니다.&quot;));
        }

        if (!loginUser.getUserId().equals(postDTO.getUserId())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;글을 삭제할 권한이 없습니다.&quot;));
        }

        scrapService.findAllByPostId(postId).forEach(scrapDTO -&gt; scrapService.delete(scrapDTO.getScrapId()));
        postService.delete(postId);

        return ResponseEntity.ok(Map.of(&quot;message&quot;, &quot;삭제가 완료되었습니다.&quot;));
    }

    @GetMapping(&quot;/{postId}&quot;)
    public ResponseEntity&lt;?&gt; findOne(@PathVariable(&quot;postId&quot;) Long postId, Principal principal) {
        PostPageDTO pDTO = postService.selectPagePost(postId);

        if (pDTO == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of(&quot;message&quot;, &quot;존재하지 않는 게시글입니다.&quot;));
        }

        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);
        boolean isAuthorized = loginUser != null &amp;&amp; loginUser.getUserId().equals(pDTO.getUserId());

        return ResponseEntity.ok(Map.of(&quot;pDTO&quot;, pDTO, &quot;isAuthorized&quot;, isAuthorized));
    }

    private Map&lt;String, String&gt; getErrors(BindingResult bindingResult) {
        return bindingResult.getFieldErrors().stream()
                .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
    }

    private String sanitizeHtml(String input) {
        return input.replaceAll(&quot;(?i)&lt;script.*?&gt;.*?&lt;/script&gt;&quot;, &quot;&quot;) // 스크립트 태그 제거
                .replaceAll(&quot;(?i)&lt;.*?javascript:.*?&gt;.*?&lt;/.*?&gt;&quot;, &quot;&quot;) // &quot;javascript:&quot; URI 사용 제거
                .replaceAll(&quot;(?i)&lt;.*?\\bon.*?&gt;.*?&lt;/.*?&gt;&quot;, &quot;&quot;); // 이벤트 핸들러 제거 (예: onclick)
    }
}</code></pre>
<h3 id="클린-코드와-예외-처리-개선의-장점">클린 코드와 예외 처리 개선의 장점</h3>
<ol>
<li><p><strong>중복 코드 제거</strong>:</p>
<ul>
<li>중복되는 코드를 분리하여 <code>MemberLoginService</code>로 통합함으로써 가독성 및 재사용성을 높였습니다.</li>
</ul>
</li>
<li><p><strong>일관된 예외 처리</strong>:</p>
<ul>
<li><code>ResponseStatusException</code>을 활용해 사용자 권한 검증과 요청 유효성 검사 시 명확한 상태 코드를 반환함으로써 예외 처리의 일관성을 확보했습니다.</li>
</ul>
</li>
<li><p><strong>보안 강화</strong>:</p>
<ul>
<li><code>sanitizeHtml</code> 메서드를 통해 사용자 입력에 대한 XSS 방어 로직을 추가하여 보안을 강화했습니다.</li>
</ul>
</li>
</ol>
<p>이번 프로젝트를 통해 클린 코드의 중요성과 예외 처리의 중요성을 다시 한번 깨닫게 되었습니다. 코드를 리팩토링하고 중복 로직을 제거함으로써 가독성과 유지보수성을 향상시키고, 일관된 예외 처리로 권한 검증 및 유효성 검사 로직을 개선했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[크로스 사이트 스크립팅(XSS) 방지하기: `<c:out>` 태그 활용하기]]></title>
            <link>https://velog.io/@sujung_shin/%ED%81%AC%EB%A1%9C%EC%8A%A4-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8C%85XSS-%EB%B0%A9%EC%A7%80%ED%95%98%EA%B8%B0-cout-%ED%83%9C%EA%B7%B8-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0-tx3wig2t</link>
            <guid>https://velog.io/@sujung_shin/%ED%81%AC%EB%A1%9C%EC%8A%A4-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8C%85XSS-%EB%B0%A9%EC%A7%80%ED%95%98%EA%B8%B0-cout-%ED%83%9C%EA%B7%B8-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0-tx3wig2t</guid>
            <pubDate>Sat, 11 May 2024 15:16:13 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 크로스 사이트 스크립팅(XSS) 공격을 방지하기 위한 방법 중 하나인 <code>&lt;c:out&gt;</code> 태그에 대해 살펴보겠습니다. <code>&lt;c:out&gt;</code> 태그는 JSP(JavaServer Pages)에서 제공하는 JSTL(JavaServer Pages Standard Tag Library)의 일부로, HTML 이스케이핑을 통해 XSS 공격을 방지할 수 있습니다.</p>
<h4 id="cout-태그란"><code>&lt;c:out&gt;</code> 태그란?</h4>
<p><code>&lt;c:out&gt;</code> 태그는 JSTL 코어 라이브러리의 태그로, JSP에서 데이터를 출력할 때 사용됩니다. 이 태그의 가장 큰 특징은 <strong>자동으로 특수 문자를 이스케이프</strong>하여 XSS 공격을 방지한다는 점입니다.</p>
<h4 id="cout-태그-사용-예시"><code>&lt;c:out&gt;</code> 태그 사용 예시</h4>
<p>기본적인 사용법은 다음과 같습니다.</p>
<pre><code class="language-jsp">&lt;%@ taglib uri=&quot;http://java.sun.com/jsp/jstl/core&quot; prefix=&quot;c&quot; %&gt;
...
&lt;c:out value=&quot;${post.title}&quot; /&gt;</code></pre>
<p>위 코드는 <code>${post.title}</code>에 포함된 HTML 특수 문자를 이스케이프하여 안전하게 출력합니다.</p>
<h4 id="cout-태그의-기본-동작"><code>&lt;c:out&gt;</code> 태그의 기본 동작</h4>
<p><code>&lt;c:out&gt;</code> 태그는 다음과 같은 특수 문자를 HTML 이스케이프 처리하여 XSS 공격을 방지합니다.</p>
<table>
<thead>
<tr>
<th>특수 문자</th>
<th>이스케이프 처리 결과</th>
</tr>
</thead>
<tbody><tr>
<td><code>&amp;</code></td>
<td><code>&amp;amp;</code></td>
</tr>
<tr>
<td><code>&lt;</code></td>
<td><code>&amp;lt;</code></td>
</tr>
<tr>
<td><code>&gt;</code></td>
<td><code>&amp;gt;</code></td>
</tr>
<tr>
<td><code>&quot;</code></td>
<td><code>&amp;quot;</code></td>
</tr>
<tr>
<td><code>&#39;</code></td>
<td><code>&amp;#039;</code></td>
</tr>
</tbody></table>
<h4 id="boardapicontroller에서-cout-활용하기"><code>BoardAPIController</code>에서 <code>&lt;c:out&gt;</code> 활용하기</h4>
<p><code>BoardAPIController</code>의 데이터를 JSP 페이지에 출력할 때 <code>&lt;c:out&gt;</code> 태그를 활용하여 안전하게 데이터를 표시해 보겠습니다.</p>
<h5 id="boardapicontroller-클래스"><code>BoardAPIController</code> 클래스</h5>
<p>게시글 데이터를 조회하여 JSP 페이지로 전달합니다.</p>
<pre><code class="language-java">package com.moonBam.controller.board;

import com.moonBam.dto.MemberDTO;
import com.moonBam.dto.board.PostPageDTO;
import com.moonBam.service.PostService;
import com.moonBam.service.member.MemberLoginService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.Map;

@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/api/post&quot;)
public class BoardAPIController {
    private final PostService postService;
    private final MemberLoginService memberLoginService;

    @GetMapping(&quot;/{postId}&quot;)
    public ResponseEntity&lt;?&gt; findOne(@PathVariable(&quot;postId&quot;) Long postId, Principal principal){
        PostPageDTO pDTO = postService.selectPagePost(postId);

        if (pDTO == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of(&quot;message&quot;, &quot;존재하지 않는 게시글입니다.&quot;));
        }

        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);
        boolean isAuthorized = loginUser != null &amp;&amp; loginUser.getUserId().equals(pDTO.getUserId());

        return ResponseEntity.ok(Map.of(&quot;pDTO&quot;, pDTO, &quot;isAuthorized&quot;, isAuthorized));
    }
}</code></pre>
<h5 id="게시글-표시를-위한-jsp-페이지">게시글 표시를 위한 JSP 페이지</h5>
<p>JSP 페이지에서 <code>&lt;c:out&gt;</code> 태그를 사용하여 안전하게 데이터를 표시합니다.</p>
<pre><code class="language-jsp">&lt;%@ taglib uri=&quot;http://java.sun.com/jsp/jstl/core&quot; prefix=&quot;c&quot; %&gt;
&lt;%@ taglib uri=&quot;http://java.sun.com/jsp/jstl/fmt&quot; prefix=&quot;fmt&quot; %&gt;
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;게시글 상세보기&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;게시글 상세보기&lt;/h1&gt;
    &lt;div&gt;
        &lt;label&gt;제목:&lt;/label&gt;
        &lt;c:out value=&quot;${pDTO.postTitle}&quot; /&gt;
    &lt;/div&gt;
    &lt;div&gt;
        &lt;label&gt;작성자:&lt;/label&gt;
        &lt;c:out value=&quot;${pDTO.nickname}&quot; /&gt;
    &lt;/div&gt;
    &lt;div&gt;
        &lt;label&gt;내용:&lt;/label&gt;
        &lt;c:out value=&quot;${pDTO.postText}&quot; /&gt;
    &lt;/div&gt;
    &lt;div&gt;
        &lt;label&gt;작성일:&lt;/label&gt;
        &lt;fmt:formatDate value=&quot;${pDTO.createdDate}&quot; pattern=&quot;yyyy-MM-dd HH:mm:ss&quot; /&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h4 id="cout-태그-활용-시-주의사항"><code>&lt;c:out&gt;</code> 태그 활용 시 주의사항</h4>
<ol>
<li><strong>미리 이스케이핑된 데이터</strong>: 이미 이스케이핑된 데이터를 <code>&lt;c:out&gt;</code> 태그로 출력할 경우, HTML 이스케이핑이 중복되어 내용이 깨질 수 있습니다.</li>
<li><strong>보안에 대한 과도한 신뢰</strong>: <code>&lt;c:out&gt;</code> 태그만으로는 XSS 공격을 완전히 방지할 수 없기 때문에, 백엔드에서도 데이터 검증이 필요합니다.</li>
</ol>
<h4 id="마치며">마치며</h4>
<p>크로스 사이트 스크립팅(XSS) 공격은 웹 애플리케이션의 보안에 큰 위협이 될 수 있습니다. 이번 포스팅에서 소개한 <code>&lt;c:out&gt;</code> 태그는 JSP 기반의 웹 애플리케이션에서 XSS 공격을 방지하는 유용한 도구이지만, 완벽한 해결책은 아닙니다. 백엔드 로직에서도 데이터 검증 및 이스케이핑을 철저히 해야 하며, 콘텐츠 보안 정책(CSP) 등을 적극 활용하는 것도 추천드립니다.</p>
<p><strong>참고 자료</strong></p>
<ul>
<li><a href="https://docs.oracle.com/javaee/5/jstl/1.1/docs/tlddocs/c/out.html">JSTL <code>&lt;c:out&gt;</code> Tag Documentation</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[크로스 사이트 스크립팅(XSS) 방지 방법: 사례를 통해 알아보는 안전한 웹 애플리케이션 개발]]></title>
            <link>https://velog.io/@sujung_shin/%ED%81%AC%EB%A1%9C%EC%8A%A4-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8C%85XSS-%EB%B0%A9%EC%A7%80-%EB%B0%A9%EB%B2%95-%EC%82%AC%EB%A1%80%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EC%95%88%EC%A0%84%ED%95%9C-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B0%9C%EB%B0%9C-awxs5y8h</link>
            <guid>https://velog.io/@sujung_shin/%ED%81%AC%EB%A1%9C%EC%8A%A4-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8C%85XSS-%EB%B0%A9%EC%A7%80-%EB%B0%A9%EB%B2%95-%EC%82%AC%EB%A1%80%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EC%95%88%EC%A0%84%ED%95%9C-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B0%9C%EB%B0%9C-awxs5y8h</guid>
            <pubDate>Sat, 11 May 2024 14:00:54 GMT</pubDate>
            <description><![CDATA[<p>실제 코드를 통해 크로스 사이트 스크립팅(XSS) 공격을 방지하는 방법을 살펴보겠습니다. 구현한 <code>BoardAPIController</code> 코드를 통해 각 메서드에서 어떻게 XSS 공격을 막았는지 구체적으로 알아보겠습니다.</p>
<h4 id="xss-방지를-위한-boardapicontroller-소개">XSS 방지를 위한 <code>BoardAPIController</code> 소개</h4>
<p><code>BoardAPIController</code>는 게시글을 관리하기 위한 REST API 컨트롤러로, 게시글 생성, 수정, 삭제 기능을 제공합니다. 여기서 중요한 것은 모든 입력 데이터를 검증하고, 클라이언트 측 스크립트 삽입을 막는 것이죠. 우선 코드를 살펴보겠습니다.</p>
<pre><code class="language-java">package com.moonBam.controller.board;

import com.moonBam.dto.MemberDTO;
import com.moonBam.dto.board.PostDTO;
import com.moonBam.dto.board.PostPageDTO;
import com.moonBam.dto.board.PostUpdateRequestDTO;
import com.moonBam.service.PostService;
import com.moonBam.service.ScrapService;
import com.moonBam.service.member.MemberLoginService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/api/post&quot;)
public class BoardAPIController {

    private final PostService postService;
    private final MemberLoginService memberLoginService;
    private final ScrapService scrapService;

    @PostMapping
    public ResponseEntity&lt;?&gt; create(@RequestBody @Validated PostDTO postDTO,
                                    BindingResult bindingResult, Principal principal) {

        if (bindingResult.hasErrors()) {
            Map&lt;String, String&gt; errors = getErrors(bindingResult);
            String s = errors.values().stream().toList().get(0);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(Map.of(&quot;message&quot;, s, &quot;errors&quot;, errors));
        }

        // XSS 방지를 위해 입력값에서 스크립트 태그를 제거하는 로직 추가
        String sanitizedPostText = sanitizeHtml(postDTO.getPostText());
        String sanitizedPostTitle = sanitizeHtml(postDTO.getPostTitle());
        postDTO.setPostText(sanitizedPostText);
        postDTO.setPostTitle(sanitizedPostTitle);

        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);

        if (loginUser == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;로그인이 필요한 서비스입니다.&quot;));
        }

        postDTO.setUserId(loginUser.getUserId());
        postDTO.setNickname(loginUser.getNickname());
        Long postID = postService.save(postDTO);
        return ResponseEntity.ok(Map.of(&quot;postID&quot;, postID)); // 동적으로 ajax success function에서 redirect 시킬 용도
    }

    @PatchMapping(&quot;/{postId}&quot;)
    public ResponseEntity&lt;?&gt; update(@RequestBody @Validated PostUpdateRequestDTO postUpdateRequestDTO, @PathVariable(&quot;postId&quot;) Long postId,
                                    BindingResult bindingResult, Principal principal) {

        PostDTO postDTO = postService.findById(postId);

        if (bindingResult.hasErrors()) {
            Map&lt;String, String&gt; errors = getErrors(bindingResult);
            String s = errors.values().stream().toList().get(0);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(Map.of(&quot;message&quot;, s, &quot;errors&quot;, errors));
        }

        // XSS 방지를 위해 입력값에서 스크립트 태그를 제거하는 로직 추가
        String sanitizedPostText = sanitizeHtml(postUpdateRequestDTO.getPostText());
        postUpdateRequestDTO.setPostText(sanitizedPostText);
        postUpdateRequestDTO.setPostTitle(sanitizeHtml(postUpdateRequestDTO.getPostTitle()));

        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);

        if (loginUser == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;로그인이 필요한 서비스입니다.&quot;));
        }

        if (!loginUser.getUserId().equals(postDTO.getUserId())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;글을 수정할 권한이 없습니다.&quot;));
        }

        postService.update(postUpdateRequestDTO);

        return ResponseEntity.ok(Map.of(&quot;postID&quot;, postId));
    }

    @DeleteMapping(&quot;{postId}&quot;)
    public ResponseEntity&lt;?&gt; delete(@PathVariable(&quot;postId&quot;) Long postId, Principal principal) {
        PostDTO postDTO = postService.findById(postId);
        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);

        if (loginUser == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;로그인이 필요한 서비스입니다.&quot;));
        }

        if (postDTO == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of(&quot;message&quot;, &quot;글이 존재하지않습니다.&quot;));
        }

        if (!loginUser.getUserId().equals(postDTO.getUserId())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;글을 삭제할 권한이 없습니다.&quot;));
        }

        scrapService.findAllByPostId(postId).forEach((scrapDTO -&gt; scrapService.delete(scrapDTO.getScrapId())));

        postService.delete(postId);

        return ResponseEntity.ok(Map.of(&quot;message&quot;, &quot;삭제가 완료되었습니다.&quot;));
    }

    @GetMapping(&quot;/{postId}&quot;)
    public ResponseEntity&lt;?&gt; findOne(@PathVariable(&quot;postId&quot;) Long postId, Principal principal){
        PostPageDTO pDTO = postService.selectPagePost(postId);

        if (postId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(&quot;message&quot;, &quot;존재하지 않는 게시글입니다.&quot;));
        }

        MemberDTO loginUser = memberLoginService.findByPrincipal(principal);
        boolean isAuthorized = false;
        if (loginUser != null) {
            isAuthorized = loginUser.getUserId().equals(pDTO.getUserId());
        }
        return ResponseEntity.ok(Map.of(&quot;pDTO&quot;, pDTO, &quot;isAuthorized&quot;, isAuthorized));
    }

    private Map&lt;String, String&gt; getErrors(BindingResult bindingResult) {
        return bindingResult.getFieldErrors().stream()
                .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
    }

    private String sanitizeHtml(String input) {
        return input.replaceAll(&quot;(?i)&lt;script.*?&gt;.*?&lt;/script&gt;&quot;, &quot;&quot;) // 스크립트 태그 제거
                .replaceAll(&quot;(?i)&lt;.*?javascript:.*?&gt;.*?&lt;/.*?&gt;&quot;, &quot;&quot;) // &quot;javascript:&quot; URI 사용 제거
                .replaceAll(&quot;(?i)&lt;.*?\\bon.*?&gt;.*?&lt;/.*?&gt;&quot;, &quot;&quot;); // 이벤트 핸들러 제거 (예: onclick)
    }
}</code></pre>
<h4 id="xss-방지-구현-방법-상세-설명">XSS 방지 구현 방법 상세 설명</h4>
<p><code>BoardAPIController</code>는 XSS 공격을 방지하기 위해 입력값을 처리하는 여러 가지 로직을 포함하고 있습니다.</p>
<h5 id="1-스크립트-태그-제거">1. 스크립트 태그 제거</h5>
<p>가장 기본적인 XSS 방지 기법은 <code>&lt;script&gt;</code> 태그를 제거하는 것입니다. 이를 위해 정규 표현식을 활용하여 <code>&lt;script&gt;</code> 태그를 감지하고 제거합니다.</p>
<pre><code class="language-java">input.replaceAll(&quot;(?i)&lt;script.*?&gt;.*?&lt;/script&gt;&quot;, &quot;&quot;)</code></pre>
<ul>
<li><code>(?i)</code>: 대소문자 무시</li>
<li><code>&lt;script.*?&gt;</code>: <code>&lt;script&gt;</code> 태그를 열고, 속성이 있을 수 있으므로 <code>.*?</code>로 처리</li>
<li><code>.*?&lt;/script&gt;</code>: <code>&lt;script&gt;</code> 태그 사이의 모든 내용을 매칭하고, 닫는 <code>&lt;/script&gt;</code> 태그까지 감지</li>
</ul>
<h5 id="2-javascript-uri-사용-제거">2. &quot;javascript:&quot; URI 사용 제거</h5>
<p>HTML 태그의 <code>href</code> 속성 등에서 <code>javascript:</code>를 통해 악성 코드를 실행할 수 있으므로 이를 제거합니다.</p>
<pre><code class="language-java">input.replaceAll(&quot;(?i)&lt;.*?javascript:.*?&gt;.*?&lt;/.*?&gt;&quot;, &quot;&quot;)</code></pre>
<ul>
<li><code>&lt;.*?javascript:.*?&gt;</code>: HTML 태그 속성에서 <code>javascript:</code> URI를 감지</li>
<li><code>.*?&lt;/.*?&gt;</code>: 태그 내의 내용을 감지하고 태그 닫기</li>
</ul>
<h5 id="3-이벤트-핸들러-제거-예-onclick">3. 이벤트 핸들러 제거 (예: <code>onclick</code>)</h5>
<p>태그에 <code>onclick</code> 같은 이벤트 핸들러를 이용해 악성 코드를 실행할 수 있습니다. 이를 제거하기 위해 <code>\\bon</code> 패턴을 사용합니다.</p>
<pre><code class="language-java">input.replaceAll(&quot;(?i)&lt;.*?\\bon.*?&gt;.*?&lt;/.*?&gt;&quot;, &quot;&quot;)</code></pre>
<ul>
<li><code>\\bon</code>: 이벤트 핸들러 속성(<code>on...</code>)을 감지</li>
<li><code>.*?&gt;</code>: HTML 태그의 끝까지 감지</li>
<li><code>.*?&lt;/.*?&gt;</code>: 태그 내의 내용을 감지하고 태그 닫기</li>
</ul>
<h4 id="마치며">마치며</h4>
<p>이번 포스팅에서는 <code>BoardAPIController</code>의 코드를 통해 크로스 사이트 스크립팅(XSS) 공격을 방지하는 방법을 살펴보았습니다. 실제 구현을 통해 알 수 있듯이, 각 입력 데이터마다 철저한 검증과 이스케이핑을 적용하여야만 안전한 웹 애플리케이션을 구축할 수 있습니다. </p>
<p><strong>참고 자료</strong></p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">Mozilla Developer Network: Content Security Policy</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[크로스 사이트 스크립팅(XSS) 알아보기: 유형 및 방지 방법]]></title>
            <link>https://velog.io/@sujung_shin/%ED%81%AC%EB%A1%9C%EC%8A%A4-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8C%85XSS-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-%EC%9C%A0%ED%98%95-%EB%B0%8F-%EB%B0%A9%EC%A7%80-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@sujung_shin/%ED%81%AC%EB%A1%9C%EC%8A%A4-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8C%85XSS-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-%EC%9C%A0%ED%98%95-%EB%B0%8F-%EB%B0%A9%EC%A7%80-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 10 May 2024 03:38:40 GMT</pubDate>
            <description><![CDATA[<p>크로스 사이트 스크립팅(XSS, Cross-Site Scripting)은 웹 애플리케이션 보안 취약점 중 하나로, 공격자가 악의적인 스크립트를 웹 페이지에 삽입하여 사용자의 브라우저에서 실행되도록 만드는 것을 의미합니다. 이를 통해 공격자는 사용자의 쿠키, 세션 정보, 웹 페이지 콘텐츠 등을 탈취하거나, 피싱 페이지로 리다이렉션하는 등의 공격을 할 수 있습니다.</p>
<h3 id="xss-공격-유형">XSS 공격 유형</h3>
<p>XSS는 크게 세 가지 유형으로 분류됩니다.</p>
<ol>
<li><p><strong>반사형(Reflected) XSS:</strong></p>
<ul>
<li>웹 애플리케이션이 사용자로부터 입력을 받아 처리한 후, 입력값을 그대로 다시 웹 페이지에 표시하는 경우에 발생합니다.</li>
<li>일반적으로 악의적인 스크립트는 URL이나 폼 데이터를 통해 전달됩니다.</li>
<li>피해자가 해당 악성 링크를 클릭해야 공격이 실행됩니다.</li>
</ul>
</li>
<li><p><strong>저장형(Persistent) XSS:</strong></p>
<ul>
<li>웹 애플리케이션이 사용자로부터 입력받은 데이터를 데이터베이스에 저장하고 이후 웹 페이지에 표시하는 경우에 발생합니다.</li>
<li>공격 스크립트는 다른 사용자가 해당 페이지에 접근할 때마다 실행됩니다.</li>
<li>주로 게시판, 댓글, 프로필 정보 등 사용자 콘텐츠를 저장하는 부분에서 발생합니다.</li>
</ul>
</li>
<li><p><strong>DOM 기반(DOM-based) XSS:</strong></p>
<ul>
<li>웹 페이지의 클라이언트 측 코드(자바스크립트)가 사용자 입력값을 적절히 처리하지 못해 발생하는 공격입니다.</li>
<li>서버 측에 요청이 전달되지 않고 클라이언트에서만 발생하므로, 서버에서 필터링을 거쳐도 취약할 수 있습니다.</li>
</ul>
</li>
</ol>
<h3 id="xss-공격-방지-방법">XSS 공격 방지 방법</h3>
<ol>
<li><p><strong>입력 데이터 검증 및 필터링:</strong></p>
<ul>
<li>모든 입력 데이터를 적절히 검증하고 필터링해야 합니다.</li>
<li>HTML 이스케이프를 적용하여 태그를 비활성화해야 합니다.</li>
</ul>
</li>
<li><p><strong>콘텐츠 보안 정책(Content Security Policy, CSP):</strong></p>
<ul>
<li>브라우저가 특정한 스크립트만 실행하도록 지시하는 HTTP 헤더를 설정합니다.</li>
<li><code>Content-Security-Policy</code> 헤더를 통해 허용된 출처만 스크립트 실행이 가능하도록 제한합니다.</li>
</ul>
</li>
<li><p><strong>출력 데이터 이스케이핑:</strong></p>
<ul>
<li>모든 출력 데이터에 대해 HTML 이스케이핑을 적용해야 합니다.</li>
<li>특히 JavaScript나 CSS에서 사용되는 값은 각 상황에 맞게 적절한 이스케이핑이 필요합니다.</li>
</ul>
</li>
<li><p><strong>HttpOnly 및 Secure 쿠키 사용:</strong></p>
<ul>
<li>쿠키에 HttpOnly 속성을 적용하여 자바스크립트로 쿠키에 접근할 수 없게 합니다.</li>
<li>Secure 속성을 사용하여 HTTPS 연결을 통해서만 쿠키가 전송되도록 합니다.</li>
</ul>
</li>
<li><p><strong>라이브러리 사용:</strong></p>
<ul>
<li>OWASP의 ESAPI, Microsoft의 AntiXSS Library 등 검증된 라이브러리를 사용하여 XSS 공격을 방지할 수 있습니다.</li>
</ul>
</li>
</ol>
<p>XSS 취약점은 웹 애플리케이션의 구조에 따라 다양한 방식으로 발생할 수 있으므로, 개발자는 입력 및 출력 데이터에 대해 적절한 보안 처리를 해야 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[병렬 프로세싱을 통한 API 사용의 장점과 주의할 점]]></title>
            <link>https://velog.io/@sujung_shin/%EB%B3%91%EB%A0%AC-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8B%B1%EC%9D%84-%ED%86%B5%ED%95%9C-API-%EC%82%AC%EC%9A%A9%EC%9D%98-%EC%9E%A5%EC%A0%90%EA%B3%BC-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90</link>
            <guid>https://velog.io/@sujung_shin/%EB%B3%91%EB%A0%AC-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8B%B1%EC%9D%84-%ED%86%B5%ED%95%9C-API-%EC%82%AC%EC%9A%A9%EC%9D%98-%EC%9E%A5%EC%A0%90%EA%B3%BC-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90</guid>
            <pubDate>Thu, 09 May 2024 14:06:44 GMT</pubDate>
            <description><![CDATA[<p>API를 활용한 개발 과정에서 병렬 프로세싱(Parallel Processing)은 효율성과 성능 향상을 위한 핵심 기술입니다. 이 글에서는 API 사용 시 병렬 프로세싱의 장점과 주의해야 할 점을 정리하겠습니다.</p>
<h4 id="목차">목차</h4>
<ol>
<li>병렬 프로세싱이란?</li>
<li>API 사용 시 병렬 프로세싱의 장점<ul>
<li>성능 개선</li>
<li>비용 최적화</li>
<li>사용자 경험 개선</li>
</ul>
</li>
<li>API 사용 시 병렬 프로세싱의 주의할 점<ul>
<li>상태 관리</li>
<li>스레드 안전성</li>
<li>API Rate Limit</li>
<li>에러 핸들링</li>
<li>의존성 관리</li>
</ul>
</li>
<li>결론</li>
</ol>
<h4 id="1-병렬-프로세싱이란">1. 병렬 프로세싱이란?</h4>
<p>병렬 프로세싱은 여러 프로세스나 스레드가 동시에 작업을 처리하는 방식입니다. 이를 통해 다수의 작업을 동시에 수행하여 전반적인 실행 시간을 단축할 수 있습니다. 특히 API를 호출하는 작업에서는 이 방식이 효과적입니다.</p>
<h4 id="2-api-사용-시-병렬-프로세싱의-장점">2. API 사용 시 병렬 프로세싱의 장점</h4>
<p><strong>성능 개선</strong><br>병렬로 여러 API를 동시에 호출하여 작업을 분산 처리하면, 전체 처리 시간이 크게 단축됩니다. 예를 들어, API 호출에 각각 2초가 소요되는 5개의 작업을 순차적으로 처리한다면 10초가 필요하지만, 병렬로 처리하면 최대 2초 내에 모든 작업이 완료됩니다.</p>
<p><strong>비용 최적화</strong><br>클라우드 인프라의 비용을 최적화할 수 있습니다. 병렬 프로세싱을 활용하면 리소스 사용률을 높여 보다 적은 인스턴스로 더 많은 작업을 처리할 수 있습니다.</p>
<p><strong>사용자 경험 개선</strong><br>동시에 여러 데이터를 처리함으로써 응답 시간을 줄여 사용자 경험을 향상시킬 수 있습니다. 이는 특히 대용량 데이터를 필요로 하는 서비스에서 중요한 요소입니다.</p>
<h4 id="3-api-사용-시-병렬-프로세싱의-주의할-점">3. API 사용 시 병렬 프로세싱의 주의할 점</h4>
<p><strong>상태 관리</strong><br>병렬 프로세싱은 각 스레드나 프로세스가 독립적으로 실행되기 때문에 공유 자원에 접근하는 경우 적절한 상태 관리를 해야 합니다. 상태 동기화에 문제가 발생하면 데이터 불일치나 충돌이 일어날 수 있습니다.</p>
<p><strong>스레드 안전성</strong><br>멀티스레드 환경에서는 스레드 간 데이터 경쟁 상태(Race Condition)가 발생할 수 있습니다. 이를 방지하기 위해서는 스레드 안전한 데이터 구조를 사용하거나 동기화 메커니즘을 도입해야 합니다.</p>
<p><strong>API Rate Limit</strong><br>API 호출이 일정량을 초과하면 서비스 제공자가 정해 놓은 Rate Limit에 도달하여 호출이 차단될 수 있습니다. 이를 방지하기 위해 요청 속도 조절, 재시도 로직, 백오프(Backoff) 전략 등을 구현해야 합니다.</p>
<p><strong>에러 핸들링</strong><br>병렬로 호출되는 API 중 하나라도 실패하면 전체 작업에 영향을 미칠 수 있습니다. 이를 방지하기 위해 에러를 개별적으로 처리하고 재시도 로직을 구현하는 것이 중요합니다.</p>
<p><strong>의존성 관리</strong><br>병렬 프로세싱 작업 간에 의존성이 있는 경우, 이를 잘 설계하지 않으면 의도치 않은 동작이 발생할 수 있습니다. 작업 순서에 의존성이 있는 경우, 순서를 명확히 정의하거나 의존성을 제거해야 합니다.</p>
<h4 id="4-결론">4. 결론</h4>
<p>병렬 프로세싱은 API를 효과적으로 활용하여 성능과 효율성을 향상시키는 데 필수적인 기술입니다. 하지만 동시에 주의해야 할 점이 많습니다. 올바른 상태 관리, 스레드 안전성 확보, Rate Limit 대응, 에러 핸들링 등 다양한 요소를 고려하여 안전하고 효율적인 병렬 프로세싱을 구현하세요. 이를 통해 API 사용의 이점을 최대한 활용할 수 있을 것입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 영속성 컨텍스트 이해하기]]></title>
            <link>https://velog.io/@sujung_shin/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sujung_shin/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 08 May 2024 05:52:16 GMT</pubDate>
            <description><![CDATA[<p>JPA(Java Persistence API)의 핵심 개념 중 하나인 <strong>영속성 컨텍스트</strong>에 대해 알아보겠습니다. 영속성 컨텍스트는 JPA를 사용한 애플리케이션 개발에서 매우 중요한 역할을 하기 때문에, 그 원리와 특징을 잘 이해하는 것이 필요합니다. </p>
<h2 id="1-영속성-컨텍스트란">1. 영속성 컨텍스트란?</h2>
<p><strong>영속성 컨텍스트</strong>는 간단히 말해 <strong>엔티티(Entity)</strong>를 관리하는 일종의 <strong>캐시(Cache)</strong>입니다. 이를 통해 엔티티를 데이터베이스와 매핑하며, 변경된 사항을 추적 및 동기화합니다. </p>
<p>공식적인 정의로는 &quot;엔티티를 영구 저장소(Database)에 보관하는 환경&quot;을 의미합니다. 다시 말해, <strong>데이터베이스의 데이터를 자바 객체로 관리하는 가상의 데이터베이스</strong>라고 생각할 수 있습니다.</p>
<h2 id="2-영속성-컨텍스트의-생명주기">2. 영속성 컨텍스트의 생명주기</h2>
<p>엔티티는 영속성 컨텍스트 내에서 다음과 같은 네 가지 상태를 가집니다.</p>
<h3 id="1-비영속-transient-상태">1. 비영속 (Transient) 상태</h3>
<ul>
<li>영속성 컨텍스트와 연관되지 않은 엔티티 상태를 말합니다.</li>
<li>데이터베이스에 저장되지 않고, 단순히 새로운 객체로 생성된 상태입니다.</li>
<li>예시:<pre><code class="language-java">Member member = new Member();
member.setName(&quot;John Doe&quot;);</code></pre>
</li>
</ul>
<h3 id="2-영속-persistent-상태">2. 영속 (Persistent) 상태</h3>
<ul>
<li><p>엔티티 매니저를 통해 영속성 컨텍스트에 저장된 상태를 의미합니다.</p>
</li>
<li><p>이 상태에 들어가면 영속성 컨텍스트에서 해당 엔티티를 관리합니다.</p>
</li>
<li><p>예시:</p>
<pre><code class="language-java">EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();

Member member = new Member();
member.setName(&quot;John Doe&quot;);
em.persist(member);

em.getTransaction().commit();</code></pre>
</li>
</ul>
<h3 id="3-준영속-detached-상태">3. 준영속 (Detached) 상태</h3>
<ul>
<li>한 번 영속 상태였던 엔티티가 영속성 컨텍스트에서 분리된 상태를 의미합니다.</li>
<li>영속성 컨텍스트에서 관리되지 않기 때문에 변경 사항이 자동으로 데이터베이스에 반영되지 않습니다.</li>
<li>예시:<pre><code class="language-java">Member member = em.find(Member.class, 1L);
em.detach(member); // 영속성 컨텍스트에서 분리</code></pre>
</li>
</ul>
<h3 id="4-삭제-removed-상태">4. 삭제 (Removed) 상태</h3>
<ul>
<li>영속성 컨텍스트에서 제거된 엔티티 상태를 의미합니다.</li>
<li>엔티티 매니저를 통해 영속성 컨텍스트에서 삭제된 엔티티는 데이터베이스에서도 삭제됩니다.</li>
<li>예시:<pre><code class="language-java">Member member = em.find(Member.class, 1L);
em.remove(member); // 영속성 컨텍스트에서 제거</code></pre>
</li>
</ul>
<h2 id="3-영속성-컨텍스트의-특징">3. 영속성 컨텍스트의 특징</h2>
<h3 id="1-1차-캐시">1. 1차 캐시</h3>
<ul>
<li>영속성 컨텍스트는 엔티티를 메모리에 저장하는 1차 캐시를 제공합니다.</li>
<li>동일한 영속성 컨텍스트에서 동일한 엔티티를 조회하면, 데이터베이스를 거치지 않고 1차 캐시에서 조회합니다.</li>
<li>예시:<pre><code class="language-java">Member member1 = em.find(Member.class, 1L);
Member member2 = em.find(Member.class, 1L);
// member1과 member2는 같은 객체를 가리킴</code></pre>
</li>
</ul>
<h3 id="2-동일성identity-보장">2. 동일성(identity) 보장</h3>
<ul>
<li>1차 캐시로 인해 영속성 컨텍스트 내에서 조회한 엔티티는 동일성을 보장합니다.</li>
<li>동일한 엔티티를 여러 번 조회해도 동일한 자바 객체가 반환됩니다.</li>
</ul>
<h3 id="3-변경-감지dirty-checking">3. 변경 감지(Dirty Checking)</h3>
<ul>
<li>영속 상태의 엔티티 변경 사항은 자동으로 감지되어 데이터베이스에 반영됩니다.</li>
<li>영속성 컨텍스트에 의해 관리되기 때문에 가능한 기능입니다.</li>
<li>예시:<pre><code class="language-java">Member member = em.find(Member.class, 1L);
member.setName(&quot;Jane Doe&quot;); // 변경 감지
em.getTransaction().commit(); // 변경사항이 데이터베이스에 반영됨</code></pre>
</li>
</ul>
<h3 id="4-지연-쓰기write-behind">4. 지연 쓰기(Write-Behind)</h3>
<ul>
<li>트랜잭션이 끝날 때까지 변경된 엔티티를 모아두고, 트랜잭션이 커밋될 때 한꺼번에 데이터베이스에 반영합니다.</li>
<li>이를 통해 다수의 데이터베이스 접근을 최적화할 수 있습니다.</li>
</ul>
<h3 id="5-지연-로딩lazy-loading">5. 지연 로딩(Lazy Loading)</h3>
<ul>
<li>영속성 컨텍스트는 연관된 엔티티를 필요할 때까지 로딩하지 않고 지연 로딩으로 관리할 수 있습니다.</li>
<li>필요한 시점에 데이터베이스에서 로딩하여 성능 최적화가 가능합니다.</li>
</ul>
<h2 id="마무리">마무리</h2>
<p>JPA에서 영속성 컨텍스트는 데이터베이스와 자바 객체 사이를 매끄럽게 연결해주는 핵심 역할을 합니다. 이를 통해 효율적인 데이터 관리와 변경 추적이 가능해지며, 개발자의 생산성을 높여줍니다.</p>
<h3 id="참고-자료">참고 자료</h3>
<ul>
<li><a href="https://spring.io/projects/spring-data-jpa">Spring Data JPA 공식 문서</a></li>
<li><a href="https://hibernate.org/documentation">Hibernate 공식 문서</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot와 JPA에서 발생한 'Field 'id' doesn't have a default value' 오류 해결 방법]]></title>
            <link>https://velog.io/@sujung_shin/Spring-Boot%EC%99%80-JPA%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%9C-Field-id-doesnt-have-a-default-value-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@sujung_shin/Spring-Boot%EC%99%80-JPA%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%9C-Field-id-doesnt-have-a-default-value-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Tue, 07 May 2024 14:13:29 GMT</pubDate>
            <description><![CDATA[<h3 id="서론">서론</h3>
<p>Spring Boot와 JPA를 사용하여 애플리케이션을 개발하는 과정에서 <code>Field &#39;id&#39; doesn&#39;t have a default value</code> 오류를 만나는 경우가 종종 있습니다. 이 오류는 자동 증가 키가 제대로 설정되지 않은 경우 발생합니다. 이번 블로그에서는 이 오류의 원인과 해결 방법을 함께 살펴보겠습니다.</p>
<h3 id="문제-상황">문제 상황</h3>
<p>회원가입 기능을 구현하는 과정에서, 다음과 같은 <code>User</code> 엔티티를 작성했습니다.</p>
<pre><code class="language-java">package com.nado.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

import java.util.HashSet;
import java.util.Set;

@Entity
@Getter
@Setter
public class User {

    @Id
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false, unique = true)
    private String nickname;

    @Enumerated(EnumType.STRING)
    private Status status;

    @Enumerated(EnumType.STRING)
    private Role role;

    @Column(nullable = false)
    private Boolean emailVerified;

    public enum Status {
        ACTIVE, INACTIVE, BANNED
    }

    public enum Role {
        USER, ADMIN
    }

    @OneToMany(mappedBy = &quot;user&quot;)
    private Set&lt;Post&gt; posts = new HashSet&lt;&gt;();

    @ManyToMany
    @JoinTable(
            name = &quot;user_followers&quot;,
            joinColumns = {@JoinColumn(name = &quot;user_id&quot;)},
            inverseJoinColumns = {@JoinColumn(name = &quot;follower_id&quot;)}
    )
    private Set&lt;User&gt; followers = new HashSet&lt;&gt;();

    @ManyToMany(mappedBy = &quot;followers&quot;)
    private Set&lt;User&gt; following = new HashSet&lt;&gt;();
}</code></pre>
<p><strong><code>application.properties</code> 설정</strong></p>
<pre><code class="language-properties">spring.jpa.hibernate.ddl-auto=update</code></pre>
<p>이 상태에서 회원가입을 테스트하기 위해 데이터를 제출했을 때 다음과 같은 오류 메시지가 나타났습니다.</p>
<pre><code>2024-05-07T20:28:09.046+09:00 ERROR 496 --- [nado] [nio-8090-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.orm.jpa.JpaSystemException: could not execute statement [Field &#39;id&#39; doesn&#39;t have a default value] [insert into user (email,email_verified,nickname,password,role,status) values (?,?,?,?,?,?)]] with root cause</code></pre><h3 id="오류-원인">오류 원인</h3>
<ol>
<li><p><strong>오류 메시지</strong>: <code>Field &#39;id&#39; doesn&#39;t have a default value</code></p>
<ul>
<li><code>org.springframework.orm.jpa.JpaSystemException: could not execute statement [Field &#39;id&#39; doesn&#39;t have a default value] [insert into user (email,email_verified,nickname,password,role,status) values (?,?,?,?,?,?)]]</code></li>
<li>이 오류는 <code>id</code> 필드에 기본값이 없다는 것을 나타냅니다.</li>
</ul>
</li>
<li><p><strong>원인 분석</strong></p>
<ul>
<li>엔티티의 <code>id</code> 필드에 <code>@GeneratedValue</code> 어노테이션이 없거나, 자동 증가 전략이 올바르게 설정되어 있지 않아 데이터베이스에 기본 키 값이 없는 상태로 삽입하려 할 때 발생하는 문제입니다.</li>
</ul>
</li>
</ol>
<h3 id="해결-방법">해결 방법</h3>
<ol>
<li><strong><code>@GeneratedValue</code> 어노테이션 추가</strong><ul>
<li>자동 증가 키를 설정하기 위해 <code>@GeneratedValue(strategy = GenerationType.IDENTITY)</code> 어노테이션을 추가했습니다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;</code></pre>
<ol start="2">
<li><p><strong>문제 여전히 발생</strong></p>
<ul>
<li>하지만 <code>@GeneratedValue(strategy = GenerationType.IDENTITY)</code>을 추가한 후에도 같은 오류가 발생했습니다.</li>
<li>이 문제는 <code>application.properties</code>에서 <code>spring.jpa.hibernate.ddl-auto=update</code> 설정이 되어 있기 때문에 발생했습니다.</li>
<li>이 설정은 기존 데이터베이스 스키마를 수정하는 방식으로 동작하지만, 기본 키 생성 전략을 업데이트하지 않습니다.</li>
</ul>
</li>
<li><p><strong>해결 절차</strong></p>
<ul>
<li>먼저 <code>application.properties</code>에서 <code>spring.jpa.hibernate.ddl-auto</code>를 <code>create-drop</code>으로 변경하여 테이블 구조를 재생성했습니다.</li>
</ul>
</li>
</ol>
<pre><code class="language-properties">spring.jpa.hibernate.ddl-auto=create-drop</code></pre>
<ul>
<li>변경된 <code>User</code> 엔티티는 다음과 같습니다.</li>
</ul>
<pre><code class="language-java">package com.nado.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

import java.util.HashSet;
import java.util.Set;

@Entity
@Getter
@Setter
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false, unique = true)
    private String nickname;

    @Enumerated(EnumType.STRING)
    private Status status;

    @Enumerated(EnumType.STRING)
    private Role role;

    @Column(nullable = false)
    private Boolean emailVerified;

    public enum Status {
        ACTIVE, INACTIVE, BANNED
    }

    public enum Role {
        USER, ADMIN
    }

    @OneToMany(mappedBy = &quot;user&quot;)
    private Set&lt;Post&gt; posts = new HashSet&lt;&gt;();

    @ManyToMany
    @JoinTable(
            name = &quot;user_followers&quot;,
            joinColumns = {@JoinColumn(name = &quot;user_id&quot;)},
            inverseJoinColumns = {@JoinColumn(name = &quot;follower_id&quot;)}
    )
    private Set&lt;User&gt; followers = new HashSet&lt;&gt;();

    @ManyToMany(mappedBy = &quot;followers&quot;)
    private Set&lt;User&gt; following = new HashSet&lt;&gt;();
}</code></pre>
<ul>
<li>이 상태에서 서버를 실행하면 테이블이 새로 생성됩니다.</li>
<li>이후 다시 <code>application.properties</code>에서 <code>spring.jpa.hibernate.ddl-auto</code> 설정을 <code>update</code>로 변경했습니다.</li>
</ul>
<pre><code class="language-properties">spring.jpa.hibernate.ddl-auto=update</code></pre>
<h3 id="최종-결과">최종 결과</h3>
<p>이제 회원가입 테스트를 했을 때 오류 없이 실행되었습니다.</p>
<h3 id="정리">정리</h3>
<p><code>Field &#39;id&#39; doesn&#39;t have a default value</code> 오류를 해결하기 위해서는 다음 사항을 확인해야 합니다.</p>
<ol>
<li><code>@GeneratedValue(strategy = GenerationType.IDENTITY)</code> 어노테이션을 추가하여 자동 증가 키를 올바르게 설정합니다.</li>
<li><code>application.properties</code>에서 <code>spring.jpa.hibernate.ddl-auto=create-drop</code> 또는 <code>create</code> 설정을 사용하여 테이블을 처음부터 생성하거나 업데이트합니다.<ul>
<li><code>create-drop</code>: 애플리케이션이 시작할 때 테이블을 생성하고, 종료 시 테이블을 삭제합니다.</li>
<li><code>create</code>: 애플리케이션이 시작할 때 테이블을 생성합니다.</li>
<li><code>update</code>: 기존 테이블을 수정하지만, 기본 키 생성 전략을 변경하지는 않습니다.</li>
</ul>
</li>
</ol>
<p>이 포스트에서는 Spring Boot와 JPA를 사용해 엔티티의 기본 키를 자동 증가 키로 설정하고, 이와 관련된 오류를 해결하는 방법을 알아보았습니다. 앞으로 데이터베이스 설계 시 자동 증가 키를 활용해야 하는 경우, 반드시 <code>@GeneratedValue</code> 어노테이션을 함께 사용하세요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[`application-dev.properties` 이해하기: 왜 사용하며 어떻게 활용하는가?]]></title>
            <link>https://velog.io/@sujung_shin/application-dev.properties-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B0-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%99%9C%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@sujung_shin/application-dev.properties-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B0-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%99%9C%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Mon, 06 May 2024 11:38:21 GMT</pubDate>
            <description><![CDATA[<p>소프트웨어 개발이 점점 더 복잡해지는 시대에서 환경별 구성은 애플리케이션을 개발, 테스트, 프로덕션 단계에 원활하게 배포하기 위해 매우 중요합니다. 스프링 부트는 유연한 구성 방식을 통해 이를 간소화해주는데, 그 중 하나가 <code>application-dev.properties</code> 파일입니다.</p>
<p>이번 글에서는 <code>application-dev.properties</code> 파일이 왜 필요한지, 그리고 어떻게 효과적으로 사용할 수 있는지 알아보겠습니다.</p>
<h2 id="왜-application-devproperties를-사용해야-할까">왜 <code>application-dev.properties</code>를 사용해야 할까?</h2>
<ol>
<li><p><strong>환경별 설정:</strong></p>
<ul>
<li>애플리케이션은 일반적으로 개발, 테스트, 프로덕션 환경에 따라 다른 설정이 필요합니다.</li>
<li>예를 들어 데이터베이스 URL, 로깅 레벨, 서버 포트 등은 환경별로 달라질 수 있습니다.</li>
<li>구성 파일을 <code>application-dev.properties</code>와 같이 환경별로 분리함으로써 각 환경에 고유한 설정을 쉽게 분리할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>간소화된 개발:</strong></p>
<ul>
<li>개발 단계에서는 디버깅이나 상세 로깅 같은 기능을 활성화해야 할 수 있지만, 이는 프로덕션 환경에서는 바람직하지 않습니다.</li>
<li><code>application-dev.properties</code> 파일을 사용하면 이런 기능들을 다른 환경에 영향을 주지 않고 편리하게 구성할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>일관된 프로필 관리:</strong></p>
<ul>
<li>스프링 부트의 프로필 기능을 통해 각 환경별 설정을 <code>application-dev.properties</code>, <code>application-prod.properties</code> 등으로 분리할 수 있습니다.</li>
<li>이를 통해 환경별 설정을 일관되게 유지하고 애플리케이션의 배포 및 관리를 간소화할 수 있습니다.</li>
</ul>
</li>
</ol>
<h2 id="application-devproperties-사용-방법"><code>application-dev.properties</code> 사용 방법</h2>
<ol>
<li><p><strong>파일 생성 및 위치:</strong></p>
<ul>
<li><code>src/main/resources</code> 경로에 <code>application-dev.properties</code> 파일을 생성합니다.</li>
</ul>
</li>
<li><p><strong>프로필 활성화:</strong></p>
<ul>
<li>개발 환경에서만 해당 설정 파일을 적용하려면 <code>application.properties</code> 또는 <code>application.yaml</code> 파일에 다음과 같이 명시합니다:<pre><code class="language-properties">spring.profiles.active=dev</code></pre>
</li>
<li>또는 애플리케이션 실행 시 커맨드라인에서 다음과 같이 지정할 수 있습니다:<pre><code class="language-bash">java -jar myapp.jar --spring.profiles.active=dev</code></pre>
</li>
</ul>
</li>
<li><p><strong>개발 환경에 맞는 설정 추가:</strong></p>
<ul>
<li>예를 들어 개발 환경에서는 서버 포트를 8090으로, 활성화할 프로필을 <code>dev</code>로 설정하고자 한다면, <code>application-dev.properties</code> 파일에 다음과 같이 작성합니다:<pre><code class="language-properties">spring.application.name=nado
server.port=8090
spring.profiles.active=dev</code></pre>
</li>
</ul>
</li>
</ol>
<h2 id="결론">결론</h2>
<p>환경별 설정은 애플리케이션을 여러 단계에 배포할 때 필수적인 요소입니다. <code>application-dev.properties</code> 파일을 활용하여 개발 환경에 적합한 설정을 명확히 분리하면 개발 과정이 한층 더 수월해지고 배포 및 유지보수가 쉬워집니다. </p>
]]></description>
        </item>
    </channel>
</rss>