<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jade_57.log</title>
        <link>https://velog.io/</link>
        <description>끊임없는 에너지를 공유하는 핫스팟 같은 개발자 최준서입니다!</description>
        <lastBuildDate>Fri, 20 Feb 2026 05:52:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jade_57.log</title>
            <url>https://velog.velcdn.com/images/jade_57/profile/38efaf0e-a21c-4cd0-b3fe-09e5681ac645/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jade_57.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jade_57" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[자바스크립트 ES6 핵심 변화]]></title>
            <link>https://velog.io/@jade_57/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-ES6-%ED%95%B5%EC%8B%AC-%EB%B3%80%ED%99%94</link>
            <guid>https://velog.io/@jade_57/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-ES6-%ED%95%B5%EC%8B%AC-%EB%B3%80%ED%99%94</guid>
            <pubDate>Fri, 20 Feb 2026 05:52:17 GMT</pubDate>
            <description><![CDATA[<h2 id="자바스크립트-es6-핵심-변화-요약-및-코드-예시">자바스크립트 ES6 핵심 변화 요약 및 코드 예시</h2>
<h2 id="1-es6-도입-배경">1. ES6 도입 배경</h2>
<p>기존 자바스크립트는 복잡한 웹 애플리케이션을 개발하기에 구조적인 한계가 있었음. 이를 해결하고 현대적인 개발 환경을 구축하기 위해 대규모 업데이트가 진행됨. 특히 신입 개발자 기술 면접이나 리액트(React) 같은 모던 프론트엔드 프레임워크를 다룰 때 반드시 알아야 하는 핵심 기반임.</p>
<ul>
<li><strong>변수 관리 문제:</strong> 엉성한 유효 범위(Scope)로 인해 예측 불가능한 버그 발생.</li>
<li><strong>콜백 지옥 (Callback Hell):</strong> 비동기 작업 처리 시 코드가 심하게 중첩됨.</li>
<li><strong>모듈 시스템 부재:</strong> 코드를 여러 파일로 쪼개고 관리하는 표준 방법이 없었음.</li>
</ul>
<hr>
<h2 id="2-핵심-변화-8가지-코드-예시-포함">2. 핵심 변화 8가지 (코드 예시 포함)</h2>
<h3 id="①-let-const-도입-블록-스코프">① <code>let</code>, <code>const</code> 도입 (블록 스코프)</h3>
<p>기존 <code>var</code>는 함수 단위 스코프를 가져서 제어가 어렵고 재선언이 가능해 버그의 주범이었음. ES6부터는 블록 단위 <code>{}</code> 스코프를 가지는 <code>let</code>과 <code>const</code>가 도입됨.</p>
<ul>
<li><code>let</code>: 재선언 불가, 값 변경 가능. (값이 바뀔 변수에 사용)</li>
<li><code>const</code>: 재선언 불가, 값 변경 불가. (상수 선언에 사용, 기본적으로 가장 많이 권장됨)</li>
</ul>
<pre><code class="language-javascript">// Before (var)
var a = 1;
var a = 2; // 에러 안 남 (의도치 않은 덮어쓰기 위험)

// After (let, const)
let b = 1;
b = 2; // 값 변경 가능

const c = 1;
// c = 2; // TypeError 발생 (상수는 변경 불가)</code></pre>
<h3 id="②-화살표-함수-arrow-function">② 화살표 함수 (Arrow Function)</h3>
<p>함수 선언 문법을 간결하게 만들고, 기존 함수의 복잡한 <code>this</code> 바인딩 문제를 해결함. 자신만의 <code>this</code>를 생성하지 않고 상위 스코프의 <code>this</code>를 그대로 유지하기 때문에 콜백 함수로 넘길 때 매우 유용함.</p>
<pre><code class="language-javascript">// Before
const addES5 = function(a, b) {
  return a + b;
};

// After
const addES6 = (a, b) =&gt; a + b; // 실행문이 한 줄이면 중괄호와 return 생략 가능</code></pre>
<h3 id="③-템플릿-리터럴-template-literal">③ 템플릿 리터럴 (Template Literal)</h3>
<p>백틱(<code>`</code>)을 사용하여 문자열과 변수 결합을 직관적으로 개선함. 줄바꿈 기호(<code>\n</code>) 없이도 줄바꿈이 그대로 인식됨.</p>
<pre><code class="language-javascript">const name = &quot;김개발&quot;;
const job = &quot;프론트엔드 개발자&quot;;

// Before
var introES5 = &quot;안녕하세요. 저는 &quot; + name + &quot;이고,\n&quot; + job + &quot;입니다.&quot;;

// After
const introES6 = `안녕하세요. 저는 ${name}이고,
${job}입니다.`;</code></pre>
<h3 id="④-구조-분해-할당-destructuring">④ 구조 분해 할당 (Destructuring)</h3>
<p>객체나 배열에서 필요한 데이터만 변수로 쉽게 추출함. 리액트에서 컴포넌트의 <code>props</code>를 넘겨받거나 <code>useState</code>를 다룰 때 아주 자주 쓰이는 패턴임.</p>
<pre><code class="language-javascript">const user = { name: &quot;지민&quot;, age: 25, city: &quot;서울&quot; };

// Before
var nameES5 = user.name;
var ageES5 = user.age;

// After (객체 구조 분해)
const { name, age } = user;
console.log(name); // &quot;지민&quot;

// 배열 구조 분해
const colors = [&quot;red&quot;, &quot;green&quot;, &quot;blue&quot;];
const [firstColor, secondColor] = colors;
console.log(firstColor); // &quot;red&quot;</code></pre>
<h3 id="⑤-전개-연산자-spread-operator">⑤ 전개 연산자 (Spread Operator)</h3>
<p>점 세 개(<code>...</code>)를 사용해 배열이나 객체의 요소를 쉽게 복사하거나 합칠 수 있음. 원본 데이터를 직접 훼손하지 않고 새로운 데이터 복사본(상태)을 만들어야 할 때 필수적으로 사용됨.</p>
<pre><code class="language-javascript">// 배열 합치기
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

// 객체 복사 및 속성 수정
const user1 = { name: &quot;철수&quot;, age: 20 };
const updatedUser = { ...user1, age: 21 }; // { name: &quot;철수&quot;, age: 21 }</code></pre>
<h3 id="⑥-기본-매개변수-default-parameters">⑥ 기본 매개변수 (Default Parameters)</h3>
<p>함수를 호출할 때 값을 전달하지 않았을 경우 사용할 기본값을 미리 설정할 수 있음. 코드의 방어력을 높여줌.</p>
<pre><code class="language-javascript">// Before
function greetES5(name) {
  var userName = name || &quot;손님&quot;;
  console.log(&quot;환영합니다, &quot; + userName);
}

// After
const greetES6 = (name = &quot;손님&quot;) =&gt; {
  console.log(`환영합니다, ${name}`);
};

greetES6(); // 환영합니다, 손님
greetES6(&quot;영희&quot;); // 환영합니다, 영희</code></pre>
<h3 id="⑦-프로미스-promise">⑦ 프로미스 (Promise)</h3>
<p>서버에서 데이터를 받아오는 등의 비동기 처리 방식을 개선하여 콜백 지옥을 해결함. <code>.then()</code>, <code>.catch()</code> 메서드로 성공/실패 결과를 깔끔하게 체이닝하여 처리함. (이후 등장한 <code>async/await</code> 문법의 기반이 됨)</p>
<pre><code class="language-javascript">// Promise 예시
const fetchData = () =&gt; {
  return new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; {
      resolve(&quot;데이터 수신 완료&quot;);
      // 에러 발생 상황: reject(&quot;에러 발생&quot;);
    }, 1000);
  });
};

fetchData()
  .then((data) =&gt; console.log(data)) // 성공 시 실행
  .catch((error) =&gt; console.error(error)); // 실패 시 실행</code></pre>
<h3 id="⑧-모듈-module---import--export">⑧ 모듈 (Module - Import / Export)</h3>
<p>파일 단위로 코드를 분리하고 필요한 것만 가져다 쓸 수 있는 표준 방식. 전역 변수 이름이 겹쳐서 충돌하는 것을 막고, 코드 재사용성을 극대화함.</p>
<pre><code class="language-javascript">// math.js 파일 (내보내기)
export const add = (a, b) =&gt; a + b;
export const sub = (a, b) =&gt; a - b;

// app.js 파일 (가져오기)
import { add, sub } from &#39;./math.js&#39;;

console.log(add(10, 5)); // 15</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] VPC 엔드포인트 정리]]></title>
            <link>https://velog.io/@jade_57/AWS-VPC-%EC%97%94%EB%93%9C%ED%8F%AC%EC%9D%B8%ED%8A%B8-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jade_57/AWS-VPC-%EC%97%94%EB%93%9C%ED%8F%AC%EC%9D%B8%ED%8A%B8-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 24 Jan 2026 12:05:37 GMT</pubDate>
            <description><![CDATA[<h1 id="aws-vpc-엔드포인트-정리feat-gateway-vs-interface">[AWS] VPC 엔드포인트 정리feat. Gateway vs Interface)</h1>
<p>EC2에서 S3 같은 AWS 서비스에 접근할 때, 굳이 외부 인터넷(IGW)을 거칠 필요가 없다. 보안과 효율을 위해 사용하는 VPC 엔드포인트의 개념을 정리하고, 헷갈리기 쉬운 NAT Gateway와의 차이점 및 보안 설정까지 다뤄본다.</p>
<hr>
<h2 id="1-vpc-엔드포인트란-개념-잡기">1. VPC 엔드포인트란? (개념 잡기)</h2>
<p>VPC 내부의 인스턴스가 인터넷 게이트웨이(IGW)를 거치지 않고, <strong>AWS의 다른 서비스(S3, DynamoDB 등)와 직접 통신</strong>할 수 있게 해주는 기술이다.</p>
<h3 id="💡-왜-쓸까-3가지-이점">💡 왜 쓸까? (3가지 이점)</h3>
<ol>
<li><strong>보안 (Security):</strong> 트래픽이 공용 인터넷(Public Internet)을 타지 않고 AWS 글로벌 네트워크 안에서만 움직인다. 해킹 위험이 줄어든다.</li>
<li><strong>비용 (Cost):</strong> NAT Gateway를 통해 데이터를 많이 보내면 데이터 처리 비용이 꽤 나온다. S3 같은 대용량 전송은 엔드포인트가 훨씬 경제적일 수 있다.</li>
<li><strong>성능 (Performance):</strong> 네트워크 경로가 단순해져서 지연 시간(Latency)이 줄어든다.</li>
</ol>
<blockquote>
<p><strong>비유: 창고로 가는 전용 통로</strong></p>
<ul>
<li><strong>기존:</strong> 집(VPC)에서 창고(S3)에 가려고 현관문(IGW)을 열고 동네 한 바퀴(인터넷)를 돌아서 창고로 들어감.</li>
<li><strong>엔드포인트:</strong> 집 벽을 뚫어서 창고로 바로 이어지는 <strong>비밀 통로</strong>를 만듦.</li>
</ul>
</blockquote>
<hr>
<h2 id="2-엔드포인트의-두-가지-유형">2. 엔드포인트의 두 가지 유형</h2>
<p>지원하는 서비스와 동작 방식에 따라 크게 두 가지로 나뉜다.</p>
<h3 id="①-gateway-endpoint-게이트웨이-엔드포인트">① Gateway Endpoint (게이트웨이 엔드포인트)</h3>
<ul>
<li><strong>대상:</strong> <strong>S3, DynamoDB</strong> (딱 2개만 기억하자)</li>
<li><strong>방식:</strong> 라우팅 테이블(Route Table)에 *&quot;S3로 가는 트래픽은 이쪽 문으로 가라&quot;* 고 표지판을 세우는 방식.</li>
<li><strong>특징:</strong><ul>
<li>VPC 내부에 별도의 IP주소(ENI)를 만들지 않는다.</li>
<li><strong>무료</strong>다. (가장 큰 장점)</li>
<li>OS단에서 별도 설정이 필요 없다.</li>
</ul>
</li>
</ul>
<h3 id="②-interface-endpoint-인터페이스-엔드포인트">② Interface Endpoint (인터페이스 엔드포인트)</h3>
<ul>
<li><strong>대상:</strong> 그 외 대부분의 서비스 (SQS, SNS, Kinesis, CloudWatch 등)</li>
<li><strong>방식:</strong> <strong>PrivateLink</strong> 기술을 사용한다. 서브넷 안에 <strong>ENI(랜카드)</strong>를 하나 생성하고, 그 IP로 트래픽을 보낸다.</li>
<li><strong>특징:</strong><ul>
<li>Private IP가 할당된다.</li>
<li><strong>유료</strong>다. (시간당 요금 + 데이터 처리 요금)</li>
<li>보안 그룹(Security Group)을 적용할 수 있다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="3-헷갈리기-쉬운-개념-비교-nat-gateway-vs-vpc-endpoint">3. 헷갈리기 쉬운 개념 비교: NAT Gateway vs VPC Endpoint</h2>
<p>AWS를 처음 배우면 이 둘의 차이가 헷갈린다. 둘 다 &quot;Private Subnet에서 밖으로 나갈 때&quot; 쓴다는 점이 비슷하기 때문이다.</p>
<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="center">NAT Gateway</th>
<th align="center">VPC Endpoint</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>목적</strong></td>
<td align="center"><strong>외부 인터넷 전체</strong> (네이버, 구글, yum update 등) 접속</td>
<td align="center"><strong>AWS 서비스</strong> (S3, DynamoDB 등) 접속</td>
</tr>
<tr>
<td align="center"><strong>트래픽 경로</strong></td>
<td align="center">인터넷 게이트웨이(IGW)를 통함</td>
<td align="center">AWS 내부 네트워크 전용</td>
</tr>
<tr>
<td align="center"><strong>비용</strong></td>
<td align="center">비쌈 (시간당 + 데이터)</td>
<td align="center">Gateway형(무료) / Interface형(유료)</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>요약:</strong> 내 서버가 &#39;구글&#39;에 접속해야 하면 NAT Gateway가 필요하고, &#39;S3&#39;에만 접속하면 되는데 굳이 비싼 NAT를 쓸 필요 없이 엔드포인트를 쓰면 된다.</p>
</blockquote>
<hr>
<h2 id="4-심화-조금-더-디테일한-설정들">4. 심화: 조금 더 디테일한 설정들</h2>
<h3 id="①-vpc-endpoint-policy-문지기-설정">① VPC Endpoint Policy (문지기 설정)</h3>
<p>엔드포인트를 뚫어놨다고 해서 아무나 다 지나가게 하면 위험하다. <strong>Endpoint Policy(JSON 정책)</strong>를 통해 제어할 수 있다.</p>
<ul>
<li>예: *&quot;이 엔드포인트로는 내 회사 S3 버킷(My-Bucket)에만 접근 가능하고, 다른 사람의 S3 버킷에는 접근 금지!&quot;*</li>
<li>IAM 권한과는 별개로, <strong>네트워크 관문 자체에서의 제어</strong>가 가능하다.</li>
</ul>
<h3 id="②-private-dns-인터페이스-엔드포인트용">② Private DNS (인터페이스 엔드포인트용)</h3>
<p>Interface Endpoint를 생성하면 복잡한 전용 도메인 주소가 나온다. 하지만 개발할 때 코드를 바꾸기 귀찮다.</p>
<ul>
<li><strong>Private DNS</strong> 기능을 켜면, AWS 기본 도메인(예: <code>sqs.ap-northeast-2.amazonaws.com</code>)을 그대로 써도, 알아서 내부 엔드포인트 IP로 연결해준다.</li>
<li>즉, <strong>애플리케이션 코드를 수정할 필요가 없다.</strong></li>
</ul>
<hr>
<h2 id="5-정리">5. 정리</h2>
<ol>
<li>AWS 서비스끼리 통신할 때는 <strong>VPC 엔드포인트</strong>를 쓰는 게 국룰이다.</li>
<li><strong>S3, DynamoDB</strong>는 무료인 <strong>Gateway</strong> 타입을 쓴다. (라우팅 테이블 건드림)</li>
<li>나머지는 유료인 <strong>Interface</strong> 타입을 쓴다. (ENI 생성, PrivateLink 기술)</li>
<li>인터넷 접속이 목적이라면 NAT Gateway, AWS 서비스 접속이 목적이라면 VPC Endpoint.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] Terraform으로 구성한 멀티 VPC와 NLB 네트워크 구조]]></title>
            <link>https://velog.io/@jade_57/AWS-Terraform%EC%9C%BC%EB%A1%9C-%EA%B5%AC%EC%84%B1%ED%95%9C-%EB%A9%80%ED%8B%B0-VPC%EC%99%80-NLB-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@jade_57/AWS-Terraform%EC%9C%BC%EB%A1%9C-%EA%B5%AC%EC%84%B1%ED%95%9C-%EB%A9%80%ED%8B%B0-VPC%EC%99%80-NLB-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Tue, 13 Jan 2026 14:45:16 GMT</pubDate>
            <description><![CDATA[<h1 id="멀티-vpc--nlb-아키텍처-핵심-개념-정리">멀티 VPC + NLB 아키텍처 핵심 개념 정리</h1>
<h2 id="구조-개요">구조 개요</h2>
<ul>
<li>VPC1 (10.0.0.0/16) : MyWeb1</li>
<li>VPC2 (172.16.0.0/16) : MyWeb2, MyWeb3</li>
<li>VPC2 앞단에 Network Load Balancer(NLB) 배치</li>
</ul>
<hr>
<h2 id="1-region과-availability-zone">1. Region과 Availability Zone</h2>
<p>Region은 AWS의 물리적 위치 단위이며, Availability Zone(AZ)은 리전 내부의 독립된 데이터센터 묶음이다.<br>각 서브넷은 하나의 AZ에 소속되어 물리적 장애 격리를 제공한다.</p>
<hr>
<h2 id="2-vpc-virtual-private-cloud">2. VPC (Virtual Private Cloud)</h2>
<p>VPC는 AWS에서 사용하는 논리적으로 분리된 네트워크 공간이다.<br>서로 다른 VPC는 기본적으로 통신할 수 없으며 CIDR도 겹칠 수 없다.</p>
<table>
<thead>
<tr>
<th>VPC</th>
<th>CIDR</th>
</tr>
</thead>
<tbody><tr>
<td>MyVPC06</td>
<td>10.0.0.0/16</td>
</tr>
<tr>
<td>MyVPC07</td>
<td>172.16.0.0/16</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-cidr과-ip-범위">3. CIDR과 IP 범위</h2>
<p>CIDR은 네트워크에서 사용할 수 있는 IP 범위를 정의한다.</p>
<p>예시:</p>
<ul>
<li>10.0.0.0/16 → 10.0.0.0 ~ 10.0.255.255</li>
<li>10.0.1.0/24 → 10.0.1.0 ~ 10.0.1.255</li>
</ul>
<p>EC2 인스턴스의 Private IP는 반드시 서브넷 CIDR 범위 안에 포함되어야 한다.</p>
<hr>
<h2 id="4-subnet">4. Subnet</h2>
<p>Subnet은 VPC를 더 작은 네트워크 단위로 나눈 것이다.<br>EC2 인스턴스는 반드시 하나의 서브넷에 속한다.</p>
<p>이번 구조:</p>
<ul>
<li>MyPublic1Subnet (10.0.1.0/24)</li>
<li>MyPublic2Subnet (172.16.1.0/24)</li>
</ul>
<hr>
<h2 id="5-internet-gateway-igw">5. Internet Gateway (IGW)</h2>
<p>IGW는 VPC와 인터넷을 연결하는 게이트웨이다.<br>IGW만 연결되어 있다고 해서 바로 인터넷 통신이 가능한 것은 아니며, Route Table 설정이 필요하다.</p>
<hr>
<h2 id="6-route-table">6. Route Table</h2>
<p>Route Table은 패킷이 이동할 경로를 정의한다.</p>
<p>Public Subnet이 되기 위한 조건:</p>
<pre><code>0.0.0.0/0 → Internet Gateway</code></pre><p>이 경로가 있어야 서브넷의 리소스가 인터넷과 통신할 수 있다.</p>
<hr>
<h2 id="7-private-ip와-public-ip">7. Private IP와 Public IP</h2>
<p>EC2는 항상 Private IP를 가진다.<br>Public IP는 외부에서 접근하기 위한 NAT 개념으로 매핑된다.</p>
<p>로드밸런서와 EC2 간 통신은 모두 Private IP를 사용한다.</p>
<hr>
<h2 id="8-network-load-balancer-nlb">8. Network Load Balancer (NLB)</h2>
<p>NLB는 L4(TCP) 레벨에서 동작하는 로드밸런서이다.<br>패킷의 IP와 포트만 보고 트래픽을 전달한다.</p>
<p>이번 구조에서는 NLB가 MyVPC07에 위치하며, MyWeb2와 MyWeb3으로 트래픽을 분산한다.</p>
<hr>
<h2 id="9-target-group">9. Target Group</h2>
<p>Target Group은 로드밸런서가 트래픽을 전달할 대상 집합이다.</p>
<p>target_type = &quot;instance&quot; 설정 시, EC2 인스턴스가 대상이 된다.</p>
<hr>
<h2 id="10-vpc-분리-의미">10. VPC 분리 의미</h2>
<p>외부에 노출되는 서비스 영역(MyVPC07)과 내부 서버 영역(MyVPC06)을 네트워크 레벨에서 분리하여 구성할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리사이쿨 프로젝트 - 트러블 슈팅] 예약 날짜 하루 전으로 조회되는 문제]]></title>
            <link>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-%EC%98%88%EC%95%BD-%EB%82%A0%EC%A7%9C-%ED%95%98%EB%A3%A8-%EC%A0%84%EC%9C%BC%EB%A1%9C-%EC%A1%B0%ED%9A%8C%EB%90%98%EB%8A%94-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-%EC%98%88%EC%95%BD-%EB%82%A0%EC%A7%9C-%ED%95%98%EB%A3%A8-%EC%A0%84%EC%9C%BC%EB%A1%9C-%EC%A1%B0%ED%9A%8C%EB%90%98%EB%8A%94-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 08 Jan 2026 14:57:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jade_57/post/7f695e83-71a6-4b57-82a5-2c719feacd80/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jade_57/post/38cef618-2b2e-4fdf-b134-919ed419e60a/image.png" alt=""></p>
<h2 id="예약-날짜가-하루-전으로-조회되는-문제-트러블-슈팅">예약 날짜가 하루 전으로 조회되는 문제 (트러블 슈팅)</h2>
<p>리사이쿨 프로젝트에서 예약 정보를 조회할 때,<br>사용자가 선택한 날짜보다 <strong>하루 전 날짜가 조회되는 문제</strong>가 발생했다.<br>특히 결제 페이지와 예약 상세 화면에서 날짜가 어긋나 보이면서 이슈를 인지하게 되었다.</p>
<hr>
<h3 id="문제-상황">문제 상황</h3>
<p>예약 생성 시 사용자가 날짜를 선택하면<br>조회 시 해당 날짜가 항상 <strong>하루 전 날짜로 표시</strong>되었다.</p>
<p>예시  </p>
<ul>
<li>선택한 날짜: 2026-01-02  </li>
<li>조회된 날짜: 2026-01-01  </li>
</ul>
<hr>
<h3 id="원인-분석">원인 분석</h3>
<p>프론트엔드에서 날짜를 서버로 전달할 때 다음과 같은 방식으로 처리하고 있었다.</p>
<pre><code class="language-js">selectedDate.toISOString().slice(0, 10)</code></pre>
<p><code>toISOString()</code>은 날짜를 UTC 기준 문자열로 변환한다.<br>이 과정에서 한국 시간(KST)을 기준으로 날짜를 선택하더라도<br>UTC 기준으로 변환되면서 날짜가 하루 전으로 계산되어 서버에 전달되고 있었다.</p>
<p>문제의 원인은 서버가 아닌,<br><strong>프론트엔드에서 날짜를 변환하는 방식</strong>에 있었다.</p>
<hr>
<h3 id="해결-방법">해결 방법</h3>
<p>UTC 변환을 제거하고,<br>프론트엔드에서 로컬 기준 날짜를 문자열로 직접 생성해 서버에 전달하도록 수정했다.</p>
<pre><code class="language-js">const formatDate = (date) =&gt; {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, &quot;0&quot;);
  const d = String(date.getDate()).padStart(2, &quot;0&quot;);
  return `${y}-${m}-${d}`;
};</code></pre>
<p>서버에서는 날짜를 <code>LocalDate</code>로 처리해<br>타임존 영향을 받지 않도록 유지했다.</p>
<hr>
<h3 id="결과">결과</h3>
<ul>
<li>예약 생성 시 선택한 날짜와</li>
<li>예약 조회 및 결제 페이지에 표시되는 날짜가 정확히 일치</li>
<li>날짜가 하루씩 밀리는 현상 해결</li>
</ul>
<p>이번 이슈를 통해 날짜와 타임존 처리는<br>프론트엔드와 백엔드가 함께 기준을 맞춰야 한다는 점을 다시 한번 정리할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS 기초 개념 학습 01/05]]></title>
            <link>https://velog.io/@jade_57/AWS-%EA%B8%B0%EC%B4%88-%EA%B0%9C%EB%85%90-%ED%95%99%EC%8A%B5-0105</link>
            <guid>https://velog.io/@jade_57/AWS-%EA%B8%B0%EC%B4%88-%EA%B0%9C%EB%85%90-%ED%95%99%EC%8A%B5-0105</guid>
            <pubDate>Mon, 05 Jan 2026 14:28:08 GMT</pubDate>
            <description><![CDATA[<h2 id="1-클라우드-컴퓨팅-서비스-유형">1. 클라우드 컴퓨팅 서비스 유형</h2>
<p>클라우드 서비스는 <strong>누가 어디까지 관리하느냐</strong>에 따라 구분된다.<br>대표적으로 On-Premise, IaaS, PaaS, SaaS 네 가지 형태가 있다.</p>
<p><img src="https://velog.velcdn.com/images/jade_57/post/84c5c6a7-399f-42a7-aa8f-a54dc275fe51/image.png" alt=""></p>
<h3 id="1-1-on-premise">1-1. On-Premise</h3>
<p>On-Premise는 모든 것을 직접 관리하는 방식이다.<br>서버, 네트워크, 운영체제, 미들웨어, 애플리케이션까지 전부 직접 구성한다.<br>초기 비용과 관리 부담이 크지만, 제어 범위가 가장 넓은 구조다.</p>
<hr>
<h3 id="1-2-iaas-infrastructure-as-a-service">1-2. IaaS (Infrastructure as a Service)</h3>
<p>IaaS는 <strong>인프라만 클라우드에서 제공</strong>받는 형태다.<br>서버, 스토리지, 네트워크, 가상화 계층까지 AWS가 관리한다.<br>운영체제부터 애플리케이션은 사용자가 직접 관리한다.</p>
<ul>
<li>대표 예시: EC2  </li>
<li>서버 환경을 직접 다뤄야 하는 경우에 적합한 구조  </li>
</ul>
<hr>
<h3 id="1-3-paas-platform-as-a-service">1-3. PaaS (Platform as a Service)</h3>
<p>PaaS는 <strong>애플리케이션 실행 환경까지 제공</strong>되는 형태다.<br>운영체제, 런타임, 미들웨어를 신경 쓰지 않아도 된다.<br>개발자는 코드와 데이터에만 집중할 수 있다.</p>
<ul>
<li>서버 설정 부담 감소  </li>
<li>빠른 개발과 배포에 유리한 구조  </li>
</ul>
<hr>
<h3 id="1-4-saas-software-as-a-service">1-4. SaaS (Software as a Service)</h3>
<p>SaaS는 <strong>완성된 소프트웨어를 서비스 형태로 제공</strong>받는 방식이다.<br>사용자는 설정이나 개발 없이 바로 사용한다.<br>이메일, 협업 도구 등이 대표적인 예시다.</p>
<hr>
<h2 id="3-aws-네트워크-기본-개념">3. AWS 네트워크 기본 개념</h2>
<p>AWS 네트워크는<br>물리적인 위치 개념에서 시작해서<br>논리적으로 분리된 네트워크를 구성하는 구조다.<br>큰 단위부터 순서대로 이해하는 것이 편하다.</p>
<hr>
<h3 id="region-리전">Region (리전)</h3>
<p>Region은 AWS 데이터 센터가 위치한 <strong>물리적 지역을 구분하는 단위</strong>다.<br>서울, 도쿄처럼 지역별로 나누어 운영하는 방식이다.<br>리전 간 리소스는 기본적으로 분리해서 사용하는 구조다.</p>
<hr>
<h3 id="availability-zone-가용-영역">Availability Zone (가용 영역)</h3>
<p>Availability Zone은 하나의 리전 안에 존재하는 <strong>독립된 데이터 센터 묶음</strong>이다.<br>각 AZ는 서로 영향을 주지 않도록 분리되어 있다.<br>장애가 발생해도 서비스를 유지하도록 구성하는 방식이다.</p>
<hr>
<h3 id="vpc-virtual-private-cloud">VPC (Virtual Private Cloud)</h3>
<p>VPC는 AWS 안에서 사용하는 <strong>논리적으로 분리된 가상 네트워크</strong>다.<br>IP 주소 대역을 직접 정해서 네트워크를 구성하는 방식이다.<br>AWS 네트워크 설정의 시작점에 해당하는 개념이다.</p>
<hr>
<h3 id="subnet">Subnet</h3>
<p>Subnet은 VPC를 더 작은 네트워크 단위로 나누는 구조다.<br>서브넷 단위로 접근 범위와 역할을 구분한다.</p>
<h4 id="public-subnet">Public Subnet</h4>
<ul>
<li>외부 인터넷과 직접 통신하도록 구성하는 방식  </li>
<li>웹 서버, 로드 밸런서 등을 배치하는 용도  </li>
</ul>
<h4 id="private-subnet">Private Subnet</h4>
<ul>
<li>외부에서 직접 접근하지 못하도록 구성하는 방식  </li>
<li>DB 서버, 내부 서비스 등을 배치하는 용도  </li>
</ul>
<hr>
<h3 id="internet-gateway">Internet Gateway</h3>
<p>Internet Gateway는 VPC와 인터넷을 연결하는 역할을 한다.<br>퍼블릭 서브넷이 외부와 통신할 수 있도록 설정하는 방식이다.<br>라우팅 테이블과 함께 사용한다.</p>
<hr>
<h3 id="routing-table">Routing Table</h3>
<p>Routing Table은 네트워크 요청의 <strong>이동 경로를 정의하는 설정</strong>이다.<br>어디로 트래픽을 보낼지 규칙을 지정하는 방식이다.<br>서브넷마다 하나의 라우팅 테이블을 연결해서 사용한다.</p>
<hr>
<h3 id="security-group">Security Group</h3>
<p>Security Group은 인스턴스 단위로 적용하는 <strong>보안 설정</strong>이다.<br>허용할 트래픽만 명시하는 방식이다.<br>인바운드와 아웃바운드 규칙을 통해 접근을 제어한다.</p>
<ul>
<li>상태를 기억하는 방식(Stateful)  </li>
<li>허용되지 않은 요청은 자동 차단  </li>
</ul>
<hr>
<h3 id="정리">정리</h3>
<p>AWS 네트워크는<br>Region 안에 Availability Zone이 있고,<br>그 안에 VPC를 만들고,<br>VPC 안에 Subnet을 나누고,<br>Security Group으로 접근을 제어하는 구조다.</p>
<p>이 흐름을 이해하는 것이 AWS 네트워크 학습의 핵심이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PortOneAPI 결제 흐름 01/04]]></title>
            <link>https://velog.io/@jade_57/PortOneAPI-%EA%B2%B0%EC%A0%9C-%ED%9D%90%EB%A6%84-0104</link>
            <guid>https://velog.io/@jade_57/PortOneAPI-%EA%B2%B0%EC%A0%9C-%ED%9D%90%EB%A6%84-0104</guid>
            <pubDate>Sun, 04 Jan 2026 14:52:57 GMT</pubDate>
            <description><![CDATA[<h1 id="recychool-프로젝트에서-portone-api-결제-흐름-정리">Recychool 프로젝트에서 PortOne API 결제 흐름 정리</h1>
<p>Recychool 프로젝트에서 예약 결제를 구현하면서<br><strong>PortOne에서 제공하는 Browser SDK v2</strong>를 사용해<br>프론트와 서버의 역할을 분리한 결제 구조로 구성</p>
<p>프론트에서는 결제 UI와 결제 요청만 담당하고,<br>결제 성공 여부 판단과 상태 변경은 서버 기준으로 처리하는 방식</p>
<hr>
<h2 id="결제-전체-흐름">결제 전체 흐름</h2>
<p>예약 생성 (PENDING)<br>→ 결제 페이지 진입 (<code>/payment/:reserveId</code>)  
→ PortOne 결제창 호출<br>→ 결제 성공 응답 (<code>impUid</code>, <code>merchantUid</code>)  
→ 서버 결제 검증<br>→ 결제 완료 / 상태 변경  </p>
<hr>
<h2 id="portone-결제창-호출-frontend">PortOne 결제창 호출 (Frontend)</h2>
<p>결제 페이지에서 <strong>PortOne Browser SDK v2</strong>를 사용해 결제창을 호출</p>
<ul>
<li>결제 수단별로 <code>channelKey</code> 분리</li>
<li>프론트에서 <code>paymentId</code> 생성 → <code>merchantUid</code> 역할</li>
<li>PortOne 결제 성공 시 반환되는 고유 ID를 서버 검증용으로 사용</li>
</ul>
<p>결제 요청 시에는 최소한의 필수 정보만 전달</p>
<ul>
<li>storeId</li>
<li>channelKey</li>
<li>paymentId (merchantUid)</li>
<li>amount</li>
<li>customer 정보 (name, email, phone)</li>
</ul>
<p>결제 성공 후에는 PortOne SDK 응답으로 받은 결제 식별자를 서버에 전달해<br>실제 결제 완료 처리 요청</p>
<hr>
<h2 id="결제-완료-처리-backend">결제 완료 처리 (Backend)</h2>
<h3 id="post-privatepaymentcomplete">POST /private/payment/complete</h3>
<p>프론트에서 전달받은 결제 정보에 대해<br>서버에서 <strong>결제 검증 → 저장 → 상태 변경</strong> 순서로 처리</p>
<h3 id="처리-순서">처리 순서</h3>
<ol>
<li><code>impUid</code> 중복 검사  </li>
<li><code>reserveId</code> 기준 결제 가능 여부 확인  </li>
<li>PortOne 서버 결제 정보 조회 및 검증  </li>
<li>결제 금액 위·변조 여부 검증  </li>
<li>Payment 저장  </li>
<li>Reserve 상태 변경  </li>
</ol>
<p>이미 처리된 결제 요청에 대해서는<br>→ <strong>HTTP 409 (Conflict)</strong> 로 응답해 중복 결제를 차단</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring boot] AOP 개념 01/02]]></title>
            <link>https://velog.io/@jade_57/Spring-boot-AOP-%EA%B0%9C%EB%85%90-0102</link>
            <guid>https://velog.io/@jade_57/Spring-boot-AOP-%EA%B0%9C%EB%85%90-0102</guid>
            <pubDate>Thu, 01 Jan 2026 15:19:32 GMT</pubDate>
            <description><![CDATA[<h2 id="aop-aspect-oriented-programming">AOP (Aspect Oriented Programming)</h2>
<p><strong>개요</strong></p>
<ul>
<li><strong>관점</strong>: 개발에 있어 <strong>관심사(Concern)</strong>를 의미</li>
<li><strong>효과</strong>:<ul>
<li>코드 중복 감소</li>
<li>핵심 로직과 주변 로직을 분리하여 관리 가능</li>
</ul>
</li>
</ul>
<p><strong>핵심 로직 vs 주변 로직</strong></p>
<ul>
<li><strong>주변 로직 체크리스트</strong><ul>
<li>파라미터가 잘 전달 되었는가?</li>
<li>로직에서 발생할 수 있는 예외가 무엇인가?</li>
</ul>
</li>
<li><strong>정의</strong>: 핵심 로직은 아니지만 반복적으로 개발에 필요한 관심사</li>
<li><strong>AOP의 역할</strong>:<ul>
<li>주변 로직 ➡ <strong>횡단 관심사</strong>로 분리</li>
<li>핵심 비즈니스 로직 ➡ <strong>종단 관심사</strong>만 작성하도록 유도</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>예시) 나눗셈 프로그램 개발</strong></p>
<ul>
<li><strong>핵심</strong>: 두 개의 숫자를 나누는 연산</li>
<li><strong>주변</strong>: 0으로 나누는지 검사 (유효성 체크)</li>
</ul>
</blockquote>
<p><strong>특징</strong></p>
<ul>
<li>반복적으로 나타나는 횡단 관심사를 모듈로 분리 후 적절한 시점에 주입</li>
<li>스프링의 중요 특징 중 하나 (별도의 복잡한 설정 없이 간편하게 기능 구현 가능)</li>
</ul>
<hr>
<h3 id="aop-적용-시점-advice">AOP 적용 시점 (Advice)</h3>
<ul>
<li>⭐ <strong>Around (전 구역)</strong>: 가장 많이 사용됨 (재정의)</li>
<li><strong>Before</strong>: 대상 메서드 실행 전</li>
<li><strong>After</strong>: 대상 메서드 실행 후 (정상 종료/예외 발생 무관하게 무조건 실행, <code>final</code>과 동일)</li>
<li><strong>AfterReturning</strong>: 메소드 정상 리턴 후</li>
<li><strong>AfterThrowing</strong>: 메소드 예외 발생 후</li>
</ul>
<hr>
<h3 id="aop-설계-순서">AOP 설계 순서</h3>
<ol>
<li>구현할 횡단 관심사를 의미하는 <strong>어노테이션 생성</strong></li>
<li>어노테이션을 AOP로 등록 (<strong>@Aspect</strong> 어노테이션 사용)</li>
<li>종단 관심사에 등록된 <strong>어노테이션 사용</strong> (프록시 생성 및 동작)</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Zustand] ]]></title>
            <link>https://velog.io/@jade_57/Zustand</link>
            <guid>https://velog.io/@jade_57/Zustand</guid>
            <pubDate>Tue, 30 Dec 2025 12:49:29 GMT</pubDate>
            <description><![CDATA[<h2 id="zustand란">Zustand란?</h2>
<p>전역 상태를 간단하고 효율적으로 관리할 수 있게 도와주는 상태 관리 라이브러리</p>
<p>메모리 기반 상태 관리를 제공하며 상태가 브라우저 메모리(RAM)에 저장되어 
   앱이 살아 있는 동안만 유지된다. <strong>상태 유지를 위해 주로 로컬스토리지와 함께 사용</strong>된다.</p>
<h2 id="store">Store</h2>
<p>컴포넌트간의 데이터를 공유하기 위해선 상위 컴포넌트를 거쳐야 하는 Props Drilling을 없애고 컴포넌트간의 Store를 사이에 두어 여러 컴포넌트가 같은 데이터를 쉽게 보고 수정할 수 있는 중앙 저장소를 의미한다.</p>
<h2 id="zustand-주요함수">Zustand 주요함수</h2>
<h3 id="1-create">1. Create</h3>
<p>State와 Action을 통합 정의할 수 있고, 반환되는 훅으로 상태 접근과 액션 호출을
   동시에 처리할 수 있다. 또한 필요한 상태만 선택적으로 구독(subscribe)해 
   불필요한 리렌더링을 방지할 수 있습니다</p>
<h3 id="2-set">2. Set</h3>
<p>상태를 업데이트하는 함수로, create 함수 내부에서 사용됩니다. 기존 상태를 
   불변성을 유지하면서 부분적으로 변경할 수 있으며, 함수 형태로도 값을 갱신할 수 있어 
   복잡한 상태 계산도 가능합니다.</p>
<h3 id="3-persist">3. Persist</h3>
<p>Zustand 상태를 브라우저의 로컬 스토리지(localStorage)나 세션 스토리지(sessionStorage)에
   자동으로 저장하고 불러올 수 있게 해주는 미들웨어입니다. 페이지를 새로고침해도 상태가 유지되므로, 로그인 정보나 테마 설정 등 지속적인 상태 관리에 유용합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리사이쿨 프로젝트 - 트러블 슈팅]결제 완료 처리 이후 예약 상태 불일치 문제]]></title>
            <link>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85%EA%B2%B0%EC%A0%9C-%EC%99%84%EB%A3%8C-%EC%B2%98%EB%A6%AC-%EC%9D%B4%ED%9B%84-%EC%98%88%EC%95%BD-%EC%83%81%ED%83%9C-%EB%B6%88%EC%9D%BC%EC%B9%98-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85%EA%B2%B0%EC%A0%9C-%EC%99%84%EB%A3%8C-%EC%B2%98%EB%A6%AC-%EC%9D%B4%ED%9B%84-%EC%98%88%EC%95%BD-%EC%83%81%ED%83%9C-%EB%B6%88%EC%9D%BC%EC%B9%98-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Sun, 28 Dec 2025 13:12:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jade_57/post/10685a99-0c9a-40d0-9bc8-61910dd766b2/image.png" alt="">
<img src="https://velog.velcdn.com/images/jade_57/post/76cc77cd-6733-4934-970a-c237ed560be7/image.png" alt=""></p>
<h2 id="결제-완료-처리-이후-예약-상태-불일치-문제">결제 완료 처리 이후 예약 상태 불일치 문제</h2>
<h3 id="문제상황">문제상황</h3>
<ul>
<li>결제 완료 요청이 처리된 이후에도 예약 상태가 변경되지 않아 결제 정보와 예약 상태 간 데이터 불일치 발생</li>
</ul>
<h3 id="해결-방법">해결 방법</h3>
<ul>
<li><p>Service 계층에서 결제 저장과 예약 상태 변경을 </p>
<pre><code>하나의 트랜잭션으로 통합</code></pre></li>
<li><p>@Transactional을 적용하여 결제 저장(Payment)과 예약 상태 변경(ReserveStatus.COMPLETED)을 </p>
<pre><code> 동일한 작업 단위로 처리</code></pre></li>
<li><p>결제 완료 서비스 로직에서 서버 기준으로 상태를 검증한 뒤</p>
<pre><code>예약 상태를 변경하도록 개선</code></pre></li>
</ul>
<h3 id="결과">결과</h3>
<ul>
<li><p>결제와 예약 상태 변경이 항상 동시에 반영되어 데이터</p>
<pre><code>일관성 확보</code></pre></li>
<li><p>중간 실패 시 전체 롤백이 보장되어 결제/예약 데이터 </p>
<pre><code>불일치 문제 해결</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[팀 프로젝트]리사이쿨 결제 에러 ]]></title>
            <link>https://velog.io/@jade_57/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%EA%B2%B0%EC%A0%9C-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@jade_57/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%EA%B2%B0%EC%A0%9C-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Wed, 24 Dec 2025 01:14:28 GMT</pubDate>
            <description><![CDATA[<h2 id="미결제-후-창-닫기-시-결제-완료가-되는-에러">미결제 후 창 닫기 시 결제 완료가 되는 에러</h2>
<p><img src="https://velog.velcdn.com/images/jade_57/post/107b4d60-feb7-4a1d-b3e0-1fcfecb7fa5b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jade_57/post/1cd59585-3ea0-43de-b9ce-2dc7914c88f3/image.png" alt="">
<img src="https://velog.velcdn.com/images/jade_57/post/df3cb730-57d6-4e6f-9f6b-e52643667c50/image.png" alt=""></p>
<h1 id="결제-취소-시-결제가-완료되는-문제-트러블슈팅">결제 취소 시 결제가 완료되는 문제 트러블슈팅</h1>
<h2 id="1-문제-상황">1. 문제 상황</h2>
<p>주차/장소 예약 후 결제 페이지에서<br>결제창(NICEPAY / PortOne)에서 X 버튼 또는 종료 버튼을 눌러 결제를 취소했음에도 불구하고,<br>프론트 화면에서는 &#39;결제가 완료되었습니다.&#39; 라는 메시지가 출력되며<br>서버에는 결제 완료로 저장되는 문제가 발생했다.</p>
<p>예약 테이블을 초기화한 상태에서도 동일한 현상이 재현되었다.</p>
<hr>
<h2 id="2-원인-분석">2. 원인 분석</h2>
<h3 id="프론트엔드-관점">프론트엔드 관점</h3>
<ul>
<li>PortOne.requestPayment 호출 후</li>
<li>결제창을 닫아도 response 객체가 반환되는 경우가 있음</li>
<li>response 존재 여부만으로 결제 성공으로 판단</li>
<li>그대로 결제 완료 API 호출</li>
</ul>
<h3 id="백엔드-관점">백엔드 관점</h3>
<ul>
<li>결제 완료 API는 요청이 오면 무조건 결제 완료 처리</li>
<li>실제 결제 성공 여부에 대한 검증 로직 부재</li>
</ul>
<hr>
<h2 id="3-핵심-원인-요약">3. 핵심 원인 요약</h2>
<p>서버는 결제창에서 사용자가 어떤 버튼(X, 종료, 완료)을 눌렀는지 알 수 없는데<br>프론트에서 결제 완료 API를 무조건 호출하고 있었다.</p>
<hr>
<h2 id="4-해결-전략">4. 해결 전략</h2>
<p>구조는 유지하되, 프론트와 백엔드에서 최소한의 검증 로직을 추가했다.</p>
<hr>
<h2 id="5-프론트엔드-해결">5. 프론트엔드 해결</h2>
<p>결제 결과 상태가 PAID인 경우에만 서버 API를 호출하도록 수정했다.</p>
<ul>
<li>결제창 종료 또는 취소 시 서버 호출 차단</li>
<li>결제 완료 상태만 서버에 전달</li>
</ul>
<hr>
<h2 id="6-백엔드-해결">6. 백엔드 해결</h2>
<h3 id="서버-기준-상태-검증">서버 기준 상태 검증</h3>
<p>결제 완료 API에서 예약 상태가 결제 가능한 상태인지 검증했다.</p>
<ul>
<li>예약 상태가 PENDING이 아닐 경우 결제 거부</li>
<li>impUid 기준 중복 결제 차단</li>
</ul>
<p>이를 통해 프론트 조작이나 중복 호출에도 안전하게 방어할 수 있도록 했다.</p>
<hr>
<h2 id="7-최종-결론">7. 최종 결론</h2>
<ul>
<li>결제 완료 여부는 프론트 이벤트가 아닌 서버 기준으로 판단해야 한다</li>
<li>프론트에서는 결제 성공 상태만 전달</li>
<li>서버에서는 예약 상태와 중복 여부로 최종 검증</li>
</ul>
<p>이 구조를 통해 결제 취소 시 결제가 완료되는 문제를 안정적으로 해결했다.</p>
<hr>
<h2 id="8-교훈">8. 교훈</h2>
<ul>
<li>결제 시스템에서 프론트는 신뢰 대상이 아니다</li>
<li>결제 완료의 진실은 서버 한 곳에서만 판단해야 한다</li>
<li>요청이 왔다와 결제가 완료됐다는 완전히 다른 개념이다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 쿼리]]></title>
            <link>https://velog.io/@jade_57/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC</link>
            <guid>https://velog.io/@jade_57/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC</guid>
            <pubDate>Tue, 23 Dec 2025 08:45:41 GMT</pubDate>
            <description><![CDATA[<p>리액트 쿼리란?
서버로부터 데이터 가져오기, 데이터 캐싱, 캐시 제어 등 데이터를 쉽고
효율적으로 관리할 수 있는 라이브러리이다.</p>
<p>Redux
클라이언트 상태 관리
전역 UI 상태, 로그인 정보
캐싱 및 비동기 직접 구현
React Query
서버 상태 관리
API 데이터 자동 캐싱
로딩/에러 상태 자동 제공
프론트에서 Fetch를 보내는 경우
fetch만 사용하는 경우
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);</p>
<p>useEffect(() =&gt; {
  fetch(&quot;/api/posts&quot;)
    .then(res =&gt; res.json())
    .then(data =&gt; setData(data))
    .catch(err =&gt; setError(err))
    .finally(() =&gt; setLoading(false));
}, []);
fetch + React Query
const fetchPosts = async () =&gt; {
  const res = await fetch(&quot;/api/posts&quot;);
  return res.json();
};</p>
<p>const { data, isLoading, isError } = useQuery({
  queryKey: [&quot;posts&quot;],
  queryFn: fetchPosts,
});
리액트 쿼리의 장점</p>
<ol>
<li>서버 상태 관리에 특화
Redux와 달리 클라이언트 상태가 아닌 서버 상태 관리에 집중
API 응답 데이터의 생명주기(요청, 캐싱, 갱신)를 자동으로 관리</li>
<li>캐싱을 자동으로 제공
동일한 요청에 대해 중복 API 호출을 방지
이전에 받아온 데이터를 캐시로 즉시 제공하여 UX 향상</li>
<li>로딩 / 에러 상태를 자동 관리
isLoading, isError, error 상태를 기본 제공
Redux나 fetch처럼 상태 변수를 직접 만들 필요 없음</li>
<li>보일러플레이트 코드 감소
Redux의 action, reducer, dispatch 불필요
순수 fetch 대비 코드량 대폭 감소</li>
<li>중복 요청 및 경쟁 상태 방지
동일한 queryKey 요청은 하나만 실행
컴포넌트 여러 개에서 사용해도 안전
마무리
React Query는 단순한 데이터 요청 도구가 아니라,
서버 상태를 효율적으로 관리하기 위한 데이터 관리 라이브러리이다.</li>
</ol>
<p>Redux나 순수 fetch로 직접 구현해야 했던
캐싱, 로딩·에러 처리, 중복 요청 방지 등의 기능을 자동화하여</p>
<p>프론트엔드 코드의 복잡도를 크게 줄여준다.
서버 데이터 중심의 애플리케이션에서는
React Query를 활용하는 것이 더 효율적인 선택이 될 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리사이쿨 프로젝트 - 연장하기 결제 시 무결성 에러]]></title>
            <link>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%97%B0%EC%9E%A5%ED%95%98%EA%B8%B0-%EA%B2%B0%EC%A0%9C-%EC%8B%9C-%EB%AC%B4%EA%B2%B0%EC%84%B1-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%97%B0%EC%9E%A5%ED%95%98%EA%B8%B0-%EA%B2%B0%EC%A0%9C-%EC%8B%9C-%EB%AC%B4%EA%B2%B0%EC%84%B1-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Tue, 23 Dec 2025 07:14:35 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jade_57/post/e16f1489-f166-4158-be89-1ad86f51113a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리사이쿨 에러]]></title>
            <link>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Mon, 22 Dec 2025 16:19:11 GMT</pubDate>
            <description><![CDATA[<p>지금 예약 정보를 가져올 때 예약 정보 보다 하루전의 날짜를 가져오는 문제</p>
<p><img src="https://velog.velcdn.com/images/jade_57/post/7f695e83-71a6-4b57-82a5-2c719feacd80/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jade_57/post/38cef618-2b2e-4fdf-b134-919ed419e60a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리사이쿨 예약 데이터 조회 에러]]></title>
            <link>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%EC%98%88%EC%95%BD-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A1%B0%ED%9A%8C</link>
            <guid>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%EC%98%88%EC%95%BD-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A1%B0%ED%9A%8C</guid>
            <pubDate>Mon, 22 Dec 2025 07:04:55 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jade_57/post/826378fe-5831-4a6e-aa78-a3c4a122f87b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jade_57/post/cc8676d4-44ae-4980-99ed-ac641764d665/image.png" alt=""></p>
<p>📌 결제 페이지 트러블 슈팅 정리 (Recychool)</p>
<h3 id="문제">문제</h3>
<p>/payment/1로 접근 시 결제 페이지가 빈 화면으로 렌더링됨</p>
<p>Swagger에서 /private/payment/page?reserveId=1 API는 정상 응답</p>
<h3 id="원인">원인</h3>
<p>라우팅 방식 불일치</p>
<p>라우터: payment/:reserveId (path parameter)</p>
<p>프론트: useSearchParams()로 쿼리스트링(?reserveId=)을 읽고 있었음
→ reserveId가 null로 전달됨</p>
<p>예약 ID 상태 누락</p>
<p>예약 조회 후 reserve state에 id를 저장하지 않아</p>
<p>결제 완료 요청 시 reserve.id가 undefined</p>
<h3 id="해결">해결</h3>
<p>useSearchParams → useParams로 변경하여 path param에서 reserveId 추출</p>
<p>예약 조회 응답(PaymentPageResponseDTO)에서 reserveId를 reserve.id로 state에 저장</p>
<p>데이터 로딩 전 빈 화면 방지를 위해 간단한 로딩 가드 추가</p>
<h3 id="결과">결과</h3>
<p>결제 페이지 정상 렌더링</p>
<p>예약 조회 → 결제 요청 → 결제 완료 흐름 정상 동작</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리사이쿨 프로젝트] 소셜 결제 에러]]></title>
            <link>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%EC%86%8C%EC%85%9C-%EA%B2%B0%EC%A0%9C-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@jade_57/%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%BF%A8-%EC%86%8C%EC%85%9C-%EA%B2%B0%EC%A0%9C-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Sun, 21 Dec 2025 18:26:39 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jade_57/post/b463b223-421c-4f10-9857-aa89fef6a693/image.png" alt=""></p>
<h1 id="🔧-결제-기능-트러블-슈팅-portone-browser-sdk-v2">🔧 결제 기능 트러블 슈팅 (PortOne Browser SDK v2)</h1>
<h2 id="1-문제-상황">1. 문제 상황</h2>
<p>React 결제 페이지에서 PortOne Browser SDK(v2)를 사용하여<br><strong>일반 결제 / 카카오페이 / 토스페이</strong> 결제를 구현하던 중,<br>토스페이 결제 시 아래와 같은 에러가 발생하였다.</p>
<pre><code>POST https://checkout-service.prod.iamport.co/api/prepare/v2 400 (Bad Request)

결제 실패: 결제 창 호출에 실패하였습니다.
토스페이먼츠의 경우 간편 결제 수단은 필수 입력입니다.</code></pre><p>에러는 <code>PortOne.requestPayment()</code> 호출 직후 발생했으며,<br>브라우저 SDK 내부의 <code>preparePayment</code> 단계에서 실패하였다.</p>
<hr>
<h2 id="2-원인-분석">2. 원인 분석</h2>
<p>초기 구현에서는 결제 수단에 따라 다음과 같이 처리하고 있었다.</p>
<ul>
<li>일반 결제  <ul>
<li><code>payMethod: &quot;CARD&quot;</code></li>
</ul>
</li>
<li>카카오페이 / 토스페이  <ul>
<li><code>payMethod: &quot;EASY_PAY&quot;</code></li>
</ul>
</li>
</ul>
<p>그러나 <strong>PortOne Browser SDK v2에서는</strong><br><code>payMethod: &quot;EASY_PAY&quot;</code>를 사용할 경우,</p>
<blockquote>
<p>반드시 어떤 간편결제 수단인지(<code>easyPayProvider</code>)를 함께 전달해야 한다.</p>
</blockquote>
<p>특히 <strong>토스페이먼츠 채널</strong>에서는<br><code>easyPayProvider</code>가 누락될 경우 <code>400 Bad Request</code> 에러가 발생한다.</p>
<p>즉, 문제의 원인은 다음과 같았다.</p>
<ul>
<li><code>payMethod: &quot;EASY_PAY&quot;</code>만 전달됨</li>
<li><code>easyPayProvider</code> 옵션 누락 ❌</li>
</ul>
<hr>
<h2 id="3-해결-방법">3. 해결 방법</h2>
<h3 id="3-1-결제-수단별-옵션을-명확히-분리">3-1. 결제 수단별 옵션을 명확히 분리</h3>
<p>결제 수단에 따라 아래 항목을 명확히 분리하였다.</p>
<ul>
<li><code>channelKey</code></li>
<li><code>payMethod</code></li>
<li><code>easyPayProvider</code></li>
</ul>
<pre><code class="language-javascript">const getPortOnePayType = () =&gt; {
  if (payType === &quot;GENERAL&quot;) {
    return {
      channelKey: process.env.REACT_APP_PORTONE_CHANNEL_CARD,
      payMethod: &quot;CARD&quot;,
      easyPayProvider: null,
    };
  }

  if (payType === &quot;KAKAOPAY&quot;) {
    return {
      channelKey: process.env.REACT_APP_PORTONE_CHANNEL_KAKAOPAY,
      payMethod: &quot;EASY_PAY&quot;,
      easyPayProvider: &quot;KAKAOPAY&quot;,
    };
  }

  return {
    channelKey: process.env.REACT_APP_PORTONE_CHANNEL_TOSSPAY,
    payMethod: &quot;EASY_PAY&quot;,
    easyPayProvider: &quot;TOSSPAY&quot;,
  };
};</code></pre>
<hr>
<h3 id="3-2-requestpayment-호출-시-조건부-옵션-추가">3-2. <code>requestPayment</code> 호출 시 조건부 옵션 추가</h3>
<p><code>payMethod</code>가 <code>EASY_PAY</code>일 때만<br><code>easyPay</code> 옵션을 조건부로 추가하도록 수정하였다.</p>
<pre><code class="language-javascript">const paymentRequest = {
  storeId: process.env.REACT_APP_PORTONE_STORE_ID,
  channelKey,
  paymentId,
  orderName,
  totalAmount,
  currency: &quot;CURRENCY_KRW&quot;,
  payMethod,
  customer: {
    fullName: user.name,
    email: user.email,
    phoneNumber: user.phone,
  },
};

if (payMethod === &quot;EASY_PAY&quot;) {
  paymentRequest.easyPay = { easyPayProvider };
}

const response = await PortOne.requestPayment(paymentRequest);</code></pre>
<hr>
<h2 id="4-결과">4. 결과</h2>
<ul>
<li>결제 채널별 요구사항을 정확히 반영하여 결제창 정상 호출</li>
<li>일반 결제 / 카카오페이 / 토스페이 모두 정상 동작 확인</li>
<li>결제 실패 원인을 명확히 분리하여 트러블 슈팅 문서로 정리 가능</li>
</ul>
<hr>
<h2 id="5-정리">5. 정리</h2>
<ul>
<li><strong>PortOne Browser SDK v2</strong>에서는<br><code>EASY_PAY</code> 사용 시 <code>easyPayProvider</code>가 필수</li>
<li>에러 메시지는 추상적이므로<br>네트워크 탭과 SDK 요구사항을 함께 확인해야 함</li>
<li>결제 수단별 설정을 명확히 분리하는 것이 중요</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[RecySchool 프로젝트 에러]]></title>
            <link>https://velog.io/@jade_57/RecySchool-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@jade_57/RecySchool-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Fri, 19 Dec 2025 09:01:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jade_57/post/9517e2a6-bbe9-49e3-869f-0880058624cb/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jade_57/post/68c8d924-1420-4c78-92f5-484b3f71a631/image.png" alt="">
<img src="https://velog.velcdn.com/images/jade_57/post/131abc24-301e-40a1-b4b1-d21818f60f22/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jade_57/post/391210e1-5471-40d6-b1e8-bbf01286edd8/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jade_57/post/658f7a8a-2ade-4f50-a9c4-cfcd02cd7aef/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jade_57/post/81f642f7-1680-4669-a756-20049bf3f17c/image.png" alt="">
PortOne 결제 ID 미저장</p>
<p>우리 시스템 결제 UID 미저장</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker 개념 12/04]]></title>
            <link>https://velog.io/@jade_57/IT-NEWS-1204</link>
            <guid>https://velog.io/@jade_57/IT-NEWS-1204</guid>
            <pubDate>Thu, 04 Dec 2025 14:52:40 GMT</pubDate>
            <description><![CDATA[<h1 id="도커-정리">도커 정리</h1>
<h2 id="도커란">도커란?</h2>
<ul>
<li>컨테이너를 생성·구동·배포할 수 있는 기능을 제공하는 리눅스 기반 오픈 소스 가상화 플랫폼</li>
<li>도커 클라이언트, 도커 서버, 이미지, 컨테이너, 레지스트리, 볼륨, 네트워크로 구성</li>
</ul>
<hr>
<h2 id="구성-요소">구성 요소</h2>
<h3 id="도커-클라이언트">도커 클라이언트</h3>
<ul>
<li>리눅스 Shell과 유사한 역할</li>
<li>사용자의 명령을 도커 서버로 전달</li>
</ul>
<h3 id="도커-서버">도커 서버</h3>
<ul>
<li>도커 클라이언트로부터 명령을 받아 이미지·컨테이너·볼륨·네트워크 등을 관리하는 데몬 수행</li>
</ul>
<h3 id="이미지-⭐">이미지 ⭐</h3>
<ul>
<li>도커 허브에서 가져오거나 직접 제작 가능</li>
<li>운영체제 커널은 포함되지 않으며, 호스트 OS를 공유</li>
<li>읽기 전용 구조(불변성)</li>
<li>컨테이너 실행 기반으로 사용</li>
</ul>
<h3 id="컨테이너">컨테이너</h3>
<ul>
<li>이미지를 실제로 실행한 독립 프로세스 단위</li>
<li>고유한 IP를 가지며 개별 시스템처럼 동작</li>
</ul>
<h3 id="레지스트리">레지스트리</h3>
<ul>
<li>이미지 저장소</li>
<li>기본 설정은 도커 허브 사용</li>
</ul>
<h3 id="볼륨">볼륨</h3>
<ul>
<li>컨테이너 데이터를 호스트에 저장하는 마운트 기능</li>
<li>컨테이너 삭제 후에도 볼륨 데이터 유지</li>
</ul>
<h3 id="네트워크">네트워크</h3>
<ul>
<li>컨테이너 간 통신 및 네트워크 분리 기능 제공</li>
</ul>
<hr>
<h2 id="도커를-사용하는-이유-⭐">도커를 사용하는 이유 ⭐</h2>
<ul>
<li>서버 장비 수만큼 물리적인 자원과 인력, 비용이 증가하는 문제를 줄일 수 있음</li>
<li>컨테이너 기반으로 환경을 이식성 있게 관리 가능</li>
</ul>
<hr>
<h2 id="주요-명령어">주요 명령어</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>docker build</code></td>
<td>이미지 생성</td>
</tr>
<tr>
<td><code>docker pull</code></td>
<td>레지스트리에서 이미지 다운로드</td>
</tr>
<tr>
<td><code>docker push</code></td>
<td>레지스트리에 이미지 업로드</td>
</tr>
<tr>
<td><code>docker run</code></td>
<td>이미지로 컨테이너 실행</td>
</tr>
<tr>
<td><code>docker stop</code></td>
<td>실행 중인 컨테이너 중지</td>
</tr>
<tr>
<td><code>docker start</code></td>
<td>컨테이너 재시작</td>
</tr>
<tr>
<td><code>docker rm</code></td>
<td>컨테이너 삭제</td>
</tr>
<tr>
<td><code>docker search</code></td>
<td>이미지 검색</td>
</tr>
<tr>
<td><code>docker image pull</code></td>
<td>이미지 다운로드</td>
</tr>
<tr>
<td><code>docker image push</code></td>
<td>이미지 업로드</td>
</tr>
<tr>
<td><code>docker image ls</code></td>
<td>이미지 목록 조회</td>
</tr>
<tr>
<td><code>docker image inspect</code></td>
<td>이미지 상세 정보 확인</td>
</tr>
<tr>
<td><code>docker image tag</code></td>
<td>이미지 태그 설정</td>
</tr>
<tr>
<td><code>docker image rm</code></td>
<td>이미지 삭제</td>
</tr>
<tr>
<td><code>docker login</code></td>
<td>도커 허브 로그인</td>
</tr>
<tr>
<td><code>docker logout</code></td>
<td>도커 허브 로그아웃</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring boot JPA 12/03]]></title>
            <link>https://velog.io/@jade_57/Spring-boot-JPA-1203</link>
            <guid>https://velog.io/@jade_57/Spring-boot-JPA-1203</guid>
            <pubDate>Wed, 03 Dec 2025 11:58:30 GMT</pubDate>
            <description><![CDATA[<h1 id="상속-관계-매핑-정리">상속 관계 매핑 정리</h1>
<h2 id="기존-rdb-설계-방식의-문제">기존 RDB 설계 방식의 문제</h2>
<p>관계형 DB에서는 상속 개념이 없기 때문에, 공통된 속성이 있어도 테이블마다 반복해서 작성해야 한다.</p>
<h3 id="기존-테이블-예시">기존 테이블 예시</h3>
<p><strong>책</strong></p>
<table>
<thead>
<tr>
<th>상품번호(PK)</th>
<th>이름</th>
<th>가격</th>
<th>저자</th>
<th>출판사</th>
</tr>
</thead>
</table>
<p><strong>옷</strong></p>
<table>
<thead>
<tr>
<th>상품번호(PK)</th>
<th>이름</th>
<th>가격</th>
<th>사이즈</th>
<th>색상</th>
<th>브랜드</th>
</tr>
</thead>
</table>
<p><strong>운동기구</strong></p>
<table>
<thead>
<tr>
<th>상품번호(PK)</th>
<th>이름</th>
<th>가격</th>
<th>무게</th>
<th>사이즈</th>
<th>브랜드</th>
</tr>
</thead>
</table>
<p>→ 공통 필드(상품번호, 이름, 가격)가 반복됨</p>
<hr>
<h2 id="슈퍼·서브-타입-모델링er-모델">슈퍼·서브 타입 모델링(ER 모델)</h2>
<p>공통 속성을 부모(슈퍼) 테이블로 올리고 자식(서브)이 상속받는 구조</p>
<p><strong>상품(슈퍼테이블)</strong></p>
<table>
<thead>
<tr>
<th>상품번호(PK)</th>
<th>이름</th>
<th>가격</th>
<th>상품타입</th>
</tr>
</thead>
<tbody><tr>
<td>책 / 옷 / 운동기구를 구분</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p><strong>서브 테이블</strong></p>
<ul>
<li>서브 테이블의 PK는 부모 테이블의 PK를 그대로 사용 → <strong>PK이자 FK</strong></li>
</ul>
<p><strong>책</strong></p>
<table>
<thead>
<tr>
<th>상품번호(PK, FK)</th>
<th>저자</th>
<th>출판사</th>
</tr>
</thead>
</table>
<p><strong>옷</strong></p>
<table>
<thead>
<tr>
<th>상품번호(PK, FK)</th>
<th>사이즈</th>
<th>색상</th>
<th>브랜드</th>
</tr>
</thead>
</table>
<p><strong>운동기구</strong></p>
<table>
<thead>
<tr>
<th>상품번호(PK, FK)</th>
<th>무게</th>
<th>사이즈</th>
<th>브랜드</th>
</tr>
</thead>
</table>
<p>※ 이 구조는 논리적 모델이며, 실제 물리 구현은 전략에 따라 달라짐</p>
<hr>
<h2 id="jpa-상속-매핑-전략-3가지">JPA 상속 매핑 전략 3가지</h2>
<h3 id="1-조인-전략joined">1) 조인 전략(Joined)</h3>
<ul>
<li>부모·자식 각각 테이블 생성  </li>
<li>자식 테이블은 부모 테이블의 PK를 받아 FK + PK로 사용  </li>
<li>조회 시 조인 연산이 자주 발생  </li>
<li>테이블 구조가 명확하여 정규화 관점에서 유리</li>
</ul>
<h3 id="2-단일-테이블-전략single-table">2) 단일 테이블 전략(Single Table)</h3>
<ul>
<li><strong>하나의 테이블에 모든 엔티티 데이터를 저장</strong></li>
<li>공통 속성 외의 필드는 NULL 허용</li>
</ul>
<p><strong>상품(단일 테이블)</strong></p>
<table>
<thead>
<tr>
<th>상품번호</th>
<th>이름</th>
<th>가격</th>
<th>저자</th>
<th>출판사</th>
<th>사이즈</th>
<th>색상</th>
<th>브랜드</th>
<th>무게</th>
<th>상품타입</th>
</tr>
</thead>
<tbody><tr>
<td>책/옷/운동기구 구분 칼럼 포함</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<ul>
<li>쿼리가 단순하지만, 불필요한 NULL 필드가 증가하여 공간 낭비</li>
</ul>
<h3 id="3-구현-클래스마다-테이블-전략table-per-class">3) 구현 클래스마다 테이블 전략(Table Per Class)</h3>
<ul>
<li>엔티티마다 테이블 생성  </li>
<li>일반적인 RDB 설계와 유사하지만<br><strong>중복 데이터가 많고 쿼리 비용이 높아 비추천</strong></li>
</ul>
<hr>
<h2 id="jpa-기본-fetch-전략">JPA 기본 Fetch 전략</h2>
<table>
<thead>
<tr>
<th>연관관계</th>
<th>기본 로딩 방식</th>
</tr>
</thead>
<tbody><tr>
<td><code>@ManyToOne</code>, <code>@OneToOne</code></td>
<td>즉시 로딩(EAGER)</td>
</tr>
<tr>
<td><code>@OneToMany</code>, <code>@ManyToMany</code></td>
<td>지연 로딩(LAZY)</td>
</tr>
</tbody></table>
<p>→ 특별히 항상 함께 조회되는 관계가 아니면 <strong>지연 로딩</strong> 권장</p>
<hr>
<h2 id="jpa-데이터-타입-종류">JPA 데이터 타입 종류</h2>
<ol>
<li><p><strong>엔터티 타입</strong>  </p>
<ul>
<li>식별자(PK)를 가지며 독립적으로 존재</li>
</ul>
</li>
<li><p><strong>임베디드 타입(복합 값 타입)</strong>  </p>
<ul>
<li>개발자가 직접 정의한 타입</li>
<li>예) 주소 정보</li>
</ul>
</li>
</ol>
<pre><code class="language-java">class Address {
    private String address;
    private String addressDetail;
    private String zipcode;
}</code></pre>
<p>→ 엔터티에서 아래처럼 통합 표현 가능</p>
<pre><code class="language-java">private Address address;</code></pre>
<ol start="3">
<li><strong>컬렉션 값 타입</strong><ul>
<li>값 타입을 컬렉션 형태로 보관하는 구조</li>
</ul>
</li>
</ol>
<hr>
<h2 id="핵심-정리">핵심 정리</h2>
<ul>
<li>기존 RDB는 상속 개념이 없어 중복된 설계를 피하기 어려움</li>
<li>JPA는 상속 매핑 전략을 통해 객체 모델과 DB 간 패러다임 차이를 해소</li>
<li>사용 목적에 따라 상속 전략을 선택하면 설계 품질과 유지보수성이 향상됨</li>
</ul>
<pre><code>JPA 상속 매핑 = 객체지향 설계 + DB 물리 모델링 간 격차 해소</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Spring boot JPA 12/02]]></title>
            <link>https://velog.io/@jade_57/Spring-boot-JPA-1202</link>
            <guid>https://velog.io/@jade_57/Spring-boot-JPA-1202</guid>
            <pubDate>Tue, 02 Dec 2025 07:57:29 GMT</pubDate>
            <description><![CDATA[<h2 id="hibernate-ddlauto-옵션-·-영속성-컨텍스트-·-연관관계-매핑-정리">Hibernate ddlAuto 옵션 · 영속성 컨텍스트 · 연관관계 매핑 정리</h2>
<h3 id="hibernate-ddlauto-옵션">Hibernate ddlAuto 옵션</h3>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>create</code></td>
<td>애플리케이션 실행 시 매핑된 테이블을 <strong>삭제 후 재생성</strong>. 기존 데이터 모두 삭제됨</td>
</tr>
<tr>
<td><code>create-drop</code></td>
<td><code>create</code>와 동일하지만, <strong>애플리케이션 종료 시 테이블도 삭제</strong></td>
</tr>
<tr>
<td><code>none</code></td>
<td>하이버네이트가 DDL 작업에 관여하지 않음</td>
</tr>
<tr>
<td><code>update</code> ⭐</td>
<td>실행 시 엔티티와 테이블을 비교하여 <strong>컬럼 변경 사항만 반영</strong>. 기존 데이터 유지<br>※ 컬럼 삭제는 직접 수행해야 함</td>
</tr>
<tr>
<td><code>validate</code></td>
<td>실행 시 엔티티와 테이블 구조가 같은지 검증. 다르면 실행 불가</td>
</tr>
</tbody></table>
<hr>
<h2 id="영속성-컨텍스트persistencecontext">영속성 컨텍스트(PersistenceContext)</h2>
<p>JPA는 트랜잭션 기반으로 동작하며, 영속성 컨텍스트를 통해 엔티티를 관리한다.<br><code>EntityManager</code>가 이 영역을 관리하며, <code>.persist()</code>를 통해 엔티티를 등록할 수 있음</p>
<h3 id="동작-흐름">동작 흐름</h3>
<ol>
<li>엔티티를 영속성 컨텍스트로 로딩</li>
<li>1차 캐시에 저장 → <strong>영속 상태</strong></li>
<li>변경 쿼리를 <strong>쓰기 지연 저장소</strong>에 보관</li>
<li>트랜잭션 종료 시 <code>flush()</code> 실행 → DB에 실제 SQL 반영</li>
<li><code>commit</code> 완료 시 DB에 최종 반영</li>
</ol>
<hr>
<h2 id="엔티티의-4가지-상태">엔티티의 4가지 상태</h2>
<table>
<thead>
<tr>
<th>상태</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>영속</strong></td>
<td>영속성 컨텍스트에 저장된 상태. 변경 시 트랜잭션 종료 시점에 반영됨</td>
</tr>
<tr>
<td><strong>준영속(detached)</strong></td>
<td>컨텍스트에서 분리된 상태. setter를 호출해도 update SQL이 나가지 않음</td>
</tr>
<tr>
<td><strong>비영속(transient)</strong></td>
<td>엔티티 객체만 생성된 상태. persist 호출 전 단계</td>
</tr>
<tr>
<td><strong>삭제(removed)</strong></td>
<td>컨텍스트 및 DB에서 삭제된 상태</td>
</tr>
</tbody></table>
<hr>
<h2 id="repository">Repository</h2>
<ul>
<li>애플리케이션과 DB 사이에서 데이터를 읽고 쓰는 역할을 담당하는 계층</li>
<li>Spring Data JPA에서는 <code>Repository</code> 인터페이스 기반으로 구현</li>
</ul>
<hr>
<h2 id="연관관계-매핑relation-mapping">연관관계 매핑(Relation Mapping)</h2>
<h3 id="테이블-구조">테이블 구조</h3>
<p><strong>USER</strong></p>
<table>
<thead>
<tr>
<th>필드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>ID(PK)</td>
<td>회원 번호</td>
</tr>
<tr>
<td>이메일</td>
<td>email</td>
</tr>
<tr>
<td>비밀번호</td>
<td>password</td>
</tr>
<tr>
<td>이름</td>
<td>name</td>
</tr>
<tr>
<td>나이</td>
<td>age</td>
</tr>
</tbody></table>
<p><strong>BOARD</strong></p>
<table>
<thead>
<tr>
<th>필드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>ID(PK)</td>
<td>게시글 번호</td>
</tr>
<tr>
<td>제목</td>
<td>boardTitle</td>
</tr>
<tr>
<td>내용</td>
<td>boardContent</td>
</tr>
<tr>
<td>작성자(FK)</td>
<td>USER.ID 참조</td>
</tr>
</tbody></table>
<hr>
<h3 id="객체-구조">객체 구조</h3>
<pre><code class="language-java">public class User {
    private Long id;
    private String userEmail;
    private String userPassword;
    private String userName;
    private Integer userAge;
}

public class Board {
    private Long id;
    private String boardTitle;
    private String boardContent;
    private User user; // 작성자
}</code></pre>
<hr>
<h2 id="연관관계와-방향성">연관관계와 방향성</h2>
<h3 id="방향direction">방향(Direction)</h3>
<ul>
<li>객체는 참조를 통해 관계를 표현하기 때문에, DB와 달리 <strong>방향</strong> 개념이 존재함</li>
</ul>
<table>
<thead>
<tr>
<th>종류</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>단방향</td>
<td>한 객체만 다른 객체를 참조</td>
</tr>
<tr>
<td>양방향</td>
<td>양쪽이 서로를 참조. 객체 그래프 탐색 가능하나 관리 주의 필요</td>
</tr>
</tbody></table>
<hr>
<h2 id="다중성multiplicity">다중성(Multiplicity)</h2>
<table>
<thead>
<tr>
<th>관계</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>1:N</td>
<td>한 명의 회원 → 여러 게시물</td>
</tr>
<tr>
<td>N:1</td>
<td>여러 게시글 → 하나의 작성자</td>
</tr>
<tr>
<td>1:1</td>
<td>하나의 엔티티와 하나만 연결</td>
</tr>
<tr>
<td>N:N</td>
<td>양쪽이 여러 개씩 연결 → 실제 구현 시 1:N + N:1로 풀어냄</td>
</tr>
</tbody></table>
<blockquote>
<p>항상 <strong>다(N)</strong> 쪽에 외래키 존재</p>
</blockquote>
<hr>
<h2 id="연관관계의-주인">연관관계의 주인</h2>
<p>양방향 관계일 경우<br><strong>둘 중 한 엔티티가 연관관계의 주인</strong>이 되며, 주인이 FK를 관리한다</p>
<p>JPA에서는 주인 아닌 쪽은 읽기 전용 역할을 하게 된다</p>
<hr>
<h2 id="핵심-정리">핵심 정리</h2>
<ul>
<li><code>ddlAuto</code>는 개발 단계에서 유용하지만 운영 환경에서는 주의 필요</li>
<li>영속성 컨텍스트는 JPA의 기반 메커니즘으로 엔티티 상태를 관리</li>
<li>연관관계 매핑은 테이블 구조를 객체 중심으로 해석해 반영하는 과정</li>
<li>JPA는 객체지향 코드와 관계형 DB 간의 간극을 줄여 유지보수성을 높여줌</li>
</ul>
<pre><code>JPA = 객체 중심 + 연관관계 기반 설계 + 영속성 관리 + SQL 절감</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring boot JPA] 12/01]]></title>
            <link>https://velog.io/@jade_57/Spring-boot-JPA-1201</link>
            <guid>https://velog.io/@jade_57/Spring-boot-JPA-1201</guid>
            <pubDate>Mon, 01 Dec 2025 11:02:27 GMT</pubDate>
            <description><![CDATA[<h1 id="ormobject-relational-mapping-정리">ORM(Object Relational Mapping) 정리</h1>
<h2 id="orm이란">ORM이란?</h2>
<ul>
<li>객체지향 언어에서 사용하는 <strong>객체</strong>와 관계형 DB의 <strong>테이블</strong>을 서로 연결해주는 기술  </li>
<li>자바의 객체 구조와 관계형 DB는 구조가 다르기 때문에 그대로 사용하면 객체지향적인 코드의 장점을 살리기 어렵다  </li>
<li>ORM은 이 문제를 해결하기 위해 <strong>객체 중심 설계가 가능하도록 매핑 기능을 제공</strong>하는 기술</li>
</ul>
<blockquote>
<p>MyBatis는 SQL과 인터페이스를 매핑하지만,<br>ORM은 <strong>객체(클래스)</strong> 와 <strong>DB 테이블</strong>을 매핑한다는 점이 가장 큰 차이</p>
</blockquote>
<hr>
<h2 id="hibernate">Hibernate</h2>
<ul>
<li>ORM 기술을 직접 구현해놓은 대표적인 오픈소스 프레임워크  </li>
<li>JPA를 기반으로 동작하는 실질적인 구현체 역할</li>
</ul>
<hr>
<h2 id="jpajava-persistence-api">JPA(Java Persistence API)</h2>
<ul>
<li>자바 진영에서 ORM을 사용하기 위한 <strong>표준 명세</strong>  </li>
<li>JPA는 직접 기능을 제공하는 것이 아니라 <strong>인터페이스(규약)</strong> 만 제공하며<br>실제 동작은 Hibernate 같은 ORM 프레임워크가 수행</li>
</ul>
<blockquote>
<p>프론트에서 jQuery 의존성이 제거되면서 React 기반으로 전환된 사례와 유사<br>특정 구현체에 의존하지 않도록 추상화된 표준을 두는 방식</p>
</blockquote>
<hr>
<h2 id="spring-data-jpa">Spring Data JPA</h2>
<ul>
<li>JPA 기반의 모듈  </li>
<li><code>Repository</code> 인터페이스만 구현하면 기본적인 CRUD 기능이 자동 제공  </li>
<li>DB 접근 코드가 간단해지는 장점이 있지만 내부 동작이 추상화되어 있어<br>문제가 발생하면 원리를 모르면 해결하기 어려움<br>→ <strong>ORM 개념 먼저 학습하는 것이 안전</strong></li>
</ul>
<hr>
<h2 id="jpa를-사용하는-이유">JPA를 사용하는 이유</h2>
<h3 id="1-객체와-관계형-db-패러다임-불일치-문제">1. 객체와 관계형 DB 패러다임 불일치 문제</h3>
<p>자바는 객체지향 언어지만, 데이터베이스는 절차적으로 데이터를 저장<br>구조가 다르다 보니 객체지향의 장점인 <strong>상속, 다형성, 캡슐화</strong>를 제대로 활용하기 어려움</p>
<p>예를 들어 사람 데이터를 확장해보면</p>
<p><strong>공통 속성(슈퍼 테이블)</strong></p>
<table>
<thead>
<tr>
<th>번호(PK)</th>
<th>이름</th>
<th>생년월일</th>
</tr>
</thead>
</table>
<p><strong>학생 테이블</strong></p>
<p>| 번호(FK, PK) | 학년 | 등급 |</p>
<p><strong>직장인 테이블</strong></p>
<p>| 번호(FK, PK) | 경력 | 직급 |</p>
<ul>
<li>DB에서는 슈퍼 엔터티와 서브 엔터티로 나누어 관리해야 함  </li>
<li>insert 시 두 테이블에 값을 저장해야 하는 경우도 생김  </li>
<li>하지만 자바에서는 <code>Student extends Person</code> 방식으로 상속 후 업캐스팅만 하면 해결<br>→ <strong>JPA는 이 패러다임 차이를 줄여주는 역할</strong></li>
</ul>
<hr>
<h3 id="2-연관관계-처리-방식-차이">2. 연관관계 처리 방식 차이</h3>
<h4 id="rdb의-연관관계">RDB의 연관관계</h4>
<ul>
<li>FK(외래키)를 통해 관계를 맺음  </li>
<li>JOIN으로 다른 테이블의 데이터를 연결해 조회</li>
</ul>
<h4 id="객체의-연관관계">객체의 연관관계</h4>
<ul>
<li>객체는 참조를 통해 접근</li>
</ul>
<pre><code class="language-java">class Board {
    User user; // 작성자
}</code></pre>
<blockquote>
<p>자바에서는 <code>new Board().getUser()</code>는 가능하지만<br>반대로 User에서 Board로 바로 접근할 수 있는 구조는 자동 제공되지 않음</p>
</blockquote>
<p>→ JPA는 이러한 관계를 객체스럽게 표현할 수 있도록 매핑 기능을 제공</p>
<hr>
<h3 id="3-객체-그래프-탐색">3. 객체 그래프 탐색</h3>
<ul>
<li>객체 지향의 핵심은 <strong>참조를 따라가며 객체를 탐색</strong>하는 것</li>
</ul>
<p>예시 구조</p>
<pre><code>File → Board → User → UserProfile
           |
         Reply</code></pre><pre><code class="language-java">new Board().getUser().getUserProfile();</code></pre>
<ul>
<li>SQL 중심 개발에서는 쿼리를 어디까지 작성했는지(XML mapper 확인) 알지 못하면<br>어느 객체까지 탐색 가능한지 알 수 없다  </li>
<li>결과적으로 예상치 못한 <code>NullPointerException</code>이 발생하기 쉬움  </li>
<li>JPA는 이 객체 그래프 탐색을 자연스럽게 지원</li>
</ul>
<hr>
<h2 id="정리">정리</h2>
<ul>
<li>ORM은 객체와 DB 테이블 간의 괴리를 줄여주는 기술</li>
<li>Hibernate는 ORM의 대표 구현체  </li>
<li>JPA는 ORM 사용을 위한 표준  </li>
<li>Spring Data JPA는 이를 더 쉽게 사용할 수 있도록 한 모듈  </li>
<li>JPA를 사용하면 상속, 연관관계, 객체 그래프 탐색 등 객체지향적인 설계를 유지하면서 DB와 연동 가능</li>
</ul>
<pre><code>ORM = 객체 중심 설계 + SQL 의존성 감소 + 유지보수성 증가</code></pre>]]></description>
        </item>
    </channel>
</rss>