<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>e_jim.log</title>
        <link>https://velog.io/</link>
        <description>Hi~</description>
        <lastBuildDate>Wed, 18 Feb 2026 07:49:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>e_jim.log</title>
            <url>https://velog.velcdn.com/images/e_jim/profile/32b55cff-c20e-4d33-a415-481d6429a5a0/image.PNG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. e_jim.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/e_jim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[createdAt / updatedAt 관리 방식 정리 (JPA vs DB)]]></title>
            <link>https://velog.io/@e_jim/createdAt-updatedAt-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EC%8B%9D-%EC%A0%95%EB%A6%AC-JPA-vs-DB</link>
            <guid>https://velog.io/@e_jim/createdAt-updatedAt-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EC%8B%9D-%EC%A0%95%EB%A6%AC-JPA-vs-DB</guid>
            <pubDate>Wed, 18 Feb 2026 07:49:58 GMT</pubDate>
            <description><![CDATA[<h2 id="1-핵심-개념">1) 핵심 개념</h2>
<ul>
<li><code>createdAt</code>: 레코드(엔티티)가 <strong>처음 생성된 시각</strong></li>
<li><code>updatedAt</code>: 레코드(엔티티)가 <strong>마지막으로 수정된 시각</strong></li>
<li>관리 방식은 크게 2가지:
1) <strong>DB가 자동으로 채움/갱신</strong>
2) <strong>애플리케이션(JPA/Hibernate)이 채움/갱신</strong></li>
</ul>
<hr>
<h2 id="2-방식-a-db에서-관리-default--on-update-등">2) 방식 A: DB에서 관리 (DEFAULT / ON UPDATE 등)</h2>
<h3 id="예시-코드">예시 코드</h3>
<pre><code class="language-java">@Column(
  columnDefinition = &quot;TIMESTAMP DEFAULT CURRENT_TIMESTAMP&quot;,
  insertable = false,
  updatable = false
)
private LocalDateTime createdAt;

@Column(
  columnDefinition = &quot;TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP&quot;,
  insertable = false,
  updatable = false
)
private LocalDateTime updatedAt;</code></pre>
<p><strong>동작 방식</strong></p>
<ul>
<li>INSERT/UPDATE 시점에 DB가 createdAt/updatedAt을 자동으로 세팅/갱신</li>
<li>insertable=false, updatable=false로 인해 JPA는 해당 컬럼을 INSERT/UPDATE SQL에 포함하지 않음</li>
<li><em>→ 애플리케이션에서 값을 넣거나 변경할 수 없음*</em></li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>시간 기준이 DB 서버로 통일(멀티 앱 인스턴스에서도 일관성)</li>
<li>애플리케이션 실수로 시간값을 변경하기 어려움(강제력)</li>
</ul>
<p><strong>단점 / 주의사항</strong></p>
<ul>
<li>DB 종속성이 큼</li>
<li>ON UPDATE CURRENT_TIMESTAMP는 주로 <code>MySQL</code> 계열에 친화적</li>
<li><code>PostgreSQL</code>은 보통 트리거로 처리</li>
<li><code>H2</code> 등은 모드에 따라 동작이 달라질 수 있음</li>
<li>저장 직후 엔티티 객체의 createdAt/updatedAt이 즉시 채워지지 않을 수 있음</li>
<li>DB가 채운 값을 엔티티에서 바로 쓰려면 flush() 후 refresh() 또는 재조회가 필요할 수 있음</li>
<li>DB 자동 갱신은 “UPDATE가 실제로 발생”해야 반영됨(변경이 없으면 update가 안 나갈 수 있음)</li>
</ul>
<h2 id="3-방식-b-애플리케이션jpa에서-관리-prepersist--preupdate">3) 방식 B: 애플리케이션(JPA)에서 관리 (@PrePersist / @PreUpdate)</h2>
<h3 id="예시-코드-1">예시 코드</h3>
<pre><code class="language-java">@Column(name = &quot;created_at&quot;, nullable = false, updatable = false)
private LocalDateTime createdAt;

@Column(name = &quot;updated_at&quot;, nullable = false)
private LocalDateTime updatedAt;

@PrePersist
protected void onCreate() {
  LocalDateTime now = LocalDateTime.now();
  this.createdAt = now;
  this.updatedAt = now;
}

