<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>mskimdev.log</title>
        <link>https://velog.io/</link>
        <description>&lt;- 개발 공부하는 나</description>
        <lastBuildDate>Wed, 22 Apr 2026 00:10:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>mskimdev.log</title>
            <url>https://velog.velcdn.com/images/mskim_/profile/a3ee3547-8be2-484a-9e56-1796b33798c2/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. mskimdev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/mskim_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[HTTP(HyperText Transfer Protocol)]]></title>
            <link>https://velog.io/@mskim_/HTTPHyperText-Transfer-Protocol</link>
            <guid>https://velog.io/@mskim_/HTTPHyperText-Transfer-Protocol</guid>
            <pubDate>Wed, 22 Apr 2026 00:10:27 GMT</pubDate>
            <description><![CDATA[<h1 id="http란">HTTP란?</h1>
<p>웹 브라우저에서 주소를 입력하면 페이지가 뜬다. 이 과정에서 브라우저와 서버가 주고받는 약속이 있다. 그게 바로 HTTP다.</p>
<hr>
<h2 id="http란-1">HTTP란</h2>
<p>HTTP(HyperText Transfer Protocol)는 클라이언트와 서버가 데이터를 주고받기 위한 <strong>통신 규약(Protocol)</strong> 이다. 웹에서 HTML, 이미지, JSON 등 거의 모든 데이터가 HTTP를 통해 전달된다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/e174bce3-3695-40e3-96ce-f0a71e352c31/image.png" alt=""></p>
<p>핵심은 <strong>요청(Request)과 응답(Response)</strong> 구조다. 클라이언트가 먼저 요청을 보내고, 서버가 그에 맞는 응답을 돌려준다. 서버가 먼저 데이터를 보내는 일은 없다.</p>
<hr>
<h2 id="http와-소켓의-차이">HTTP와 소켓의 차이</h2>
<p>소켓은 연결을 유지하면서 자유롭게 데이터를 주고받는다. 반면 HTTP는 기본적으로 <strong>요청 하나에 응답 하나</strong>로 끝나는 단순한 구조다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>Socket</th>
<th>HTTP</th>
</tr>
</thead>
<tbody><tr>
<td><strong>연결 방식</strong></td>
<td>지속 연결</td>
<td>요청-응답 후 종료</td>
</tr>
<tr>
<td><strong>형식</strong></td>
<td>자유 형식</td>
<td>정해진 헤더/바디 구조</td>
</tr>
<tr>
<td><strong>용도</strong></td>
<td>채팅, 게임 등 실시간</td>
<td>웹 요청, API 호출</td>
</tr>
</tbody></table>
<p>소켓이 전화 통화라면, HTTP는 편지다. 편지를 보내면 답장이 오고 끝난다.</p>
<hr>
<h2 id="http-요청-구조">HTTP 요청 구조</h2>
<p>HTTP 요청은 세 부분으로 이루어진다.</p>
<pre><code>GET /search?q=java HTTP/1.1
Host: www.example.com
Accept: text/html

(본문 없음 - GET은 보통 본문이 없다)</code></pre><p><strong>메서드(Method)</strong>: 무엇을 할지 지정한다.
<strong>URL</strong>: 어디에 요청할지 지정한다.
<strong>헤더(Header)</strong>: 부가 정보(인증, 콘텐츠 타입 등).
<strong>바디(Body)</strong>: 전송할 데이터 (POST, PUT 등에서 사용).</p>
<hr>
<h2 id="http-메서드">HTTP 메서드</h2>
<p><img src="https://velog.velcdn.com/images/mskim_/post/e68a94ec-000d-4b41-9f85-12acf8eb8b4c/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>메서드</th>
<th>의미</th>
<th>주요 용도</th>
</tr>
</thead>
<tbody><tr>
<td><strong>GET</strong></td>
<td>조회</td>
<td>데이터 읽기</td>
</tr>
<tr>
<td><strong>POST</strong></td>
<td>생성</td>
<td>데이터 등록</td>
</tr>
<tr>
<td><strong>PUT</strong></td>
<td>수정</td>
<td>데이터 전체 교체</td>
</tr>
<tr>
<td><strong>PATCH</strong></td>
<td>수정</td>
<td>데이터 일부 수정</td>
</tr>
<tr>
<td><strong>DELETE</strong></td>
<td>삭제</td>
<td>데이터 삭제</td>
</tr>
</tbody></table>
<p>GET은 URL에 데이터를 담아 보내고, POST는 바디에 담아 보낸다. 그래서 GET은 주소창에서 내용이 보이고, POST는 보이지 않는다.</p>
<hr>
<h2 id="http-응답-구조">HTTP 응답 구조</h2>
<pre><code>HTTP/1.1 200 OK
Content-Type: application/json

{&quot;name&quot;: &quot;김민수&quot;, &quot;age&quot;: 25}</code></pre><p>응답도 세 부분이다. <strong>상태 라인</strong>, <strong>헤더</strong>, <strong>바디</strong>.</p>
<hr>
<h2 id="http-상태-코드">HTTP 상태 코드</h2>
<p>서버는 요청 결과를 숫자로 알려준다.</p>
<table>
<thead>
<tr>
<th>범위</th>
<th>의미</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>2xx</strong></td>
<td>성공</td>
<td>200 OK, 201 Created</td>
</tr>
<tr>
<td><strong>3xx</strong></td>
<td>리다이렉트</td>
<td>301 Moved Permanently</td>
</tr>
<tr>
<td><strong>4xx</strong></td>
<td>클라이언트 오류</td>
<td>400 Bad Request, 404 Not Found</td>
</tr>
<tr>
<td><strong>5xx</strong></td>
<td>서버 오류</td>
<td>500 Internal Server Error</td>
</tr>
</tbody></table>
<p>404는 요청한 페이지가 없다는 뜻이고, 500은 서버 쪽에서 문제가 생겼다는 뜻이다. 2xx가 나오면 정상적으로 처리됐다고 보면 된다.</p>
<hr>
<h2 id="java에서-http-요청-보내기">Java에서 HTTP 요청 보내기</h2>
<p>Java 11부터는 <code>HttpClient</code>가 표준으로 포함됐다. 별도 라이브러리 없이 HTTP 요청을 보낼 수 있다.</p>
<pre><code class="language-java">import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;

HttpClient client = HttpClient.newHttpClient();

// GET 요청
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(&quot;https://jsonplaceholder.typicode.com/posts/1&quot;))
        .GET()
        .build();

HttpResponse&lt;String&gt; response = client.send(request, HttpResponse.BodyHandlers.ofString());

System.out.println(response.statusCode()); // 200
System.out.println(response.body());       // JSON 응답 본문</code></pre>
<p>POST 요청도 비슷하다. 바디에 데이터를 담아 보낸다.</p>
<pre><code class="language-java">String json = &quot;{\&quot;title\&quot;: \&quot;테스트\&quot;, \&quot;body\&quot;: \&quot;내용\&quot;, \&quot;userId\&quot;: 1}&quot;;

HttpRequest postRequest = HttpRequest.newBuilder()
        .uri(URI.create(&quot;https://jsonplaceholder.typicode.com/posts&quot;))
        .header(&quot;Content-Type&quot;, &quot;application/json&quot;)
        .POST(HttpRequest.BodyPublishers.ofString(json))
        .build();

HttpResponse&lt;String&gt; postResponse = client.send(postRequest, HttpResponse.BodyHandlers.ofString());
System.out.println(postResponse.statusCode()); // 201</code></pre>
<hr>
<h2 id="https란">HTTPS란</h2>
<p>HTTP 앞에 S가 붙은 HTTPS(HTTP Secure)는 데이터를 암호화해서 주고받는다. 주소창에 자물쇠 아이콘이 붙어 있으면 HTTPS다. 요즘 웹사이트는 대부분 HTTPS를 쓴다. Java의 <code>HttpClient</code>는 HTTPS도 별도 설정 없이 그대로 지원한다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>HTTP는 웹의 기본 언어다. 요청과 응답, 메서드, 상태 코드 이 세 가지만 이해해도 웹이 돌아가는 방식을 읽을 수 있다. 나중에 REST API나 Spring을 다루게 되면 여기서 배운 개념이 계속 등장한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Map]]></title>
            <link>https://velog.io/@mskim_/Map</link>
            <guid>https://velog.io/@mskim_/Map</guid>
            <pubDate>Tue, 21 Apr 2026 08:32:52 GMT</pubDate>
            <description><![CDATA[<h1 id="map">Map</h1>
<p>지금까지 본 List와 Set은 값 하나씩을 담는다. 그런데 &quot;이름 → 전화번호&quot;, &quot;학번 → 이름&quot;처럼 <strong>쌍으로 묶인 데이터</strong>를 다뤄야 할 때가 있다. 이를 위한 컬렉션이 <code>Map</code>이다.</p>
<hr>
<h2 id="map이란">Map이란</h2>
<p><code>Map</code>은 <strong>키(Key)와 값(Value)의 쌍</strong>으로 데이터를 저장하는 컬렉션이다. 사전처럼, 단어(Key)를 찾으면 뜻(Value)이 나오는 구조다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/6c5a9cbb-d15d-4d8c-976f-db987621265c/image.png" alt=""></p>
<pre><code class="language-java">import java.util.HashMap;
import java.util.Map;

Map&lt;String, String&gt; phoneBook = new HashMap&lt;&gt;();
phoneBook.put(&quot;김민수&quot;, &quot;010-1234-5678&quot;);
phoneBook.put(&quot;이지은&quot;, &quot;010-9876-5432&quot;);
phoneBook.put(&quot;박준호&quot;, &quot;010-1111-2222&quot;);

System.out.println(phoneBook.get(&quot;김민수&quot;)); // 010-1234-5678</code></pre>
<hr>
<h2 id="map의-핵심-특징">Map의 핵심 특징</h2>
<p><strong>키는 중복될 수 없다.</strong> 같은 키로 값을 두 번 넣으면 나중 값이 덮어쓴다.</p>
<pre><code class="language-java">Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.put(&quot;사과&quot;, 1000);
map.put(&quot;사과&quot;, 2000); // 덮어쓰기

System.out.println(map.get(&quot;사과&quot;)); // 2000</code></pre>
<p><strong>값은 중복될 수 있다.</strong> 다른 키가 같은 값을 가져도 된다.</p>
<p><strong>순서를 보장하지 않는다.</strong> (HashMap 기준)</p>
<hr>
<h2 id="map의-종류">Map의 종류</h2>
<h3 id="hashmap">HashMap</h3>
<p>가장 일반적으로 쓰이는 Map이다. 내부적으로 해시 테이블을 사용해서 검색/추가/삭제가 빠르다. 순서는 보장하지 않는다.</p>
<pre><code class="language-java">Map&lt;String, Integer&gt; hashMap = new HashMap&lt;&gt;();
hashMap.put(&quot;바나나&quot;, 500);
hashMap.put(&quot;사과&quot;, 1000);
hashMap.put(&quot;딸기&quot;, 800);
System.out.println(hashMap); // 순서 보장 없음</code></pre>
<h3 id="linkedhashmap">LinkedHashMap</h3>
<p>입력한 순서를 유지하는 Map이다.</p>
<pre><code class="language-java">import java.util.LinkedHashMap;

Map&lt;String, Integer&gt; linkedMap = new LinkedHashMap&lt;&gt;();
linkedMap.put(&quot;바나나&quot;, 500);
linkedMap.put(&quot;사과&quot;, 1000);
linkedMap.put(&quot;딸기&quot;, 800);
System.out.println(linkedMap); // {바나나=500, 사과=1000, 딸기=800} (입력 순서 유지)</code></pre>
<h3 id="treemap">TreeMap</h3>
<p>키를 <strong>정렬된 순서</strong>로 유지하는 Map이다. 키가 문자열이면 사전 순, 숫자면 오름차순으로 자동 정렬된다.</p>
<pre><code class="language-java">import java.util.TreeMap;

Map&lt;String, Integer&gt; treeMap = new TreeMap&lt;&gt;();
treeMap.put(&quot;바나나&quot;, 500);
treeMap.put(&quot;사과&quot;, 1000);
treeMap.put(&quot;딸기&quot;, 800);
System.out.println(treeMap); // {바나나=500, 딸기=800, 사과=1000} (키 기준 정렬)</code></pre>
<p><img src="https://velog.velcdn.com/images/mskim_/post/5a43c1aa-d6fa-466a-9f30-5926a3f39dff/image.png" alt=""></p>
<hr>
<h2 id="주요-메서드">주요 메서드</h2>
<pre><code class="language-java">Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();

// 추가 / 수정
map.put(&quot;사과&quot;, 1000);

// 조회
map.get(&quot;사과&quot;);             // 1000 (없으면 null)
map.getOrDefault(&quot;포도&quot;, 0); // 키가 없으면 기본값 반환

// 삭제
map.remove(&quot;사과&quot;);

// 확인
map.containsKey(&quot;바나나&quot;);   // 키 존재 여부
map.containsValue(1000);     // 값 존재 여부
map.size();                  // 크기
map.isEmpty();               // 비어있는지

// 전체 조회
map.keySet();                // 키 목록 (Set)
map.values();                // 값 목록 (Collection)
map.entrySet();              // 키-값 쌍 목록</code></pre>
<hr>
<h2 id="map-순회">Map 순회</h2>
<p>Map은 인덱스가 없으므로 <code>keySet()</code> 또는 <code>entrySet()</code>으로 순회한다.</p>
<pre><code class="language-java">Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.put(&quot;사과&quot;, 1000);
map.put(&quot;바나나&quot;, 500);
map.put(&quot;딸기&quot;, 800);

// 키로 순회
for (String key : map.keySet()) {
    System.out.println(key + &quot;: &quot; + map.get(key));
}

// 키-값 쌍으로 순회 (더 효율적)
for (Map.Entry&lt;String, Integer&gt; entry : map.entrySet()) {
    System.out.println(entry.getKey() + &quot;: &quot; + entry.getValue());
}</code></pre>
<hr>
<h2 id="컬렉션-정리">컬렉션 정리</h2>
<p>지금까지 배운 컬렉션 프레임워크를 한눈에 정리하면 이렇다.</p>
<table>
<thead>
<tr>
<th>컬렉션</th>
<th>특징</th>
<th>대표 구현체</th>
</tr>
</thead>
<tbody><tr>
<td><strong>List</strong></td>
<td>순서 유지, 중복 허용, 인덱스 접근</td>
<td>ArrayList, LinkedList</td>
</tr>
<tr>
<td><strong>Set</strong></td>
<td>중복 불허, 순서 미보장</td>
<td>HashSet, TreeSet</td>
</tr>
<tr>
<td><strong>Map</strong></td>
<td>키-값 쌍, 키 중복 불허</td>
<td>HashMap, TreeMap</td>
</tr>
</tbody></table>
<p>어떤 컬렉션을 써야 할지 막막할 때는 세 가지만 따져보면 된다.</p>
<pre><code>중복을 허용해야 하는가?       → List
중복 없이 값만 모아야 하는가?  → Set
키로 값을 찾아야 하는가?       → Map</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Set]]></title>
            <link>https://velog.io/@mskim_/Set</link>
            <guid>https://velog.io/@mskim_/Set</guid>
            <pubDate>Tue, 21 Apr 2026 08:26:11 GMT</pubDate>
            <description><![CDATA[<h1 id="set">Set</h1>
<p>리스트는 같은 값을 여러 번 담을 수 있다. 그런데 때로는 &quot;중복 없이&quot; 데이터를 모아야 할 때가 있다. 예를 들어 설문 응답자 목록, 태그 목록처럼 같은 항목이 두 번 들어가면 안 되는 경우다. 이럴 때 쓰는 게 <code>Set</code>이다.</p>
<hr>
<h2 id="set의-핵심-특징">Set의 핵심 특징</h2>
<p><code>Set</code>은 <code>List</code>와 달리 두 가지 특징이 있다.</p>
<p><strong>중복을 허용하지 않는다.</strong> 같은 값을 두 번 추가하면 무시된다.</p>
<p><strong>순서를 보장하지 않는다.</strong> 추가한 순서대로 저장되지 않는다.</p>
<pre><code class="language-java">import java.util.HashSet;
import java.util.Set;

Set&lt;String&gt; set = new HashSet&lt;&gt;();
set.add(&quot;사과&quot;);
set.add(&quot;바나나&quot;);
set.add(&quot;사과&quot;); // 중복 → 무시됨

System.out.println(set); // [바나나, 사과] (순서는 실행마다 다를 수 있다)
System.out.println(set.size()); // 2</code></pre>
<hr>
<h2 id="set의-종류">Set의 종류</h2>
<h3 id="hashset">HashSet</h3>
<p>가장 많이 쓰이는 구현체다. 내부적으로 해시 테이블(Hash Table)을 사용한다. 덕분에 추가/삭제/검색이 빠르다. 단, 순서를 전혀 보장하지 않는다.</p>
<pre><code class="language-java">Set&lt;String&gt; hashSet = new HashSet&lt;&gt;();
hashSet.add(&quot;C&quot;);
hashSet.add(&quot;A&quot;);
hashSet.add(&quot;B&quot;);
System.out.println(hashSet); // [A, B, C] 또는 다른 순서 (보장 없음)</code></pre>
<h3 id="linkedhashset">LinkedHashSet</h3>
<p>추가한 순서를 유지하는 Set이다. HashSet보다 약간 느리지만, 입력 순서를 보장해야 할 때 쓴다.</p>
<pre><code class="language-java">import java.util.LinkedHashSet;

Set&lt;String&gt; linkedSet = new LinkedHashSet&lt;&gt;();
linkedSet.add(&quot;C&quot;);
linkedSet.add(&quot;A&quot;);
linkedSet.add(&quot;B&quot;);
System.out.println(linkedSet); // [C, A, B] (입력 순서 유지)</code></pre>
<h3 id="treeset">TreeSet</h3>
<p>데이터를 <strong>정렬된 순서</strong>로 유지하는 Set이다. 내부적으로 이진 탐색 트리를 사용한다. 숫자는 오름차순, 문자열은 사전 순으로 자동 정렬된다.</p>
<pre><code class="language-java">import java.util.TreeSet;

Set&lt;Integer&gt; treeSet = new TreeSet&lt;&gt;();
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);
System.out.println(treeSet); // [1, 2, 3] (자동 정렬)</code></pre>
<p><img src="https://velog.velcdn.com/images/mskim_/post/8b99aef5-8626-4bb9-abb6-34a8989bc81d/image.png" alt=""></p>
<hr>
<h2 id="주요-메서드">주요 메서드</h2>
<p><code>Set</code>도 <code>List</code>와 마찬가지로 인터페이스다. <code>HashSet</code>, <code>LinkedHashSet</code>, <code>TreeSet</code> 모두 <code>Set</code> 인터페이스를 구현한다.</p>
<pre><code class="language-java">Set&lt;String&gt; set = new HashSet&lt;&gt;();

set.add(&quot;사과&quot;);         // 추가 (중복이면 false 반환)
set.remove(&quot;사과&quot;);      // 삭제
set.contains(&quot;바나나&quot;);  // 포함 여부
set.size();              // 크기
set.clear();             // 전체 삭제
set.isEmpty();           // 비어있는지 확인</code></pre>
<p><code>List</code>와 달리 인덱스가 없으므로 <code>get(0)</code> 같은 인덱스 접근은 불가능하다. 순회는 for-each를 쓴다.</p>
<pre><code class="language-java">for (String item : set) {
    System.out.println(item);
}</code></pre>
<hr>
<h2 id="중복-제거-활용">중복 제거 활용</h2>
<p>Set의 가장 실용적인 활용은 <strong>중복 제거</strong>다. ArrayList에 담긴 데이터에서 중복을 없애고 싶을 때, Set에 넣었다가 다시 꺼내면 된다.</p>
<pre><code class="language-java">import java.util.*;

List&lt;String&gt; listWithDups = new ArrayList&lt;&gt;(Arrays.asList(&quot;사과&quot;, &quot;바나나&quot;, &quot;사과&quot;, &quot;딸기&quot;, &quot;바나나&quot;));

Set&lt;String&gt; set = new HashSet&lt;&gt;(listWithDups); // 중복 제거
List&lt;String&gt; result = new ArrayList&lt;&gt;(set);    // 다시 List로

System.out.println(result); // [바나나, 사과, 딸기] (순서는 다를 수 있음)</code></pre>
<hr>
<h2 id="list-vs-set-정리">List vs Set 정리</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>List</th>
<th>Set</th>
</tr>
</thead>
<tbody><tr>
<td><strong>중복</strong></td>
<td>허용</td>
<td>불허</td>
</tr>
<tr>
<td><strong>순서</strong></td>
<td>유지</td>
<td>미보장 (LinkedHashSet 제외)</td>
</tr>
<tr>
<td><strong>인덱스 접근</strong></td>
<td>가능</td>
<td>불가</td>
</tr>
<tr>
<td><strong>대표 구현체</strong></td>
<td>ArrayList, LinkedList</td>
<td>HashSet, TreeSet</td>
</tr>
</tbody></table>
<hr>
<h2 id="마무리">마무리</h2>
<p>&quot;담을 데이터가 중복될 수 있냐, 없냐&quot;를 기준으로 List와 Set을 구분하면 된다. 순서가 중요하다면 List, 중복 없이 유일한 값만 모아야 한다면 Set. 그 안에서 정렬이 필요하면 TreeSet, 입력 순서가 필요하면 LinkedHashSet을 선택하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[List]]></title>
            <link>https://velog.io/@mskim_/List</link>
            <guid>https://velog.io/@mskim_/List</guid>
            <pubDate>Tue, 21 Apr 2026 08:23:54 GMT</pubDate>
            <description><![CDATA[<h1 id="list">List</h1>
<p>ArrayList도 있고 Vector도 있고, 비슷하게 동작하는 클래스가 여럿이다. 그런데 코드를 보다 보면 변수 타입으로 <code>ArrayList</code> 대신 <code>List</code>를 쓴 경우를 자주 마주친다. <code>List</code>는 뭘까?</p>
<hr>
<h2 id="list는-인터페이스다">List는 인터페이스다</h2>
<p><code>List</code>는 클래스가 아니라 <strong>인터페이스</strong>다. &quot;순서가 있고, 중복을 허용하는 컬렉션&quot;이 갖춰야 할 규칙(메서드 목록)을 정의해놓은 것이다.</p>
<p><code>ArrayList</code>와 <code>Vector</code>, <code>LinkedList</code> 모두 <code>List</code> 인터페이스를 구현(implements)한 클래스다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/6a080d8f-0ec3-4f79-8e78-d06cad0ad83b/image.png" alt=""></p>
<pre><code>List (인터페이스)
├── ArrayList
├── Vector
└── LinkedList</code></pre><hr>
<h2 id="list-타입으로-선언하는-이유">List 타입으로 선언하는 이유</h2>
<pre><code class="language-java">// ArrayList 타입으로 선언
ArrayList&lt;String&gt; list1 = new ArrayList&lt;&gt;();

// List 타입으로 선언
List&lt;String&gt; list2 = new ArrayList&lt;&gt;();</code></pre>
<p>두 줄 다 <code>ArrayList</code> 객체를 만들지만, 선언 타입이 다르다. 왜 <code>List</code>로 선언할까?</p>
<p>나중에 구현체를 바꿔야 할 때 선언부만 수정하면 되기 때문이다.</p>
<pre><code class="language-java">// ArrayList에서 LinkedList로 교체할 때
// List&lt;String&gt; list = new ArrayList&lt;&gt;();  // 이 한 줄만 바꾸면 된다
List&lt;String&gt; list = new LinkedList&lt;&gt;();</code></pre>
<p><code>list</code>를 사용하는 나머지 코드는 <code>List</code> 인터페이스의 메서드만 쓰기 때문에, 구현체가 바뀌어도 그대로 동작한다. 이게 인터페이스를 타입으로 쓰는 이유다.</p>
<hr>
<h2 id="list의-핵심-특징">List의 핵심 특징</h2>
<p><code>List</code>를 구현한 클래스들이 공통으로 갖는 특징이 있다.</p>
<p><strong>순서가 유지된다.</strong> 추가한 순서대로 데이터가 저장되고, 인덱스로 접근할 수 있다.</p>
<p><strong>중복을 허용한다.</strong> 같은 값을 여러 번 추가해도 모두 저장된다.</p>
<pre><code class="language-java">List&lt;String&gt; list = new ArrayList&lt;&gt;();
list.add(&quot;사과&quot;);
list.add(&quot;사과&quot;); // 중복 허용
list.add(&quot;바나나&quot;);

System.out.println(list); // [사과, 사과, 바나나]
System.out.println(list.get(0)); // 사과 (인덱스 접근 가능)</code></pre>
<hr>
<h2 id="list가-제공하는-주요-메서드">List가 제공하는 주요 메서드</h2>
<p><code>List</code> 인터페이스에 정의된 메서드들은 <code>ArrayList</code>, <code>LinkedList</code> 등 모든 구현체에서 동일하게 사용할 수 있다.</p>
<pre><code class="language-java">List&lt;String&gt; list = new ArrayList&lt;&gt;();

list.add(&quot;사과&quot;);           // 추가
list.add(0, &quot;포도&quot;);        // 특정 위치에 추가
list.get(0);                // 인덱스 조회
list.set(0, &quot;망고&quot;);        // 수정
list.remove(0);             // 인덱스로 삭제
list.remove(&quot;바나나&quot;);      // 값으로 삭제
list.size();                // 크기
list.contains(&quot;딸기&quot;);      // 포함 여부
list.indexOf(&quot;딸기&quot;);       // 위치
list.clear();               // 전체 삭제
list.isEmpty();             // 비어있는지 확인</code></pre>
<hr>
<h2 id="arraylist-vs-linkedlist">ArrayList vs LinkedList</h2>
<p><code>List</code>를 구현한 클래스 중 실제로 자주 쓰이는 건 <code>ArrayList</code>와 <code>LinkedList</code>다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>ArrayList</th>
<th>LinkedList</th>
</tr>
</thead>
<tbody><tr>
<td><strong>내부 구조</strong></td>
<td>배열</td>
<td>노드 연결</td>
</tr>
<tr>
<td><strong>인덱스 접근</strong></td>
<td>빠름</td>
<td>느림</td>
</tr>
<tr>
<td><strong>중간 삽입/삭제</strong></td>
<td>느림</td>
<td>빠름</td>
</tr>
<tr>
<td><strong>메모리</strong></td>
<td>연속 공간</td>
<td>노드마다 참조 저장</td>
</tr>
</tbody></table>
<p><img src="https://velog.velcdn.com/images/mskim_/post/8e11861a-0645-422b-88e9-b6d6fb58e81f/image.png" alt=""></p>
<p>데이터를 쌓아두고 순서대로 읽는 상황이면 <code>ArrayList</code>, 중간에 자주 삽입하거나 삭제하는 상황이면 <code>LinkedList</code>가 더 적합하다. 대부분의 경우엔 <code>ArrayList</code>를 기본으로 쓴다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p><code>List</code>는 인터페이스고, <code>ArrayList</code>와 <code>LinkedList</code>는 그것을 구현한 클래스다. 변수를 <code>List</code> 타입으로 선언하면 나중에 구현체를 바꾸기 쉬워진다. 이 관계를 이해하면 컬렉션 프레임워크 전체를 훨씬 수월하게 읽을 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vector]]></title>
            <link>https://velog.io/@mskim_/Vector</link>
            <guid>https://velog.io/@mskim_/Vector</guid>
            <pubDate>Tue, 21 Apr 2026 08:19:04 GMT</pubDate>
            <description><![CDATA[<h1 id="vector">Vector</h1>
<p>ArrayList와 거의 똑같이 생긴 클래스가 하나 더 있다. <code>Vector</code>다. 사용법도 비슷하고, 크기가 자동으로 늘어나는 것도 같다. 그런데 왜 굳이 둘로 나뉘어 있을까?</p>
<hr>
<h2 id="vector란">Vector란</h2>
<p><code>Vector</code>는 ArrayList보다 먼저 나온 클래스다. Java 1.0 시절부터 있었고, ArrayList는 Java 1.2에서 등장했다. 기능 자체는 거의 동일하다. 크기가 동적으로 늘어나는 배열 기반의 리스트라는 점에서.</p>
<pre><code class="language-java">import java.util.Vector;

Vector&lt;String&gt; vector = new Vector&lt;&gt;();
vector.add(&quot;사과&quot;);
vector.add(&quot;바나나&quot;);
vector.add(&quot;딸기&quot;);

System.out.println(vector); // [사과, 바나나, 딸기]</code></pre>
<hr>
<h2 id="arraylist와의-차이-동기화synchronized">ArrayList와의 차이: 동기화(Synchronized)</h2>
<p><img src="https://velog.velcdn.com/images/mskim_/post/852dcfa4-53b6-438f-8588-6e0bdd2293cc/image.png" alt=""></p>
<p>둘의 핵심 차이는 <strong>동기화(Synchronization)</strong> 여부다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>Vector</th>
<th>ArrayList</th>
</tr>
</thead>
<tbody><tr>
<td><strong>동기화</strong></td>
<td>O (thread-safe)</td>
<td>X</td>
</tr>
<tr>
<td><strong>성능</strong></td>
<td>상대적으로 느림</td>
<td>상대적으로 빠름</td>
</tr>
<tr>
<td><strong>등장 시기</strong></td>
<td>Java 1.0</td>
<td>Java 1.2</td>
</tr>
</tbody></table>
<p><code>Vector</code>의 모든 메서드는 <code>synchronized</code> 키워드가 붙어 있다. 여러 스레드가 동시에 접근하더라도 한 번에 하나의 스레드만 처리하도록 잠금이 걸린다. 덕분에 멀티스레드 환경에서도 안전하게 동작한다.</p>
<p>반면 ArrayList는 동기화 처리가 없어서 더 빠르다. 단일 스레드 환경에서는 ArrayList가 낫다.</p>
<hr>
<h2 id="주요-메서드">주요 메서드</h2>
<p>사용법은 ArrayList와 거의 동일하다.</p>
<pre><code class="language-java">Vector&lt;String&gt; v = new Vector&lt;&gt;();

v.add(&quot;사과&quot;);
v.add(0, &quot;포도&quot;);       // 특정 위치에 삽입

v.get(0);               // 인덱스로 조회
v.set(1, &quot;망고&quot;);       // 수정
v.remove(0);            // 인덱스로 삭제
v.size();               // 크기
v.contains(&quot;딸기&quot;);     // 포함 여부
v.clear();              // 전체 삭제</code></pre>
<p>Vector에만 있는 구식 메서드도 있다. <code>addElement()</code>, <code>elementAt()</code>, <code>removeElement()</code> 같은 것들인데, 지금은 쓰지 않는다. ArrayList와 동일한 <code>add()</code>, <code>get()</code>, <code>remove()</code>를 쓰면 된다.</p>
<hr>
<h2 id="그러면-vector는-언제-쓸까">그러면 Vector는 언제 쓸까</h2>
<p>솔직히 말하면, 요즘은 거의 쓰지 않는다.</p>
<p>멀티스레드 환경에서 안전한 리스트가 필요하다면 <code>Collections.synchronizedList()</code>로 ArrayList를 감싸거나, <code>java.util.concurrent</code> 패키지의 <code>CopyOnWriteArrayList</code>를 쓰는 게 더 현대적인 방식이다.</p>
<pre><code class="language-java">import java.util.Collections;
import java.util.ArrayList;
import java.util.List;

// ArrayList를 동기화된 리스트로 감싸기
List&lt;String&gt; syncList = Collections.synchronizedList(new ArrayList&lt;&gt;());</code></pre>
<p>Vector는 레거시 코드(예전에 작성된 코드)에서 마주칠 수 있는 클래스라고 이해하면 된다. 새로 작성하는 코드에서는 ArrayList를 기본으로 쓰자.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>Vector는 ArrayList의 전신이다. 동기화가 기본으로 걸려 있다는 점이 유일한 차이인데, 그 때문에 성능이 느리다. 지금 새로 코드를 짠다면 ArrayList를 쓰고, 멀티스레드가 필요한 경우엔 더 나은 대안이 따로 있다는 걸 기억해두면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ArrayList]]></title>
            <link>https://velog.io/@mskim_/ArrayList</link>
            <guid>https://velog.io/@mskim_/ArrayList</guid>
            <pubDate>Tue, 21 Apr 2026 08:16:23 GMT</pubDate>
            <description><![CDATA[<h1 id="arraylist">ArrayList</h1>
<p>배열은 분명 편리한데, 한 가지 불편한 점이 있다. 크기를 처음에 정해야 한다는 것이다. 10칸짜리 배열을 만들었는데 11번째 데이터가 들어오면? 방법이 없다. 이 문제를 해결하기 위해 <code>ArrayList</code>가 등장했다.</p>
<hr>
<h2 id="arraylist란">ArrayList란</h2>
<p><code>ArrayList</code>는 크기가 자동으로 늘어나는 배열이다. 내부적으로는 배열을 사용하지만, 데이터가 꽉 차면 더 큰 배열을 새로 만들어 자동으로 확장한다. 겉에서 보면 그냥 &quot;크기 제한 없는 배열&quot;처럼 동작한다.</p>
<pre><code class="language-java">import java.util.ArrayList;

ArrayList&lt;String&gt; list = new ArrayList&lt;&gt;();
list.add(&quot;사과&quot;);
list.add(&quot;바나나&quot;);
list.add(&quot;딸기&quot;);

System.out.println(list); // [사과, 바나나, 딸기]</code></pre>
<p><code>&lt;String&gt;</code> 처럼 꺾쇠 안에 타입을 명시하는 것이 제네릭(Generics)이다. 어떤 타입의 데이터를 담을지 미리 지정해두면, 꺼낼 때 형변환 없이 바로 쓸 수 있다.</p>
<hr>
<h2 id="배열과-arraylist의-차이">배열과 ArrayList의 차이</h2>
<p><img src="https://velog.velcdn.com/images/mskim_/post/ef38ed0e-3d65-4902-93f5-5c41fab8c607/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>배열</th>
<th>ArrayList</th>
</tr>
</thead>
<tbody><tr>
<td><strong>크기</strong></td>
<td>고정</td>
<td>자동 확장</td>
</tr>
<tr>
<td><strong>타입</strong></td>
<td>기본형 포함 가능</td>
<td>객체만 가능</td>
</tr>
<tr>
<td><strong>길이 확인</strong></td>
<td><code>length</code></td>
<td><code>size()</code></td>
</tr>
<tr>
<td><strong>추가/삭제</strong></td>
<td>직접 처리</td>
<td>메서드 제공</td>
</tr>
</tbody></table>
<p>배열은 int, double 같은 기본형을 바로 담을 수 있지만, <code>ArrayList</code>는 객체만 담을 수 있다. 그래서 <code>int</code> 대신 <code>Integer</code>, <code>double</code> 대신 <code>Double</code>처럼 래퍼 클래스(Wrapper Class)를 사용한다.</p>
<hr>
<h2 id="주요-메서드">주요 메서드</h2>
<pre><code class="language-java">ArrayList&lt;String&gt; list = new ArrayList&lt;&gt;();

// 추가
list.add(&quot;사과&quot;);           // 맨 뒤에 추가
list.add(0, &quot;포도&quot;);        // 특정 인덱스에 추가

// 조회
String fruit = list.get(0); // 인덱스로 조회
int size = list.size();      // 크기

// 수정
list.set(1, &quot;망고&quot;);        // 특정 인덱스 값 교체

// 삭제
list.remove(0);              // 인덱스로 삭제
list.remove(&quot;바나나&quot;);       // 값으로 삭제

// 검색
boolean has = list.contains(&quot;딸기&quot;); // 포함 여부
int idx = list.indexOf(&quot;딸기&quot;);       // 위치

// 전체 삭제
list.clear();</code></pre>
<hr>
<h2 id="반복-처리">반복 처리</h2>
<p>ArrayList는 for-each 문으로 순회하는 게 일반적이다.</p>
<pre><code class="language-java">ArrayList&lt;String&gt; list = new ArrayList&lt;&gt;();
list.add(&quot;사과&quot;);
list.add(&quot;바나나&quot;);
list.add(&quot;딸기&quot;);

for (String fruit : list) {
    System.out.println(fruit);
}</code></pre>
<p>인덱스가 필요한 경우엔 일반 for문을 쓴다.</p>
<pre><code class="language-java">for (int i = 0; i &lt; list.size(); i++) {
    System.out.println(i + &quot;: &quot; + list.get(i));
}</code></pre>
<hr>
<h2 id="arraylist의-한계">ArrayList의 한계</h2>
<p>ArrayList는 <strong>중간 삽입/삭제가 느리다.</strong> 예를 들어 100개짜리 리스트에서 첫 번째 요소를 삭제하면, 나머지 99개가 한 칸씩 앞으로 당겨진다. 데이터가 많을수록 이 비용이 커진다.</p>
<p>반면 <strong>순서대로 읽는 속도(인덱스 접근)는 빠르다.</strong> 인덱스로 바로 위치를 계산할 수 있기 때문이다.</p>
<p>중간 삽입/삭제가 자주 일어나는 상황이라면 <code>LinkedList</code>를, 데이터를 쌓고 순서대로 읽는 용도라면 <code>ArrayList</code>가 적합하다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>배열의 불편함을 거의 다 해소한 것이 ArrayList다. 크기를 신경 쓰지 않아도 되고, 추가/삭제/검색 메서드도 갖춰져 있다. 컬렉션을 처음 쓴다면 ArrayList부터 시작하는 게 자연스럽다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Socket(소켓)]]></title>
            <link>https://velog.io/@mskim_/Socket%EC%86%8C%EC%BC%93</link>
            <guid>https://velog.io/@mskim_/Socket%EC%86%8C%EC%BC%93</guid>
            <pubDate>Tue, 21 Apr 2026 08:08:40 GMT</pubDate>
            <description><![CDATA[<h1 id="socket">Socket</h1>
<p>코드로 두 컴퓨터가 대화할 수 있을까? 소켓(Socket)이 그 창구다. 자바에서는 <code>java.net</code> 패키지로 네트워크 통신을 구현할 수 있다.</p>
<hr>
<h2 id="소켓이란">소켓이란?</h2>
<p>소켓은 네트워크 상에서 두 프로그램이 데이터를 주고받기 위한 연결 끝점이다. 전화기로 비유하면, 소켓은 전화기고 IP 주소와 포트 번호는 전화번호다.</p>
<p>자바에서는 두 가지 소켓을 사용한다.</p>
<ul>
<li><code>ServerSocket</code>: 서버 측에서 클라이언트의 연결을 기다린다</li>
<li><code>Socket</code>: 클라이언트 측에서 서버에 연결하거나, 연결이 수락된 후 실제 통신에 사용한다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/mskim_/post/a963f00d-63ae-41d3-9933-c58dd9641476/image.png" alt=""></p>
<hr>
<h2 id="기본-구조">기본 구조</h2>
<p>서버와 클라이언트로 역할이 나뉜다.</p>
<h3 id="서버">서버</h3>
<pre><code class="language-java">import java.net.*;
import java.io.*;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080); // 8080 포트에서 대기
        System.out.println(&quot;서버 시작. 연결 대기 중...&quot;);

        Socket socket = serverSocket.accept(); // 클라이언트 연결 수락 (블로킹)
        System.out.println(&quot;클라이언트 연결됨&quot;);

        // 데이터 읽기
        BufferedReader in = new BufferedReader(
            new InputStreamReader(socket.getInputStream())
        );
        String message = in.readLine();
        System.out.println(&quot;받은 메시지: &quot; + message);

        socket.close();
        serverSocket.close();
    }
}</code></pre>
<h3 id="클라이언트">클라이언트</h3>
<pre><code class="language-java">import java.net.*;
import java.io.*;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket(&quot;localhost&quot;, 8080); // 서버에 연결

        // 데이터 보내기
        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
        out.println(&quot;안녕하세요, 서버!&quot;);

        socket.close();
    }
}</code></pre>
<p><code>accept()</code>는 클라이언트가 연결될 때까지 그 자리에서 기다린다. 연결이 오면 <code>Socket</code> 객체를 반환하고, 이후 이 소켓을 통해 데이터를 주고받는다.</p>
<hr>
<h2 id="스트림으로-데이터-주고받기">스트림으로 데이터 주고받기</h2>
<p>소켓 통신은 I/O 스트림을 통해 이루어진다. <code>getInputStream()</code>으로 읽고, <code>getOutputStream()</code>으로 쓴다.</p>
<pre><code class="language-java">Socket socket = new Socket(&quot;localhost&quot;, 8080);