@PreUpdate
protected void onUpdate() {
  this.updatedAt = LocalDateTime.now();
}</code></pre>
<p><strong>동작 방식</strong></p>
<ul>
<li>엔티티가 처음 저장되기 직전: <code>@PrePersist</code> 실행 → <code>createdAt</code>, <code>updatedAt</code> 세팅</li>
<li>엔티티가 수정되기 직전: <code>@PreUpdate</code> 실행 → <code>updatedAt</code> 갱신</li>
<li>JPA Dirty Checking으로 <strong>“변경이 감지되어 UPDATE가 발생”</strong>할 때 <code>@PreUpdate</code>가 호출됨</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>DB 종류에 독립적(이식성 좋음): MySQL/PG/H2 등 대부분 동일하게 동작</li>
<li>저장 직후에도 엔티티에 값이 들어있어 응답/테스트가 편함</li>
<li><code>nullable=false</code> 등 제약을 코드로 명확히 표현 가능</li>
</ul>
<p><strong>단점 / 주의사항</strong></p>
<ul>
<li>시간 기준이 애플리케이션 서버 시간</li>
<li>멀티 서버 환경에서는 NTP 등으로 시간 동기화가 필요(보통 운영에서 해결)</li>
<li><code>save()</code>를 호출했다고 항상 <code>@PreUpdate</code>가 실행되는 것은 아님</li>
<li>실제로 값 변경이 없으면 UPDATE가 발생하지 않아 콜백도 실행되지 않을 수 있음</li>
</ul>
<blockquote>
<h3 id="ntp란">NTP란?</h3>
</blockquote>
<ul>
<li><strong>NTP</strong>는 <strong>Network Time Protocol</strong>의 약자이며,
서버/컴퓨터들이 <strong>현재 시간을 정확하게 동기화</strong>하도록 해주는 표준 프로토콜이다.</li>
<li><blockquote>
<p><code>여러 서버의 “시계”를 같은 시간으로 맞춰주는 동기화 시스템</code></p>
</blockquote>
<h4 id="왜-필요할까">왜 필요할까?</h4>
</li>
<li>서버가 여러 대인 환경에서는 각 서버의 시스템 시간이 조금씩 어긋날 수 있다.</li>
<li>이런 상태에서 <code>LocalDateTime.now()</code>로 <code>createdAt/updatedAt</code>을 찍으면:<ul>
<li>서버 A와 서버 B의 시간이 달라 <strong>시간 순서가 뒤바뀌거나</strong></li>
<li>로그/정렬/감사 기록이 <strong>일관되지 않게</strong> 될 수 있다.<h4 id="ntp가-해주는-일">NTP가 해주는 일</h4>
</li>
</ul>
</li>
<li>NTP 서버(시간 기준 서버)와 주기적으로 통신해
<strong>각 서버의 시간을 동일한 기준(보통 UTC)으로 맞춘다.</strong><h4 id="실무에서는-보통-어떻게-쓰나">실무에서는 보통 어떻게 쓰나?</h4>
</li>
<li>OS 수준에서 자동으로 동기화가 설정되어 있는 경우가 많다.<ul>
<li>Linux: <code>chrony</code> 또는 <code>ntpd</code></li>
<li>클라우드(AWS/GCP/Azure)도 기본적인 시간 동기화 체계를 제공하는 편</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS 기초] HTTP 프로토콜 핵심 정리]]></title>
            <link>https://velog.io/@e_jim/CS-%EA%B8%B0%EC%B4%88-HTTP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%ED%95%B5%EC%8B%AC-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@e_jim/CS-%EA%B8%B0%EC%B4%88-HTTP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%ED%95%B5%EC%8B%AC-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 22 Jan 2026 04:21:13 GMT</pubDate>
            <description><![CDATA[<p>오늘 공부한 네트워크 계층의 핵심, <strong>HTTP(HyperText Transfer Protocol)</strong>의 주요 개념을 정리합니다.</p>
<hr>
<h2 id="1-http란">1. HTTP란?</h2>
<ul>
<li><strong>정의</strong>: 웹 브라우저(클라이언트)와 웹 서버 간에 데이터를 주고받기 위한 <strong>응용 계층(Application Layer)</strong> 프로토콜입니다.</li>
<li><strong>특징</strong>: 주로 <strong>TCP/IP</strong> 위에서 동작하며, 기본 포트는 <strong>80</strong>번을 사용합니다.</li>
</ul>
<hr>
<h2 id="2-핵심-특징-면접-단골-질문-⭐">2. 핵심 특징 (면접 단골 질문 ⭐)</h2>
<h3 id="①-비연결성-connectionless">① 비연결성 (Connectionless)</h3>
<ul>
<li>클라이언트가 요청을 보내고 서버가 응답을 마치면 즉시 연결을 끊는 방식입니다.</li>
<li><strong>장점</strong>: 서버 자원을 효율적으로 관리할 수 있습니다.</li>
<li><strong>단점</strong>: 매 요청마다 새로운 연결을 맺는 오버헤드가 발생합니다. (HTTP/1.1부터 <code>Keep-Alive</code>로 보완)</li>
</ul>
<h3 id="②-무상태-stateless">② 무상태 (Stateless)</h3>
<ul>
<li>서버가 클라이언트의 이전 상태를 기억하지 않습니다.</li>
<li><strong>장점</strong>: 서버의 확장성이 높습니다 (어느 서버가 응답해도 상관없음).</li>
<li><strong>단점</strong>: 로그인을 유지하기 위해 <strong>쿠키(Cookie), 세션(Session), 토큰(JWT)</strong> 같은 추가 메커니즘이 필요합니다.</li>
</ul>
<hr>
<h2 id="3-http-메시지-구조">3. HTTP 메시지 구조</h2>
<p>[Image of HTTP request and response message structure]</p>
<ol>
<li><strong>시작줄 (Start Line / Status Line)</strong><ul>
<li>요청: <code>메소드 URL 버전</code> (예: <code>GET /index.html HTTP/1.1</code>)</li>
<li>응답: <code>버전 상태코드 상태문구</code> (예: <code>HTTP/1.1 200 OK</code>)</li>
</ul>
</li>
<li><strong>헤더 (Header)</strong>: 메시지 전송에 필요한 부가 정보 (Host, User-Agent, Content-Type 등)</li>
<li><strong>본문 (Body)</strong>: 실제 전송할 데이터 (HTML, JSON, 이미지 등)</li>
</ol>
<hr>
<h2 id="4-http-주요-메소드">4. HTTP 주요 메소드</h2>
<table>
<thead>
<tr>
<th align="left">메소드</th>
<th align="left">역할</th>
<th align="left">특징</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>GET</strong></td>
<td align="left">리소스 조회</td>
<td align="left">URL에 데이터 노출, 캐싱 가능, 멱등성(O)</td>
</tr>
<tr>
<td align="left"><strong>POST</strong></td>
<td align="left">리소스 생성</td>
<td align="left">Body에 데이터 포함, 서버 상태 변경, 멱등성(X)</td>
</tr>
<tr>
<td align="left"><strong>PUT</strong></td>
<td align="left">리소스 전체 수정</td>
<td align="left">리소스를 완전히 교체, 멱등성(O)</td>
</tr>
<tr>
<td align="left"><strong>PATCH</strong></td>
<td align="left">리소스 부분 수정</td>
<td align="left">리소스의 일부만 변경, 멱등성(X)</td>
</tr>
<tr>
<td align="left"><strong>DELETE</strong></td>
<td align="left">리소스 삭제</td>
<td align="left">특정 리소스 삭제 요청, 멱등성(O)</td>
</tr>
</tbody></table>
<hr>
<h2 id="5-필수-응답-상태-코드-status-code">5. 필수 응답 상태 코드 (Status Code)</h2>
<ul>
<li><strong>2xx (Success)</strong>: 성공. <code>200(OK)</code>, <code>201(Created)</code></li>
<li><strong>3xx (Redirection)</strong>: 요청 완료를 위해 추가 동작 필요. <code>301(영구 이동)</code>, <code>302(임시 이동)</code></li>
<li><strong>4xx (Client Error)</strong>: 클라이언트 요청 오류. <code>400(Bad Request)</code>, <code>401(미인증)</code>, <code>404(Not Found)</code></li>
<li><strong>5xx (Server Error)</strong>: 서버 처리 오류. <code>500(Internal Server Error)</code>, <code>503(서비스 이용 불가)</code></li>
</ul>
<hr>
<h2 id="6-http의-발전-요약">6. HTTP의 발전 요약</h2>
<ul>
<li><strong>HTTP/1.1</strong>: 커넥션 재사용(Keep-Alive) 도입.</li>
<li><strong>HTTP/2.0</strong>: 멀티플렉싱을 통한 속도 개선.</li>
<li><strong>HTTP/3.0</strong>: <strong>UDP(QUIC)</strong> 기반으로 지연 시간 최소</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React - 기초]]></title>
            <link>https://velog.io/@e_jim/React-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@e_jim/React-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Fri, 09 May 2025 07:56:13 GMT</pubDate>
            <description><![CDATA[<h2 id="1-react-컴포넌트component">1. React 컴포넌트(Component)</h2>
<ul>
<li><strong>함수형 컴포넌트</strong> vs <strong>클래스형 컴포넌트</strong>  <ul>
<li>함수형: <code>function MyComp() { … }</code> 또는 <code>const MyComp = () =&gt; { … }</code>  </li>
<li>클래스형: <code>class MyComp extends React.Component { render() { … } }</code> (현재는 거의 사용하지 않음)  </li>
</ul>
</li>
<li>컴포넌트는 <strong>재사용 가능한 UI 조각</strong>  </li>
<li>부모–자식 관계로 컴포넌트를 조립하여 최종 UI를 구성  </li>
</ul>
<hr>
<h2 id="2-jsx-문법-심화">2. JSX 문법 심화</h2>
<ul>
<li><strong>한 번에 하나의 최상위 태그</strong>로 감싸기 (<code>&lt;div&gt;</code> 또는 <code>&lt;&gt;&lt;/&gt;</code>)  </li>
<li>JavaScript 표현식은 <code>{}</code> 안에 작성  </li>
<li>일부 속성 이름 변경:  <ul>
<li><code>class</code> → <code>className</code>  </li>
<li><code>for</code> → <code>htmlFor</code>  </li>
</ul>
</li>
<li><strong>Self-Closing 태그</strong>: <code>&lt;input /&gt;</code>, <code>&lt;img /&gt;</code> 등 반드시 닫기  </li>
<li><strong>조건부 렌더링</strong>:  <ul>
<li>삼항 연산자: <code>{cond ? &lt;A /&gt; : &lt;B /&gt;}</code>  </li>
<li><code>&amp;&amp;</code> 연산자: <code>{cond &amp;&amp; &lt;A /&gt;}</code>  </li>
</ul>
</li>
<li><strong>리스트 렌더링</strong>:  <pre><code class="language-jsx">  {items.map(item =&gt; &lt;li key={item.id}&gt;{item.name}&lt;/li&gt;)}</code></pre>
</li>
</ul>
<hr>
<h2 id="3-props">3. Props</h2>
<ul>
<li><p>부모 → 자식으로 <strong>데이터(속성)</strong> 전달</p>
</li>
<li><p>컴포넌트 함수의 매개변수(<code>props</code>)로 받음</p>
</li>
<li><p><strong>구조 분해 할당</strong>으로 간결화:</p>
<pre><code class="language-jsx">function Profile({ name, age }) { … }</code></pre>
</li>
<li><p><strong>전개 연산자</strong>를 이용해 객체를 한 번에 넘길 수도 있음:</p>
<pre><code class="language-jsx">&lt;Profile {...person} /&gt;</code></pre>
</li>
<li><p><code>defaultProps</code> 로 기본값 설정</p>
</li>
</ul>
<hr>
<h2 id="4-state">4. State</h2>
<h3 id="✅-state란">✅ State란?</h3>
<ul>
<li><strong>컴포넌트 내부에서 관리되는 동적인 데이터</strong><br>== <strong>컴포넌트 내부에서 관리하는 “변할 수 있는 값”</strong></li>
<li>시간이 지남에 따라 바뀔 수 있는 값 (ex. 버튼 클릭 수, 입력 값, 토글 상태 등)</li>
<li><strong>컴포넌트가 리렌더링되는 이유</strong>가 되는 핵심 데이터</li>
</ul>
<hr>
<h3 id="🛠️-사용법-usestate-훅">🛠️ 사용법: <code>useState</code> 훅</h3>
<pre><code class="language-jsx">import { useState } from &#39;react&#39;;

function Counter() {
  const [count, setCount] = useState(0); // 초기값 0

  return (
    &lt;div&gt;
      &lt;p&gt;현재 카운트: {count}&lt;/p&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;+1&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<ul>
<li><p><code>useState(초기값)</code> 호출 시 → <code>[값, 설정함수]</code> 배열 반환</p>
</li>
<li><p><code>setCount(newValue)</code> 를 호출하면:</p>
<ol>
<li>React가 내부 state 값을 변경</li>
<li>변경된 값을 기반으로 컴포넌트를 <strong>자동 리렌더링</strong></li>
</ol>
</li>
</ul>
<hr>
<h3 id="🧠-state의-메모리-구조">🧠 state의 메모리 구조</h3>
<ul>
<li><code>useState</code>는 React 내부의 <strong>Hooks 메모리 저장소</strong>에 상태값을 저장</li>
<li>이 저장소는 <strong>브라우저의 메모리(RAM)</strong> 위에 위치하며, <strong>가상 DOM과 함께 관리됨</strong></li>
<li><code>setState</code>를 사용하지 않고 변수만 바꾸면 리렌더링되지 않음
→ React는 <code>setState</code> 호출을 기준으로 변경을 감지</li>
</ul>
<hr>
<h3 id="🔁-재렌더링-vs-초기화">🔁 재렌더링 vs 초기화</h3>
<ul>
<li>컴포넌트가 리렌더링될 때도 <code>useState</code>의 값은 유지됨</li>
<li><code>useState(초기값)</code>의 초기값은 <strong>처음 마운트 시 딱 한 번만 사용됨</strong></li>
<li>상태는 브라우저가 꺼지거나 새로고침되기 전까지 유지됨</li>
</ul>
<hr>
<h3 id="⚠️-상태-변경은-비동기일-수-있음">⚠️ 상태 변경은 비동기일 수 있음</h3>
<pre><code class="language-jsx">setCount(count + 1);
setCount(count + 1);</code></pre>
<p>이렇게 두 번 호출해도 값이 1씩 증가하지 않음.</p>
<br> 
👉 해결법:

<pre><code class="language-jsx">setCount(prev =&gt; prev + 1);
setCount(prev =&gt; prev + 1); // 이러면 2 증가</code></pre>
<hr>
<h3 id="📌-주의할-점-정리">📌 주의할 점 정리</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>직접 값 변경 금지</td>
<td><code>count = count + 1</code> 은 동작 안 함</td>
</tr>
<tr>
<td>set함수 사용 필수</td>
<td>항상 <code>setState</code>로 변경해야 반영됨</td>
</tr>
<tr>
<td>비동기 처리됨</td>
<td>즉시 반영되지 않고, React 스케줄링 후 반영</td>
</tr>
<tr>
<td>초기값은 한 번만</td>
<td><code>useState(초기값)</code>의 초기값은 컴포넌트가 처음 실행될 때만 적용</td>
</tr>
</tbody></table>
<hr>
<h3 id="💡-상태가-필요한-경우-예시">💡 상태가 필요한 경우 예시</h3>
<ul>
<li>버튼 클릭 수</li>
<li>입력 폼 제어값</li>
<li>모달 열림/닫힘 상태</li>
<li>탭 선택 상태</li>
<li>API 데이터 로딩 상태</li>
</ul>
<hr>
<h2 id="5-state-저장-및-렌더링-흐름">5. State 저장 및 렌더링 흐름</h2>
<ol>
<li><strong>마운트 시</strong> <code>useState(initial)</code>의 <code>initial</code>은 한 번만 사용</li>
<li>이후 렌더링에서는 <strong>React가 내부 메모리에 저장한 값</strong>을 꺼내 사용</li>
<li>상태 변경 → React가 가상 DOM(diff) 비교 → 변경된 부분만 실제 DOM에 반영</li>
<li>상태는 <strong>브라우저 새로고침 전까지</strong> 유지됨</li>
</ol>
<hr>
<h2 id="6-브라우저-·-react-·-메모리-관계">6. 브라우저 · React · 메모리 관계</h2>
<ul>
<li><strong>노트북 물리적 RAM</strong>: OS와 모든 앱이 공유</li>
<li><strong>브라우저 RAM</strong>: 브라우저 프로세스(탭)별로 JS 실행, DOM/CSS/가상 DOM, React state 저장</li>
<li>React state는 <strong>브라우저 프로세스의 메모리</strong>(물리적 RAM 할당 영역)에 저장</li>
<li>브라우저가 React 컴포넌트를 실행·관리하며, 화면은 <strong>실제 DOM</strong>을 통해 렌더링</li>
</ul>
<hr>
<h2 id="7-주요-키워드">7. 주요 키워드</h2>
<ul>
<li>함수형 컴포넌트, 클래스형 컴포넌트</li>
<li>JSX, Fragment, 표현식 <code>{}</code>, self-closing</li>
<li>Props, 구조분해, 전개 연산자, defaultProps</li>
<li>State, <code>useState</code>, setState, 리렌더링</li>
<li>가상 DOM(diff), 실제 DOM 업데이트</li>
<li>브라우저 RAM vs 물리적 RAM</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL - Fulltext Index]]></title>
            <link>https://velog.io/@e_jim/SQL-Fulltext-Index</link>
            <guid>https://velog.io/@e_jim/SQL-Fulltext-Index</guid>
            <pubDate>Thu, 08 May 2025 06:45:05 GMT</pubDate>
            <description><![CDATA[<h1 id="🧠-fulltext-index">🧠 Fulltext Index</h1>
<p>MySQL에서 대량의 텍스트 데이터를 빠르게 검색하려면 <code>FULLTEXT INDEX</code>가 효과적입니다. 이 글에서는 Fulltext 인덱스의 개념, 사용법, 검색 모드, Boolean 연산자 등을 예시와 함께 정리합니다.</p>
<hr>
<h2 id="🔍-fulltext-index란">🔍 Fulltext Index란?</h2>
<p><code>FULLTEXT INDEX</code>는 문자열 컬럼에 대한 <strong>자연어 기반 검색</strong>을 빠르게 수행할 수 있도록 지원하는 <strong>전용 인덱스</strong>입니다.</p>
<ul>
<li>적용 가능한 컬럼: <code>CHAR</code>, <code>VARCHAR</code>, <code>TEXT</code></li>
<li>지원 엔진: <strong>InnoDB</strong>, <strong>MyISAM</strong></li>
<li>MySQL 5.6+부터 InnoDB에서도 사용 가능</li>
</ul>
<hr>
<h2 id="🛠️-생성-방법">🛠️ 생성 방법</h2>
<pre><code class="language-sql">-- 테이블 생성 시
CREATE TABLE articles (
  id INT PRIMARY KEY,
  title VARCHAR(100),
  body TEXT,
  FULLTEXT(title, body)
);

-- 기존 테이블에 추가
ALTER TABLE articles ADD FULLTEXT(title, body);</code></pre>
<hr>
<h2 id="🔍-기본-사용법">🔍 기본 사용법</h2>
<pre><code class="language-sql">SELECT * FROM articles
WHERE MATCH(title, body) AGAINST(&#39;database&#39;);</code></pre>
<ul>
<li><code>MATCH(...)</code>: 검색 대상 컬럼 지정</li>
<li><code>AGAINST(&#39;검색어&#39;)</code>: 찾을 문자열 지정</li>
</ul>
<hr>
<h2 id="⚙️-검색-모드">⚙️ 검색 모드</h2>
<pre><code class="language-sql">AGAINST(&#39;검색어&#39; IN NATURAL LANGUAGE MODE);
AGAINST(&#39;검색어&#39; IN BOOLEAN MODE);
AGAINST(&#39;검색어&#39; WITH QUERY EXPANSION);</code></pre>
<table>
<thead>
<tr>
<th>모드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>NATURAL LANGUAGE MODE</strong></td>
<td>기본값. 불용어(stopwords) 제외, 연산자 미지원</td>
</tr>
<tr>
<td><strong>BOOLEAN MODE</strong></td>
<td><code>+</code>, <code>-</code>, <code>&quot;...&quot;</code>, <code>*</code> 등 연산자 사용 가능</td>
</tr>
<tr>
<td><strong>WITH QUERY EXPANSION</strong></td>
<td>첫 검색 결과를 바탕으로 자동으로 키워드를 확장하여 재검색</td>
</tr>
</tbody></table>
<hr>
<h2 id="✅-boolean-mode-연산자-정리">✅ BOOLEAN MODE 연산자 정리</h2>
<table>
<thead>
<tr>
<th>연산자</th>
<th>의미</th>
<th>예시</th>
<th>설명</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td><code>+</code></td>
<td><strong>필수 포함 (AND)</strong></td>
<td><code>+mysql +index</code></td>
<td>두 단어 모두 포함</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>-</code></td>
<td><strong>제외 (NOT)</strong></td>
<td><code>mysql -oracle</code></td>
<td>oracle이 없는 문서만</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>*</code></td>
<td><strong>접두사 검색</strong></td>
<td><code>dev*</code></td>
<td><code>developer</code>, <code>devops</code> 등</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>&quot;</code></td>
<td><strong>정확히 일치하는 구문</strong></td>
<td><code>&quot;database index&quot;</code></td>
<td>해당 구문 그대로 포함</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>~</code></td>
<td><strong>관련성 낮춤</strong></td>
<td><code>~oracle</code></td>
<td>포함해도 되지만 점수 낮게</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>&gt;</code> / <code>&lt;</code></td>
<td><strong>중요도 조절</strong></td>
<td><code>&gt;mysql &lt;oracle</code></td>
<td>mysql 더 중요하게 평가</td>
<td></td>
<td></td>
</tr>
<tr>
<td>`</td>
<td>`</td>
<td><strong>OR 조건</strong></td>
<td>`mysql</td>
<td>oracle`</td>
<td>둘 중 하나 포함</td>
</tr>
<tr>
<td><code>()</code></td>
<td><strong>그룹 조건</strong></td>
<td><code>+(mysql oracle)</code></td>
<td>mysql 또는 oracle 필수 포함</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<hr>
<h2 id="💡-예제">💡 예제</h2>
<pre><code class="language-sql">SELECT * FROM articles
WHERE MATCH(title, body)
AGAINST(&#39;+database -&quot;oracle cloud&quot;&#39; IN BOOLEAN MODE);</code></pre>
<ul>
<li><code>database</code>는 <strong>반드시 포함</strong></li>
<li><code>&quot;oracle cloud&quot;</code>는 <strong>제외</strong></li>
<li>정교한 검색이 가능해짐</li>
</ul>
<hr>
<h2 id="🚧-주의사항">🚧 주의사항</h2>
<ul>
<li>단어 길이가 <strong>3자 이하</strong>이거나 **불용어(stopwords)**는 검색되지 않을 수 있음</li>
<li><code>*</code>는 <strong>접두사만</strong> 지원 (뒤에만 붙일 수 있음)</li>
<li>관련성 점수는 <code>MATCH ... AGAINST</code>에만 적용 가능</li>
</ul>
<hr>
<h2 id="📚-마무리">📚 마무리</h2>
<p><code>FULLTEXT INDEX</code>는 대량의 텍스트 데이터에서 빠르고 정교한 검색이 필요한 서비스(예: 게시판, 문서 검색)에 매우 유용합니다. 특히 <code>BOOLEAN MODE</code>의 조합을 이해하고 잘 활용하면, SQL에서 강력한 검색 기능을 구현할 수 있습니다.</p>
<hr>
<p>궁금한 점이나 실습 예제가 필요하다면 댓글로 알려주세요 😊
Happy Querying!</p>
<hr>
<p>필요하시면 이 글에 추가로 실습 예제나 이미지도 만들어드릴 수 있어요. JSON 검색과의 연계도 가능하니 원하시면 알려주세요!</p>
<hr>
<h2 id="⚙️-innodb-fulltext-index-튜닝-및-내부-정보-조회">⚙️ InnoDB Fulltext Index 튜닝 및 내부 정보 조회</h2>
<p>MySQL의 InnoDB 스토리지 엔진에서 Fulltext 인덱스를 보다 <strong>정밀하게 제어하거나 디버깅</strong>하려면 아래와 같은 <strong>시스템 변수 및 정보 스키마 테이블</strong>을 활용할 수 있습니다.</p>
<hr>
<h3 id="🔧-innodb_ft_aux_table-설정">🔧 <code>innodb_ft_aux_table</code> 설정</h3>
<pre><code class="language-sql">SET GLOBAL innodb_ft_aux_table = &#39;testdb/Notice&#39;;</code></pre>
<ul>
<li>InnoDB의 Fulltext 인덱스를 <strong>분석하거나 조사</strong>할 때 사용하는 <strong>보조 테이블 설정</strong>입니다.</li>
<li><code>&#39;DB명/테이블명&#39;</code> 형식으로 지정해야 합니다.</li>
<li><strong>단일 테이블만 지정 가능</strong> (복수 설정은 불가능)</li>
</ul>
<p>이 설정을 통해 다음과 같은 테이블들을 조회할 수 있게 됩니다:</p>
<pre><code class="language-sql">SELECT * FROM information_schema.innodb_ft_index_table;
SELECT * FROM information_schema.innodb_ft_index_cache;</code></pre>
<hr>
<h3 id="📦-fulltext-index-테이블-정보-조회">📦 Fulltext Index 테이블 정보 조회</h3>
<pre><code class="language-sql">SHOW VARIABLES LIKE &#39;innodb_ft_aux%&#39;;</code></pre>
<pre><code class="language-sql">SELECT * FROM information_schema.innodb_ft_index_table;</code></pre>
<p>이 명령어들을 통해 현재 Fulltext 인덱스에 등록된 단어, 문서 ID, 내부 구조 등을 확인할 수 있습니다.</p>
<hr>
<h3 id="🧹-fulltext-index-최적화-rebuild">🧹 Fulltext Index 최적화 (Rebuild)</h3>
<p>Fulltext 인덱스를 <strong>정리하거나 재구성</strong>하려면 다음과 같은 절차를 따릅니다:</p>
<pre><code class="language-sql">-- 1. 최적화 모드 활성화
SET GLOBAL innodb_optimize_fulltext_only = ON;

-- 2. 인덱스 최적화 실행
OPTIMIZE TABLE testdb.Notice;

-- 3. 모드 비활성화
SET GLOBAL innodb_optimize_fulltext_only = OFF;</code></pre>
<ul>
<li><code>OPTIMIZE TABLE</code> 실행 시 <code>innodb_optimize_fulltext_only</code>가 <code>ON</code>이면 Fulltext 인덱스만 최적화합니다.</li>
<li><strong>삭제된 단어</strong>, <strong>중복된 토큰</strong> 등을 제거하여 인덱스 크기 감소 및 성능 향상에 도움됩니다.</li>
</ul>
<hr>
<h2 id="📝-요약">📝 요약</h2>
<table>
<thead>
<tr>
<th>기능</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>innodb_ft_aux_table</code></td>
<td>Fulltext 인덱스 내부 구조를 조회할 테이블 지정</td>
</tr>
<tr>
<td><code>innodb_ft_index_table</code>, <code>innodb_ft_index_cache</code></td>
<td>현재 인덱스에 저장된 토큰과 구조를 확인</td>
</tr>
<tr>
<td><code>innodb_optimize_fulltext_only</code></td>
<td>Fulltext 인덱스만 대상으로 OPTIMIZE 수행 가능</td>
</tr>
</tbody></table>
<hr>
<p>이러한 고급 기능들은 <strong>검색 품질 개선</strong>, <strong>성능 디버깅</strong>, <strong>인덱스 재정비</strong>에 매우 유용합니다. 대규모 게시판이나 문서 검색 시스템에서는 정기적인 점검 루틴에 포함시키는 것도 좋은 전략입니다.</p>
<hr>
<p>필요하다면 위 기능들을 포함한 <strong>Fulltext Index 점검 스크립트</strong>도 만들어드릴 수 있습니다. 원하시나요?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mysql - 고급 기법]]></title>
            <link>https://velog.io/@e_jim/Mysql-%EA%B3%A0%EA%B8%89-%EA%B8%B0%EB%B2%95</link>
            <guid>https://velog.io/@e_jim/Mysql-%EA%B3%A0%EA%B8%89-%EA%B8%B0%EB%B2%95</guid>
            <pubDate>Wed, 07 May 2025 04:20:55 GMT</pubDate>
            <description><![CDATA[<h1 id="📊-sql-고급-집계-정리-cte-윈도우-함수-rollup-pivot">📊 SQL 고급 집계 정리: CTE, 윈도우 함수, ROLLUP, PIVOT</h1>
<h2 id="1-✅-with-cte-common-table-expression">1. ✅ WITH CTE (Common Table Expression)</h2>
<h3 id="🔍-개념">🔍 개념</h3>
<p>CTE는 서브쿼리를 <strong>재사용 가능하고 가독성 좋게</strong> 만든 문법입니다.
복잡한 쿼리를 계층적으로 구성할 수 있습니다.</p>
<h3 id="🛠-사용법">🛠 사용법</h3>
<pre><code class="language-sql">WITH cte_name AS (
    SELECT ...
)
SELECT ...
FROM cte_name;</code></pre>
<h3 id="📌-예시">📌 예시</h3>
<pre><code class="language-sql">WITH HighSalaryEmp AS (
    SELECT *
    FROM Emp
    WHERE salary &gt; 3000
)
SELECT ename, dept
FROM HighSalaryEmp;</code></pre>
<hr>
<h2 id="2-🔁-윈도우-함수-window-functions">2. 🔁 윈도우 함수 (Window Functions)</h2>
<h3 id="🔍-개념-1">🔍 개념</h3>
<p>윈도우 함수는 집계함수처럼 동작하지만 <strong>그룹핑하지 않고</strong>도 각 행에 대해 누적합, 순위 등을 계산할 수 있습니다.</p>
<h3 id="🛠-사용법-1">🛠 사용법</h3>
<pre><code class="language-sql">SELECT ename, salary,
       SUM(salary) OVER (PARTITION BY dept) AS dept_total
FROM Emp;</code></pre>
<h3 id="📌-자주-쓰는-함수">📌 자주 쓰는 함수</h3>
<ul>
<li><code>ROW_NUMBER()</code> : 전체 결과에서 파티션 기준 출력되는 행 번호(순번)</li>
<li><code>RANK()</code> : 값의 랭킹(순위), 공동 랭킹은 동일값을 보이며 다음 랭킹은 공동 랭킹을 건너뛰어 표시</li>
<li><code>DENSE_RANK()</code> : RANK() 함수와 동일한 기능이지만 공동 랭킹을 건너뛰지 않는다.</li>
<li><code>SUM() OVER(...)</code></li>
<li><code>AVG() OVER(...)</code></li>
<li><code>LAG(n)</code> : 현재 행 기준 앞(이전) n 번째 값</li>
<li><code>LEAD(n)</code> : 현재 행 기준 뒤(다음) n 번째 값</li>
</ul>
<h3 id="📌-예시-1">📌 예시</h3>
<pre><code class="language-sql">SELECT ename, dept, salary,
       RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_rank
FROM Emp;</code></pre>
<hr>
<h2 id="3-📈-group-by-with-rollup">3. 📈 GROUP BY WITH ROLLUP</h2>
<h3 id="🔍-개념-2">🔍 개념</h3>
<p><code>ROLLUP</code>은 그룹핑된 컬럼을 기반으로 <strong>소계, 총계</strong>를 자동 계산합니다.</p>
<h3 id="🛠-사용법-2">🛠 사용법</h3>
<pre><code class="language-sql">SELECT dept, job, SUM(salary)
FROM Emp
GROUP BY dept, job WITH ROLLUP;</code></pre>
<h3 id="📌-예시-2">📌 예시</h3>
<pre><code class="language-sql">SELECT p.id pid, d.id did, 
       (CASE WHEN p.id IS NOT NULL THEN MAX(p.dname) ELSE &#39;--총계--&#39; END) AS &#39;상위부서&#39;,
       (CASE WHEN d.id IS NOT NULL THEN MAX(d.dname) ELSE &#39;--소계--&#39; END) AS &#39;하위부서&#39;,
       FORMAT(SUM(e.salary), 0) AS &#39;급여합&#39;
FROM Dept p
INNER JOIN Dept d ON p.id = d.pid
INNER JOIN Emp e ON e.dept = d.id
GROUP BY p.id, d.id
WITH ROLLUP;</code></pre>
<p><img src="https://velog.velcdn.com/images/e_jim/post/ffe44d14-9f55-4d51-b21f-3ff9a2a44298/image.png" alt=""></p>
<hr>
<h2 id="4-🔄-pivot">4. 🔄 PIVOT</h2>
<h3 id="🔍-개념-3">🔍 개념</h3>
<p>행 → 열로 전환해 <strong>집계 데이터를 보기 좋게 가공</strong>하는 방식입니다.
MySQL에는 직접적인 <code>PIVOT</code> 구문은 없지만, <strong>CASE WHEN + GROUP BY</strong>로 구현합니다.</p>
<h3 id="🛠-사용법-3">🛠 사용법</h3>
<pre><code class="language-sql">SELECT dept,
       SUM(CASE WHEN job = &#39;Manager&#39; THEN salary ELSE 0 END) AS Manager,
       SUM(CASE WHEN job = &#39;Clerk&#39; THEN salary ELSE 0 END) AS Clerk,
       SUM(CASE WHEN job = &#39;Sales&#39; THEN salary ELSE 0 END) AS Sales
FROM Emp
GROUP BY dept;</code></pre>
<hr>
<h2 id="🧠-정리-요약">🧠 정리 요약</h2>
<table>
<thead>
<tr>
<th>기술</th>
<th>목적</th>
<th>핵심 특징</th>
</tr>
</thead>
<tbody><tr>
<td><code>WITH CTE</code></td>
<td>복잡 쿼리 가독성 향상</td>
<td>서브쿼리 대체, 재사용 가능</td>
</tr>
<tr>
<td>윈도우 함수</td>
<td>누적, 순위 등 개별 행 통계 계산</td>
<td><code>OVER()</code> 사용, 그룹 유지됨</td>
</tr>
<tr>
<td><code>ROLLUP</code></td>
<td>소계, 총계 자동 계산</td>
<td><code>GROUP BY ... WITH ROLLUP</code> 사용</td>
</tr>
<tr>
<td>PIVOT</td>
<td>행 → 열 전환, 가독성 향상</td>
<td><code>CASE WHEN</code> + <code>GROUP BY</code> 방식</td>
</tr>
</tbody></table>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[MySql - Cursor
]]></title>
            <link>https://velog.io/@e_jim/MySql-Cursor</link>
            <guid>https://velog.io/@e_jim/MySql-Cursor</guid>
            <pubDate>Mon, 05 May 2025 09:28:20 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-cursor">📌 Cursor</h1>
<blockquote>
<p><strong>결과 집합(Result Set)을 한 행(row)씩 순차적으로 처리할 수 있게 해주는 도구</strong></p>
</blockquote>
<ul>
<li>SELECT 결과로 여러 행이 나왔을 때, 
이를 하나씩 꺼내어 반복 처리할 수 있도록 하는 반복 포인터라 생각하면 쉽다.</li>
</ul>
<p><br><br></p>
<h2 id="✅-cursor를-사용하는-이유">✅ Cursor를 사용하는 이유</h2>
<p>일반적으로 SQL은 한 번에 여러 행을 처리하지만 어떤 작업은 &quot;하나씩&quot; 처리해야 할 때가 있다.</p>
<blockquote>
<p><strong>Cursor 대표 예시</strong></p>
</blockquote>
<ul>
<li>학생 테이블에서 학생들 하나씩 불러와서, 각각의 학점 계산 결과를 다른 테이블에 기록할 때<blockquote>
<BR></blockquote>
</li>
<li>여러 명에게 개별 알림 메시지를 보낼 때<blockquote>
<BR></blockquote>
</li>
<li>어떤 조건에 따라 하나씩 다른 UPDATE를 해야 할 때</li>
</ul>
<BR>

<h2 id="✅-커서의-기본-흐름-4단계">✅ 커서의 기본 흐름 (4단계)</h2>
<ol>
<li><p><strong>DECLARE CURSOR</strong></p>
<ul>
<li>사용할 커서를 선언합니다. 어떤 SELECT 결과를 순회할 것인지 지정합니다.</li>
</ul>
</li>
<li><p><strong>OPEN CURSOR</strong></p>
<ul>
<li>커서를 열어 결과 집합을 준비합니다.</li>
</ul>
</li>
<li><p><strong>FETCH</strong></p>
<ul>
<li>한 줄씩 데이터를 가져옵니다. 반복문(WHILE 등)과 함께 자주 사용합니다.</li>
</ul>
</li>
<li><p><strong>CLOSE CURSOR</strong></p>
<ul>
<li>커서를 닫습니다. 메모리 누수를 막기 위해 꼭 해야 합니다.</li>
</ul>
</li>
</ol>
<BR>

<h2 id="❗주의-사항">❗주의 사항</h2>
<blockquote>
</blockquote>
<ul>
<li><p>Cursor는 성능이 느릴 수 있다.</p>
<ul>
<li>수천, 수만 건 이상 순회하면 오히려 SQL 자체의 집합 처리 방식이 더 빠르다. <blockquote>
 <br>
</blockquote>
<ul>
<li>커서는 트랜잭션 안에서 사용되며, 에러나 종료 시 반드시 닫아야 한다.<blockquote>
<br></blockquote>
</li>
<li>커서는 Stored Procedure 안에서만 사용 가능하다.</li>
</ul>
</li>
</ul>
<p><br><br></p>
</li>
</ul>
<h2 id="🔚-언제-사용하나">🔚 언제 사용하나?</h2>
<ul>
<li><p>개별 행을 조건에 따라 다른 로직으로 처리할 때</p>
</li>
<li><p>SQL로는 한 번에 처리할 수 없는 절차적 로직이 필요할 때</p>
</li>
<li><p>루프가 필요한 반복 작업을 데이터에 대해 수행해야 할 때</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[MySql - Routine]]></title>
            <link>https://velog.io/@e_jim/MySql-Routine</link>
            <guid>https://velog.io/@e_jim/MySql-Routine</guid>
            <pubDate>Mon, 05 May 2025 08:11:23 GMT</pubDate>
            <description><![CDATA[<h1 id="routine-이란">Routine 이란?</h1>
<blockquote>
<p>MySQL에서 Routine은 저장 프로시저(Stored Procedure)와 함수(Function)를 포함하는 프로그램 코드를 말한다.
--&gt; MySQL에서 반복적으로 사용되는 쿼리나 작업을 반복 실행할 수 있도록 미리 정의해 놓은 코드</p>
</blockquote>
<h3 id="routine을-사용하는-이유">Routine을 사용하는 이유</h3>
<blockquote>
</blockquote>
<ol>
<li><strong>재사용성</strong>: 한 번 정의한 프로시저나 함수를 여러 번 호출하여 사용할 수 있습니다.<blockquote>
</blockquote>
</li>
<li><strong>효율성</strong>: 복잡한 작업을 서버 측에서 처리하므로 클라이언트에서 매번 실행할 필요가 없습니다.<blockquote>
</blockquote>
</li>
<li><strong>보안성</strong>: 중요한 로직을 데이터베이스 내부에 숨길 수 있으며, 사용자에게 필요한 권한만 부여할 수 있습니다.<blockquote>
</blockquote>
</li>
<li><strong>구성 관리</strong>: 여러 SQL 명령을 그룹화하여 관리할 수 있습니다.</li>
</ol>
<p><br><br></p>
<h1 id="stored-procedure">Stored Procedure</h1>
<blockquote>
<p><strong><code>MySQL 서버에 저장된 SQL 코드</code></strong>로, 특정 작업을 수행하는 데 필요한 여러 SQL 문을 그룹화하여 하나의 이름으로 실행할 수 있도록 만든 프로그램
일반적으로 반복적인 작업이나 복잡한 로직을 처리할 때 사용한다.</p>
</blockquote>
<pre><code class="language-sql">DELIMITER $$

CREATE PROCEDURE GetEmployeeDetails(IN emp_id INT)
BEGIN
    SELECT name, department, salary
    FROM employees
    WHERE id = emp_id;
END $$

DELIMITER ;
</code></pre>
<blockquote>
</blockquote>
<p><code>DELIMITER $$</code></p>
<ul>
<li>원래는 종료를 나타내는 문자로 <code>;</code>를 사용하지만, DELIMITER를 선언하면 뒤에 나오는 문자를 종료를 나타내는 문자로 사용하겠다는 의미이다. <br><br><pre><code class="language-sql">CREATE PROCEDURE GetEmployeeDetails(IN emp_id INT)</code></pre>
</li>
<li><blockquote>
<p>input으로 Int 타입의 emp_id를 받는다.
<br><br></p>
</blockquote>
<h3 id="stored-procedure-실행하는-법">Stored Procedure 실행하는 법</h3>
<pre><code class="language-sql">CALL GetEmployeeDetails(101);</code></pre>
</li>
</ul>
<p><br><br></p>
<h1 id="function">Function</h1>
<blockquote>
<p>MySQL에서 함수는 특정 작업을 수행하고 결과를 반환하는 코드
함수는 보통 데이터를 처리하거나 계산한 후, 그 결과를 반환하는 데 사용된다. 
함수는 <code>SELECT 문</code> 안에서 호출할 수 있다.</p>
</blockquote>
<pre><code class="language-sql">DELIMITER $$

CREATE FUNCTION GetEmployeeSalary(emp_id INT) RETURNS DECIMAL(10, 2)
BEGIN
    DECLARE salary DECIMAL(10, 2);

    SELECT salary INTO salary
    FROM employees
    WHERE id = emp_id;

    RETURN salary;
END $$

DELIMITER ;
</code></pre>
<blockquote>
</blockquote>
<pre><code class="language-sql">CREATE FUNCTION GetEmployeeSalary(emp_id INT) RETURNS DECIMAL(10, 2)</code></pre>
<p>-&gt; input으로 Int 타입의 emp_id를 받고, 
전체 자릿수를 10자리로 하되, 그 중 소수점 이하를 2자리로 하는 값을 리턴한다.</p>
<blockquote>
</blockquote>
<p><br><br></p>
<h2 id="❗️stored-procedure-와--function-의----차이">❗️Stored Procedure 와  Function 의    차이</h2>
<p><img src="https://velog.velcdn.com/images/e_jim/post/0a836a8d-a12e-4f37-baaa-acacf0e02e5e/image.png" alt=""></p>
<h3 id="🔍-최종정리"><strong>🔍 최종정리</strong></h3>
<p>절차적인 작업, 데이터 조작, 복잡한 흐름 → <strong>Stored Procedure</strong>
계산, 값 반환, SELECT문 안에서 사용 → <strong>Function</strong></p>
<h4 id="참고">참고</h4>
<ul>
<li><a href="https://velog.io/@juhyeon1114/MySQL-Routine-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-Stored-procedure-Function#-routine%EC%9D%98-%EC%82%AC%EC%9A%A9%EB%B2%95%EA%B3%BC-%EC%98%88%EC%A0%9C">Mysql Routine</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DB] - Index]]></title>
            <link>https://velog.io/@e_jim/DB-Index</link>
            <guid>https://velog.io/@e_jim/DB-Index</guid>
            <pubDate>Tue, 29 Apr 2025 08:22:43 GMT</pubDate>
            <description><![CDATA[<h1 id="index">Index</h1>
<blockquote>
<p><code>Index</code> 는 원하는 데이터를 빠르게 찾기 위한 도구이다.</p>
</blockquote>
<ul>
<li>데이터 조회를 빠르게 하기 위해 사용된다.</li>
<li>데이터 INSERT, UPDATE, DELETE 쿼리 사용 시, 오히려 느려진다.</li>
<li>SQL Server에서 Heap은 다음과 같은 의미를 가진다.
:Heap = 클러스터형 인덱스(Clustered Index)가 없는 테이블의 데이터 저장 구조
<br><br></li>
</ul>
<h1 id="index에-사용하는-자료구조">Index에 사용하는 자료구조</h1>
<h2 id="b-tree">B-Tree</h2>
<blockquote>
<p>정렬된 데이터를 효율적으로 저장하고 검색할 수 있도록 설계된 트리 구조
<strong>특징</strong></p>
</blockquote>
<ul>
<li>일반적으로 데이터베이스 인덱스 OR 파일 시스템에서 사용된다.</li>
<li>조회, 삽입, 삭제 연산 -&gt; (O(log n))</li>
<li><strong>MySQL(InnoDB)에서 사용하는 인덱스 --&gt; B+Tree</strong></li>
</ul>
<h2 id="hash">Hash</h2>
<blockquote>
<p>key를 Hash 함수에 넣어 나온 Hash value를 이용하여 배열 인덱스에 데이터를 저장한다.
<strong>특징</strong></p>
</blockquote>
<ul>
<li>조회, 삽입, 삭제 연산 -&gt; O(1)</li>
<li>HashMap, Python의 dict, MySQL MEMORY 엔진의 Hash Index</li>
</ul>
<h2 id="b-tree를-index에-사용하는-이유">B-Tree를 Index에 사용하는 이유</h2>
<blockquote>
<h4 id="b-tree-1">B-Tree</h4>
</blockquote>
<ul>
<li>컬럼의 값을 변형하지 않고, 원래의 값을 이용하여 인덱싱한다.<blockquote>
</blockquote>
<h4 id="hash-1">Hash</h4>
</li>
<li>컬럼의 값으로 해시 값을 계산해서 인덱싱하여 값을 변형하게 된다.</li>
<li>특정 문자로 시작하는 값으로 검색을 하는 등 전방 일치와 같이 값의 일부만으로 검색하고자 할 때, Hash index를 사용할 수 없다. (ex - LIKE, IN)</li>
</ul>
<h4 id="-select-절의-조건에-부등호-연산-이-포함될-경우-문제가-발생한다-hashtable은-동등-연산에-특화되어있어-데이터베이스의-자료구조에-적합하지-않다">==&gt; SELECT 절의 조건에 부등호 연산(&gt;, &lt;)이 포함될 경우 문제가 발생한다. HashTable은 동등 연산(=)에 특화되어있어 데이터베이스의 자료구조에 적합하지 않다.</h4>
<p><br><br></p>
<h1 id="index-종류">Index 종류</h1>
<h2 id="unique-key">Unique key</h2>
<h4 id="특징">특징</h4>
<ul>
<li><p>값 중복을 허용하지 않는다.</p>
</li>
<li><p>primary key와 다르게, null 값을 허용한다.</p>
</li>
<li><p>primary key와 다르게, 테이블에서 한 개만 생성 가능하다.</p>
</li>
<li><p>2개 이상의 컬럼을 Unique index로 생성할 시, 앞 선 컬럼이 항상 더 중복성이 낮은(카디널러티가 높은)것으로 골라야 성능이 좋다.
<br><br></p>
<h2 id="clustered-index">Clustered Index</h2>
<blockquote>
<p><code>Def</code> : 데이터베이스의 테이블의 레코드 순서를 Index의 키 값 순서대로 정렬하는 것을 의미한다.
  ex ) Primary key (InnoDB 스토리지 엔진 기준)</p>
</blockquote>
<h4 id="특징-1">특징</h4>
<ul>
<li>한 테이블 당 하나만 존재해야 한다.</li>
<li>가장 빠르게 처리한다.</li>
<li>조회는 빠르나 삽입 시 재정렬이 필요해 성능이 떨어질 수 있다. -&gt; 재정렬이 일어나지 않고 순차적으로 쌓이는 컬럼에 설정해야 한다. (ex - id)
<br><br><h2 id="non-clustered-index">Non-Clustered Index</h2>
<blockquote>
<p><code>Def</code> : 테이블에 저장된 물리적 순서에 따라 데이터를 <strong>정렬하지 않는다.</strong></p>
</blockquote>
</li>
</ul>
<h4 id="특징-2">특징</h4>
<ul>
<li>leaf node에는 데이터 페이지에 대한 포인터가 있어 이를 통해 데이터 페이지를 조회할 수 있는 형태</li>
<li>한 테이블에 여러 인덱스를 생성할 수 있다.</li>
<li>데이터 조회 시, leaf level에서 data 페이지에 접근하는 추가적인 step이 필요하기 때문에, clustered index 보다는 속도가 느리다.</li>
<li>데이터 입력 시, 약 10% 정도의 별도 공간에 인덱스를 생성해야 하기 때문에 추가 작업이 요구된다.</li>
</ul>
</li>
</ul>
<h2 id="bitmap-index">Bitmap Index</h2>
<blockquote>
<p><code>Def</code> : 인덱스 컬럼의 데이터를 Bit 값인 0 또는 1로 변환하여 Index key로 사용하는 방법</p>
</blockquote>
<h4 id="특징-3">특징</h4>
<ul>
<li>키 값을 포함하는 행의 주소를 제공한다는 목적을 제공한다.</li>
<li>B-Tree 인덱스와 달리, 카디널리티(: 중복된 수치)가 낮은 경우(중복된 경우가 많은 경우)에 사용하기 좋다.
ex) 성별 테이블...?(이런 테이블을 만들 일이 있나...)</li>
<li>효율적인 논리 연산이 가능하고 저장공간이 작다.</li>
<li>동일한 값이 반복되는 경우가 많기에 압출 효율이 좋다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/e_jim/post/58f17e6f-f225-40a0-b1a4-1cf7922a7e19/image.png" alt=""></p>
<h2 id="secondary-key">Secondary key</h2>
<blockquote>
<p><code>Def</code> : 클러스터드 인덱스와 별개로 따로 만든 인덱스 = 필요한 컬럼만 저장 + Primary Key 값을 같이 저장</p>
</blockquote>
<h4 id="특징-4">특징</h4>
<ul>
<li>Primary key와 항상 같이 다녀야 한다.</li>
<li>인덱스에 테이블 일부 정보만 있다.</li>
<li>인덱스를 보고 테이블로 다시 찾아가야 한다.</li>
<li>읽는 I/O 수가 많다.</li>
<li>쿼리속도가 Clustered Index에 비해 느리다.</li>
</ul>
<blockquote>
<pre><code class="language-sql">  CREATE TABLE employees (
      emp_id INT PRIMARY KEY,
      name VARCHAR(100),
      department_id INT,
      hire_date DATE,
      INDEX idx_name (name)
  );</code></pre>
</blockquote>
<p>``` </p>
<blockquote>
</blockquote>
<ul>
<li>emp_id는 Primary Key → Clustered Index</li>
<li>name은 Secondary Index (보조 인덱스)  </li>
</ul>
<br>

<h2 id="그-외--fulltext-index-composite-index">그 외 : Fulltext Index, Composite Index</h2>
<h3 id="참조">참조</h3>
<blockquote>
<p> <a href="https://mag1c.tistory.com/528">Index</a>
<a href="https://velog.io/@gayeong39/%EB%B9%84%ED%8A%B8%EB%A7%B5-%EC%9D%B8%EB%8D%B1%EC%8A%A4BitMap-Index">Bitmap Index</a>
<a href="https://velog.io/@qkrtkdwns3410/DB-%EC%9D%B8%EB%8D%B1%EC%8A%A4%EC%9D%98-%EC%A2%85%EB%A5%98%EC%97%90-%EB%8C%80%ED%95%B4">Index 종류</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScipt - Hoisting]]></title>
            <link>https://velog.io/@e_jim/JavaScipt-Hoisting</link>
            <guid>https://velog.io/@e_jim/JavaScipt-Hoisting</guid>
            <pubDate>Mon, 07 Apr 2025 05:18:05 GMT</pubDate>
            <description><![CDATA[<h1 id="hoisting-이란">Hoisting 이란?</h1>
<blockquote>
<p><strong>코드 평가 시 선언문을 해당 스코프의 상단으로 끌어 올려 메모리 선점/생성
**
**목적</strong>: 각 식별자의 메모리 위치를 미리 잡아 stack 영역 세팅 ⇒ 실행이 빠름!</p>
</blockquote>
<p><br><br></p>
<h2 id="함수-hoisting">함수 Hoisting</h2>
<blockquote>
<p>함수 선언 전에 함수 실행 코드가 있어도 정상적으로 작동한다.</p>
</blockquote>
<pre><code class="language-javascript">test(); // &quot;Hosting&quot; -&gt; 정상 실행
&gt;
function test() {
  document.writeln(&quot;Hoisting&quot;);
}
&gt;
test(); // &quot;Hoisting&quot;</code></pre>
<p><br><br></p>
<h2 id="변수-hoisting">변수 Hoisting</h2>
<blockquote>
<p>변수 호이스팅할 때는 </p>
</blockquote>
<ul>
<li>선언 O</li>
<li>초기화 O</li>
<li>할당 X</li>
</ul>
<br>

<h3 id="var-변수-호이스팅">var 변수 호이스팅</h3>
<blockquote>
<p>: 선언 전에 호출 시 undefined로 접근한다. </p>
</blockquote>
<pre><code class="language-javascript">console.log(name); // undefined
var name = &quot;Hong GilDong&quot;;
console.log(name); // Hong GilDong</code></pre>
<br>

<h3 id="const-let-변수-호이스팅">const, let 변수 호이스팅</h3>
<blockquote>
<p>: 선언 전에 호출 시 Error 발생</p>
</blockquote>
<pre><code class="language-javascript">console.log(name1); // ReferenceError
const name1 = &quot;Hong GilDong&quot;;
console.log(name1); // Hong GilDong
&gt;
console.log(name2); // ReferenceError
let name2 = &quot;Hong GilDong&quot;;
console.log(name2); // Hong GilDong</code></pre>
<p><br><br></p>
<h2 id="tdztemporal-dead-zone">TDZ(Temporal Dead Zone)</h2>
<blockquote>
<p>JS에만 있는 변수가 선언되기 전 코드 영역 - 임시사각지대</p>
</blockquote>
<ul>
<li>변수가 선언되었지만 아직 초기화 되지 않는 상태
== &#39;선언만 되고 아직 초기화 되지 않는 변수가 머무는 공간&#39;</li>
</ul>
<p><br><br></p>
<blockquote>
<h3 id="참고">참고</h3>
</blockquote>
<ul>
<li><a href="https://ccomccomhan.tistory.com/288">https://ccomccomhan.tistory.com/288</a></li>
<li><a href="https://ccomccomhan.tistory.com/290">https://ccomccomhan.tistory.com/290</a></li>
<li><a href="https://velog.io/@saemileee/Javascript-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85-%ED%81%B4%EB%A1%9C%EC%A0%B8">https://velog.io/@saemileee/Javascript-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85-%ED%81%B4%EB%A1%9C%EC%A0%B8</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Javascript - 변수와 타입]]></title>
            <link>https://velog.io/@e_jim/Javascript-%EB%B3%80%EC%88%98%EC%99%80-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@e_jim/Javascript-%EB%B3%80%EC%88%98%EC%99%80-%ED%83%80%EC%9E%85</guid>
            <pubDate>Mon, 07 Apr 2025 04:52:51 GMT</pubDate>
            <description><![CDATA[<h1 id="✅-primitive-type-value">✅ Primitive Type (Value)</h1>
<blockquote>
<h3 id="값이-stack에-들어간다">값이 Stack에 들어간다.</h3>
<ul>
<li>메모리의 값은 변경이 안되고, 값이 변경되면 새로운 메모리가 할당됨!</li>
</ul>
</blockquote>
<ul>
<li><code>숫자(number)</code>, <code>문자열/문자열템플릿</code>, <code>boolean</code>, <code>null</code>, <code>undefined</code>, <code>Symbol</code> </li>
</ul>
<p><br><br></p>
<h1 id="✅-object-type-reference">✅ Object Type (Reference)</h1>
<blockquote>
<ul>
<li><code>Array</code>, <code>Date</code>, <code>RegExp</code>, <code>Map/WeakMap</code>, <code>Set/WeakSet</code></li>
</ul>
</blockquote>
<p><br><br></p>
<h1 id="✚-constant-pool">✚ Constant pool</h1>
<blockquote>
<h3 id="🔹-constant-pool이란">🔹 Constant Pool이란?</h3>
<p> : Constant Pool(상수 풀) 은 자바 클래스 파일(.class 파일) 내에 포함된 리터럴 상수값과 심볼 참조(Symbolic References) 를 저장하는 테이블</p>
</blockquote>
<ul>
<li>즉, 자바 컴파일러가 <code>.java</code> 파일을 <code>.class</code> 파일로 변환할 때, 프로그램에서 사용하는 문자열, 정수, 클래스 및 메서드 참조 등을 이 테이블에 저장한다.</li>
<li><code>String.intern()</code> 메서드는 문자열을 명시적으로 Constant Pool에 등록하고, 동일한 문자열이 이미 존재한다면 그 참조를 반환합니다.<ul>
<li>Constant Pool은 메모리 절약에 유리하지만, 크기가 제한되어 있어서 너무 많은 상수를 선언하면 <code>java.lang.ClassFormatError: constant pool full</code> 예외가 발생할 수 있습니다.</li>
</ul>
</li>
</ul>
<blockquote>
<h3 id="🔸-종류별-constant-pool">🔸 종류별 Constant Pool</h3>
</blockquote>
<h4 id="1-클래스-파일의-constant-pool">1. 클래스 파일의 Constant Pool</h4>
<p>.class 파일 내부에는 constant_pool이라는 테이블이 있으며, 여기에 다음과 같은 정보가 저장됩니다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>문자열 리터럴</code> (&quot;hello&quot;) / <code>숫자 리터럴</code> (10, 3.14) / <code>클래스/인터페이스 이름</code>/ <code>필드/메서드 이름 및 타입 정보</code> / <code>메서드 참조 (Methodref)</code>/ <code>필드 참조 (Fieldref)</code> 등
👉 이건 JVM이 클래스를 로드할 때 참고하는 정보입니다.</li>
</ul>
<blockquote>
</blockquote>
<h4 id="2-런타임-constant-pool">2. 런타임 Constant Pool</h4>
<p>클래스가 JVM에 로드되면 .class 파일의 Constant Pool은 Runtime Constant Pool로 변환되어 메모리에 올라갑니다.</p>
<ul>
<li>String literal은 이 런타임 상수 풀에 저장되며, 중복을 방지하고 메모리 효율성을 높이기 위해 사용됩니다.<pre><code class="language-java">  String a = &quot;hello&quot;;
  String b = &quot;hello&quot;;
  System.out.println(a == b); // true</code></pre>
<blockquote>
</blockquote>
→ &quot;hello&quot;라는 문자열은 Constant Pool에 한 번만 저장되므로, a와 b는 같은 객체를 참조합니다.</li>
</ul>
<h3 id="contant-pool-vs-string-constant-pool">Contant Pool VS String Constant Pool</h3>
<p><img src="https://velog.velcdn.com/images/e_jim/post/3c5ebcca-cf2d-493d-98ae-8bd2f8a11223/image.png" alt=""></p>
<blockquote>
<h4 id="예제">예제</h4>
</blockquote>
<pre><code class="language-java">    public class Main {
      public static void main(String[] args) {
          String a = &quot;hello&quot;;
          String b = &quot;hello&quot;;
          String c = new String(&quot;hello&quot;);
          String d = c.intern();
&gt;
          System.out.println(a == b); // true (둘 다 String Constant Pool에서 공유)
          System.out.println(a == c); // false (c는 Heap에 새로 생성된 객체)
          System.out.println(a == d); // true (c를 intern해서 풀에서 찾아씀)
      }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git ]]></title>
            <link>https://velog.io/@e_jim/Git</link>
            <guid>https://velog.io/@e_jim/Git</guid>
            <pubDate>Fri, 04 Apr 2025 00:30:09 GMT</pubDate>
            <description><![CDATA[<h1 id="구글링-그만하려고-작성하는-깃-정리본">구글링 그만하려고 작성하는 깃 정리본</h1>
<blockquote>
<h2 id="git-작업영역">Git 작업영역</h2>
<p><img src="https://velog.velcdn.com/images/e_jim/post/7213dea9-998d-4431-9277-022988875248/image.png" alt=""></p>
</blockquote>
<h4 id="working-directory--이력-관리-대상tracked-파일들이-위치하는-영역">Working Directory : 이력 관리 대상(tracked) 파일들이 위치하는 영역</h4>
<p>✓ 지정된 디렉토리에서 .git 폴더를 제외한 공간
✓ 작업된 파일이나 코드가 저장되는 공간
✓ add 전 상태</p>
<blockquote>
</blockquote>
<h4 id="staging-area--이력을-기록할-다시-말해-commit할-대상-파일들이-위치하는-영역">Staging Area : 이력을 기록할, 다시 말해 commit할 대상 파일들이 위치하는 영역</h4>
<p>✓ .git 폴더 하위에 파일형태로 존재(index)
✓ add 후 상태</p>
<blockquote>
</blockquote>
<h4 id="repository--이력이-기록된committed-파일들이-위치하는-영역">Repository : 이력이 기록된(committed) 파일들이 위치하는 영역</h4>
<p>✓ .git 폴더에 이력관리를 위한 모든 정보가 저장, 관리됨
✓ commit 후 상태</p>
<h2 id="명령어-정리">명령어 정리</h2>
 <br>

<h3 id="git-checkout">git checkout</h3>
<blockquote>
<ul>
<li>git checkout &lt;브랜치 명&gt; == git switch : 해당 브랜치로 작업영역 바뀐다.</li>
</ul>
</blockquote>
<ul>
<li><p>ex) <code>git checkout develop -&gt; develop 브랜치로 작업영역 변경</code></p>
<blockquote>
<br>
</blockquote>
<ul>
<li>git checkout &lt;&#39;commit id&#39;&gt; : 해당 커밋내역으로 작업영역 바뀐다.</li>
</ul>
</li>
<li><p>ex) git checkout 81c183r -&gt; commit id가 81c183r인 작업영역으로 바뀐다.</p>
<ul>
<li>해당 커밋에서 새로운 작업을 하고, 그 작업영역 그대로 새로운 브랜치로 만들고 싶다면 <code>git branch &quot;새로 만들 브랜치명&quot; &quot;commit id&quot;</code><blockquote>
<br></blockquote>
</li>
<li>git checkout - : 최신 커밋으로 파일을 되돌린다.</li>
</ul>
<br>

</li>
</ul>
<h3 id="head">HEAD</h3>
<blockquote>
<p>: 현재 작업하는 브랜치의 최종 커밋을 가리키는 포인터</p>
</blockquote>
 <br>

<h3 id="git-commit">git commit</h3>
<blockquote>
<ul>
<li>git commit -m&quot;커밋 메세지&quot;<br>
</li>
</ul>
</blockquote>
<ul>
<li><p>git commit -am&quot;커밋 메세지&quot; : git add + git commit -m</p>
<br></li>
<li><p>git commit --amend : 커밋 수정하기
  ▪ 커밋 메시지를 수정하려면 ‘-m’ 옵션 사용
▪ 저자 정보 등을 수정하려면 ‘--author’ 옵션 사용
▪ 커밋 메시지를 수정하지 않으려면 ‘--no-edit’ 옵션 사용</p>
<br>

</li>
</ul>
<h3 id="git-restore">git restore</h3>
<blockquote>
<ul>
<li>git restore : 작업 디렉토리에서 변경된 파일을 되돌리는 데 사용 → 마지막 커밋 상태로 복원<br>
</li>
</ul>
</blockquote>
<ul>
<li><p>git restore --staged
  ▪ staging area(스테이징 영역)에서 파일을 복원하는 데 사용
  ▪ 목적: 스테이징된 파일을 언스테이징하여 다시 커밋되지 않도록 설정
  ▪ 동작: 파일을 스테이징 영역에서만 복원하고, 작업 디렉토리의 내용은 그대로 유지</p>
<br>

<h3 id="git-rm">git rm</h3>
<blockquote>
<ul>
<li>git rm --cache : 스테이징 영역에서 파일을 제거할 때 사용</li>
</ul>
</blockquote>
<ul>
<li>working directory에서 파일이 삭제되지 않는다.<br></li>
<li>git rm : staging area + working directory 둘다 삭제<br></li>
<li>만약 실수로 git rm --cached가 아닌 git rm을 사용하여 파일이 삭제되었다면?<pre><code>git restore --staged ttt.txt
git restore ttt.txt</code></pre></li>
</ul>
</li>
</ul>
<h3 id="branch">branch</h3>
<blockquote>
<ul>
<li>git branch &quot;브랜치 명&quot; : 브랜치 만들기</li>
</ul>
</blockquote>
<ul>
<li>(main) -&gt; git branch develop -&gt; main에서 develop이라는 브랜치명을 가진 브랜치 만들기<br>
- git branch -d "브랜치 명" : 브랜치 삭제하기
<br>
- git push -d origin "원격 브랜치 명" : github에 있는 원격브랜치 삭제



</li>
</ul>
<h3 id="stash">stash</h3>
<blockquote>
<ul>
<li>git stash </li>
</ul>
</blockquote>
<ul>
<li>작업하던 중 아직 add하지 않은 상황에서, 다른  것을 먼저 처리하거나 다른 브랜치로 이동할 때, 지금까지 작업하던 것을 임시저장 -&gt; stack 형식<br>
- git stash save "label 명" : 헷갈리지 않게 라벨을 지정해서 임시저장할 수 있음 
<br>
- git stash pop : 임시저장해 놓은 작업물 다시 working directory에 가져오기
- 가장 나중에 저장한 것 먼저 나온다 -> STACK(FILO)
<br>
- git stash apply "stash 번호"</li>
<li>Ex)  <code>git stash pop stash@{2}</code>   -&gt; 3번째(index가 2)로 저장한 임시저장본 가져오기<br>
- git stash list : stash 목록 가져오기
<br>
- git stash drop : 임시저장 본 삭제</li>
<li>stash pop 같은 경우, 적용하고 바로 해당 stash가 사라지지만, 
stash apply 같은 경우, 적용만 하고 사라지지 않는다.<br>
- git stash branch <branch> : 현재 작업중인 내역으로 새로운 브랜치 생성


</li>
</ul>
<h3 id="clean">clean</h3>
<blockquote>
<ul>
<li>git clean : untracked 파일 삭제<br>
</li>
</ul>
</blockquote>
<ul>
<li>git clean -n : 삭제할 목록만 확인, 실제로 삭제 X<ul>
<li>.gitignore에 정의된 파일은 대상이 아님<br></li>
</ul>
</li>
<li>git clean -d : ignore file외 untracked 파일 삭제(하위폴더포함)<ul>
<li>git clean -di : 안전하게 interactive하게!</li>
<li>git clean -df : 강제로 삭제<br></li>
</ul>
</li>
<li>git clean -x : ignore file 포함 모든 untracked 삭제 <ul>
<li>git clean -xi : 안전하게 interactive하게!<ul>
<li>git clean -xf : 강제로 삭제 <br></li>
</ul>
</li>
</ul>
</li>
<li>git clean -X : only ignore file만 삭제</li>
</ul>
<h3 id="merge">merge</h3>
<blockquote>
<ul>
<li>merge에는 총 3가지 방식이 있다.</li>
</ul>
</blockquote>
<ol>
<li>Merge
   1.1 Fast-Forward merge
 1.2. 3-Way merge</li>
<li>Rebase</li>
<li>Squash<br>
#### 1.1 Fast-Forward merge
- 시간의 흐름대로 커밋된 내역을 병합
- 충돌(conflict) 발생 없고 100% Auto-merge
- merge 후 모든 commit이 복제
![](https://velog.velcdn.com/images/e_jim/post/1fb5ccec-27ed-48d8-8a15-2089b224fb23/image.png)
> 
<br>
#### 1.2 3-Way merge</li>
</ol>
<ul>
<li>두 개 이상의 브랜치로 파생된 커밋을 병합<ul>
<li>충돌(conflict) 가능성 있음</li>
<li>병합 메시지(merge commit) 존재
<img src="https://velog.velcdn.com/images/e_jim/post/7f493e51-988b-4539-80e7-65ccd56020a2/image.png" alt=""><blockquote>
</blockquote>
<br>
#### 2. Rebase</li>
<li>공통조상(base) 병합</li>
<li>3-Way ⇒ Fast-Forward 화
<img src="https://velog.velcdn.com/images/e_jim/post/54c1aa45-e81f-404e-ad05-3c371003d839/image.png" alt=""><blockquote>
</blockquote>
<br>
#### Squash</li>
</ul>
</li>
<li>많은 커밋의 양이 생기는데 여러 개의 커밋을 하나로 합치는 기능을 말한다.
<img src="https://velog.velcdn.com/images/e_jim/post/101bb0a9-4eca-492f-9d39-048c38e5fa1f/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Backjoon] ABCDE - 13023]]></title>
            <link>https://velog.io/@e_jim/Backjoon-ABCDE</link>
            <guid>https://velog.io/@e_jim/Backjoon-ABCDE</guid>
            <pubDate>Tue, 15 Oct 2024 04:50:10 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><img src="https://velog.velcdn.com/images/e_jim/post/38def7a8-66e9-455f-8a83-02498b3e724a/image.png" alt=""></p>
<h3 id="입력">입력</h3>
<p><img src="https://velog.velcdn.com/images/e_jim/post/caafdd5b-47a9-4411-9bbb-aa6aca0cce51/image.png" alt=""></p>
<h3 id="출력">출력</h3>
<p><img src="https://velog.velcdn.com/images/e_jim/post/748e574a-20e3-428d-a1d0-26c864e98b9a/image.png" alt=""></p>
<h2 id="문제풀이">문제풀이</h2>
<ul>
<li>처음에는 노드가 서로 순환하고 있는 것(원 모양)을 찾는 문제인 줄 알고, 방문노드(visited)에 들어 있는 노드는 건너뛰고, 없는 노드는 넣어서 지금 방문하고 있는 노드가 처음 방문한 노드와 일치하는지 확인하는 방식으로 접근하려고 했다.</li>
<li>깊이가 4인 그래프를 찾으라는 문제라는 것을 깨닫고 다음과 같은 방식으로 코드를 구현했다.
<br><br><br></li>
</ul>
<h3 id="첫번째-코드---시간초과"><strong>첫번째 코드</strong> -&gt; 시간초과</h3>
<blockquote>
</blockquote>
<pre><code class="language-python"># ABCDE
import sys
input = sys.stdin.readline
&gt;
n, m = map(int, input().split())
relations = [[] for _ in range(n)]
answer = False
&gt;
for i in range(m):
    x, y = map(int, input().split())
&gt;    
    relations[x].append(y)
    relations[y].append(x)
&gt;
def dfs(now, cnt):
    global answer
&gt;   
    if cnt == 4:
        answer = True
        return
&gt;    
    visited.append(now)
    for i in relations[now]: 
        if i not in visited:
            dfs(i, cnt + 1)
&gt;
for i in range(n):
    visited = []
    dfs(i,0)
&gt;    
if answer:
    print(1)
else:
    print(0)</code></pre>
<h4 id="시간초과-이유"><strong>시간초과 이유</strong></h4>
<blockquote>
<ul>
<li>성공한 코드와 비교했을 때, visited를 넣기만 하고 pop을 해주지 않아서 시간초과가 뜬 것 같다.</li>
</ul>
</blockquote>
<pre><code class="language-python">for i in relations[now]: 
        if i not in visited:
            dfs(i, cnt + 1)</code></pre>
<p>visited를 pop을 해 주지 않으면 최대 1999개의 원소가 든 리스트를 순회하며 원소가 들어있는지 비교해야 하기 때문에 시간초과가 발생한 것 같다.</p>
<p><br><br></p>
<h3 id="최종-코드---성공">최종 코드 - 성공</h3>
<blockquote>
<pre><code class="language-python"></code></pre>
</blockquote>
<h1 id="abcde">ABCDE</h1>
<p>import sys
input = sys.stdin.readline</p>
<blockquote>
</blockquote>
<p>n, m = map(int, input().split())
relations = [[] for _ in range(n)]
answer = False</p>
<blockquote>
</blockquote>
<p>for i in range(m):
    x, y = map(int, input().split())</p>
<blockquote>
<pre><code>relations[x].append(y)
relations[y].append(x)</code></pre></blockquote>
<p>def dfs(now, cnt):
    global answer</p>
<blockquote>
<pre><code>if cnt == 4:
    answer = True
    return

visited.append(now)
for i in relations[now]: 
    if i not in visited:
        dfs(i, cnt + 1)
visited.pop()</code></pre></blockquote>
<p>for i in range(n):
    visited = []
    dfs(i,0)
    if answer:
        break</p>
<blockquote>
<p>if answer:
    print(1)
else:
    print(0)
```</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Backjoon] 연구소 - 14502]]></title>
            <link>https://velog.io/@e_jim/Backjoon-%EC%97%B0%EA%B5%AC%EC%86%8C-14502</link>
            <guid>https://velog.io/@e_jim/Backjoon-%EC%97%B0%EA%B5%AC%EC%86%8C-14502</guid>
            <pubDate>Mon, 14 Oct 2024 08:29:16 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><img src="https://velog.velcdn.com/images/e_jim/post/cb7a21d1-573f-4caa-938b-1c8b71a28bbc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/e_jim/post/9b1a3014-a04b-4580-a082-3e1a95c37803/image.png" alt=""></p>
<h3 id="입력">입력</h3>
<p><img src="https://velog.velcdn.com/images/e_jim/post/17605c75-ef63-4716-b6c1-45859b08f623/image.png" alt=""></p>
<h3 id="출력">출력</h3>
<p><img src="https://velog.velcdn.com/images/e_jim/post/e3eae24e-8885-4ba5-b585-6ac8b9a56376/image.png" alt=""></p>
<h2 id="문제풀이---dfs--pypy3로-성공">문제풀이 - DFS : Pypy3로 성공</h2>
<ul>
<li>백트래킹으로 하나하나 벽을 세운 후, 그 때의 안전영역 크기를 구해 최댓값을 저장하는 방식으로 접근해야 된다는 생각을 했다.</li>
<li>처음에는 값이 &#39;0&#39;인 방들의 index를 조합(collections)을 이용해 벽을 세우는 방식을 떠올렸으나, 이후 풀이가 안 떠올라 포기했다.</li>
<li>DFS 알고리즘으로 접근하기로 했다.</li>
</ul>
<blockquote>
<p><strong>바이러스를 퍼트리는 함수</strong></p>
</blockquote>
<pre><code class="language-python">def infect(x,y):
    for i in range(4):
        nx = x + dx[i]
        ny = y + dy[i]
&gt;
        if 0 &lt;= nx &lt; n and 0 &lt;= ny &lt; m and tmp[nx][ny] == 0:
            tmp[nx][ny] = 2
            infect(nx, ny)</code></pre>
<blockquote>
<p><strong>안전영역 크기 구하는 함수</strong></p>
</blockquote>
<pre><code class="language-python">def count():
    temp = 0
    for i in range(n):
        for j in range(m):
            if tmp[i][j] == 0:
                temp += 1
    return temp</code></pre>
<blockquote>
<p><strong>전체 코드</strong></p>
</blockquote>
<pre><code class="language-python"># 연구소
import sys
input = sys.stdin.readline
&gt;
n, m = map(int, input().split())
array = [list(map(int, input().split())) for _ in range(n)]
tmp = [[0]*m for _ in range(n)]
answer = 0
&gt;
dx = [0, 0, -1, 1]
dy = [1, -1, 0, 0]
&gt;
def infect(x,y):
    for i in range(4):
        nx = x + dx[i]
        ny = y + dy[i]
&gt;        
        if 0 &lt;= nx &lt; n and 0 &lt;= ny &lt; m and tmp[nx][ny] == 0:
            tmp[nx][ny] = 2
            infect(nx, ny)
&gt;
def count():
    temp = 0
    for i in range(n):
        for j in range(m):
            if tmp[i][j] == 0:
                temp += 1
    return temp
&gt;                
def dfs(w):
    global answer 
&gt;    
    if w == 3: # 벽을 다 세우면
        for i in range(n):
            for j in range(m):
                tmp[i][j] = array[i][j] # copy
&gt;                
        for i in range(n):
            for j in range(m):
                if array[i][j] == 2:
                    infect(i,j)
        answer = max(answer, count())
        return
    for i in range(n):
        for j in range(m):
            if array[i][j] == 0:
                array[i][j] = 1 # 벽 세우기
                w += 1
                dfs(w)
                array[i][j] = 0 # 세운 벽 거두기
                w -= 1
&gt;
dfs(0)
print(answer)</code></pre>
<h2 id="다른-풀이---bfs--python3로-성공">다른 풀이 - BFS : Python3로 성공</h2>
<ul>
<li>처음 생각했던 방법으로, 조합으로 벽을 세울 수 있는 3군데 리스트로 뽑아내기</li>
<li>조합 순회하면서 벽을 세우고, 바이러스 퍼트린 뒤, 안전영역 크기 구하기</li>
</ul>
<blockquote>
<pre><code class="language-python">import sys
from itertools import combinations
from collections import deque
import copy
</code></pre>
</blockquote>
<p>input = sys.stdin.readline
n, m = map(int, input().split())
graph = [list(map(int, input().split())) for _ in range(n)]
points = []
virus = []
answer = 0</p>
<blockquote>
</blockquote>
<p>def three_point(points): # 벽 세울 곳 
    new_wall = list(combinations(points, 3))
    return new_wall</p>
<blockquote>
</blockquote>
<p>dx = [0, 0, -1, 1]
dy = [1, -1, 0, 0]</p>
<blockquote>
</blockquote>
<p>def bfs(graph, points):
    for i, j in points: # 벽 세우기
        graph[i][j] = 1</p>
<blockquote>
</blockquote>
<pre><code>queue = deque()
for v in virus:
    queue.append(v)</code></pre><blockquote>
</blockquote>
<pre><code>while queue:
    x, y = queue.popleft()</code></pre><blockquote>
</blockquote>
<pre><code>    for i in range(4): # 바이러스 퍼트리기
        nx = x + dx[i]
        ny = y + dy[i]</code></pre><blockquote>
<pre><code>        if 0 &lt;= nx &lt; n and 0 &lt;= ny &lt; m and graph[nx][ny] == 0:
            graph[nx][ny] = 2
            queue.append((nx, ny))</code></pre></blockquote>
<pre><code>count = 0
for k in range(n): # 안전영역 크기 구하기
    count += graph[k].count(0)</code></pre><blockquote>
</blockquote>
<pre><code>return count</code></pre><blockquote>
</blockquote>
<p>for i in range(n):
    for j in range(m): # 리스트 순회하면서 
        if graph[i][j] == 0:
            points.append((i, j)) # 벽 세울 수 있는 곳 
        elif graph[i][j] == 2:
            virus.append((i, j)) # 바이러스 있는 곳</p>
<blockquote>
</blockquote>
<p>new_wall = three_point(points) # 벽 세울 수 있는 곳 조사하기</p>
<blockquote>
</blockquote>
<p>for p in new_wall:
    new_graph = copy.deepcopy(graph)
    answer = max(answer, bfs(new_graph, p))</p>
<blockquote>
</blockquote>
<p>print(answer)
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Backjoon] 집합의 표현 - 1717]]></title>
            <link>https://velog.io/@e_jim/%EB%B0%B1%EC%A4%80-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A7%91%ED%95%A9%EC%9D%98-%ED%91%9C%ED%98%84-1717</link>
            <guid>https://velog.io/@e_jim/%EB%B0%B1%EC%A4%80-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A7%91%ED%95%A9%EC%9D%98-%ED%91%9C%ED%98%84-1717</guid>
            <pubDate>Tue, 09 Apr 2024 06:27:19 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/e_jim/post/87c04ed5-372c-4525-9c49-7c42db78727b/image.png" alt=""></p>
<h2 id="✏️-1번째-풀이-방법">✏️ 1번째 풀이 방법</h2>
<p>0 ~ (n+1) 까지 리스트를 생성한 후 명령어가 0이면 index가 a와 b인 리스트를 서로 복사하는 방법으로 풀이를 생각했다.</p>
<pre><code class="language-python">import sys
input = sys.stdin.readline

n, m = map(int, input().split())

hash = [[i] for i in range(n+1)]

for i in range(m):
    op, a, b = map(int, input().split())

    if op == 0:
        hash[a].extend(hash[b])
        hash[b].extend(hash[a])
    else:
        if b in hash[a] or a in hash[b]:
            print(&quot;YES&quot;)
        else:
            print(&quot;NO&quot;)</code></pre>
<h3 id="❌-1번째-방법---실패">❌ 1번째 방법 - 실패</h3>
<p>리스트가 a, b는 서로 연결이 되지만 합집합으로는 되지 못하기에 이 방법은 틀렸다.</p>
<h4 id="예-입력값으로-다음과-같을-때">예) 입력값으로 다음과 같을 때,</h4>
<pre><code>7, 3
0 1 2
0 1 3
0 2 4
</code></pre><p>** 저장된 리스트 **</p>
<pre><code>[[0], [1, 2, 3], [2, 1, 2, 4], [3, 1, 2, 3], [4, 2, 1, 2, 4], [5], [6], [7]]</code></pre><h3 id="✅-2번째-방법---성공">✅ 2번째 방법 - 성공</h3>
<p>부모가 누구인지 알 수 있는 함수인 find 함수와 
집합을 합쳤을 때, 부모를 같게 만들어주는 union 함수를 따로 정의하여 문제를 풀었다.</p>
<pre><code class="language-python">import sys
sys.setrecursionlimit(1000000)
input = sys.stdin.readline

n, m = map(int, input().split())

p = [i for i in range(n+1)]

def find(x):
    if p[x] == x:
        return x
    p[x] = find(p[x])
    return p[x]

def union(a, b):
    a = find(a)
    b = find(b)
    if a &lt;= b:
        p[b] = a
    else:
        p[a] = b

for i in range(m):
    op, a, b = map(int, input().split())

    if op == 0:
        union(a, b)
    else:
        if find(a) == find(b):
            print(&quot;YES&quot;)
        else:
            print(&quot;NO&quot;)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] 호텔방 배정]]></title>
            <link>https://velog.io/@e_jim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%98%B8%ED%85%94%EB%B0%A9-%EB%B0%B0%EC%A0%95</link>
            <guid>https://velog.io/@e_jim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%98%B8%ED%85%94%EB%B0%A9-%EB%B0%B0%EC%A0%95</guid>
            <pubDate>Tue, 26 Mar 2024 07:23:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p>&quot;스노우타운&quot;에서 호텔을 운영하고 있는 &quot;스카피&quot;는 호텔에 투숙하려는 고객들에게 방을 배정하려 합니다. 처음에는 모든 방이 비어 있으며 &quot;스카피&quot;는 다음과 같은 규칙에 따라 고객에게 방을 배정하려고 합니다.</p>
<ul>
<li>호텔에는 방이 총 k개 있으며, 각각의 방은 1번부터 k번까지 번호로 구분하고 있습니다. </li>
<li>한 번에 한 명씩 신청한 순서대로 방을 배정합니다.</li>
<li>고객은 투숙하기 원하는 방 번호를 제출합니다.</li>
<li>고객이 원하는 방이 비어 있다면 즉시 배정합니다.</li>
<li>고객이 원하는 방이 이미 배정되어 있으면 원하는 방보다 번호가 크면서 비어있는 방 중 가장 번호가 작은 방을 배정합니다.</li>
</ul>
<h3 id="1차-접근-방법">1차 접근 방법</h3>
<ul>
<li>처음에 10^12를 보지 못하고 중첩 반복문을 사용하려고 하였다.</li>
<li>아래와 같이 배정 여부를 저장하려고 했다.</li>
</ul>
<pre><code class="language-python">booked = [0 for in range(k+1)]</code></pre>
<h3 id="1차-접근-실패">1차 접근 실패!</h3>
<p>-&gt; 접근성 테스트는 모두 통과 했지만, 10^12이다 보니 효율성 테스트에서 모두 실패했다.</p>
<hr>
<h3 id="2차-접근-방법">2차 접근 방법</h3>
<ul>
<li>시간복잡도를 줄이는 것이 관건이라고 생각했다.</li>
<li>O(logn)의 시간복잡도를 가지는 알고리즘을 찾기 시작했다.</li>
<li>Union Find 알고리즘을 발견했고, 이를 사용하기로 결정했다.</li>
</ul>
<blockquote>
<p>제출 코드 (통과)</p>
</blockquote>
<pre><code class="language-python">import sys
sys.setrecursionlimit(100000000)

def check_room(num, rooms):
    if num not in rooms.keys():
        rooms[num] = num + 1
        return num
    empty = check_room(rooms[num], rooms)
    rooms[num] = empty + 1
    return empty

def solution(k, room_number):
    answer = []
    room = {}

    for i in room_number:
        checkIn = check_room(i, room)


    return list(room.keys())</code></pre>
<p>참고
<a href="https://m.blog.naver.com/js568/221957852279">호텔방배정</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Chapter 4.1] 데이터베이스의 기본]]></title>
            <link>https://velog.io/@e_jim/Chapter-4.1-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EC%9D%98-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@e_jim/Chapter-4.1-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EC%9D%98-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Wed, 02 Aug 2023 08:15:57 GMT</pubDate>
            <description><![CDATA[<h3 id="datebase">Datebase</h3>
<p>: 일정한 규칙, 규약을 통해 구조화되어 저장되는 데이터의 모음</p>
<h3 id="dbms">DBMS</h3>
<p>: 데이터베이스를 제어, 관리하는 통합 시스템</p>
<p>ex) Mysql</p>
<hr>
<h1 id="411-엔터티">4.1.1 엔터티</h1>
<h3 id="entity">Entity</h3>
<p>: 여러 개의 속성을 지닌 명사</p>
<aside>

<p><strong>💡 약한 엔터티 VS 강한 엔터티</strong></p>
<blockquote>
</blockquote>
<p>*<em>약한 엔터티
*</em>
: 혼자 존재하지 못하고 다른 엔터티 존재 여부에 따라 종속적인 존재
ex) 방</p>
<blockquote>
</blockquote>
<p>*<em>강한 엔터티
*</em>
: 다른 엔터티가 자신의 존재 여부에 따라 종속된다면 강한 엔터티이다.
ex) 건물</p>
</aside>

<hr>
<h1 id="412-릴레이션">4.1.2 릴레이션</h1>
<h3 id="relation">Relation</h3>
<p>: 데이터베이스에서 정보를 구분하여 저장하는 기본 단위</p>
<ul>
<li>엔터티에 관한 데이터를 데이터베이스는 릴레이션 하나에 담아 관리한다.</li>
<li>관계형 데이터베이스에서는 “Table” / Nosql에서는 “Collection” 이라고 한다.</li>
</ul>
<h2 id="테이블과-컬렉션">테이블과 컬렉션</h2>
<p>데이터베이스 종류</p>
<blockquote>
</blockquote>
<ol>
<li>관계형 데이터베이스<br> ex) Mysql<ul>
<li>mysql 구조는 레코드-테이블-데이터베이스로 이루어져 있다.</li>
<li>레코드가 쌓여 테이블이 되고, 테이블이 쌓여 데이터베이스가 된다.<blockquote>
<br></blockquote>
</li>
</ul>
</li>
<li>Nosql 데이터베이스
 ex) MongoDB<ul>
<li>MongoDB 구조는 도큐먼트-컬렉션-데이터베이스</li>
</ul>
</li>
</ol>
<hr>
<h1 id="413-속성">4.1.3 속성</h1>
<h3 id="attribute">Attribute</h3>
<p>: 릴레이션에서 관리하는 구체적이며 고유한 이름을 갖는 정보</p>
<hr>
<h1 id="414-도메인">4.1.4 도메인</h1>
<h3 id="domain">Domain</h3>
<p>: 릴레이션에 포함된 각각의 속성들이 가질 수 있는 값의 집합을 말한다.</p>
<hr>
<h1 id="415-필드와-레코드">4.1.5 필드와 레코드</h1>
<ul>
<li>행 단위의 데이터 == 레코드</li>
</ul>
<h3 id="필드-타입">필드 타입</h3>
<ul>
<li>필드는 타입을 갖는다.</li>
</ul>
<p>숫자 타입</p>
<table>
<thead>
<tr>
<th>타입</th>
<th>용량(byte)</th>
<th>범위(부호없음)</th>
<th>범위(부호있음)</th>
</tr>
</thead>
<tbody><tr>
<td>TINYINT</td>
<td>1</td>
<td>0 ~ 255</td>
<td>-128 ~ 127</td>
</tr>
<tr>
<td>SMALLINT</td>
<td>2</td>
<td>0 ~ 655535</td>
<td>-32768 ~32767</td>
</tr>
<tr>
<td>MEDIUMINT</td>
<td>4</td>
<td>0 ~ 16777215</td>
<td>- 8388608 ~ 8388607</td>
</tr>
<tr>
<td>INT</td>
<td>8</td>
<td>0 ~ 4294967295</td>
<td>-2147483648 ~ 2147483647</td>
</tr>
<tr>
<td>BIGINT</td>
<td></td>
<td>0 ~ 264-1</td>
<td>-263 ~ 263-1</td>
</tr>
</tbody></table>
<p><strong>날짜 타입</strong></p>
<p>DATE</p>
<ul>
<li>날짜부분만 존재</li>
<li>시간 부분은 없음</li>
<li>3바이트 용량을 가짐</li>
</ul>
<p>1000-01-01 ~ 9999-12-31</p>
<p>DATETIME</p>
<ul>
<li>날짜부분 &amp; 시간부분 모두 포함</li>
<li>8바이트 용량을 가짐</li>
</ul>
<p>1000-01-01 00:00:00
~ 9999-12-31 23:59:59</p>
<p>TIMESTAMP</p>
<ul>
<li>날짜 부분 &amp; 시간 부분 모두 포함</li>
<li>4바이트 용량 가짐</li>
</ul>
<p>1970-01-01 00:00:00 
~ 2038:01-19 03:14:07</p>
<p><strong>문자 타입</strong></p>
<p>CHAR</p>
<ul>
<li>CHAR에 경우 최대 30자까지 입력 가능함</li>
<li>고정 길이 문자열이고 길이는 0 ~ 255 사이의 값을 가진다.</li>
<li>레코드를 저장할 때 무조건 선언한 길이 값으로 고정해서 저장된다.<ul>
<li>CHAR(100)이라 선언한 경우 10글자이더라도 100바이트로 저장되게 된다.</li>
</ul>
</li>
</ul>
<p>VARCHAR</p>
<ul>
<li>가변 길이 문자열</li>
<li>0 ~ 65535 사이의 값으로 지정할 수 있다.</li>
<li>입력된 데이터에 따라 용량을 가변시켜 저장한다.<ul>
<li>10글자의 이메일을 저장할 경우 10글자에 해당하는 바이트 + 길이기록용 1바이트로 저장함</li>
</ul>
</li>
</ul>
<p>TEXT</p>
<ul>
<li>큰 문자열 저장에 쓰며 주로 게시판의 본문을 저장할 때 쓴다.</li>
</ul>
<p>BLOB</p>
<ul>
<li>이미지, 동영상 등 큰 데이터 저장에 쓴다.</li>
<li>하지만 보통은 S3를 사용하여 서버에 파일을 올리고 파일경로를 VARCHAR로 저장한다.</li>
</ul>
<p>ENUM</p>
<ul>
<li>문자열을 열거한 타입</li>
<li>‘x-small’ , ‘small’, ‘medium’, ‘large’, ‘x-large’ 형태로 쓰인다.</li>
<li>이 중에서 하나만 선택하는 단일 선택만 가능하고, ENUM 리스트에 없는 잘못된 값을 삽입하면 빈 문자열이 대신 삽입된다.</li>
<li>이를 사용하면 ‘x-small’ 등이 0, 1 등으로 매핑되어 메모리를 적게 사용하는 이점을 얻는다.</li>
<li>최대 65535개의 요소들을 넣을 수 있다.</li>
</ul>
<p>SET</p>
<ul>
<li>문자열을 열거한 타입</li>
<li>여러 개의 데이터를 선택할 수 있고, 비트 단위의 연산을 할 수 있으며, 최대 64개의 요소를 집어 넣을 수 있다는 점이 ENUM과 다르다.</li>
</ul>
<p>—&gt; ENUM, SET을 사용할 경우 공간적으로는 이점을 볼 수 있지만, 애플리케이션의 수정에 따라 데이터베이스의 </p>
<p>ENUM, SET에서 정의한 목록을 수정해야 한다는 단점이 있다.</p>
<hr>
<h1 id="416-관계">4.1.6 관계</h1>
<ul>
<li>1대1 관계</li>
<li>1대다 관계</li>
<li>다대다 관계</li>
</ul>
<hr>
<h1 id="417-키">4.1.7 키</h1>
<h2 id="기본키">기본키</h2>
<p>: 유일성과 최소성을 만족하는 키</p>
<h3 id="자연키">자연키</h3>
<p>: 중복된 값들을 제외하며 중복되지 않은 것을 자연스레 뽑다가 나오는 키</p>
<ul>
<li>언젠가는 변하는 속성을 가진다.</li>
</ul>
<p>ex) 이름, 성별, 주민번호라는 속성이 있을 때, 주민번호가 자연키</p>
<h3 id="인조키">인조키</h3>
<p>: 인위적으로 생성한 키</p>
<ul>
<li>자연키와는 대조적으로 변하지 않는다.</li>
<li>보통 기본키는 인조키로 설정한다.</li>
</ul>
<p>ex) 사용자가 설정한 ID</p>
<h2 id="외래키">외래키</h2>
<p>: 다른 테이블의 기본키를 그대로 참조하는 값으로 개체와의 관계를 식별하는 데 사용한다.</p>
<ul>
<li>외래키는 중복되어도 괜찮다.</li>
</ul>
<h2 id="후보키">후보키</h2>
<p>: 기본키가 될 수 있는 후보들이며, 유일성과 최소성을 동시에 만족하는 키</p>
<h2 id="대체키">대체키</h2>
<p>: 후보키가 두 개 이상일 경우, 어느 하나를 기본키로 지정하고 남은 후보키를 의미한다. </p>
<h2 id="슈퍼키">슈퍼키</h2>
<p>: 각 레코드를 유일하게 식별할 수 있는 유일하게 식별할 수 있는 유일성을 갖춘 키</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스키마(Schema)]]></title>
            <link>https://velog.io/@e_jim/%EC%8A%A4%ED%82%A4%EB%A7%88Schema</link>
            <guid>https://velog.io/@e_jim/%EC%8A%A4%ED%82%A4%EB%A7%88Schema</guid>
            <pubDate>Tue, 04 Apr 2023 09:36:07 GMT</pubDate>
            <description><![CDATA[<p>Schema : DB에서 자료의 구조, 자료의 표현 방법, 자료 간의 관계를 형식 언어로 정의한 구조</p>
<ul>
<li>DB의 구조와 제약 조건에 관한 전반적인 명세를 기술한 metadata의 집합</li>
<li>DB를 구성하는 Entity, Attribute, Relationship 및 데이터 조작 시 데이터 값들이 갖는 제약 조건 등에 관해 전반적으로 정의함</li>
<li>사용자 관점에 따라 외부 스키마, 개념 스키마, 내부 스키마로 나눠진다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/e_jim/post/430c0db3-ccf1-48f3-a7f0-efa994402c3f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/e_jim/post/267ae3ac-e886-4f3b-94df-37f622cbf73b/image.png" alt=""></p>
<h3 id="특징">특징</h3>
<ol>
<li>데이터 사전에 저장되며, 다른 이름으로 메타데이터라고도 함</li>
<li>현실 세계의 특정한 한 부분의 표현으로서 특정 데이터 모델을 이용해서 만들어짐</li>
<li>시간에 따라 불변인 특성을 갖는다. </li>
<li>데이터의 구조적 특성을 의미하며, 인스턴스에 의해 규정됨</li>
</ol>
<blockquote>
<aside>
💡 외부 스키마(External Schema) → 서브 스키마, 사용자 뷰
</blockquote>
<ul>
<li>사용자나 응용 프로그래머가 개인의 입장에서 필요한 DB의 논리적 구조를 정의한다.</li>
<li>전체 데이터베이스의 한 논리적인 부분으로 볼 수 있기 때문에 서브 스키마라고도 한다.</li>
<li>하나의 데이터베이스 시스템에는 여러 개의 외부 스키마가 존재할 수 있다.</li>
<li>하나의 외부 스키마를 여러 개의 응용 프로그램 혹은 사용자가 공유할 수 있다.</li>
<li>일반 사용자는 SQL과 같은 질의어를 이용하여 DB를 쉽게 사용할 수 있다.</li>
<li>응용 프로그래머는 C나 JAVA 등의 언어를 사용하여 DB에 접근한다.</aside>

</li>
</ul>
<blockquote>
<aside>
💡 내부 스키마(Internal Schema) - 시스템 설계자 뷰
</blockquote>
<ul>
<li>물리적인 저장장치 입장에서 데이터가 저장되는 방법을 기술한 것이다.</li>
<li>실제 DB에 저장될 레코드의 물리적인 구조를 정의한다.</li>
<li>저장 데이터 항목의 표현방법, 내부 레코드의 물리적 순서, index 유/무 등을 나타낸다.</li>
<li>시스템 프로그래머나 시스템 설계자가 관리한다.</aside>

</li>
</ul>
<blockquote>
<aside>
💡 개념 스키마(Conceptual Schema) - 전체적인 뷰
</blockquote>
<ul>
<li>DB의 전체적인 논리적 구조, 모든 응용 프로그램이나 사용자들이 필요로 하는 데이터를 종합한 조직 전체의 DB로 하나만 존재한다.</li>
<li>개체 간의 관계와 제약 조건을 나타내고 DB의 접근 권한, 보안 및 무결성 규칙에 관한 명세를 정의한다.</li>
<li>DB파일에 저장되는 데이터의 형태를 나타내는 것으로, 단순히 스키마라고 하면 개념 스키마를 의미한다.</li>
<li>기관이나 조직체의 관점에서 데이터베이스를 정의한 것이다.</li>
<li>DBA에 의해서 구성된다.</aside></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[RDBMS vs NoSQL]]></title>
            <link>https://velog.io/@e_jim/RDBMS-vs-NoSQL</link>
            <guid>https://velog.io/@e_jim/RDBMS-vs-NoSQL</guid>
            <pubDate>Tue, 04 Apr 2023 09:34:16 GMT</pubDate>
            <description><![CDATA[<h1 id="rdbms">RDBMS</h1>
<p>: Relationship DataBase Management System → 관계형 데이터베이스 관리 시스템</p>
<ul>
<li><p>관계형 모델을 기반으로 하는 DBMS 유형이다.</p>
<blockquote>
<aside>
  💡 관계형 모델이란?

</blockquote>
<p>  : 실제 세계의 데이터를 &#39;관계&#39; 라는 개념을 사용해서 표현한 데이터 모델</p>
<blockquote>
</blockquote>
  </aside>
</li>
<li><p>RDBMS의 테이블은 서로 연관되어 있어 일반 DBMS보다 효율적으로 데이터를 저장, 구성 및 관리할 수 있다.</p>
</li>
<li><p>정규화를 통해 데이터의 중복성을 최소화하여 트랜잭션을 수행하는 것이 더 쉽다.</p>
</li>
<li><p>데이터의 원자성, 일관성, 격리 및 내구성을 유지하며 데이터 무결성을 높인다.</p>
</li>
<li><p>ex) MSSQL, MySQL, Oracle</p>
</li>
<li><p>SQL에 의해 저장되고 있으며 정해진 <a href="https://www.notion.so/17292001226a46ad9fe41a39f67cc9de">스키마(Schema)</a>에 따라 저장하여야 한다.</p>
</li>
</ul>
<h3 id="dbms">DBMS</h3>
<ul>
<li>사용자와 DB사이에서 사용자의 요구에 따라 데이터를 생성해주고, DB를 관리해주는 소프트웨어</li>
<li>데이터를 계층 또는 탐색 형식으로 저장한다.</li>
<li>파일 시스템을 사용해 저장하며, 따라서 테이블 간에는 아무런 관계가 없다.</li>
<li>데이터에 대한 많은 보안을 제공하지 않으며 정규화를 수행할 수 없어 데이터는 높은 중복성을 가질 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>RDB model</th>
<th>SQL</th>
</tr>
</thead>
<tbody><tr>
<td>Relation</td>
<td>Table</td>
</tr>
<tr>
<td>Tuple</td>
<td>행(Row)</td>
</tr>
<tr>
<td>Attribute</td>
<td>Column</td>
</tr>
</tbody></table>
<h3 id="rdb">RDB</h3>
<ul>
<li>관계형 데이터 모델에 기초를 둔 데이터 베이스이다.</li>
<li>모든 데이터를 2차원의 테이블 형태로 표현한다.</li>
</ul>
<h3 id="rdbms-장점">RDBMS 장점</h3>
<ul>
<li>정해진 스키마에 따라 데이터를 저장해야 하므로 명확한 구조를 보장하고 있다.</li>
<li>관계는 각 데이터를 중복없이 한 번만 저장할 수 있다.</li>
</ul>
<h3 id="rdbms-단점">RDBMS 단점</h3>
<ul>
<li>시스템이 커질 경우 JOIN문이 많은 복잡한 쿼리가 만들어질 수 있다.</li>
<li>성능 향상을 위해서는 서버의 성능을 향상시켜야 하는 scale-up만을 지원하고, 이로 인해 비용이 기하급수적으로 늘어날 수 있다.</li>
<li>스키마로 인해 데이터가 유연하지 못하다.</li>
<li>스키마가 변경될 경우 번거롭고 어렵다.</li>
</ul>
<h1 id="nosql">NoSQL</h1>
<p>: Not Only SQL의 약자로 관계형 데이터베이스가 아닌 다른 형태의 데이터 저장 기술을 의미한다.</p>
<ul>
<li>테이블 간 관계를 정의하지 않는다.</li>
<li>데이터 테이블은 하나의 테이블이며 테이블 간의 관계를 정의하지 않아 일반적으로 테이블간 join도 불가능하다.</li>
<li>수평적 확장성(scale-out)을 쉽게 할 수 있다.</li>
</ul>
<ol>
<li>Key - value DB<ul>
<li>데이터가 key - value의 쌍으로 저장된다.</li>
<li>이미지나 비디오도 가능하다.</li>
<li>간단한 Api를 제공하는 만큼 질의의 속도도 굉장히 빠르다.</li>
<li>ex) Redis, Riak, Amazon Dynamo DB</li>
</ul>
</li>
<li>Document DB<ul>
<li>key, document의 형태로 저장된다.</li>
<li>key - value와 다르게 value가 계층적인 형태인 document로 저장된다.</li>
<li>객체 지향의 객체와 유사하며 하나의 단위로 취급하여 저장된다.</li>
<li>객체  - 관계 맵핑이 필요하지 않다. → key-value 모델과 동일</li>
<li>사용이 번거롭고 쿼리가 sql과는 다르다.</li>
<li>질의의 결과가 JSON, xml 형태로 출력된다.</li>
<li>ex) MongoDB, CouthDB</li>
</ul>
</li>
<li>Wide Column DB<ul>
<li>column-family model 기반의 db이며 키에서 필드를 결정한다.</li>
<li>키는 row(키 값)와 column-family, column-name을 가진다.</li>
<li>연관된 데이터들은 같은 column-family 안에 속해 있으며 각자의 column-name을 가진다.</li>
<li>관계형 모델로 따지면, attribute가 계층적인 구조를 가지고 있는 셈</li>
<li>하나의 커다란 테이블로 표현가능하며, 질의는 Row, column-family, column-name을 통해 수행된다.</li>
<li>ex) HBase, Hypertable</li>
</ul>
</li>
<li>Graph DB<ul>
<li>node, edge, property와 함께 그래프 구조를 사용하여 데이터를 표현하고 저장한다.</li>
<li>개체와 관계를 그래프 형태로 표현한 것이므로 관계형 모델이라고 할 수 있다.</li>
<li>데이터 간의 관계가 탐색의 키일 경우에 적합하다.</li>
<li>ex) Neo4J</li>
</ul>
</li>
</ol>
<blockquote>
<aside>
💡 SQL