// 쓰기 스트림
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

// 읽기 스트림
BufferedReader in = new BufferedReader(
    new InputStreamReader(socket.getInputStream())
);

out.println(&quot;Hello&quot;);          // 서버로 전송
String response = in.readLine(); // 서버 응답 수신
System.out.println(response);

socket.close();</code></pre>
<p><code>PrintWriter</code>의 두 번째 인자 <code>true</code>는 자동 flush 옵션이다. <code>println()</code> 호출 시 즉시 전송된다.</p>
<hr>
<h2 id="양방향-채팅-예시">양방향 채팅 예시</h2>
<p>서버가 클라이언트 메시지를 받아서 다시 돌려보내는 에코 서버다.</p>
<pre><code class="language-java">// 에코 서버
public class EchoServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        Socket socket = serverSocket.accept();

        BufferedReader in = new BufferedReader(
            new InputStreamReader(socket.getInputStream())
        );
        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

        String line;
        while ((line = in.readLine()) != null) {
            System.out.println(&quot;받음: &quot; + line);
            out.println(&quot;에코: &quot; + line); // 그대로 돌려보냄
        }

        socket.close();
        serverSocket.close();
    }
}</code></pre>
<hr>
<h2 id="thread와-함께-쓰는-이유">Thread와 함께 쓰는 이유</h2>
<p>위 코드는 클라이언트 하나만 처리할 수 있다. <code>accept()</code>는 한 번만 호출되기 때문이다. 여러 클라이언트를 동시에 처리하려면 연결마다 스레드를 만들어야 한다.</p>
<pre><code class="language-java">public class MultiServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);

        while (true) {
            Socket socket = serverSocket.accept(); // 연결마다 반복
            new Thread(() -&gt; {
                try {
                    BufferedReader in = new BufferedReader(
                        new InputStreamReader(socket.getInputStream())
                    );
                    PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

                    String line;
                    while ((line = in.readLine()) != null) {
                        out.println(&quot;에코: &quot; + line);
                    }
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start(); // 각 클라이언트를 별도 스레드에서 처리
        }
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/mskim_/post/b4443e18-49bb-42c5-8ffe-c01350ab24c6/image.png" alt=""></p>
<hr>
<h2 id="소켓-닫기">소켓 닫기</h2>
<p>소켓을 다 쓰고 닫지 않으면 자원이 낭비된다. <code>try-with-resources</code>를 쓰면 자동으로 닫힌다.</p>
<pre><code class="language-java">try (Socket socket = new Socket(&quot;localhost&quot;, 8080);
     PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
     BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

    out.println(&quot;Hello&quot;);
    System.out.println(in.readLine());

} // 블록을 벗어나면 자동으로 close()</code></pre>
<hr>
<h2 id="마무리">마무리</h2>
<p>소켓은 네트워크 통신의 가장 기본 단위다. 서버는 <code>ServerSocket</code>으로 기다리고, 클라이언트는 <code>Socket</code>으로 연결한다. 연결 후엔 스트림으로 데이터를 주고받는다. 클라이언트가 여럿이라면 Thread와 함께 써야 한다는 것도 기억해두면 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭 (Generic)]]></title>
            <link>https://velog.io/@mskim_/%EC%A0%9C%EB%84%A4%EB%A6%AD-Generic</link>
            <guid>https://velog.io/@mskim_/%EC%A0%9C%EB%84%A4%EB%A6%AD-Generic</guid>
            <pubDate>Sat, 18 Apr 2026 09:07:48 GMT</pubDate>
            <description><![CDATA[<h1 id="제네릭-generics">제네릭 (Generics)</h1>
<p><code>List</code>를 처음 쓰다 보면 이런 코드를 만나게 된다.</p>
<pre><code class="language-java">List&lt;String&gt; names = new ArrayList&lt;&gt;();</code></pre>
<p><code>&lt;String&gt;</code>이 뭔지 모르고 그냥 따라 쓰다가, 어느 순간 &quot;이게 왜 있는 거지?&quot;라는 의문이 생긴다. 이게 바로 <strong>제네릭(Generics)</strong>이다.</p>
<hr>
<h2 id="제네릭이-없었다면">제네릭이 없었다면</h2>
<p>제네릭이 생기기 전에는 컬렉션에 뭘 담든 <code>Object</code> 타입으로 처리했다.</p>
<pre><code class="language-java">List list = new ArrayList();
list.add(&quot;안녕&quot;);
list.add(123);  // 문자열이든 숫자든 다 들어간다

String name = (String) list.get(0);  // 꺼낼 때 직접 형변환해야 한다
String wrong = (String) list.get(1); // 런타임 오류 — 123은 String이 아니다</code></pre>
<p>어떤 타입이든 넣을 수 있다 보니 꺼낼 때 형변환이 필요했고, 잘못된 타입을 넣어도 <strong>컴파일 시점에 오류가 나지 않았다</strong>. 실행해봐야 터지는 오류는 찾기도 어렵고 고치기도 까다롭다.</p>
<p>제네릭은 이 문제를 해결하기 위해 등장했다.</p>
<hr>
<h2 id="제네릭의-기본-문법">제네릭의 기본 문법</h2>
<p>타입을 <code>&lt;&gt;</code> 안에 명시해서 <strong>어떤 타입만 담을 수 있는지</strong> 컴파일러에게 알려준다.</p>
<pre><code class="language-java">List&lt;String&gt; names = new ArrayList&lt;&gt;();
names.add(&quot;김민수&quot;);
names.add(&quot;이지현&quot;);
names.add(123);  // 컴파일 오류 — String만 들어갈 수 있다

String name = names.get(0);  // 형변환 불필요</code></pre>
<p>타입이 고정되니까 꺼낼 때 형변환도 필요 없고, 잘못된 타입을 넣으면 <strong>실행 전에 오류</strong>를 잡아준다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/35f5126e-361c-46e4-a186-c3bcc5089afe/image.png" alt=""></p>
<hr>
<h2 id="제네릭-클래스-만들기">제네릭 클래스 만들기</h2>
<p>직접 제네릭 클래스를 만들 수도 있다. 타입 파라미터는 보통 <code>T</code>(Type), <code>E</code>(Element), <code>K</code>(Key), <code>V</code>(Value) 같은 대문자 한 글자를 관례로 쓴다.</p>
<pre><code class="language-java">public class Box&lt;T&gt; {
    private T item;

    public void set(T item) {
        this.item = item;
    }

    public T get() {
        return item;
    }
}</code></pre>
<p><code>T</code>는 실제 타입이 들어올 자리를 표시하는 placeholder다. 사용할 때 타입을 지정하면 그 자리에 들어간다.</p>
<pre><code class="language-java">Box&lt;String&gt; strBox = new Box&lt;&gt;();
strBox.set(&quot;안녕&quot;);
String value = strBox.get();  // 형변환 없이 String으로 받는다

Box&lt;Integer&gt; intBox = new Box&lt;&gt;();
intBox.set(42);
Integer num = intBox.get();</code></pre>
<p><code>Box&lt;String&gt;</code>과 <code>Box&lt;Integer&gt;</code>는 같은 클래스지만 다른 타입으로 동작한다.</p>
<hr>
<h2 id="제네릭-메서드">제네릭 메서드</h2>
<p>메서드 단위로도 제네릭을 적용할 수 있다. 반환 타입 앞에 <code>&lt;T&gt;</code>를 붙인다.</p>
<pre><code class="language-java">public static &lt;T&gt; void print(T item) {
    System.out.println(item);
}

print(&quot;안녕&quot;);   // String
print(42);       // Integer
print(3.14);     // Double</code></pre>
<p>타입에 관계없이 동작하는 유틸리티 메서드를 만들 때 유용하다.</p>
<hr>
<h2 id="타입-제한--extends">타입 제한 — extends</h2>
<p><code>&lt;T&gt;</code> 자리에 아무 타입이나 오지 못하도록 <strong>상한선</strong>을 걸 수 있다.</p>
<pre><code class="language-java">// T는 Number 또는 Number의 하위 클래스만 가능
public static &lt;T extends Number&gt; double sum(List&lt;T&gt; list) {
    double total = 0;
    for (T item : list) {
        total += item.doubleValue();
    }
    return total;
}

sum(List.of(1, 2, 3));        // Integer — 가능
sum(List.of(1.1, 2.2, 3.3)); // Double — 가능
sum(List.of(&quot;a&quot;, &quot;b&quot;));      // 컴파일 오류 — String은 Number가 아니다</code></pre>
<p><code>doubleValue()</code>는 <code>Number</code> 클래스의 메서드다. <code>&lt;T extends Number&gt;</code>로 제한해야 T가 <code>doubleValue()</code>를 가지고 있다고 컴파일러가 보장해준다.</p>
<hr>
<h2 id="와일드카드--">와일드카드 — ?</h2>
<p>제네릭 타입을 정확히 특정하지 않고 <strong>&quot;어떤 타입이든&quot;</strong> 받고 싶을 때 <code>?</code>를 쓴다.</p>
<pre><code class="language-java">// 어떤 타입의 List든 받아서 출력
public static void printList(List&lt;?&gt; list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

printList(List.of(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;));  // List&lt;String&gt;
printList(List.of(1, 2, 3));        // List&lt;Integer&gt;</code></pre>
<p><code>List&lt;Object&gt;</code>는 <code>List&lt;String&gt;</code>을 받을 수 없다. 제네릭은 상속 관계를 따르지 않기 때문이다. 이때 <code>List&lt;?&gt;</code>를 쓰면 어떤 타입의 리스트든 받을 수 있다.</p>
<hr>
<p>제네릭은 처음엔 <code>&lt;&gt;</code>가 낯설게 느껴지지만, 결국 <strong>&quot;이 컨테이너에 어떤 타입을 담을 건지 미리 알려주는 것&quot;</strong>이다. 타입을 명시하는 순간 형변환도 사라지고, 잘못된 타입을 넣는 실수도 컴파일 시점에 잡힌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL 쿼리 실행 순서]]></title>
            <link>https://velog.io/@mskim_/SQL-%EC%BF%BC%EB%A6%AC-%EC%8B%A4%ED%96%89-%EC%88%9C%EC%84%9C</link>
            <guid>https://velog.io/@mskim_/SQL-%EC%BF%BC%EB%A6%AC-%EC%8B%A4%ED%96%89-%EC%88%9C%EC%84%9C</guid>
            <pubDate>Sat, 18 Apr 2026 08:28:48 GMT</pubDate>
            <description><![CDATA[<h1 id="sql-쿼리-실행-순서">SQL 쿼리 실행 순서</h1>
<pre><code class="language-sql">SELECT member_id, COUNT(*) AS 주문횟수
FROM orders
WHERE status = &#39;paid&#39;
GROUP BY member_id
HAVING COUNT(*) &gt;= 2
ORDER BY 주문횟수 DESC;</code></pre>
<p>SQL을 처음 배울 때 한 번쯤 이런 의문이 생긴다. <code>SELECT</code>가 제일 앞에 있는데 왜 <code>WHERE</code>에서는 <code>SELECT</code>에서 정한 별칭을 못 쓰지? <code>HAVING</code>은 <code>WHERE</code>랑 뭐가 다른 거지? 이 의문들은 SQL이 <strong>작성 순서대로 실행되지 않는다</strong>는 걸 알면 한 번에 풀린다.</p>
<hr>
<h2 id="실행-순서">실행 순서</h2>
<p>작성할 때는 <code>SELECT</code>가 맨 앞에 오지만, 실제로 실행될 때는 <code>FROM</code>부터 시작한다.</p>
<pre><code>작성 순서                실행 순서
-----------             -----------
1. SELECT          →    1. FROM
2. FROM            →    2. WHERE
3. WHERE           →    3. GROUP BY
4. GROUP BY        →    4. HAVING
5. HAVING          →    5. SELECT
6. ORDER BY        →    6. ORDER BY
7. LIMIT           →    7. LIMIT</code></pre><p><code>SELECT</code>는 작성할 때 1번이지만 실행은 5번째다. <code>WHERE</code>는 3번째로 작성하지만 2번째로 실행된다. 이 순서 차이가 별칭 오류와 <code>WHERE</code> vs <code>HAVING</code> 혼동의 원인이다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/97f16e65-d825-4d18-8439-fb3d59ed059a/image.png" alt=""></p>
<hr>
<h2 id="각-단계별-처리-내용">각 단계별 처리 내용</h2>
<h3 id="1-from">1. FROM</h3>
<p>어느 테이블에서 데이터를 가져올지 결정한다. JOIN이 있다면 이 단계에서 테이블을 합친다. 이후 모든 단계는 여기서 만들어진 데이터를 대상으로 동작한다.</p>
<h3 id="2-where">2. WHERE</h3>
<p>행 단위로 조건을 검사해서 조건에 맞지 않는 행을 제거한다. <code>GROUP BY</code> 이전에 실행되기 때문에 아직 그룹이 없다. 집계 함수(<code>COUNT</code>, <code>SUM</code> 등)는 그룹이 있어야 계산할 수 있으므로 <code>WHERE</code>에서는 쓸 수 없다.</p>
<pre><code class="language-sql">-- 오류: WHERE에서 집계 함수 사용 불가
WHERE COUNT(*) &gt;= 2</code></pre>
<h3 id="3-group-by">3. GROUP BY</h3>
<p><code>WHERE</code>를 통과한 행들을 지정한 컬럼 기준으로 묶는다. 이 단계부터 개별 행이 아니라 그룹 단위로 데이터가 처리된다.</p>
<h3 id="4-having">4. HAVING</h3>
<p>그룹을 대상으로 조건을 검사한다. <code>GROUP BY</code> 이후에 실행되기 때문에 집계 함수를 조건으로 쓸 수 있다.</p>
<pre><code class="language-sql">-- 가능: HAVING에서 집계 함수 사용
HAVING COUNT(*) &gt;= 2</code></pre>
<h3 id="5-select">5. SELECT</h3>
<p>출력할 컬럼을 결정하고 별칭을 적용한다. 실행 순서상 여기서 처음으로 별칭이 만들어지기 때문에, 이전 단계인 <code>WHERE</code>나 <code>HAVING</code>에서는 <code>SELECT</code>의 별칭을 쓸 수 없다.</p>
<pre><code class="language-sql">-- 오류: HAVING에서 SELECT 별칭 사용 불가
SELECT COUNT(*) AS 주문횟수
...
HAVING 주문횟수 &gt;= 2;  -- 아직 &#39;주문횟수&#39;라는 별칭이 없다

-- 올바른 방법
HAVING COUNT(*) &gt;= 2;</code></pre>
<h3 id="6-order-by">6. ORDER BY</h3>
<p><code>SELECT</code> 이후에 실행되기 때문에 별칭을 쓸 수 있다. 결과를 정렬한다.</p>
<pre><code class="language-sql">-- 가능: ORDER BY에서 SELECT 별칭 사용
SELECT COUNT(*) AS 주문횟수
...
ORDER BY 주문횟수 DESC;</code></pre>
<h3 id="7-limit">7. LIMIT</h3>
<p>최종적으로 반환할 행의 수를 제한한다. 모든 처리가 끝난 뒤 마지막에 잘라낸다.</p>
<hr>
<h2 id="별칭-사용-가능-여부-정리">별칭 사용 가능 여부 정리</h2>
<p>실행 순서를 알면 어디서 별칭을 쓸 수 있는지 바로 판단할 수 있다.</p>
<table>
<thead>
<tr>
<th>절</th>
<th>SELECT 별칭 사용 가능 여부</th>
</tr>
</thead>
<tbody><tr>
<td><code>WHERE</code></td>
<td>불가 — SELECT보다 먼저 실행</td>
</tr>
<tr>
<td><code>GROUP BY</code></td>
<td>불가 — SELECT보다 먼저 실행</td>
</tr>
<tr>
<td><code>HAVING</code></td>
<td>불가 — SELECT보다 먼저 실행</td>
</tr>
<tr>
<td><code>ORDER BY</code></td>
<td>가능 — SELECT보다 나중에 실행</td>
</tr>
<tr>
<td><code>LIMIT</code></td>
<td>가능 — SELECT보다 나중에 실행</td>
</tr>
</tbody></table>
<hr>
<p>SQL이 작성 순서와 실행 순서가 다르다는 게 처음엔 헷갈리지만, 한 번 순서를 머릿속에 박아두면 별칭 오류나 <code>WHERE</code> vs <code>HAVING</code> 혼동 같은 실수가 눈에 띄게 줄어든다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DISTINCT]]></title>
            <link>https://velog.io/@mskim_/DISTINCT</link>
            <guid>https://velog.io/@mskim_/DISTINCT</guid>
            <pubDate>Sat, 18 Apr 2026 08:20:55 GMT</pubDate>
            <description><![CDATA[<h1 id="distinct">DISTINCT</h1>
<pre><code class="language-sql">SELECT member_id FROM orders;</code></pre>
<pre><code>member_id
---------
        1
        1
        2
        1
        3
        2</code></pre><p>같은 회원이 여러 번 주문하면 <code>member_id</code>가 중복으로 나온다. 여기서 &quot;몇 명이 주문했는지&quot;가 궁금한 거라면 중복을 없애야 한다. <code>DISTINCT</code>는 조회 결과에서 중복된 행을 제거한다.</p>
<hr>
<h2 id="기본-사용법">기본 사용법</h2>
<pre><code class="language-sql">SELECT DISTINCT member_id FROM orders;</code></pre>
<pre><code>member_id
---------
        1
        2
        3</code></pre><p><code>SELECT</code> 바로 뒤에 <code>DISTINCT</code>를 붙이면 된다. 중복된 값은 한 번만 나온다.</p>
<hr>
<h2 id="여러-컬럼에-적용">여러 컬럼에 적용</h2>
<p><code>DISTINCT</code>는 컬럼 하나가 아니라 <strong>행 전체</strong>를 기준으로 중복을 판단한다.</p>
<pre><code class="language-sql">SELECT DISTINCT member_id, product_id FROM order_items;</code></pre>
<pre><code>member_id | product_id
----------+-----------
        1 |        101
        1 |        102
        2 |        101</code></pre><p><code>member_id</code>가 같아도 <code>product_id</code>가 다르면 다른 행으로 취급한다. <code>(1, 101)</code>과 <code>(1, 102)</code>는 중복이 아니다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/56b6c5d5-6744-45ae-b635-2c4fae3990cd/image.png" alt=""></p>
<hr>
<h2 id="count와-함께-쓰기">COUNT와 함께 쓰기</h2>
<p>중복을 제거한 개수를 셀 때 <code>COUNT</code>와 함께 쓴다.</p>
<pre><code class="language-sql">-- 전체 주문 수
SELECT COUNT(*) FROM orders;          -- 6

-- 주문한 회원 수 (중복 제거)
SELECT COUNT(DISTINCT member_id) FROM orders;  -- 3</code></pre>
<p><code>COUNT(*)</code>는 행의 총 개수, <code>COUNT(DISTINCT member_id)</code>는 고유한 <code>member_id</code>의 개수다. 둘의 차이를 구분하는 게 중요하다.</p>
<hr>
<h2 id="distinct-vs-group-by">DISTINCT vs GROUP BY</h2>
<p>중복 제거만 필요하다면 <code>DISTINCT</code>가 간결하다. 그룹별로 집계까지 필요하다면 <code>GROUP BY</code>를 쓴다.</p>
<pre><code class="language-sql">-- 주문한 적 있는 회원 목록 — 둘 다 결과 동일
SELECT DISTINCT member_id FROM orders;

SELECT member_id FROM orders GROUP BY member_id;</code></pre>
<p>단순히 중복만 없앨 때는 <code>DISTINCT</code>, 집계 함수(<code>COUNT</code>, <code>SUM</code> 등)와 함께 쓸 때는 <code>GROUP BY</code>로 구분하면 된다.</p>
<hr>
<p><code>DISTINCT</code>는 기능 자체는 단순하다. 단, 데이터가 많을수록 중복 제거 비용이 커지기 때문에 꼭 필요한 상황에서만 쓰는 게 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GROUP BY, HAVING]]></title>
            <link>https://velog.io/@mskim_/GROUP-BY-HAVING</link>
            <guid>https://velog.io/@mskim_/GROUP-BY-HAVING</guid>
            <pubDate>Sat, 18 Apr 2026 08:13:27 GMT</pubDate>
            <description><![CDATA[<h1 id="group-by와-having">GROUP BY와 HAVING</h1>
<p><code>COUNT</code>, <code>SUM</code>, <code>AVG</code> 같은 집계 함수는 전체 데이터를 하나로 요약한다. 그런데 &quot;전체 주문 수&quot;가 아니라 &quot;회원별 주문 수&quot;가 필요하다면? 데이터를 특정 기준으로 묶어서 각 그룹마다 집계해야 한다. 이때 쓰는 게 <strong>GROUP BY</strong>다.</p>
<hr>
<h2 id="group-by">GROUP BY</h2>
<pre><code class="language-sql">SELECT member_id, COUNT(*) AS 주문횟수
FROM orders
GROUP BY member_id;</code></pre>
<pre><code>member_id | 주문횟수
----------+--------
        1 |      3
        2 |      1
        3 |      2</code></pre><p><code>GROUP BY member_id</code>는 <code>member_id</code>가 같은 행끼리 묶는다. 묶인 각 그룹에 <code>COUNT(*)</code>를 적용하면 그룹별 개수가 나온다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/f033ceae-86d6-499e-ac3d-716f3c36785d/image.png" alt=""></p>
<p>여러 컬럼을 기준으로 묶을 수도 있다.</p>
<pre><code class="language-sql">-- 회원별, 날짜별 주문 수
SELECT member_id, order_date, COUNT(*) AS 주문횟수
FROM orders
GROUP BY member_id, order_date;</code></pre>
<p><code>GROUP BY</code> 뒤에 컬럼을 콤마로 나열하면 그 조합이 같은 행끼리 묶인다.</p>
<hr>
<h2 id="having">HAVING</h2>
<p><code>GROUP BY</code>로 그룹을 만든 뒤, 특정 조건을 만족하는 그룹만 남기고 싶을 때 <code>HAVING</code>을 쓴다.</p>
<pre><code class="language-sql">-- 주문 횟수가 2번 이상인 회원만
SELECT member_id, COUNT(*) AS 주문횟수
FROM orders
GROUP BY member_id
HAVING COUNT(*) &gt;= 2;</code></pre>
<pre><code>member_id | 주문횟수
----------+--------
        1 |      3
        3 |      2</code></pre><p>처음엔 <code>WHERE</code>를 쓰면 되지 않나 싶다. 하지만 <code>WHERE</code>와 <code>HAVING</code>은 필터링하는 시점이 다르다.</p>
<ul>
<li><code>WHERE</code> — 그룹을 만들기 <strong>전</strong>에 행을 필터링한다</li>
<li><code>HAVING</code> — 그룹을 만든 <strong>후</strong>에 그룹을 필터링한다</li>
</ul>
<p>집계 함수 결과(<code>COUNT(*)</code>, <code>SUM()</code> 등)를 조건으로 쓰려면 반드시 <code>HAVING</code>이어야 한다. <code>WHERE</code> 절에서는 집계 함수를 쓸 수 없다.</p>
<pre><code class="language-sql">-- 오류: WHERE에서 집계 함수 사용 불가
SELECT member_id, COUNT(*)
FROM orders
WHERE COUNT(*) &gt;= 2
GROUP BY member_id;

-- 올바른 방법
SELECT member_id, COUNT(*)
FROM orders
GROUP BY member_id
HAVING COUNT(*) &gt;= 2;</code></pre>
<p><img src="https://velog.velcdn.com/images/mskim_/post/e8671f4d-511f-4fe7-829f-905b86dfad9f/image.png" alt=""></p>
<hr>
<h2 id="where와-having-함께-쓰기">WHERE와 HAVING 함께 쓰기</h2>
<p>둘을 같이 쓸 수도 있다. <code>WHERE</code>로 먼저 행을 걸러낸 뒤, <code>GROUP BY</code>로 묶고, <code>HAVING</code>으로 그룹을 다시 필터링한다.</p>
<pre><code class="language-sql">-- 2026년 주문 중에서, 회원별 총 결제금액이 50000원 이상인 경우만
SELECT member_id, SUM(price) AS 총결제금액
FROM orders
WHERE YEAR(order_date) = 2026
GROUP BY member_id
HAVING SUM(price) &gt;= 50000;</code></pre>
<p>쿼리 실행 순서를 기억해두면 헷갈리지 않는다.</p>
<pre><code>FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY</code></pre><p><code>SELECT</code>에서 정한 별칭을 <code>HAVING</code>에서 쓸 수 없는 이유도 여기에 있다. <code>HAVING</code>이 <code>SELECT</code>보다 먼저 실행되기 때문이다.</p>
<pre><code class="language-sql">-- 오류: HAVING에서 별칭 사용 불가
SELECT member_id, SUM(price) AS 총결제금액
FROM orders
GROUP BY member_id
HAVING 총결제금액 &gt;= 50000;

-- 올바른 방법
SELECT member_id, SUM(price) AS 총결제금액
FROM orders
GROUP BY member_id
HAVING SUM(price) &gt;= 50000;</code></pre>
<hr>
<h2 id="order-by와-함께-쓰기">ORDER BY와 함께 쓰기</h2>
<p>그룹 결과를 정렬할 때는 <code>ORDER BY</code>를 마지막에 붙인다.</p>
<pre><code class="language-sql">-- 총 결제금액이 높은 순으로 정렬
SELECT member_id, SUM(price) AS 총결제금액
FROM orders
GROUP BY member_id
HAVING SUM(price) &gt;= 10000
ORDER BY 총결제금액 DESC;</code></pre>
<p><code>ORDER BY</code>는 <code>SELECT</code> 이후에 실행되기 때문에 여기서는 별칭(<code>총결제금액</code>)을 써도 된다.</p>
<hr>
<p><code>GROUP BY</code>는 처음엔 단순히 &quot;묶는 것&quot;으로 보이는데, <code>HAVING</code>이 붙으면서 훨씬 다양한 분석이 가능해진다. 어떤 조건이 <code>WHERE</code>에 가야 하고 어떤 조건이 <code>HAVING</code>에 가야 하는지 — 그 기준은 &quot;집계 전이냐, 집계 후냐&quot;로 생각하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자주 쓰는 MySQL 함수]]></title>
            <link>https://velog.io/@mskim_/%EC%9E%90%EC%A3%BC-%EC%93%B0%EB%8A%94-MySQL-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@mskim_/%EC%9E%90%EC%A3%BC-%EC%93%B0%EB%8A%94-MySQL-%ED%95%A8%EC%88%98</guid>
            <pubDate>Sat, 18 Apr 2026 07:54:54 GMT</pubDate>
            <description><![CDATA[<h1 id="자주-쓰는-mysql-함수">자주 쓰는 MySQL 함수</h1>
<p>SQL을 쓰다 보면 단순히 데이터를 꺼내는 것 이상이 필요해진다. 이름을 대문자로 바꾸거나, 날짜 차이를 계산하거나, 소수점을 정리하거나. 이런 처리를 쿼리 안에서 바로 할 수 있게 해주는 것이 <strong>내장 함수</strong>다.</p>
<p>자주 쓰는 것들만 추렸다.</p>
<hr>
<h2 id="문자열-함수">문자열 함수</h2>
<h3 id="concat">CONCAT</h3>
<p>여러 문자열을 이어 붙인다.</p>
<pre><code class="language-sql">SELECT CONCAT(first_name, &#39; &#39;, last_name) AS full_name
FROM members;</code></pre>
<pre><code>full_name
---------
김 민수
이 지현</code></pre><h3 id="substring">SUBSTRING</h3>
<p>문자열의 일부를 잘라낸다. 시작 위치는 1부터 센다.</p>
<pre><code class="language-sql">-- SUBSTRING(문자열, 시작위치, 길이)
SELECT SUBSTRING(&#39;Hello World&#39;, 1, 5);  -- Hello
SELECT SUBSTRING(&#39;Hello World&#39;, 7);     -- World (끝까지)</code></pre>
<h3 id="length--char_length">LENGTH / CHAR_LENGTH</h3>
<p><code>LENGTH</code>는 바이트 수, <code>CHAR_LENGTH</code>는 문자 수를 반환한다. 한글처럼 멀티바이트 문자를 다룰 때 차이가 난다.</p>
<pre><code class="language-sql">SELECT LENGTH(&#39;안녕&#39;);       -- 6 (UTF-8에서 한글 1자 = 3바이트)
SELECT CHAR_LENGTH(&#39;안녕&#39;);  -- 2 (문자 수)</code></pre>
<p>한글 문자열의 실제 글자 수를 셀 때는 <code>CHAR_LENGTH</code>를 쓴다.</p>
<h3 id="upper--lower">UPPER / LOWER</h3>
<p>문자열을 대문자 또는 소문자로 변환한다.</p>
<pre><code class="language-sql">SELECT UPPER(&#39;hello&#39;);  -- HELLO
SELECT LOWER(&#39;WORLD&#39;);  -- world</code></pre>
<p>이메일이나 코드값을 비교할 때 대소문자를 통일하는 용도로 자주 쓴다.</p>
<h3 id="trim">TRIM</h3>
<p>문자열 앞뒤의 공백을 제거한다.</p>
<pre><code class="language-sql">SELECT TRIM(&#39;  hello  &#39;);  -- &#39;hello&#39;</code></pre>
<p>사용자 입력값에 공백이 섞여 들어오는 경우 <code>WHERE</code> 조건 전에 정리할 때 쓴다.</p>
<h3 id="replace">REPLACE</h3>
<p>특정 문자열을 다른 문자열로 교체한다.</p>
<pre><code class="language-sql">-- REPLACE(대상, 찾을 문자열, 바꿀 문자열)
SELECT REPLACE(&#39;010-1234-5678&#39;, &#39;-&#39;, &#39;&#39;);  -- 01012345678</code></pre>
<hr>
<h2 id="숫자-함수">숫자 함수</h2>
<h3 id="round">ROUND</h3>
<p>지정한 소수점 자리에서 반올림한다.</p>
<pre><code class="language-sql">SELECT ROUND(3.456, 2);   -- 3.46
SELECT ROUND(3.456, 0);   -- 3
SELECT ROUND(3.456, -1);  -- 0 (일의 자리에서 반올림)</code></pre>
<h3 id="ceil--floor">CEIL / FLOOR</h3>
<p><code>CEIL</code>은 올림, <code>FLOOR</code>는 내림이다.</p>
<pre><code class="language-sql">SELECT CEIL(3.1);   -- 4
SELECT FLOOR(3.9);  -- 3</code></pre>
<h3 id="abs">ABS</h3>
<p>절댓값을 반환한다.</p>
<pre><code class="language-sql">SELECT ABS(-15);  -- 15
SELECT ABS(15);   -- 15</code></pre>
<p>두 값의 차이를 구할 때 음수가 나오지 않도록 할 때 쓴다.</p>
<h3 id="mod">MOD</h3>
<p>나머지를 반환한다. <code>%</code> 연산자와 동일하다.</p>
<pre><code class="language-sql">SELECT MOD(10, 3);  -- 1
SELECT 10 % 3;      -- 1</code></pre>
<hr>
<h2 id="날짜-함수">날짜 함수</h2>
<h3 id="now--curdate--curtime">NOW / CURDATE / CURTIME</h3>
<p>현재 날짜와 시간을 반환한다.</p>
<pre><code class="language-sql">SELECT NOW();      -- 2026-04-18 14:30:00  (날짜 + 시간)
SELECT CURDATE();  -- 2026-04-18           (날짜만)
SELECT CURTIME();  -- 14:30:00             (시간만)</code></pre>
<h3 id="date_format">DATE_FORMAT</h3>
<p>날짜를 원하는 형식의 문자열로 변환한다.</p>
<pre><code class="language-sql">SELECT DATE_FORMAT(NOW(), &#39;%Y년 %m월 %d일&#39;);  -- 2026년 04월 18일
SELECT DATE_FORMAT(NOW(), &#39;%Y-%m-%d&#39;);         -- 2026-04-18</code></pre>
<p>자주 쓰는 포맷 기호:</p>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>%Y</code></td>
<td>4자리 연도</td>
</tr>
<tr>
<td><code>%m</code></td>
<td>2자리 월</td>
</tr>
<tr>
<td><code>%d</code></td>
<td>2자리 일</td>
</tr>
<tr>
<td><code>%H</code></td>
<td>24시간 기준 시</td>
</tr>
<tr>
<td><code>%i</code></td>
<td>분</td>
</tr>
<tr>
<td><code>%s</code></td>
<td>초</td>
</tr>
</tbody></table>
<h3 id="datediff">DATEDIFF</h3>
<p>두 날짜 사이의 일수 차이를 반환한다.</p>
<pre><code class="language-sql">-- DATEDIFF(기준날짜, 빼는날짜)
SELECT DATEDIFF(&#39;2026-04-18&#39;, &#39;2026-01-01&#39;);  -- 107</code></pre>
<h3 id="date_add--date_sub">DATE_ADD / DATE_SUB</h3>
<p>날짜에 특정 기간을 더하거나 뺀다.</p>
<pre><code class="language-sql">SELECT DATE_ADD(&#39;2026-04-18&#39;, INTERVAL 7 DAY);   -- 2026-04-25
SELECT DATE_SUB(&#39;2026-04-18&#39;, INTERVAL 1 MONTH); -- 2026-03-18</code></pre>
<hr>
<h2 id="집계-함수">집계 함수</h2>
<p>집계 함수는 여러 행을 하나의 결과로 요약한다. 보통 <code>GROUP BY</code>와 함께 쓴다.</p>
<h3 id="count">COUNT</h3>
<p>행의 개수를 센다. <code>COUNT(*)</code>는 NULL 포함 전체 행, <code>COUNT(컬럼)</code>은 NULL 제외한 개수다.</p>
<pre><code class="language-sql">SELECT COUNT(*) FROM orders;              -- 전체 주문 수
SELECT COUNT(DISTINCT member_id) FROM orders;  -- 주문한 회원 수 (중복 제거)</code></pre>
<h3 id="sum--avg">SUM / AVG</h3>
<p>합계와 평균을 구한다.</p>
<pre><code class="language-sql">SELECT SUM(price) AS 총매출 FROM orders;
SELECT AVG(price) AS 평균금액 FROM orders;</code></pre>
<h3 id="max--min">MAX / MIN</h3>
<p>최댓값과 최솟값을 반환한다.</p>
<pre><code class="language-sql">SELECT MAX(price) AS 최고가 FROM products;
SELECT MIN(price) AS 최저가 FROM products;</code></pre>
<h3 id="group-by와-함께-쓰기">GROUP BY와 함께 쓰기</h3>
<p>집계 함수의 진짜 쓸모는 <code>GROUP BY</code>와 붙었을 때 나온다.</p>
<pre><code class="language-sql">-- 회원별 주문 횟수와 총 결제금액
SELECT
    member_id,
    COUNT(*)      AS 주문횟수,
    SUM(price)    AS 총결제금액
FROM orders
GROUP BY member_id;</code></pre>
<pre><code>member_id | 주문횟수 | 총결제금액
----------+--------+---------
        1 |      3 |   75000
        2 |      1 |   32000</code></pre><p>특정 조건으로 그룹 결과를 필터링할 때는 <code>WHERE</code> 대신 <code>HAVING</code>을 쓴다.</p>
<pre><code class="language-sql">-- 주문 횟수가 2번 이상인 회원만
SELECT member_id, COUNT(*) AS 주문횟수
FROM orders
GROUP BY member_id
HAVING COUNT(*) &gt;= 2;</code></pre>
<hr>
<p>함수 이름은 외우려고 하면 끝이 없다. 어떤 처리가 필요한 상황이 생겼을 때 &quot;이런 함수가 있지 않을까&quot; 하고 찾아보는 게 더 빠르다. 자주 쓰다 보면 자연스럽게 익혀진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트랜잭션 (Transaction)]]></title>
            <link>https://velog.io/@mskim_/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-Transaction</link>
            <guid>https://velog.io/@mskim_/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-Transaction</guid>
            <pubDate>Sat, 18 Apr 2026 07:48:50 GMT</pubDate>
            <description><![CDATA[<h1 id="트랜잭션-transaction">트랜잭션 (Transaction)</h1>
<p>계좌 이체를 생각해보자. A가 B에게 10만 원을 보내는 과정은 두 단계다.</p>
<ol>
<li>A 계좌에서 10만 원 차감</li>
<li>B 계좌에 10만 원 추가</li>
</ol>
<p>그런데 1번은 성공했는데 2번 도중에 서버가 꺼지면 어떻게 될까. A의 돈은 빠져나갔는데 B에게는 입금이 안 된다. 이런 상황을 막기 위해 존재하는 것이 <strong>트랜잭션(Transaction)</strong>이다.</p>
<p>트랜잭션은 여러 쿼리를 하나의 작업 단위로 묶는다. 전부 성공하거나, 전부 실패하거나 — 둘 중 하나만 있다.</p>
<hr>
<h2 id="commit과-rollback">COMMIT과 ROLLBACK</h2>
<p>트랜잭션의 핵심은 두 명령어다.</p>
<ul>
<li><code>COMMIT</code> — 트랜잭션 안의 모든 변경사항을 실제 DB에 반영한다</li>
<li><code>ROLLBACK</code> — 트랜잭션 시작 전 상태로 되돌린다</li>
</ul>
<pre><code class="language-sql">-- 계좌 테이블 예시
CREATE TABLE account (
    id      INT PRIMARY KEY,
    name    VARCHAR(20),
    balance INT
);

INSERT INTO account VALUES (1, &#39;A&#39;, 100000);
INSERT INTO account VALUES (2, &#39;B&#39;, 50000);</code></pre>
<pre><code class="language-sql">-- 트랜잭션 시작
START TRANSACTION;

UPDATE account SET balance = balance - 100000 WHERE id = 1;
UPDATE account SET balance = balance + 100000 WHERE id = 2;

-- 문제 없으면 확정
COMMIT;</code></pre>
<p>두 UPDATE가 모두 성공한 뒤 <code>COMMIT</code>하면 변경이 확정된다. 중간에 문제가 생겼다면 <code>ROLLBACK</code>으로 되돌린다.</p>
<pre><code class="language-sql">START TRANSACTION;

UPDATE account SET balance = balance - 100000 WHERE id = 1;
-- 여기서 오류 발생 시

ROLLBACK;  -- A 계좌 차감도 없었던 일로 돌아간다</code></pre>
<p><img src="https://velog.velcdn.com/images/mskim_/post/832fbffd-c1c9-4748-8425-0897597ec48e/image.png" alt=""></p>
<hr>
<h2 id="savepoint">SAVEPOINT</h2>
<p>트랜잭션 중간에 체크포인트를 만들 수 있다. 전체를 되돌리지 않고 특정 지점으로만 돌아가고 싶을 때 <code>SAVEPOINT</code>를 쓴다.</p>
<pre><code class="language-sql">START TRANSACTION;

INSERT INTO account VALUES (3, &#39;C&#39;, 30000);
SAVEPOINT sp1;  -- 체크포인트 저장

INSERT INTO account VALUES (4, &#39;D&#39;, 20000);

-- D 추가는 취소하고 C 추가까지만 남기고 싶다면
ROLLBACK TO sp1;

COMMIT;  -- C만 추가된 상태로 확정</code></pre>
<p><code>ROLLBACK TO sp1</code>은 <code>sp1</code> 이후의 변경만 취소한다. <code>sp1</code> 이전 작업은 그대로 유지된다.</p>
<hr>
<h2 id="acid">ACID</h2>
<p>트랜잭션이 보장해야 하는 네 가지 성질이다. 앞 글자를 따서 ACID라고 부른다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/bd1b53e5-826a-4b0d-91d8-107a6b38ed12/image.png" alt=""></p>
<h3 id="atomicity-원자성">Atomicity (원자성)</h3>
<p>트랜잭션 안의 작업은 전부 성공하거나 전부 실패한다. 계좌 이체에서 A 차감만 성공하고 B 입금이 실패하는 경우가 없도록 보장한다.</p>
<h3 id="consistency-일관성">Consistency (일관성)</h3>
<p>트랜잭션 전후로 DB의 제약 조건이 유지된다. 잔액이 음수가 되거나 FK가 깨지는 등 규칙에 어긋난 상태가 되지 않는다.</p>
<h3 id="isolation-격리성">Isolation (격리성)</h3>
<p>동시에 실행되는 트랜잭션은 서로 간섭하지 않는다. 내가 이체하는 도중 다른 사람이 같은 계좌를 조회해도 중간 상태가 보이지 않는다.</p>
<h3 id="durability-지속성">Durability (지속성)</h3>
<p><code>COMMIT</code>된 데이터는 시스템 장애가 발생해도 유지된다. 서버가 꺼져도 커밋한 내용은 사라지지 않는다.</p>
<hr>
<h2 id="auto-commit">AUTO COMMIT</h2>
<p>MySQL은 기본적으로 <strong>AUTO COMMIT</strong> 모드다. 별도로 <code>START TRANSACTION</code>을 선언하지 않으면 쿼리 하나하나가 자동으로 COMMIT된다.</p>
<pre><code class="language-sql">-- AUTO COMMIT 상태 확인
SELECT @@autocommit;  -- 1이면 AUTO COMMIT 켜진 상태

-- AUTO COMMIT 끄기
SET autocommit = 0;

-- AUTO COMMIT 다시 켜기
SET autocommit = 1;</code></pre>
<p>AUTO COMMIT을 끄면 명시적으로 <code>COMMIT</code>을 해줘야 변경이 반영된다. <code>START TRANSACTION</code>을 쓰면 AUTO COMMIT 설정과 관계없이 해당 트랜잭션 블록 안에서는 수동 제어가 된다.</p>
<hr>
<p>트랜잭션을 처음 접하면 &quot;굳이 이게 왜 필요하지?&quot;라는 생각이 든다. 데이터를 혼자 다루는 상황에선 크게 체감이 안 되기 때문이다. 하지만 여러 쿼리가 하나의 논리적 작업을 이루는 순간, 중간에 실패했을 때 데이터가 어떤 상태로 남는지가 중요해진다. 트랜잭션은 그 불확실성을 없애는 장치다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[alias (별칭)]]></title>
            <link>https://velog.io/@mskim_/alias-%EB%B3%84%EC%B9%AD</link>
            <guid>https://velog.io/@mskim_/alias-%EB%B3%84%EC%B9%AD</guid>
            <pubDate>Fri, 17 Apr 2026 08:02:43 GMT</pubDate>
            <description><![CDATA[<h1 id="별칭-alias">별칭 (Alias)</h1>
<pre><code class="language-sql">SELECT m.member_name, o.order_id
FROM members m
INNER JOIN orders o ON m.member_id = o.member_id;</code></pre>
<p>JOIN을 쓰다 보면 자연스럽게 테이블 이름 뒤에 짧은 글자를 붙이게 된다. 위 코드에서 <code>members m</code>, <code>orders o</code>가 바로 <strong>별칭(Alias)</strong>이다. 컬럼과 테이블 모두에 붙일 수 있고, 쿼리를 훨씬 읽기 쉽게 만들어준다.</p>
<hr>
<h2 id="별칭의-종류">별칭의 종류</h2>
<h3 id="컬럼-별칭">컬럼 별칭</h3>
<p>조회 결과에서 컬럼 이름을 바꿔서 보여준다. <code>AS</code> 키워드를 쓰거나 생략해도 된다.</p>
<pre><code class="language-sql">SELECT
    member_name AS 이름,
    email       AS 이메일
FROM members;</code></pre>
<pre><code>이름   | 이메일
------+---------------
김민수  | kim@email.com
이지현  | lee@email.com</code></pre><p><code>member_name</code>이라는 컬럼명 대신 결과에는 <code>이름</code>으로 표시된다. 테이블 구조는 바뀌지 않고, 조회 결과에서만 이름이 달라진다.</p>
<p>공백이 포함된 별칭은 따옴표로 감싼다.</p>
<pre><code class="language-sql">SELECT member_name AS &#39;회원 이름&#39; FROM members;</code></pre>
<p>집계 함수 결과에 이름을 붙일 때도 유용하다.</p>
<pre><code class="language-sql">SELECT
    COUNT(*)  AS 총주문수,
    SUM(price) AS 총금액
FROM orders;</code></pre>
<h3 id="테이블-별칭">테이블 별칭</h3>
<p>테이블 이름을 짧게 줄여서 쓴다. JOIN처럼 테이블을 여러 개 다룰 때 특히 편리하다.</p>
<pre><code class="language-sql">-- 별칭 없이
SELECT members.member_name, orders.order_date
FROM members
INNER JOIN orders ON members.member_id = orders.member_id;

-- 별칭 사용
SELECT m.member_name, o.order_date
FROM members m
INNER JOIN orders o ON m.member_id = o.member_id;</code></pre>
<p>테이블 이름이 길수록 효과가 크다. 별칭을 한 번 정하면 해당 쿼리 안에서는 원래 테이블 이름 대신 별칭만 써야 한다. 섞어 쓰면 오류가 난다.</p>
<pre><code class="language-sql">-- 오류: m으로 별칭을 정했으면 members는 더 이상 못 쓴다
SELECT members.member_name
FROM members m;</code></pre>
<hr>
<h2 id="as는-생략-가능하다">AS는 생략 가능하다</h2>
<p>컬럼 별칭과 테이블 별칭 모두 <code>AS</code>를 생략해도 동작한다.</p>
<pre><code class="language-sql">-- 두 쿼리는 동일하다
SELECT member_name AS 이름 FROM members m;
SELECT member_name 이름   FROM members m;</code></pre>
<p>보통 컬럼 별칭엔 <code>AS</code>를 명시하고, 테이블 별칭엔 생략하는 경우가 많다. 명확하게 읽히는 쪽을 선택하면 된다.</p>
<hr>
<h2 id="별칭-사용-시-주의할-점">별칭 사용 시 주의할 점</h2>
<p><code>WHERE</code> 절에서는 컬럼 별칭을 쓸 수 없다. 쿼리 실행 순서상 <code>WHERE</code>가 <code>SELECT</code>보다 먼저 처리되기 때문에, <code>SELECT</code>에서 정한 별칭을 <code>WHERE</code>는 아직 모른다.</p>
<pre><code class="language-sql">-- 오류: WHERE에서 별칭 사용 불가
SELECT member_name AS 이름
FROM members
WHERE 이름 = &#39;김민수&#39;;

-- 올바른 방법
SELECT member_name AS 이름
FROM members
WHERE member_name = &#39;김민수&#39;;</code></pre>
<p>반면 <code>ORDER BY</code>는 <code>SELECT</code> 이후에 실행되기 때문에 별칭을 쓸 수 있다.</p>
<pre><code class="language-sql">SELECT member_name AS 이름
FROM members
ORDER BY 이름;</code></pre>
<hr>
<p>별칭은 기능보다 가독성의 영역이다. 쿼리가 길어질수록 테이블 이름을 풀네임으로 반복하는 건 읽기도 쓰기도 불편하다. 짧고 의미 있는 별칭 하나가 쿼리 전체를 훨씬 깔끔하게 만든다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JOIN]]></title>
            <link>https://velog.io/@mskim_/JOIN</link>
            <guid>https://velog.io/@mskim_/JOIN</guid>
            <pubDate>Fri, 17 Apr 2026 07:58:03 GMT</pubDate>
            <description><![CDATA[<h1 id="join">JOIN</h1>
<p>테이블을 잘 나눠뒀는데, 막상 데이터를 꺼내려고 하면 문제가 생긴다. 주문 목록을 보고 싶은데 주문 테이블엔 <code>member_id</code>만 있고 이름은 없다. 회원 이름까지 같이 보려면 두 테이블을 합쳐서 조회해야 한다. 이때 쓰는 게 <strong>JOIN</strong>이다.</p>
<hr>
<h2 id="예시-데이터">예시 데이터</h2>
<p>설명에 사용할 테이블과 데이터를 먼저 준비한다.</p>
<pre><code class="language-sql">-- 회원 테이블
INSERT INTO members VALUES
(1, &#39;김민수&#39;, &#39;kim@email.com&#39;),
(2, &#39;이지현&#39;, &#39;lee@email.com&#39;),
(3, &#39;박준혁&#39;, &#39;park@email.com&#39;);  -- 주문 없는 회원

-- 주문 테이블
INSERT INTO orders VALUES
(1, 1, &#39;2026-03-01&#39;),
(2, 1, &#39;2026-03-05&#39;),
(3, 2, &#39;2026-03-07&#39;),
(4, 99, &#39;2026-03-10&#39;);  -- 존재하지 않는 member_id</code></pre>
<pre><code>members                          orders
member_id | member_name          order_id | member_id | order_date
----------+------------          ---------+-----------+-----------
        1 | 김민수                       1 |         1 | 2026-03-01
        2 | 이지현                       2 |         1 | 2026-03-05
        3 | 박준혁                       3 |         2 | 2026-03-07
                                         4 |        99 | 2026-03-10</code></pre><p>박준혁은 주문이 없고, <code>order_id = 4</code>는 존재하지 않는 회원의 주문이다. JOIN 종류마다 이 데이터가 어떻게 다르게 나오는지 비교하면 이해가 빠르다.</p>
<hr>
<h2 id="join의-종류">JOIN의 종류</h2>
<h3 id="inner-join">INNER JOIN</h3>
<p>두 테이블 <strong>모두에 존재하는 행</strong>만 결과에 포함된다. 매칭되는 데이터가 없으면 결과에서 빠진다.</p>
<pre><code class="language-sql">SELECT m.member_name, o.order_id, o.order_date
FROM members m
INNER JOIN orders o ON m.member_id = o.member_id;</code></pre>
<pre><code>member_name | order_id | order_date
------------+----------+-----------
김민수       |        1 | 2026-03-01
김민수       |        2 | 2026-03-05
이지현       |        3 | 2026-03-07</code></pre><p>박준혁은 주문이 없어서 결과에 없다. <code>member_id = 99</code>인 주문도 <code>members</code>에 없는 회원이라 빠진다. 양쪽 다 존재하는 것만 남긴다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/a89b709c-b2a0-49b3-95a2-783755c658eb/image.png" alt=""></p>
<h3 id="left-join">LEFT JOIN</h3>
<p><strong>왼쪽 테이블의 모든 행</strong>을 기준으로 오른쪽 테이블을 합친다. 오른쪽에 매칭되는 데이터가 없으면 <code>NULL</code>로 채운다.</p>
<pre><code class="language-sql">SELECT m.member_name, o.order_id, o.order_date
FROM members m
LEFT JOIN orders o ON m.member_id = o.member_id;</code></pre>
<pre><code>member_name | order_id | order_date
------------+----------+-----------
김민수       |        1 | 2026-03-01
김민수       |        2 | 2026-03-05
이지현       |        3 | 2026-03-07
박준혁       |     NULL | NULL</code></pre><p>박준혁은 주문이 없지만 왼쪽(members)에 있으니 결과에 포함된다. 주문 관련 컬럼은 <code>NULL</code>이다. &quot;주문을 한 번도 안 한 회원을 찾아라&quot; 같은 상황에서 유용하다.</p>
<pre><code class="language-sql">-- 주문이 없는 회원만 조회
SELECT m.member_name
FROM members m
LEFT JOIN orders o ON m.member_id = o.member_id
WHERE o.order_id IS NULL;</code></pre>
<pre><code>member_name
-----------
박준혁</code></pre><p><img src="https://velog.velcdn.com/images/mskim_/post/4a2de47e-4cbd-4019-ba34-c012cb86af9e/image.png" alt=""></p>
<h3 id="right-join">RIGHT JOIN</h3>
<p>LEFT JOIN의 반대다. <strong>오른쪽 테이블의 모든 행</strong>을 기준으로 왼쪽을 합친다. 왼쪽에 매칭되는 데이터가 없으면 <code>NULL</code>로 채운다.</p>
<pre><code class="language-sql">SELECT m.member_name, o.order_id, o.order_date
FROM members m
RIGHT JOIN orders o ON m.member_id = o.member_id;</code></pre>
<pre><code>member_name | order_id | order_date
------------+----------+-----------
김민수       |        1 | 2026-03-01
김민수       |        2 | 2026-03-05
이지현       |        3 | 2026-03-07
NULL         |        4 | 2026-03-10</code></pre><p><code>member_id = 99</code>인 주문은 <code>members</code>에 없는 회원이지만 오른쪽(orders)에 있으니 포함된다. 회원 이름은 <code>NULL</code>이다.</p>
<p>현실적으로 LEFT JOIN이 훨씬 많이 쓰인다. <code>FROM</code> 뒤에 기준 테이블을 두고 LEFT JOIN으로 붙이는 방식이 읽기 쉽기 때문에, RIGHT JOIN이 필요한 상황은 대부분 테이블 순서를 바꿔서 LEFT JOIN으로 대체한다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/866beb26-2c87-4481-b41c-e3f942c40e3f/image.png" alt=""></p>
<hr>
<h2 id="여러-테이블-join">여러 테이블 JOIN</h2>
<p>JOIN은 두 테이블 이상도 이어 붙일 수 있다. 주문에 상품명까지 함께 보고 싶다면 <code>order_items</code>, <code>products</code>까지 연결한다.</p>
<pre><code class="language-sql">SELECT
    m.member_name,
    o.order_date,
    p.product_name,
    oi.quantity
FROM orders o
INNER JOIN members     m  ON o.member_id   = m.member_id
INNER JOIN order_items oi ON o.order_id    = oi.order_id
INNER JOIN products    p  ON oi.product_id = p.product_id;</code></pre>
<pre><code>member_name | order_date  | product_name  | quantity
------------+-------------+---------------+---------
김민수       | 2026-03-01  | 무선 마우스    |        2
김민수       | 2026-03-05  | 기계식 키보드  |        1
이지현       | 2026-03-07  | 기계식 키보드  |        1</code></pre><p><code>FROM</code> 뒤에 기준 테이블을 두고, <code>JOIN</code>을 이어 붙이는 방식이다. <code>ON</code> 조건만 정확히 맞춰주면 몇 개든 연결할 수 있다.</p>
<hr>
<h2 id="join-종류-비교">JOIN 종류 비교</h2>
<table>
<thead>
<tr>
<th>JOIN</th>
<th>포함되는 행</th>
</tr>
</thead>
<tbody><tr>
<td><code>INNER JOIN</code></td>
<td>양쪽 모두 매칭되는 행만</td>
</tr>
<tr>
<td><code>LEFT JOIN</code></td>
<td>왼쪽 테이블 전체 + 오른쪽 매칭 (없으면 NULL)</td>
</tr>
<tr>
<td><code>RIGHT JOIN</code></td>
<td>오른쪽 테이블 전체 + 왼쪽 매칭 (없으면 NULL)</td>
</tr>
</tbody></table>
<p>JOIN을 처음 쓸 때 가장 헷갈리는 부분은 &quot;어느 쪽이 기준이냐&quot;다. LEFT, RIGHT는 방향이 아니라 <strong>어느 테이블의 행을 모두 살릴 건가</strong>의 문제다. 이걸 기준으로 생각하면 선택이 쉬워진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ERD 다이어그램]]></title>
            <link>https://velog.io/@mskim_/ERD-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8</link>
            <guid>https://velog.io/@mskim_/ERD-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8</guid>
            <pubDate>Fri, 17 Apr 2026 07:41:12 GMT</pubDate>
            <description><![CDATA[<h1 id="erd-다이어그램">ERD 다이어그램</h1>
<p>테이블이 세 개, 네 개 넘어가면 말로 설명하기가 힘들어진다. &quot;회원 테이블이 주문 테이블과 연결되고, 주문 테이블은 상품 테이블과 중간 테이블을 통해 연결되고...&quot; — 듣는 사람도, 말하는 사람도 금방 헷갈린다. <strong>ERD(Entity-Relationship Diagram)</strong>는 이 관계를 그림으로 표현한 것이다.</p>
<hr>
<h2 id="erd의-구성-요소">ERD의 구성 요소</h2>
<p>ERD는 세 가지로 구성된다. <strong>엔티티</strong>, <strong>속성</strong>, <strong>관계</strong>.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/0244951e-fd26-47b4-b361-2b4debd9cea4/image.png" alt=""></p>
<h3 id="엔티티-entity">엔티티 (Entity)</h3>
<p>데이터로 관리할 대상이다. 테이블 하나가 엔티티 하나에 해당한다. ERD에서는 <strong>사각형</strong>으로 표현한다.</p>
<p>쇼핑몰을 예로 들면 <code>회원</code>, <code>주문</code>, <code>상품</code>이 각각 엔티티다.</p>
<h3 id="속성-attribute">속성 (Attribute)</h3>
<p>엔티티가 가지는 데이터 항목이다. 테이블의 컬럼에 해당한다. ERD에서는 엔티티 안에 컬럼 목록으로 표현하거나, 별도로 타원으로 이어 표현하기도 한다.</p>
<p><code>회원</code> 엔티티의 속성은 <code>member_id</code>, <code>member_name</code>, <code>email</code> 등이다. 기본 키는 보통 밑줄이나 <code>PK</code> 표시로 구분한다.</p>
<h3 id="관계-relationship">관계 (Relationship)</h3>
<p>엔티티 사이의 연결이다. ERD에서는 엔티티를 잇는 <strong>선</strong>으로 표현하며, 선 끝에 기호를 붙여 관계 차수(1:1, 1:N, N:M)를 나타낸다.</p>
<hr>
<h2 id="erd-표기법">ERD 표기법</h2>
<p>ERD를 그리는 방식이 여러 가지 있는데, 가장 많이 쓰이는 건 <strong>IE 표기법(Information Engineering Notation)</strong>이다. 선 끝 모양이 새 발처럼 생겼다고 해서 <strong>Crow&#39;s Foot</strong> 표기법이라고도 부른다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/eef333a3-25e1-4778-8b8d-d87d93959c39/image.png" alt=""></p>
<p>선 끝에 붙는 기호의 의미는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>`</td>
<td>` (수직선 하나)</td>
</tr>
<tr>
<td>`</td>
<td></td>
</tr>
<tr>
<td><code>O</code> (원)</td>
<td>0 (없을 수도 있음)</td>
</tr>
<tr>
<td><code>&lt;</code> (새 발)</td>
<td>여러 개 (N)</td>
</tr>
</tbody></table>
<p>이 기호들을 조합해 관계 차수를 표현한다.</p>
<table>
<thead>
<tr>
<th>관계</th>
<th>표기 예시</th>
</tr>
</thead>
<tbody><tr>
<td>1:1</td>
<td>|——|</td>
</tr>
<tr>
<td>1:N</td>
<td>|<code>——</code>&lt;</td>
</tr>
<tr>
<td>0 또는 1 : N</td>
<td>O|<code>——</code>&lt;</td>
</tr>
<tr>
<td>N:M</td>
<td><code>&gt;</code>——<code>&lt;</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="실제-erd-예시">실제 ERD 예시</h2>
<p>앞서 만든 쇼핑몰 구조를 ERD로 표현하면 이렇다.</p>
<pre><code>members               orders                order_items           products
+-------------+       +-------------+       +-------------+       +-------------+
| member_id PK|       | order_id  PK|       | order_id  PK|       | product_id PK|
| member_name |       | member_id FK|       | product_id PK       | product_name |
| email       |       | order_date  |       | quantity    |       | price        |
+-------------+       +-------------+       +-------------+       +-------------+
      |                     |                     |                     |
      |_____________________|                     |_____________________|
            1:N                                          N:M 중간 테이블</code></pre><p>텍스트로 표현하면 한계가 있지만, 구조를 읽어보면 이렇다.</p>
<ul>
<li><code>members</code> — <code>orders</code>: 회원 한 명이 주문을 여러 건 할 수 있다 (1:N)</li>
<li><code>orders</code> — <code>order_items</code> — <code>products</code>: 주문과 상품은 중간 테이블을 통해 N:M으로 연결된다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/mskim_/post/5944b80e-9347-4820-8060-dd573f1f1afc/image.png" alt=""></p>
<p>실제로는 <a href="https://draw.io">draw.io</a>, MySQL Workbench, dbdiagram.io 같은 도구로 그린다. 이 중 <strong>dbdiagram.io</strong>는 SQL 문법과 비슷한 텍스트를 입력하면 ERD를 자동으로 그려줘서 편리하다.</p>
<hr>
<h2 id="erd를-먼저-그리는-이유">ERD를 먼저 그리는 이유</h2>
<p>코드를 먼저 짜고 나중에 ERD를 그리는 경우도 있지만, 반대 순서가 더 낫다. 테이블을 만들기 전에 ERD를 그려보면 관계 차수를 잘못 설정한 부분, 빠진 엔티티, 불필요하게 중복된 속성 등이 눈에 보인다.</p>
<p>SQL로 테이블을 다 만든 뒤에 구조를 바꾸는 건 번거롭다. 기존 데이터가 있으면 더 복잡해진다. ERD 단계에서 미리 잡아두는 게 훨씬 수월하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[관계 차수]]></title>
            <link>https://velog.io/@mskim_/%EA%B4%80%EA%B3%84-%EC%B0%A8%EC%88%98</link>
            <guid>https://velog.io/@mskim_/%EA%B4%80%EA%B3%84-%EC%B0%A8%EC%88%98</guid>
            <pubDate>Fri, 17 Apr 2026 07:32:05 GMT</pubDate>
            <description><![CDATA[<h1 id="관계-차수-cardinality">관계 차수 (Cardinality)</h1>
<p>테이블을 나눴으면 이제 나눈 테이블들이 서로 어떻게 연결되는지를 따져야 한다. &quot;한 회원이 주문을 몇 개나 할 수 있나?&quot;, &quot;한 주문에 상품이 몇 개 들어갈 수 있나?&quot; — 이런 질문이 바로 <strong>관계 차수(Cardinality)</strong>다. 테이블 간의 관계를 수량 관점에서 정의하는 것이다.</p>
<p>관계 차수는 세 가지다. <strong>1:1</strong>, <strong>1:N</strong>, <strong>N:M</strong>.</p>
<hr>
<h2 id="관계-차수의-종류">관계 차수의 종류</h2>
<h3 id="11-일대일">1:1 (일대일)</h3>
<p>한 테이블의 행 하나가 다른 테이블의 행 하나와 정확히 대응되는 관계다.</p>
<p>회원과 회원 상세 정보를 예로 들면, 회원 한 명에게 상세 정보가 하나만 존재한다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/63bc6578-a6ab-45b2-8b72-69b8606233e0/image.png" alt=""></p>
<pre><code class="language-sql">CREATE TABLE members (
    member_id   INT         PRIMARY KEY AUTO_INCREMENT,
    member_name VARCHAR(50) NOT NULL,
    email       VARCHAR(100) NOT NULL UNIQUE
);

CREATE TABLE member_profiles (
    profile_id  INT         PRIMARY KEY AUTO_INCREMENT,
    member_id   INT         NOT NULL UNIQUE,  -- UNIQUE로 1:1 보장
    address     VARCHAR(200),
    birth_date  DATE,
    FOREIGN KEY (member_id) REFERENCES members(member_id)
);</code></pre>
<p><code>member_profiles.member_id</code>에 <code>UNIQUE</code>가 붙어 있다. 같은 <code>member_id</code>가 두 번 들어올 수 없으니, 회원 한 명당 프로필이 하나뿐임을 DB 레벨에서 강제한다.</p>
<p>굳이 테이블을 나누는 이유는, 자주 쓰지 않는 컬럼(주소, 생년월일 등)을 메인 테이블과 분리해 조회 성능을 높이거나, 선택적으로 존재하는 정보를 별도로 관리하기 위해서다.</p>
<h3 id="1n-일대다">1:N (일대다)</h3>
<p>가장 흔한 관계다. 한 테이블의 행 하나가 다른 테이블의 행 여러 개와 연결된다.</p>
<p>회원 한 명이 주문을 여러 건 할 수 있다. 반대로 주문 하나는 한 명의 회원에게만 속한다. 이게 1:N이다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/dcab360a-9218-4291-9a38-c805d7266fea/image.png" alt=""></p>
<pre><code class="language-sql">CREATE TABLE members (
    member_id   INT         PRIMARY KEY AUTO_INCREMENT,
    member_name VARCHAR(50) NOT NULL
);

CREATE TABLE orders (
    order_id   INT  PRIMARY KEY AUTO_INCREMENT,
    member_id  INT  NOT NULL,
    order_date DATE NOT NULL,
    FOREIGN KEY (member_id) REFERENCES members(member_id)
);</code></pre>
<pre><code>members                    orders
-----------                ----------------------------
member_id | member_name    order_id | member_id | date
----------+------------    ---------+-----------+------
        1 | 김민수                 1 |         1 | 03-01
        2 | 이지현                 2 |         1 | 03-05
                                   3 |         2 | 03-07
                                   4 |         1 | 03-12</code></pre><p>김민수(member_id=1)의 주문이 세 건이다. <code>orders</code> 테이블에 <code>member_id</code>를 외래 키로 두는 것만으로 1:N 관계가 만들어진다. &quot;N쪽 테이블&quot;이 외래 키를 가진다는 게 핵심이다.</p>
<h3 id="nm-다대다">N:M (다대다)</h3>
<p>양쪽 모두 여러 개와 연결될 수 있는 관계다. 한 주문에 상품이 여러 개 담길 수 있고, 한 상품은 여러 주문에 담길 수 있다. 이게 N:M이다.</p>
<p>N:M 관계는 두 테이블을 직접 연결할 수 없다. 외래 키를 어느 쪽에 두든 구조가 맞지 않는다. 이 문제를 해결하는 방법이 <strong>중간 테이블(Junction Table)</strong>이다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/0d163234-f27f-4808-9090-095f219932a6/image.png" alt=""></p>
<pre><code class="language-sql">CREATE TABLE orders (
    order_id   INT PRIMARY KEY AUTO_INCREMENT,
    member_id  INT NOT NULL,
    order_date DATE NOT NULL,
    FOREIGN KEY (member_id) REFERENCES members(member_id)
);

CREATE TABLE products (
    product_id   INT          PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100) NOT NULL,
    price        INT          NOT NULL
);

-- 중간 테이블
CREATE TABLE order_items (
    order_id   INT NOT NULL,
    product_id INT NOT NULL,
    quantity   INT NOT NULL DEFAULT 1,
    PRIMARY KEY (order_id, product_id),
    FOREIGN KEY (order_id)   REFERENCES orders(order_id),
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);</code></pre>
<pre><code>orders          order_items              products
--------        ---------------------    ------------------
order_id        order_id | product_id    product_id | name
--------        ---------+-----------    -----------+------
       1               1 |          1             1 | 마우스
       2               1 |          2             2 | 키보드
                        2 |          2</code></pre><p>1번 주문에 마우스와 키보드가 담겼고, 2번 주문에도 키보드가 담겼다. 키보드(product_id=2)는 두 주문에 모두 연결돼 있다. 중간 테이블이 이 관계를 표현한다.</p>
<p><code>order_items</code>의 PRIMARY KEY가 <code>(order_id, product_id)</code> 복합 기본 키인 점도 눈여겨볼 만하다. 같은 주문에 같은 상품이 두 번 들어가는 걸 막는다.</p>
<hr>
<h2 id="관계-차수-정리">관계 차수 정리</h2>
<table>
<thead>
<tr>
<th>관계</th>
<th>설명</th>
<th>구현 방법</th>
</tr>
</thead>
<tbody><tr>
<td>1:1</td>
<td>행 하나 ↔ 행 하나</td>
<td>N쪽 FK에 UNIQUE 추가</td>
</tr>
<tr>
<td>1:N</td>
<td>행 하나 ↔ 행 여러 개</td>
<td>N쪽 테이블에 FK</td>
</tr>
<tr>
<td>N:M</td>
<td>행 여러 개 ↔ 행 여러 개</td>
<td>중간 테이블(Junction Table)</td>
</tr>
</tbody></table>
<p>관계 차수를 정확히 파악하는 게 테이블 설계의 출발점이다. 관계를 잘못 파악하면 나중에 구조를 뒤집어야 하는 상황이 생긴다. &quot;이 두 테이블이 서로 몇 개씩 연결될 수 있나?&quot;를 먼저 따지고 설계를 시작하는 습관이 중요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인덱스(Index)]]></title>
            <link>https://velog.io/@mskim_/%EC%9D%B8%EB%8D%B1%EC%8A%A4Index</link>
            <guid>https://velog.io/@mskim_/%EC%9D%B8%EB%8D%B1%EC%8A%A4Index</guid>
            <pubDate>Fri, 17 Apr 2026 07:23:14 GMT</pubDate>
            <description><![CDATA[<h1 id="인덱스-index">인덱스 (INDEX)</h1>
<p>행이 수십만 건 쌓인 테이블에서 특정 이메일을 조회한다고 생각해보자. DB는 기본적으로 첫 번째 행부터 마지막 행까지 전부 훑어본다. 데이터가 많을수록 느려지는 건 당연하다. <strong>인덱스(Index)</strong>는 이 문제를 해결하기 위해 존재한다.</p>
<hr>
<h2 id="인덱스란">인덱스란</h2>
<p>책 뒤에 붙어 있는 색인을 떠올리면 쉽다. &quot;트랜잭션&quot;이라는 단어가 몇 페이지에 나오는지 찾으려면, 책을 처음부터 읽는 것보다 색인에서 &#39;ㅌ&#39; 항목을 찾는 게 훨씬 빠르다. 인덱스가 정확히 그 역할이다.</p>
<p>DB는 특정 컬럼에 인덱스를 만들어두면, 검색할 때 전체 행을 훑는 대신 인덱스를 통해 원하는 위치로 바로 이동한다. 이를 <strong>풀 테이블 스캔(Full Table Scan)</strong> 대신 <strong>인덱스 스캔(Index Scan)</strong>이라고 한다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/310db6a4-4662-41b4-b739-bd2a00ca3d64/image.png" alt=""></p>
<p>단, 인덱스는 공짜가 아니다. 조회 속도는 빨라지지만, 인덱스 자체를 별도로 저장하는 공간이 필요하고, INSERT/UPDATE/DELETE 시에는 인덱스도 함께 갱신해야 해서 쓰기 성능이 떨어진다. 조회가 많고 쓰기가 적은 컬럼에 거는 게 기본 원칙이다.</p>
<hr>
<h2 id="인덱스의-종류">인덱스의 종류</h2>
<h3 id="primary-key-인덱스">PRIMARY KEY 인덱스</h3>
<p><code>PRIMARY KEY</code>를 지정하면 MySQL이 자동으로 인덱스를 만든다. 이를 <strong>클러스터형 인덱스(Clustered Index)</strong>라고 하는데, 실제 데이터가 PRIMARY KEY 순서로 물리적으로 정렬되어 저장된다. 테이블에 하나만 존재한다.</p>
<p>별도로 생성할 필요 없이, PRIMARY KEY 선언만으로 자동 생성된다.</p>
<pre><code class="language-sql">CREATE TABLE members (
    member_id   INT         PRIMARY KEY AUTO_INCREMENT, -- 자동으로 인덱스 생성
    member_name VARCHAR(50) NOT NULL
);</code></pre>
<h3 id="unique-인덱스">UNIQUE 인덱스</h3>
<p><code>UNIQUE</code> 제약 조건을 걸면 자동으로 고유 인덱스가 생성된다. PRIMARY KEY 인덱스와 구조는 같지만, NULL을 허용하고 여러 개를 만들 수 있다는 점이 다르다.</p>
<pre><code class="language-sql">CREATE TABLE members (
    member_id   INT          PRIMARY KEY AUTO_INCREMENT,
    email       VARCHAR(100) NOT NULL UNIQUE -- 자동으로 UNIQUE 인덱스 생성
);</code></pre>
<h3 id="일반-인덱스">일반 인덱스</h3>
<p>중복을 허용하는 일반적인 인덱스다. 자주 조회하는 컬럼, <code>WHERE</code> 절에 자주 등장하는 컬럼에 직접 생성한다.</p>
<pre><code class="language-sql">-- 테이블 생성 시 함께 만들기
CREATE TABLE members (
    member_id   INT         PRIMARY KEY AUTO_INCREMENT,
    member_name VARCHAR(50) NOT NULL,
    INDEX idx_name (member_name)
);

-- 이미 있는 테이블에 추가하기
CREATE INDEX idx_name ON members (member_name);</code></pre>
<p><code>member_name</code>으로 검색하는 쿼리가 많다면 이렇게 인덱스를 걸어두면 된다.</p>
<pre><code class="language-sql">-- 이 쿼리가 빨라진다
SELECT * FROM members WHERE member_name = &#39;김민수&#39;;</code></pre>
<h3 id="복합-인덱스">복합 인덱스</h3>
<p>두 개 이상의 컬럼을 묶어서 만드는 인덱스다. 여러 조건을 동시에 쓰는 쿼리에 효과적이다.</p>
<pre><code class="language-sql">CREATE INDEX idx_name_email ON members (member_name, email);</code></pre>
<p>복합 인덱스는 <strong>순서가 중요</strong>하다. <code>(member_name, email)</code>로 만든 인덱스는 <code>member_name</code> 단독 검색이나 <code>member_name + email</code> 검색에는 사용되지만, <code>email</code> 단독 검색에는 사용되지 않는다. 인덱스의 앞 컬럼부터 순서대로 사용되는 구조이기 때문이다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/5bf77d20-bcdf-4276-b550-c6bc97f26401/image.png" alt=""></p>
<hr>
<h2 id="인덱스-확인과-삭제">인덱스 확인과 삭제</h2>
<p>현재 테이블에 어떤 인덱스가 걸려 있는지 확인하려면 <code>SHOW INDEX</code>를 쓴다.</p>
<pre><code class="language-sql">SHOW INDEX FROM members;</code></pre>
<p>인덱스를 삭제할 때는 <code>DROP INDEX</code>를 사용한다.</p>
<pre><code class="language-sql">DROP INDEX idx_name ON members;</code></pre>
<hr>
<h2 id="explain으로-인덱스-사용-여부-확인">EXPLAIN으로 인덱스 사용 여부 확인</h2>
<p>쿼리 앞에 <code>EXPLAIN</code>을 붙이면 MySQL이 실제로 인덱스를 사용하고 있는지 확인할 수 있다.</p>
<pre><code class="language-sql">EXPLAIN SELECT * FROM members WHERE member_name = &#39;김민수&#39;;</code></pre>
<p>결과의 <code>type</code> 컬럼이 <code>ALL</code>이면 풀 테이블 스캔, <code>ref</code>나 <code>range</code> 등이면 인덱스를 사용하고 있다는 뜻이다. <code>rows</code> 컬럼은 DB가 몇 개의 행을 읽을 것으로 예상하는지 보여주는데, 인덱스가 없을 때와 있을 때의 차이를 직접 비교해볼 수 있다.</p>
<hr>
<h2 id="언제-인덱스를-걸어야-하는가">언제 인덱스를 걸어야 하는가</h2>
<p>인덱스를 무조건 많이 걸면 좋을 것 같지만, 실제로는 판단이 필요하다.</p>
<p><strong>인덱스가 유용한 경우:</strong></p>
<ul>
<li><code>WHERE</code> 절에 자주 등장하는 컬럼</li>
<li><code>JOIN</code>에서 연결 조건으로 쓰이는 컬럼 (FOREIGN KEY 컬럼)</li>
<li><code>ORDER BY</code>, <code>GROUP BY</code>에 자주 쓰이는 컬럼</li>
<li>데이터가 많고 조회 빈도가 높은 테이블</li>
</ul>
<p><strong>인덱스를 피해야 하는 경우:</strong></p>
<ul>
<li>행이 몇 백 건밖에 없는 작은 테이블 (풀 스캔이 오히려 빠름)</li>
<li>INSERT/UPDATE/DELETE가 매우 빈번한 컬럼</li>
<li><code>TRUE/FALSE</code>, 성별처럼 값의 종류가 거의 없는 컬럼 (중복이 많으면 인덱스 효과가 없음)</li>
</ul>
<p>인덱스는 조회 성능을 높이는 대신 쓰기 비용과 저장 공간을 내는 구조다. 필요한 곳에 적절하게 거는 것이 핵심이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Primary key, Unique, Foreign key]]></title>
            <link>https://velog.io/@mskim_/Primary-key-Unique-Foreign-key</link>
            <guid>https://velog.io/@mskim_/Primary-key-Unique-Foreign-key</guid>
            <pubDate>Fri, 17 Apr 2026 07:14:33 GMT</pubDate>
            <description><![CDATA[<h1 id="primary-key-unique-foreign-key">PRIMARY KEY, UNIQUE, FOREIGN KEY</h1>
<p>테이블을 처음 설계할 때 가장 자주 마주치는 세 가지 제약 조건이 있다. <code>PRIMARY KEY</code>, <code>UNIQUE</code>, <code>FOREIGN KEY</code>. 셋 다 &quot;중복을 막는다&quot;는 인상을 주는데, 역할이 미묘하게 다르다. 각각이 왜 존재하는지, 어떤 상황에서 쓰이는지를 구분해두면 테이블 설계가 훨씬 명확해진다.</p>
<hr>
<h2 id="키-제약-조건">키 제약 조건</h2>
<h3 id="primary-key">PRIMARY KEY</h3>
<p><strong>기본 키(Primary Key)</strong>는 테이블의 각 행을 유일하게 식별하는 컬럼이다. 사람으로 치면 주민등록번호 같은 것이다. 이름은 같은 사람이 여러 명일 수 있지만, 주민등록번호는 절대 겹치지 않는다.</p>
<p>PRIMARY KEY는 내부적으로 <code>NOT NULL + UNIQUE</code>가 동시에 적용된다. 즉, 값이 반드시 있어야 하고, 중복되면 안 된다.</p>
<pre><code class="language-sql">CREATE TABLE members (
    member_id   INT          PRIMARY KEY AUTO_INCREMENT,
    member_name VARCHAR(50)  NOT NULL,
    email       VARCHAR(100) NOT NULL
);</code></pre>
<p><code>member_id</code>가 PRIMARY KEY다. <code>AUTO_INCREMENT</code>를 함께 쓰면 INSERT할 때 값을 직접 넣지 않아도 DB가 자동으로 1, 2, 3... 순서로 채워준다.</p>
<pre><code class="language-sql">INSERT INTO members (member_name, email) VALUES (&#39;김민수&#39;, &#39;kim@email.com&#39;);
INSERT INTO members (member_name, email) VALUES (&#39;이지현&#39;, &#39;lee@email.com&#39;);</code></pre>
<pre><code>member_id | member_name | email
----------+-------------+---------------
        1 | 김민수       | kim@email.com
        2 | 이지현       | lee@email.com</code></pre><p>PRIMARY KEY는 테이블당 딱 하나만 지정할 수 있다. 여러 컬럼을 묶어서 하나의 기본 키로 만드는 <strong>복합 기본 키(Composite Primary Key)</strong>도 가능하지만, 그 경우에도 기본 키 자체는 하나다.</p>
<pre><code class="language-sql">-- 복합 기본 키: 두 컬럼의 조합이 유일해야 한다
CREATE TABLE order_items (
    order_id   INT NOT NULL,
    product_id INT NOT NULL,
    quantity   INT NOT NULL,
    PRIMARY KEY (order_id, product_id)
);</code></pre>
<p><img src="https://velog.velcdn.com/images/mskim_/post/b3b0d40d-91c1-4c9d-a7bf-b7cc99edde67/image.png" alt=""></p>
<h3 id="unique">UNIQUE</h3>
<p><strong>UNIQUE</strong> 제약 조건도 중복을 막는다는 점에서 PRIMARY KEY와 비슷하다. 차이는 두 가지다.</p>
<ul>
<li>PRIMARY KEY는 테이블에 하나뿐이지만, UNIQUE는 여러 컬럼에 동시에 붙일 수 있다.</li>
<li>PRIMARY KEY는 NULL을 허용하지 않지만, UNIQUE는 NULL을 허용한다. (NULL은 &quot;값 없음&quot;이라서 중복으로 보지 않는다)</li>
</ul>
<p>이메일 컬럼을 예시로 보면, 같은 이메일로 두 계정을 만들 수 없게 막고 싶다. 이메일은 행의 식별자로 쓰기엔 적합하지 않고(바뀔 수 있으니까), 그래도 중복은 안 된다 — 이럴 때 UNIQUE를 쓴다.</p>
<pre><code class="language-sql">CREATE TABLE members (
    member_id   INT          PRIMARY KEY AUTO_INCREMENT,
    member_name VARCHAR(50)  NOT NULL,
    email       VARCHAR(100) NOT NULL UNIQUE,
    phone       VARCHAR(20)  UNIQUE
);</code></pre>
<p><code>email</code>과 <code>phone</code> 모두 UNIQUE가 붙어 있다. 각각 중복 없이 유일해야 한다.</p>
<pre><code class="language-sql">INSERT INTO members (member_name, email) VALUES (&#39;김민수&#39;, &#39;kim@email.com&#39;);

-- 오류: email &#39;kim@email.com&#39;이 이미 존재한다
INSERT INTO members (member_name, email) VALUES (&#39;김민수2&#39;, &#39;kim@email.com&#39;);</code></pre>
<p>phone처럼 값이 없을 수도 있는 컬럼은 NULL을 허용하면서 UNIQUE만 걸어두면 된다. NULL끼리는 중복으로 보지 않기 때문에 여러 행에 phone이 없어도 오류가 나지 않는다.</p>
<h3 id="foreign-key">FOREIGN KEY</h3>
<p><strong>외래 키(Foreign Key)</strong>는 앞의 두 제약 조건과 성격이 조금 다르다. PRIMARY KEY와 UNIQUE가 하나의 컬럼 안에서 값을 검사한다면, FOREIGN KEY는 <strong>다른 테이블의 값을 참조</strong>한다.</p>
<p>예를 들어 주문 테이블(<code>orders</code>)이 있고, 어떤 회원이 주문했는지 기록해야 한다면, <code>orders</code> 테이블에 <code>member_id</code>를 넣는다. 이때 존재하지 않는 회원 ID가 들어오면 안 된다. FOREIGN KEY가 이걸 막아준다.</p>
<p><img src="https://velog.velcdn.com/images/mskim_/post/5b144365-1fd9-45d8-8ad2-2b665b33680b/image.png" alt=""></p>
<pre><code class="language-sql">CREATE TABLE members (
    member_id   INT         PRIMARY KEY AUTO_INCREMENT,
    member_name VARCHAR(50) NOT NULL
);

CREATE TABLE orders (
    order_id   INT  PRIMARY KEY AUTO_INCREMENT,
    member_id  INT  NOT NULL,
    order_date DATE NOT NULL,
    FOREIGN KEY (member_id) REFERENCES members(member_id)
);</code></pre>
<p><code>orders.member_id</code>는 <code>members.member_id</code>를 참조한다. 이 관계를 <strong>참조 무결성(Referential Integrity)</strong>이라고 부른다.</p>
<pre><code class="language-sql">INSERT INTO members (member_name) VALUES (&#39;김민수&#39;); -- member_id = 1

-- 정상: member_id 1은 members에 존재한다
INSERT INTO orders (member_id, order_date) VALUES (1, &#39;2026-04-17&#39;);

-- 오류: member_id 99는 members에 없다
INSERT INTO orders (member_id, order_date) VALUES (99, &#39;2026-04-17&#39;);</code></pre>
<p>FOREIGN KEY가 걸려 있으면 참조 중인 부모 행을 함부로 지울 수 없다. 회원에게 주문 내역이 있는 상태에서 삭제하려 하면 오류가 난다. 이 동작을 <code>ON DELETE</code>, <code>ON UPDATE</code> 옵션으로 제어할 수 있다.</p>
<pre><code class="language-sql">FOREIGN KEY (member_id) REFERENCES members(member_id)
    ON DELETE CASCADE
    ON UPDATE CASCADE</code></pre>
<table>
<thead>
<tr>
<th>옵션</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td><code>RESTRICT</code> (기본값)</td>
<td>참조 중인 행이 있으면 삭제/수정 불가</td>
</tr>
<tr>
<td><code>CASCADE</code></td>
<td>부모가 삭제/수정되면 자식도 함께 삭제/수정</td>
</tr>
<tr>
<td><code>SET NULL</code></td>
<td>부모가 삭제되면 자식의 FK 컬럼을 NULL로 변경</td>
</tr>
<tr>
<td><code>NO ACTION</code></td>
<td><code>RESTRICT</code>와 동일 (MySQL에서는 같게 처리)</td>
</tr>
</tbody></table>
<p><code>CASCADE</code>는 편리하지만 주의가 필요하다. 회원 하나를 지웠는데 연결된 주문 수십 건이 줄줄이 삭제될 수 있다.</p>
<hr>
<h2 id="세-가지-비교-정리">세 가지 비교 정리</h2>
<table>
<thead>
<tr>
<th></th>
<th>PRIMARY KEY</th>
<th>UNIQUE</th>
<th>FOREIGN KEY</th>
</tr>
</thead>
<tbody><tr>
<td>목적</td>
<td>행의 고유 식별</td>
<td>컬럼 값의 중복 방지</td>
<td>다른 테이블 참조</td>
</tr>
<tr>
<td>NULL 허용</td>
<td>불가</td>
<td>가능</td>
<td>컬럼 설정에 따름</td>
</tr>
<tr>
<td>테이블당 개수</td>
<td>1개</td>
<td>여러 개 가능</td>
<td>여러 개 가능</td>
</tr>
<tr>
<td>자동 인덱스</td>
<td>생성됨</td>
<td>생성됨</td>
<td>생성 권장</td>
</tr>
</tbody></table>
<p>세 제약 조건은 각자의 역할이 있다. PRIMARY KEY는 &quot;이 행이 누구인지&quot;, UNIQUE는 &quot;이 값이 겹치면 안 된다&quot;, FOREIGN KEY는 &quot;이 값이 저쪽 테이블에 반드시 있어야 한다&quot;는 것을 보장한다. 하나의 테이블에 셋이 함께 쓰이는 것도 자연스럽다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SELECT 와 WHERE]]></title>
            <link>https://velog.io/@mskim_/SELECT-%EC%99%80-WHERE</link>
            <guid>https://velog.io/@mskim_/SELECT-%EC%99%80-WHERE</guid>
            <pubDate>Tue, 07 Apr 2026 08:45:21 GMT</pubDate>
            <description><![CDATA[<h1 id="select와-where">SELECT와 WHERE</h1>
<p>데이터를 넣었으면 꺼내야 한다. SQL에서 데이터를 조회할 때 쓰는 게 <code>SELECT</code>다. 단순히 전체를 가져오는 것부터 조건을 걸고 정렬하고 개수를 제한하는 것까지 — SELECT 하나에 옵션이 꽤 많다.</p>
<hr>
<h2 id="기본-조회">기본 조회</h2>
<pre><code class="language-sql">-- 모든 컬럼, 모든 행 조회
SELECT * FROM members;</code></pre>
<p><code>*</code>는 모든 컬럼을 뜻한다. 컬럼이 많을 때는 필요한 것만 명시하는 게 낫다.</p>
<pre><code class="language-sql">SELECT member_name, email FROM members;</code></pre>
<hr>
<h2 id="where--조건-걸기">WHERE — 조건 걸기</h2>
<p>전체가 아니라 특정 조건에 맞는 행만 가져올 때 쓴다.</p>
<pre><code class="language-sql">SELECT * FROM members WHERE member_id = 1;</code></pre>
<h3 id="비교-연산자">비교 연산자</h3>
<table>
<thead>
<tr>
<th>연산자</th>
<th>의미</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>=</code></td>
<td>같다</td>
<td><code>WHERE member_id = 1</code></td>
</tr>
<tr>
<td><code>!=</code> / <code>&lt;&gt;</code></td>
<td>다르다</td>
<td><code>WHERE member_id != 1</code></td>
</tr>
<tr>
<td><code>&gt;</code> / <code>&lt;</code></td>
<td>크다 / 작다</td>
<td><code>WHERE price &gt; 10000</code></td>
</tr>
<tr>
<td><code>&gt;=</code> / <code>&lt;=</code></td>
<td>크거나 같다 / 작거나 같다</td>
<td><code>WHERE price &gt;= 10000</code></td>
</tr>
</tbody></table>
<h3 id="and--or">AND / OR</h3>
<p>조건을 여러 개 걸 때 쓴다.</p>
<pre><code class="language-sql">-- 둘 다 만족하는 행
SELECT * FROM products WHERE price &gt; 10000 AND stock &gt; 0;

-- 하나라도 만족하는 행
SELECT * FROM members WHERE member_name = &#39;김민수&#39; OR member_name = &#39;이지현&#39;;</code></pre>
<h3 id="between">BETWEEN</h3>
<p>범위 조건을 걸 때 쓴다. <code>AND</code>로 쓰는 것과 결과는 같지만 더 읽기 편하다.</p>
<pre><code class="language-sql">-- 가격이 10000 이상 50000 이하인 상품
SELECT * FROM products WHERE price BETWEEN 10000 AND 50000;</code></pre>
<h3 id="in">IN</h3>
<p>여러 값 중 하나와 일치하는 행을 가져올 때 쓴다. <code>OR</code>를 여러 번 쓰는 것과 같다.</p>
<pre><code class="language-sql">-- member_id가 1, 3, 5인 회원
SELECT * FROM members WHERE member_id IN (1, 3, 5);</code></pre>
<h3 id="like">LIKE</h3>
<p>문자열 패턴 매칭에 쓴다. <code>%</code>는 아무 문자 0개 이상, <code>_</code>는 아무 문자 정확히 1개를 뜻한다.</p>
<pre><code class="language-sql">-- &#39;김&#39;으로 시작하는 이름
SELECT * FROM members WHERE member_name LIKE &#39;김%&#39;;

-- 이메일이 &#39;@gmail.com&#39;으로 끝나는 회원
SELECT * FROM members WHERE email LIKE &#39;%@gmail.com&#39;;

-- 이름이 정확히 3글자인 회원 (&#39;_&#39; 하나가 한 글자)
SELECT * FROM members WHERE member_name LIKE &#39;___&#39;;</code></pre>
<h3 id="is-null--is-not-null">IS NULL / IS NOT NULL</h3>
<p><code>NULL</code>은 <code>=</code>로 비교할 수 없다. NULL인지 확인할 때는 반드시 <code>IS NULL</code>을 써야 한다.</p>
<pre><code class="language-sql">-- 전화번호가 없는 회원
SELECT * FROM members WHERE phone IS NULL;

-- 전화번호가 있는 회원
SELECT * FROM members WHERE phone IS NOT NULL;</code></pre>
<p><img src="https://velog.velcdn.com/images/mskim_/post/8af995c9-eefb-4f9f-8276-5c7422457b4c/image.png" alt=""></p>
<hr>
<h2 id="order-by--정렬">ORDER BY — 정렬</h2>
<p>조회 결과를 특정 컬럼 기준으로 정렬한다.</p>
<pre><code class="language-sql">-- 가격 낮은 순 (오름차순, 기본값)
SELECT * FROM products ORDER BY price ASC;

-- 가격 높은 순 (내림차순)
SELECT * FROM products ORDER BY price DESC;</code></pre>
<p>여러 컬럼으로 정렬할 수도 있다. 앞에 쓴 컬럼이 우선순위가 높다.</p>
<pre><code class="language-sql">-- 가격 낮은 순으로 정렬하되, 가격이 같으면 이름 오름차순
SELECT * FROM products ORDER BY price ASC, product_name ASC;</code></pre>
<hr>
<h2 id="limit--개수-제한">LIMIT — 개수 제한</h2>
<p>결과 행의 수를 제한한다.</p>
<pre><code class="language-sql">-- 가격 높은 순으로 상위 5개만
SELECT * FROM products ORDER BY price DESC LIMIT 5;</code></pre>
<p><code>OFFSET</code>을 함께 쓰면 몇 번째 행부터 가져올지 지정할 수 있다. 페이지 처리할 때 자주 쓰는 패턴이다.</p>
<pre><code class="language-sql">-- 6번째 행부터 5개 (2페이지)
SELECT * FROM products ORDER BY price DESC LIMIT 5 OFFSET 5;</code></pre>
<hr>
<h2 id="select-실행-순서">SELECT 실행 순서</h2>
<p>SELECT 쿼리를 쓸 때 절의 순서가 헷갈릴 수 있다. 작성 순서와 실제 실행 순서가 다르기 때문이다.</p>
<p><strong>작성 순서</strong></p>
<pre><code class="language-sql">SELECT 컬럼
FROM 테이블
WHERE 조건
ORDER BY 컬럼
LIMIT 개수</code></pre>
<p><strong>실행 순서</strong></p>
<ol>
<li><code>FROM</code> — 어느 테이블에서 가져올지</li>
<li><code>WHERE</code> — 조건으로 행 필터링</li>
<li><code>SELECT</code> — 가져올 컬럼 선택</li>
<li><code>ORDER BY</code> — 정렬</li>
<li><code>LIMIT</code> — 개수 제한</li>
</ol>
<p>WHERE가 SELECT보다 먼저 실행되기 때문에, WHERE 절에서는 SELECT에서 붙인 별칭(alias)을 쓸 수 없다. 이 부분에서 처음에 한 번씩 막히는 경우가 있다.</p>
<hr>
<h2 id="정리">정리</h2>
<table>
<thead>
<tr>
<th>절</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>WHERE</code></td>
<td>조건으로 행 필터링</td>
</tr>
<tr>
<td><code>BETWEEN</code></td>
<td>범위 조건</td>
</tr>
<tr>
<td><code>IN</code></td>
<td>여러 값 중 일치</td>
</tr>
<tr>
<td><code>LIKE</code></td>
<td>문자열 패턴 매칭</td>
</tr>
<tr>
<td><code>IS NULL</code></td>
<td>NULL 여부 확인</td>
</tr>
<tr>
<td><code>ORDER BY</code></td>
<td>정렬</td>
</tr>
<tr>
<td><code>LIMIT</code></td>
<td>결과 개수 제한</td>
</tr>
</tbody></table>
<p>SELECT는 옵션이 많아서 처음엔 복잡해 보이지만, 결국 &quot;어디서(FROM), 뭘(SELECT), 어떤 조건으로(WHERE), 어떻게 정렬해서(ORDER BY), 몇 개(LIMIT)&quot; 가져올지를 조합하는 것이다.</p>
<hr>
]]></description>
        </item>
    </channel>
</rss>