</blockquote>
<p>: Strucured Query Language</p>
<blockquote>
</blockquote>
<ul>
<li>관리 시스템의 데이터를 관리하기 위해 설계된 특수 목적의 프로그래밍 언어</li>
<li>관계형 데이터베이스 관리 시스템에서 자료의 검색과 관리, 데이터베이스 스키마 생성과 수정, 데이터베이스 객체 접근 조정 관리</aside>

</li>
</ul>
<h3 id="nosql-장점">NoSQL 장점</h3>
<ul>
<li>스키마가 없기 때문에 유연하며 자유로운 데이터 구조를 가질 수 있다.</li>
<li>언제든 저장된 데이터를 조정하고 새로운 필드를 추가할 수 있습니다.</li>
<li>데이터 분산이 용이하며 성능 향상을 위한 Scale-up 뿐만이 아닌 Scale-out 또한 가능합니다.</li>
</ul>
<h3 id="nosql-단점">NoSQL 단점</h3>
<ul>
<li>데이터 중복이 발생할 수 있으며 중복된 데이터가 변경 될 경우 수정을 모든 컬렉션에서 수행을 해야 합니다.</li>
<li>스키마가 존재하지 않기에 명확한 데이터 구조를 보장하지 않으며 데이터 구조 결정가 어려울 수 있습니다.</li>
</ul>
<blockquote>
<h1 id="결론">결론</h1>
</blockquote>
<p><strong>RDBMS는</strong> </p>
<ul>
<li>데이터 구조가 명확하고, 변경될 여지가 없으며, 명확한 스키마가 중요한 경우 사용하는 것이 좋다.</li>
<li>관계를 맺고 있는 데이터가 자주 변경이 이루어지는 시스템에 적합하다.</li>
</ul>
<p>NoSQL은</p>
<ul>
<li>정확한 데이터 구조를 알 수 없고 데이터가 변경/확장이 될 수 있는 경우에 사용하는 겻이 좋다.</li>
<li>Update가 많이 이루어지지 않는 시스템이 좋다.</li>
<li>막대한 데이터를 저장해야 해서 DB를 Scale-out를 해야 하는 시스템에 적합하다.</li>
</ul>
<p><br><br></p>
<blockquote>
<h1 id="rdbms와-nosql의-차이점">RDBMS와 NoSQL의 차이점</h1>
</blockquote>
<p>RDBMS는 관계형 데이터베이스로 테이블이 서로 연관되어 있어, 효율적으로 데이터를 저장 및 관리를 할 수 있다. 정해진 스키마에 따라 데이터를 저장해야 한다.
NoSQL은 테이블 간의 관계를 정의하지 않으며, 데이터 테이블은 보통 하나의 테이블이다. 스키마가 존재하지 않기 때문에 자유로운 데이터 구조를 가질 수 있고, 데이터 분산이 용이하다.</p>
<p><br><br></p>
<h1 id="출처">출처</h1>
<p><a href="https://velog.io/@sysop/DBMS-%EC%99%80-RDBMS%EC%9D%98-%EA%B0%9C%EB%85%90">https://velog.io/@sysop/DBMS-와-RDBMS의-개념</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Backjoon]1932-정수 삼각형]]></title>
            <link>https://velog.io/@e_jim/1932-%EC%A0%95%EC%88%98-%EC%82%BC%EA%B0%81%ED%98%95</link>
            <guid>https://velog.io/@e_jim/1932-%EC%A0%95%EC%88%98-%EC%82%BC%EA%B0%81%ED%98%95</guid>
            <pubDate>Thu, 30 Mar 2023 08:22:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="dp-dynamic-programming">DP (Dynamic Programming)</h1>
</blockquote>
<h2 id="문제">문제</h2>
<pre><code>        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5</code></pre><p>위 그림은 크기가 5인 정수 삼각형의 한 모습이다.</p>
<p>맨 위층 7부터 시작해서 아래에 있는 수 중 하나를 선택하여 아래층으로 내려올 때, 이제까지 선택된 수의 합이 최대가 되는 경로를 구하는 프로그램을 작성하라. 아래층에 있는 수는 현재 층에서 선택된 수의 대각선 왼쪽 또는 대각선 오른쪽에 있는 것 중에서만 선택할 수 있다.</p>
<p>삼각형의 크기는 1 이상 500 이하이다. 삼각형을 이루고 있는 각 수는 모두 정수이며, 범위는 0 이상 9999 이하이다.</p>
<hr>
<h2 id="solution">Solution</h2>
<p><img src="https://velog.velcdn.com/images/e_jim/post/55459acf-6913-45cd-acdf-b7c99cd9ca5c/image.JPG" alt=""></p>
<blockquote>
<h4 id="제출-코드">제출 코드</h4>
</blockquote>
<pre><code class="language-python"># 정수 삼각형

import sys
input = sys.stdin.readline

n = int(input())
triangle = []

for i in range(n):
    triangle.append(list(map(int, input().split())))    

for i in range(n-2,-1,-1):
    for j in range(len(triangle[i])):
        triangle[i][j] = max(triangle[i+1][j], triangle[i+1][j+1])+ triangle[i][j]

print(triangle[0][0])</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[S3] 파일 업로드 & 삭제]]></title>
            <link>https://velog.io/@e_jim/S3-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%82%AD%EC%A0%9C</link>
            <guid>https://velog.io/@e_jim/S3-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%82%AD%EC%A0%9C</guid>
            <pubDate>Fri, 25 Nov 2022 07:26:59 GMT</pubDate>
            <description><![CDATA[<aside>

<blockquote>
</blockquote>
<ol>
<li><p>build.gradle에 추가하기</p>
<pre><code class="language-java"> implementation &#39;org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE&#39;</code></pre>
<blockquote>
</blockquote>
</li>
<li><p>application.yml에 추가하기</p>
<pre><code class="language-java"> cloud:
   aws:
     s3:
       bucket: {버킷이름}
     credentials:
       access-key: ???
       secret-key: ???
     region:
       static: ap-northeast-2
       auto: false
     stack:
       auto: false</code></pre>
<blockquote>
</blockquote>
</li>
<li><p>Config</p>
<ul>
<li><p><code>AWSS3Config</code></p>
<pre><code class="language-java">package jpabook.jpashop;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AWSS3Config {
   @Value(&quot;${cloud.aws.credentials.access-key}&quot;)
   private String accessKey;

   @Value(&quot;${cloud.aws.credentials.secret-key}&quot;)
   private String secretKey;

   @Value(&quot;${cloud.aws.region.static}&quot;)
   private String region;

   @Bean
   public AmazonS3Client amazonS3Client() {
       BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
       return (AmazonS3Client) AmazonS3ClientBuilder.standard()
               .withRegion(region)
               .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
               .build();
   }
}</code></pre>
<blockquote>
<ol start="4">
<li>Controller</li>
</ol>
</blockquote>
</li>
<li><p>AmazonS3Controller</p>
<pre><code class="language-java">package jpabook.jpashop.controller;

import jpabook.jpashop.Service.AWSS3Service;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/s3&quot;)
public class AmazonS3Controller {

   private final AWSS3Service awsS3Service;

   /**
    * Amazon S3에 파일 업로드
    * @return 성공 시 string Success 반환하게 함
    */
//    @ApiOperation(value = &quot;Amazon S3에 파일 업로드&quot;, notes = &quot;Amazon S3에 파일 업로드 &quot;)
   @PostMapping(&quot;/file&quot;)
   public String uploadFile(@RequestPart List&lt;MultipartFile&gt; multipartFile) {
       awsS3Service.uploadFile(multipartFile);
       return &quot;success&quot;;
   }

   /**
    * Amazon S3에 업로드 된 파일을 삭제
    * @return 성공 시 string Success 반환하게 함
    */
//    @ApiOperation(value = &quot;Amazon S3에 업로드 된 파일을 삭제&quot;, notes = &quot;Amazon S3에 업로드된 파일 삭제&quot;)
   @DeleteMapping(&quot;/file&quot;)
   public ResponseEntity&lt;Void&gt; deleteFile(@RequestParam String fileName) {
       awsS3Service.deleteFile(fileName);
       return null;
   }
}</code></pre>
<blockquote>
<ol start="5">
<li>Service</li>
</ol>
</blockquote>
</li>
<li><p>AWSS3Service</p>
<pre><code class="language-java">package jpabook.jpashop.Service;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.DeleteObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class AWSS3Service {

   @Value(&quot;${cloud.aws.s3.bucket}&quot;)
   private String bucket;

   private final AmazonS3 amazonS3;

   public List&lt;String&gt; uploadFile(List&lt;MultipartFile&gt; multipartFile) {
       List&lt;String&gt; fileNameList = new ArrayList&lt;&gt;();

       // forEach 구문을 통해 multipartFile로 넘어온 파일들 하나씩 fileNameList에 추가
       multipartFile.forEach(file -&gt; {
           String fileName = createFileName(file.getOriginalFilename());
           ObjectMetadata objectMetadata = new ObjectMetadata();
           objectMetadata.setContentLength(file.getSize());
           objectMetadata.setContentType(file.getContentType());

           try(InputStream inputStream = file.getInputStream()) {
               amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata)
                       .withCannedAcl(CannedAccessControlList.PublicRead));
               System.out.println(&quot;fileName = &quot; + fileName);
           } catch(IOException e) {
               throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, &quot;파일 업로드에 실패했습니다.&quot;);
           }

           fileNameList.add(fileName);
       });

       return fileNameList;
   }

   public void deleteFile(String fileName) {
       amazonS3.deleteObject(new DeleteObjectRequest(bucket, fileName));
   }

   private String createFileName(String fileName) { // 먼저 파일 업로드 시, 파일명을 난수화하기 위해 random으로 돌립니다.
       return UUID.randomUUID().toString().concat(getFileExtension(fileName));
   }

   private String getFileExtension(String fileName) { // file 형식이 잘못된 경우를 확인하기 위해 만들어진 로직이며, 파일 타입과 상관없이 업로드할 수 있게 하기 위해 .의 존재 유무만 판단하였습니다.
       try {
           return fileName.substring(fileName.lastIndexOf(&quot;.&quot;));
       } catch (StringIndexOutOfBoundsException e) {
           throw new ResponseStatusException(HttpStatus.BAD_REQUEST, &quot;잘못된 형식의 파일(&quot; + fileName + &quot;) 입니다.&quot;);
       }
   }
}</code></pre>
</li>
</ul>
</li>
</ol>
<blockquote>
</blockquote>
<h3 id="결과">결과</h3>
<ul>
<li>파일 업로드</li>
</ul>
<p><img src="https://velog.velcdn.com/images/e_jim/post/e63e3f11-1f0b-4cb8-bc1f-6db0ac1a8ad2/image.png" alt="">
<img src="https://velog.velcdn.com/images/e_jim/post/78573be3-c0e5-4bc0-a4ff-0c489b3f5132/image.png" alt=""></p>
<ul>
<li>파일 삭제
<img src="https://velog.velcdn.com/images/e_jim/post/abaa20a6-2e17-4d97-b7ac-71667834e63f/image.png" alt="">
<img src="https://velog.velcdn.com/images/e_jim/post/84bcb4ee-358c-40f1-a117-cb4bf87dc21c/image.png" alt=""></li>
</ul>
</aside>]]></description>
        </item>
    </channel>
</rss>