<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>woohaha</title>
    <link>https://story2248.tistory.com/</link>
    <description>개발자 블로그</description>
    <language>ko</language>
    <pubDate>Mon, 1 Jun 2026 19:43:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>woohaha</managingEditor>
    <image>
      <title>woohaha</title>
      <url>https://tistory1.daumcdn.net/tistory/7247284/attach/83b9fdf050804c969f2a714b4615e17a</url>
      <link>https://story2248.tistory.com</link>
    </image>
    <item>
      <title>[Frontend] 이벤트루프 완전 정복 - 콜스택, 마이크로태스크, 태스크큐 한 번에</title>
      <link>https://story2248.tistory.com/23</link>
      <description>&lt;style&gt;
  .post-wrap {
    background: #0d1117; color: #e6edf3;
    font-family: 'Pretendard', -apple-system, sans-serif;
    padding: 0; line-height: 1.8;
  }
  .post-wrap * { box-sizing: border-box; }
  .post-wrap p { margin: 0 0 16px; color: #8b949e; font-size: 15px; }
  .post-wrap p strong { color: #e6edf3; }
  .post-divider { border: none; border-top: 1px solid #21262d; margin: 32px 0; }

  .post-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px;
    background: rgba(88,166,255,0.1); border: 1px solid rgba(88,166,255,0.3);
    border-radius: 20px; font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: #58a6ff; margin-bottom: 20px;
  }
  .post-badge::before { content: ' '; }

  /* 목차 */
  .toc { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 24px 0; }
  .toc-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .toc-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .toc-item { font-size: 13px; color: #8b949e; padding: 3px 0; display: flex; align-items: center; gap: 8px; }
  .toc-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; }

  .post-wrap h2 {
    font-size: 18px; font-weight: 600; color: #e6edf3;
    margin: 32px 0 16px; padding-bottom: 8px;
    border-bottom: 1px solid #21262d;
    display: flex; align-items: center; gap: 8px;
  }
  .post-wrap h2 .num {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #58a6ff; background: rgba(88,166,255,0.1);
    border: 1px solid rgba(88,166,255,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h3 {
    font-size: 15px; font-weight: 600; color: #8b949e;
    margin: 20px 0 10px; font-family: 'JetBrains Mono', monospace;
  }
  .post-wrap h3::before { content: '// '; color: #484f58; }

  /* 이벤트루프 시각화 */
  .el-viz {
    background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; padding: 24px; margin: 20px 0;
    font-family: 'JetBrains Mono', monospace;
  }
  .el-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 20px; display: block; }
  .el-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin-bottom: 16px; }
  .el-box {
    border-radius: 8px; padding: 14px 16px;
    border: 1px solid #30363d; background: #161b22;
  }
  .el-box-title { font-size: 10px; letter-spacing: 0.08em; margin-bottom: 10px; display: block; font-weight: 700; }
  .el-item { font-size: 11px; padding: 4px 8px; border-radius: 4px; margin-bottom: 4px; text-align: center; }
  .ei-call { background: rgba(88,166,255,0.15);  color: #58a6ff;  border: 1px solid rgba(88,166,255,0.3); }
  .ei-macro{ background: rgba(255,166,87,0.15);  color: #ffa657;  border: 1px solid rgba(255,166,87,0.3); }
  .ei-micro{ background: rgba(63,185,80,0.15);   color: #3fb950;  border: 1px solid rgba(63,185,80,0.3); }
  .ei-web  { background: rgba(188,140,255,0.15); color: #bc8cff;  border: 1px solid rgba(188,140,255,0.3); }
  .el-arrow-row { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 12px; font-size: 11px; color: #484f58; }
  .el-loop { background: rgba(57,211,83,0.1); border: 1px solid rgba(57,211,83,0.2); color: #39d353; padding: 8px 20px; border-radius: 6px; font-size: 12px; font-weight: 700; }

  /* 실행 순서 시각화 */
  .exec-order {
    background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; padding: 18px 22px; margin: 16px 0;
    font-family: 'JetBrains Mono', monospace;
  }
  .eo-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 14px; display: block; }
  .eo-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; font-size: 12px; }
  .eo-num { font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 4px; flex-shrink: 0; min-width: 28px; text-align: center; }
  .en-sync  { background: rgba(88,166,255,0.15);  color: #58a6ff;  border: 1px solid rgba(88,166,255,0.3); }
  .en-micro { background: rgba(63,185,80,0.15);   color: #3fb950;  border: 1px solid rgba(63,185,80,0.3); }
  .en-macro { background: rgba(255,166,87,0.15);  color: #ffa657;  border: 1px solid rgba(255,166,87,0.3); }
  .eo-desc  { color: #e6edf3; }
  .eo-sub   { font-size: 10px; color: #484f58; margin-left: 4px; }

  /* 코드 블럭 */
  .code-block { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; margin: 12px 0; overflow: hidden; }
  .code-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; background: #161b22; border-bottom: 1px solid #30363d; }
  .code-lang { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; letter-spacing: 0.05em; }
  .code-dots { display: flex; gap: 5px; }
  .code-dots span { width: 10px; height: 10px; border-radius: 50%; }
  .code-dots span:nth-child(1) { background: #ff5f56; }
  .code-dots span:nth-child(2) { background: #ffbd2e; }
  .code-dots span:nth-child(3) { background: #27c93f; }
  .code-body { padding: 18px 22px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.85; overflow-x: auto; color: #e6edf3; }
  .kw  { color: #ff7b72; } .fn  { color: #d2a8ff; }
  .cm  { color: #484f58; font-style: italic; } .str { color: #a5d6ff; }
  .num { color: #79c0ff; }
  .sync  { color: #58a6ff; font-weight: 600; }
  .micro { color: #3fb950; font-weight: 600; }
  .macro { color: #ffa657; font-weight: 600; }
  .out   { color: #39d353; }

  /* 용어 카드 */
  .term-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 16px 0; }
  .term-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 14px 16px; }
  .term-name { font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 700; margin-bottom: 6px; }
  .term-desc { font-size: 13px; color: #8b949e; line-height: 1.65; }
  .term-desc strong { color: #e6edf3; }
  .term-example { margin-top: 8px; padding: 6px 10px; background: #0d1117; border-radius: 4px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; }
  .term-example::before { content: 'ex) '; color: #58a6ff; }

  /* tip / warn 박스 */
  .tip-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #58a6ff; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .tip-box strong { color: #e6edf3; }
  .tip-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .warn-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ffa657; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .warn-box strong { color: #e6edf3; }
  .warn-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }

  /* 비교 테이블 */
  .compare-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
  .compare-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 10px 14px; border: 1px solid #30363d; letter-spacing: 0.05em; text-align: left; }
  .compare-table td { padding: 10px 14px; border: 1px solid #30363d; color: #e6edf3; }
  .compare-table tr:nth-child(even) td { background: #161b22; }

  .ic { font-family: 'JetBrains Mono', monospace; font-size: 0.88em; padding: 1px 6px; background: #1c2128; border: 1px solid #30363d; border-radius: 4px; color: #ffa657; }
  .post-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 32px; padding-top: 20px; border-top: 1px solid #21262d; }
  .post-tag { padding: 3px 10px; background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.25); border-radius: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; }
  .post-tag::before { content: '#'; opacity: 0.6; }
  @media (max-width: 600px) {
    .toc-list { grid-template-columns: 1fr; }
    .el-grid { grid-template-columns: 1fr; }
    .term-grid { grid-template-columns: 1fr; }
  }
&lt;/style&gt;

&lt;div class=&quot;post-wrap&quot;&gt;
  &lt;div class=&quot;post-badge&quot;&gt;CS 면접 대비 · 프론트엔드&lt;/div&gt;

  &lt;p&gt;면접에서 &lt;strong&gt;&quot;JS는 싱글스레드인데 어떻게 비동기 처리를 하나요?&quot;&lt;/strong&gt;라고 물어보면&lt;br&gt;
  이벤트루프를 설명할 수 있어야 해요.&lt;br&gt;
  콜스택, 태스크큐, 마이크로태스크까지 한 번에 정리해드릴게요!&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
    &lt;span class=&quot;toc-title&quot;&gt;// TABLE OF CONTENTS&lt;/span&gt;
    &lt;div class=&quot;toc-list&quot;&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;01&lt;/span&gt; JS는 싱글스레드&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;02&lt;/span&gt; 이벤트루프 구성요소&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;03&lt;/span&gt; 콜스택 (Call Stack)&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;04&lt;/span&gt; Web APIs&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;05&lt;/span&gt; 태스크큐 vs 마이크로태스크큐&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;06&lt;/span&gt; 실행 순서 예제&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;07&lt;/span&gt; Promise / async 실행 순서&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;08&lt;/span&gt; 면접 답변 정리&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 01. 싱글스레드 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;01&lt;/span&gt; JS는 싱글스레드&lt;/h2&gt;

  &lt;p&gt;JavaScript는 &lt;strong&gt;싱글스레드&lt;/strong&gt; 언어예요.&lt;br&gt;
  한 번에 하나의 작업만 처리할 수 있어요.&lt;br&gt;
  그런데 어떻게 setTimeout, fetch 같은 비동기 작업을 처리할까요?&lt;br&gt;
  바로 &lt;strong&gt;이벤트루프(Event Loop)&lt;/strong&gt; 덕분이에요!&lt;/p&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  핵심 개념&lt;/span&gt;
    JS 엔진은 싱글스레드지만, &lt;strong&gt;브라우저(런타임)&lt;/strong&gt;는 멀티스레드예요.&lt;br&gt;
    setTimeout, fetch, DOM 이벤트 같은 비동기 작업은 &lt;strong&gt;Web API(브라우저)&lt;/strong&gt;가 처리하고,&lt;br&gt;
    완료되면 &lt;strong&gt;콜백을 큐에 넣어서 JS 엔진이 나중에 실행&lt;/strong&gt;하는 구조예요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 02. 구성요소 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;02&lt;/span&gt; 이벤트루프 구성요소&lt;/h2&gt;

  &lt;div class=&quot;el-viz&quot;&gt;
    &lt;span class=&quot;el-title&quot;&gt;// 이벤트루프 전체 구조&lt;/span&gt;
    &lt;div class=&quot;el-grid&quot;&gt;
      &lt;div class=&quot;el-box&quot;&gt;
        &lt;span class=&quot;el-box-title&quot; style=&quot;color:#58a6ff;&quot;&gt;  Call Stack&lt;/span&gt;
        &lt;div class=&quot;el-item ei-call&quot;&gt;console.log()&lt;/div&gt;
        &lt;div class=&quot;el-item ei-call&quot;&gt;setTimeout()&lt;/div&gt;
        &lt;div class=&quot;el-item ei-call&quot;&gt;fetch()&lt;/div&gt;
        &lt;div style=&quot;font-size:10px;color:#484f58;margin-top:8px;font-family:'Pretendard',sans-serif;&quot;&gt;JS 코드 실행 공간&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;el-box&quot;&gt;
        &lt;span class=&quot;el-box-title&quot; style=&quot;color:#bc8cff;&quot;&gt;  Web APIs&lt;/span&gt;
        &lt;div class=&quot;el-item ei-web&quot;&gt;Timer (setTimeout)&lt;/div&gt;
        &lt;div class=&quot;el-item ei-web&quot;&gt;HTTP (fetch/XHR)&lt;/div&gt;
        &lt;div class=&quot;el-item ei-web&quot;&gt;DOM Events&lt;/div&gt;
        &lt;div style=&quot;font-size:10px;color:#484f58;margin-top:8px;font-family:'Pretendard',sans-serif;&quot;&gt;브라우저가 처리&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;el-box&quot;&gt;
        &lt;span class=&quot;el-box-title&quot; style=&quot;color:#3fb950;&quot;&gt;✅ Microtask Queue&lt;/span&gt;
        &lt;div class=&quot;el-item ei-micro&quot;&gt;Promise.then()&lt;/div&gt;
        &lt;div class=&quot;el-item ei-micro&quot;&gt;queueMicrotask()&lt;/div&gt;
        &lt;div class=&quot;el-item ei-micro&quot;&gt;MutationObserver&lt;/div&gt;
        &lt;div style=&quot;font-size:10px;color:#484f58;margin-top:8px;font-family:'Pretendard',sans-serif;&quot;&gt;우선순위 높음&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;el-grid&quot; style=&quot;grid-template-columns: 1fr 1fr; margin-bottom:0;&quot;&gt;
      &lt;div class=&quot;el-box&quot;&gt;
        &lt;span class=&quot;el-box-title&quot; style=&quot;color:#ffa657;&quot;&gt;  Macrotask Queue (Task Queue)&lt;/span&gt;
        &lt;div class=&quot;el-item ei-macro&quot;&gt;setTimeout 콜백&lt;/div&gt;
        &lt;div class=&quot;el-item ei-macro&quot;&gt;setInterval 콜백&lt;/div&gt;
        &lt;div class=&quot;el-item ei-macro&quot;&gt;DOM 이벤트 핸들러&lt;/div&gt;
        &lt;div style=&quot;font-size:10px;color:#484f58;margin-top:8px;font-family:'Pretendard',sans-serif;&quot;&gt;우선순위 낮음&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;el-box&quot; style=&quot;display:flex;align-items:center;justify-content:center;text-align:center;&quot;&gt;
        &lt;div&gt;
          &lt;span class=&quot;el-loop&quot;&gt;  Event Loop&lt;/span&gt;
          &lt;div style=&quot;font-size:11px;color:#484f58;margin-top:10px;font-family:'Pretendard',sans-serif;&quot;&gt;
            콜스택 비면&lt;br&gt;
            마이크로태스크 → 매크로태스크&lt;br&gt;
            순으로 콜백 가져옴
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 03. 콜스택 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;03&lt;/span&gt; 콜스택 (Call Stack)&lt;/h2&gt;

  &lt;p&gt;함수가 호출되면 &lt;strong&gt;콜스택에 쌓이고&lt;/strong&gt;, 함수가 끝나면 &lt;strong&gt;콜스택에서 제거&lt;/strong&gt;돼요.&lt;br&gt;
  LIFO(Last In First Out) 구조예요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;콜스택 동작 예시&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;kw&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;c&lt;/span&gt;() { console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'c'&lt;/span&gt;); }&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;b&lt;/span&gt;() { &lt;span class=&quot;fn&quot;&gt;c&lt;/span&gt;(); }&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;a&lt;/span&gt;() { &lt;span class=&quot;fn&quot;&gt;b&lt;/span&gt;(); }&lt;br&gt;
&lt;span class=&quot;fn&quot;&gt;a&lt;/span&gt;();&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 콜스택 변화&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [a]&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [a, b]&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [a, b, c]  ← c 실행&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [a, b]     ← c 완료 후 제거&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [a]        ← b 완료 후 제거&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// []         ← a 완료 후 제거 = 콜스택 비어있음!&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ 콜스택이 꽉 차면?&lt;/span&gt;
    재귀함수가 무한히 호출되면 콜스택이 가득 차서&lt;br&gt;
    &lt;strong&gt;&quot;Maximum call stack size exceeded&quot;&lt;/strong&gt; 에러가 발생해요.&lt;br&gt;
    이게 바로 &lt;strong&gt;스택 오버플로우(Stack Overflow)&lt;/strong&gt;예요!
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 04. Web APIs --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;04&lt;/span&gt; Web APIs — 비동기 작업은 브라우저가&lt;/h2&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;setTimeout / setInterval&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;타이머 작업. &lt;strong&gt;브라우저가 시간을 재고&lt;/strong&gt; 완료되면 콜백을 태스크큐에 넣어요.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;setTimeout(() =&gt; ..., 1000) → 1초 후 태스크큐에 추가&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;fetch / XHR&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;HTTP 요청. &lt;strong&gt;브라우저가 네트워크 통신&lt;/strong&gt;하고 완료 시 콜백 처리.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;fetch() → 서버 응답 후 .then() 실행&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;DOM 이벤트&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;클릭, 키보드 등 이벤트. &lt;strong&gt;브라우저가 감지&lt;/strong&gt;하고 핸들러를 태스크큐에 추가.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;btn.click() → onclick 핸들러 태스크큐에 추가&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;requestAnimationFrame&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;다음 렌더링 전에 실행. &lt;strong&gt;애니메이션 최적화&lt;/strong&gt;에 사용. 약 16ms(60fps) 주기.&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 05. 태스크큐 vs 마이크로태스크큐 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;05&lt;/span&gt; 태스크큐 vs 마이크로태스크큐&lt;/h2&gt;

  &lt;p&gt;이벤트루프의 핵심은 &lt;strong&gt;우선순위&lt;/strong&gt;예요.&lt;br&gt;
  콜스택이 비면 마이크로태스크큐를 먼저, 다 비운 다음 태스크큐를 처리해요.&lt;/p&gt;

  &lt;table class=&quot;compare-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;구분&lt;/th&gt;&lt;th&gt;마이크로태스크큐&lt;/th&gt;&lt;th&gt;태스크큐 (매크로태스크)&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;우선순위&lt;/td&gt;
      &lt;td style=&quot;color:#3fb950;font-weight:700;&quot;&gt;높음 (먼저 처리)&lt;/td&gt;
      &lt;td style=&quot;color:#ffa657;&quot;&gt;낮음 (나중에 처리)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;포함 항목&lt;/td&gt;
      &lt;td&gt;Promise.then/catch, queueMicrotask, MutationObserver&lt;/td&gt;
      &lt;td&gt;setTimeout, setInterval, DOM 이벤트, fetch 콜백&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;처리 방식&lt;/td&gt;
      &lt;td style=&quot;color:#3fb950;font-weight:700;&quot;&gt;큐가 완전히 빌 때까지 모두 처리&lt;/td&gt;
      &lt;td&gt;한 번에 하나씩 처리 후 렌더링&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;렌더링&lt;/td&gt;
      &lt;td&gt;마이크로태스크 처리 후 렌더링&lt;/td&gt;
      &lt;td&gt;매크로태스크 하나 처리 후 렌더링 가능&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  이벤트루프 실행 순서&lt;/span&gt;
    &lt;strong&gt;① 콜스택의 동기 코드 전부 실행&lt;/strong&gt;&lt;br&gt;
    &lt;strong&gt;② 마이크로태스크큐 전부 처리&lt;/strong&gt; (Promise.then 등)&lt;br&gt;
    &lt;strong&gt;③ 렌더링 (필요한 경우)&lt;/strong&gt;&lt;br&gt;
    &lt;strong&gt;④ 태스크큐에서 하나 꺼내 실행&lt;/strong&gt; (setTimeout 등)&lt;br&gt;
    &lt;strong&gt;⑤ ②~④ 반복&lt;/strong&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 06. 실행 순서 예제 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;06&lt;/span&gt; 실행 순서 예제&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;실행 순서 퀴즈 — 출력 순서는?&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;sync&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'1'&lt;/span&gt;);                    &lt;span class=&quot;cm&quot;&gt;// ①&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;macro&quot;&gt;setTimeout&lt;/span&gt;(() =&gt; {&lt;br&gt;
&amp;nbsp;&amp;nbsp;console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'2'&lt;/span&gt;);               &lt;span class=&quot;cm&quot;&gt;// ⑤ 태스크큐&lt;/span&gt;&lt;br&gt;
}, &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;);&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;micro&quot;&gt;Promise&lt;/span&gt;.&lt;span class=&quot;fn&quot;&gt;resolve&lt;/span&gt;().&lt;span class=&quot;fn&quot;&gt;then&lt;/span&gt;(() =&gt; {&lt;br&gt;
&amp;nbsp;&amp;nbsp;console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'3'&lt;/span&gt;);               &lt;span class=&quot;cm&quot;&gt;// ③ 마이크로태스크큐&lt;/span&gt;&lt;br&gt;
});&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;micro&quot;&gt;Promise&lt;/span&gt;.&lt;span class=&quot;fn&quot;&gt;resolve&lt;/span&gt;().&lt;span class=&quot;fn&quot;&gt;then&lt;/span&gt;(() =&gt; {&lt;br&gt;
&amp;nbsp;&amp;nbsp;console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'4'&lt;/span&gt;);               &lt;span class=&quot;cm&quot;&gt;// ④ 마이크로태스크큐&lt;/span&gt;&lt;br&gt;
});&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;sync&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'5'&lt;/span&gt;);                    &lt;span class=&quot;cm&quot;&gt;// ②&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 출력 순서: 1 → 5 → 3 → 4 → 2&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;exec-order&quot;&gt;
    &lt;span class=&quot;eo-title&quot;&gt;// 단계별 실행 설명&lt;/span&gt;
    &lt;div class=&quot;eo-row&quot;&gt;
      &lt;span class=&quot;eo-num en-sync&quot;&gt;①&lt;/span&gt;
      &lt;span class=&quot;eo-desc&quot;&gt;&lt;span class=&quot;out&quot;&gt;'1'&lt;/span&gt; 출력&lt;/span&gt;
      &lt;span class=&quot;eo-sub&quot;&gt;← 동기 코드, 콜스택에서 즉시 실행&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;eo-row&quot;&gt;
      &lt;span class=&quot;eo-num en-macro&quot;&gt;-&lt;/span&gt;
      &lt;span class=&quot;eo-desc&quot;&gt;setTimeout 등록&lt;/span&gt;
      &lt;span class=&quot;eo-sub&quot;&gt;← Web API가 타이머 시작, 0ms 후 태스크큐에 콜백 추가&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;eo-row&quot;&gt;
      &lt;span class=&quot;eo-num en-micro&quot;&gt;-&lt;/span&gt;
      &lt;span class=&quot;eo-desc&quot;&gt;Promise.then 등록&lt;/span&gt;
      &lt;span class=&quot;eo-sub&quot;&gt;← 마이크로태스크큐에 콜백 2개 추가&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;eo-row&quot;&gt;
      &lt;span class=&quot;eo-num en-sync&quot;&gt;②&lt;/span&gt;
      &lt;span class=&quot;eo-desc&quot;&gt;&lt;span class=&quot;out&quot;&gt;'5'&lt;/span&gt; 출력&lt;/span&gt;
      &lt;span class=&quot;eo-sub&quot;&gt;← 동기 코드, 콜스택에서 즉시 실행&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;eo-row&quot;&gt;
      &lt;span class=&quot;eo-num en-micro&quot;&gt;③&lt;/span&gt;
      &lt;span class=&quot;eo-desc&quot;&gt;&lt;span class=&quot;out&quot;&gt;'3'&lt;/span&gt; 출력&lt;/span&gt;
      &lt;span class=&quot;eo-sub&quot;&gt;← 콜스택 비어있음 → 마이크로태스크큐 처리 시작&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;eo-row&quot;&gt;
      &lt;span class=&quot;eo-num en-micro&quot;&gt;④&lt;/span&gt;
      &lt;span class=&quot;eo-desc&quot;&gt;&lt;span class=&quot;out&quot;&gt;'4'&lt;/span&gt; 출력&lt;/span&gt;
      &lt;span class=&quot;eo-sub&quot;&gt;← 마이크로태스크큐 두 번째 콜백&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;eo-row&quot;&gt;
      &lt;span class=&quot;eo-num en-macro&quot;&gt;⑤&lt;/span&gt;
      &lt;span class=&quot;eo-desc&quot;&gt;&lt;span class=&quot;out&quot;&gt;'2'&lt;/span&gt; 출력&lt;/span&gt;
      &lt;span class=&quot;eo-sub&quot;&gt;← 마이크로태스크 완료 → 태스크큐에서 setTimeout 콜백 실행&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 07. Promise / async --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;07&lt;/span&gt; Promise / async-await 실행 순서&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;async/await 실행 순서&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;kw&quot;&gt;async function&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;main&lt;/span&gt;() {&lt;br&gt;
&amp;nbsp;&amp;nbsp;console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt;);           &lt;span class=&quot;cm&quot;&gt;// ①&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;micro&quot;&gt;Promise&lt;/span&gt;.&lt;span class=&quot;fn&quot;&gt;resolve&lt;/span&gt;();  &lt;span class=&quot;cm&quot;&gt;// await 만나면 여기서 멈추고 제어권 반환&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'B'&lt;/span&gt;);           &lt;span class=&quot;cm&quot;&gt;// ④ await 이후 코드는 마이크로태스크로&lt;/span&gt;&lt;br&gt;
}&lt;br&gt;&lt;br&gt;
console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'C'&lt;/span&gt;);               &lt;span class=&quot;cm&quot;&gt;// ②&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;fn&quot;&gt;main&lt;/span&gt;();&lt;br&gt;
console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'D'&lt;/span&gt;);               &lt;span class=&quot;cm&quot;&gt;// ③&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 출력: C → A → D → B&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 왜냐하면...&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 1. C 출력 (동기)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 2. main() 호출 → A 출력&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 3. await 만나서 main() 일시 중단, 제어권 반환&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 4. D 출력 (동기)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 5. 콜스택 비어짐 → 마이크로태스크 처리 → B 출력&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;심화 — 중첩 Promise 실행 순서&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'start'&lt;/span&gt;);                           &lt;span class=&quot;cm&quot;&gt;// ①&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;macro&quot;&gt;setTimeout&lt;/span&gt;(() =&gt; console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'timeout'&lt;/span&gt;), &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;);  &lt;span class=&quot;cm&quot;&gt;// ⑥ 태스크큐&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;micro&quot;&gt;Promise&lt;/span&gt;.&lt;span class=&quot;fn&quot;&gt;resolve&lt;/span&gt;()&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;fn&quot;&gt;then&lt;/span&gt;(() =&gt; {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'promise 1'&lt;/span&gt;);              &lt;span class=&quot;cm&quot;&gt;// ③ 마이크로태스크&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;micro&quot;&gt;Promise&lt;/span&gt;.&lt;span class=&quot;fn&quot;&gt;resolve&lt;/span&gt;();&lt;br&gt;
&amp;nbsp;&amp;nbsp;})&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;fn&quot;&gt;then&lt;/span&gt;(() =&gt; console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'promise 2'&lt;/span&gt;));    &lt;span class=&quot;cm&quot;&gt;// ④ 마이크로태스크&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;micro&quot;&gt;Promise&lt;/span&gt;.&lt;span class=&quot;fn&quot;&gt;resolve&lt;/span&gt;()&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;fn&quot;&gt;then&lt;/span&gt;(() =&gt; console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'promise 3'&lt;/span&gt;));    &lt;span class=&quot;cm&quot;&gt;// ⑤ 마이크로태스크&lt;/span&gt;&lt;br&gt;&lt;br&gt;
console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'end'&lt;/span&gt;);                             &lt;span class=&quot;cm&quot;&gt;// ②&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 출력: start → end → promise 1 → promise 3 → promise 2 → timeout&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// promise 2가 3보다 늦는 이유:&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// promise 1의 then에서 새 Promise를 반환하면&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// promise 2는 마이크로태스크 큐 뒤로 밀림!&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 08. 면접 답변 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;08&lt;/span&gt; 면접 답변 정리&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;면접 예상 Q&amp;A&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;Q. JS는 싱글스레드인데 어떻게 비동기 처리를 하나요?&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;out&quot;&gt;&quot;JS 엔진 자체는 싱글스레드이지만, 브라우저의 Web API가&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;out&quot;&gt;setTimeout, fetch 같은 비동기 작업을 별도로 처리합니다.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;out&quot;&gt;작업이 완료되면 콜백을 태스크큐에 넣고,&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;out&quot;&gt;이벤트루프가 콜스택이 비어있을 때 큐에서 꺼내 실행합니다.&quot;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;Q. Promise와 setTimeout 중 무엇이 먼저 실행되나요?&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;out&quot;&gt;&quot;Promise.then은 마이크로태스크큐에, setTimeout은 태스크큐에 들어갑니다.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;out&quot;&gt;이벤트루프는 콜스택이 비면 마이크로태스크큐를 먼저 전부 처리한 후&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;out&quot;&gt;태스크큐를 처리하므로 Promise.then이 setTimeout보다 먼저 실행됩니다.&quot;&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;✅ CONCLUSION&lt;/span&gt;
    &lt;strong&gt;콜스택&lt;/strong&gt; → JS 코드 실행 공간 (LIFO)&lt;br&gt;
    &lt;strong&gt;Web APIs&lt;/strong&gt; → 브라우저가 비동기 작업 처리&lt;br&gt;
    &lt;strong&gt;마이크로태스크큐&lt;/strong&gt; → Promise.then, 우선순위 높음&lt;br&gt;
    &lt;strong&gt;태스크큐&lt;/strong&gt; → setTimeout, 우선순위 낮음&lt;br&gt;
    &lt;strong&gt;이벤트루프&lt;/strong&gt; → 콜스택 비면 마이크로 → 매크로 순으로 실행&lt;br&gt;&lt;br&gt;
    실행 순서: &lt;strong&gt;동기 → 마이크로태스크 → 렌더링 → 매크로태스크&lt;/strong&gt; 로 기억하세요!
  &lt;/div&gt;

  &lt;div class=&quot;post-tags&quot;&gt;
    &lt;span class=&quot;post-tag&quot;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;이벤트루프&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;콜스택&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;마이크로태스크&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;태스크큐&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;Promise&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;비동기&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;CS면접&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;프론트엔드&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;</description>
      <category>  CS 면접 대비/프론트엔드 심화</category>
      <author>woohaha</author>
      <guid isPermaLink="true">https://story2248.tistory.com/23</guid>
      <comments>https://story2248.tistory.com/23#entry23comment</comments>
      <pubDate>Mon, 1 Jun 2026 19:01:51 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript] - map filter reduce 함수</title>
      <link>https://story2248.tistory.com/22</link>
      <description>&lt;style&gt;
  .post-wrap {
    background: #0d1117; color: #e6edf3;
    font-family: 'Pretendard', -apple-system, sans-serif;
    padding: 0; line-height: 1.8;
  }
  .post-wrap * { box-sizing: border-box; }
  .post-wrap p { margin: 0 0 16px; color: #8b949e; font-size: 15px; }
  .post-wrap p strong { color: #e6edf3; }
  .post-divider { border: none; border-top: 1px solid #21262d; margin: 32px 0; }

  .post-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px;
    background: rgba(255,166,87,0.1); border: 1px solid rgba(255,166,87,0.3);
    border-radius: 20px; font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: #ffa657; margin-bottom: 20px;
  }
  .post-badge::before { content: ' '; }

  /* 목차 */
  .toc { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 24px 0; }
  .toc-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .toc-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .toc-item { font-size: 13px; color: #8b949e; padding: 3px 0; display: flex; align-items: center; gap: 8px; }
  .toc-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; }

  .post-wrap h2 {
    font-size: 18px; font-weight: 600; color: #e6edf3;
    margin: 32px 0 16px; padding-bottom: 8px;
    border-bottom: 1px solid #21262d;
    display: flex; align-items: center; gap: 8px;
  }
  .post-wrap h2 .num {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #ffa657; background: rgba(255,166,87,0.1);
    border: 1px solid rgba(255,166,87,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h3 {
    font-size: 15px; font-weight: 600; color: #8b949e;
    margin: 20px 0 10px; font-family: 'JetBrains Mono', monospace;
  }
  .post-wrap h3::before { content: '// '; color: #484f58; }

  /* 흐름 시각화 */
  .flow-viz {
    background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; padding: 18px 22px; margin: 16px 0;
    font-family: 'JetBrains Mono', monospace;
  }
  .fv-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 14px; display: block; }
  .fv-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
  .fv-item { padding: 7px 14px; border-radius: 6px; font-size: 12px; font-weight: 600; }
  .fi-arr  { background: rgba(88,166,255,0.15);  color: #58a6ff;  border: 1px solid rgba(88,166,255,0.3); }
  .fi-map  { background: rgba(255,166,87,0.15);  color: #ffa657;  border: 1px solid rgba(255,166,87,0.3); }
  .fi-fil  { background: rgba(63,185,80,0.15);   color: #3fb950;  border: 1px solid rgba(63,185,80,0.3); }
  .fi-red  { background: rgba(188,140,255,0.15); color: #bc8cff;  border: 1px solid rgba(188,140,255,0.3); }
  .fi-res  { background: rgba(57,211,83,0.15);   color: #39d353;  border: 1px solid rgba(57,211,83,0.3); }
  .fv-arrow { color: #484f58; font-size: 16px; }
  .fv-label { font-size: 10px; color: #484f58; font-family: 'Pretendard', sans-serif; }

  /* 코드 블럭 */
  .code-block { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; margin: 12px 0; overflow: hidden; }
  .code-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; background: #161b22; border-bottom: 1px solid #30363d; }
  .code-lang { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; letter-spacing: 0.05em; }
  .code-dots { display: flex; gap: 5px; }
  .code-dots span { width: 10px; height: 10px; border-radius: 50%; }
  .code-dots span:nth-child(1) { background: #ff5f56; }
  .code-dots span:nth-child(2) { background: #ffbd2e; }
  .code-dots span:nth-child(3) { background: #27c93f; }
  .code-body { padding: 18px 22px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.85; overflow-x: auto; color: #e6edf3; }
  .kw  { color: #ff7b72; } .fn  { color: #d2a8ff; }
  .cm  { color: #484f58; font-style: italic; } .str { color: #a5d6ff; }
  .num { color: #79c0ff; }
  .map { color: #ffa657; font-weight: 700; }
  .fil { color: #3fb950; font-weight: 700; }
  .red { color: #bc8cff; font-weight: 700; }
  .good { color: #3fb950; } .bad { color: #ff7b72; }

  /* 비교 테이블 */
  .compare-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
  .compare-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 10px 14px; border: 1px solid #30363d; letter-spacing: 0.05em; text-align: left; }
  .compare-table td { padding: 10px 14px; border: 1px solid #30363d; color: #e6edf3; }
  .compare-table tr:nth-child(even) td { background: #161b22; }
  .td-map { color: #ffa657 !important; font-weight: 700; font-family: 'JetBrains Mono', monospace; }
  .td-fil { color: #3fb950 !important; font-weight: 700; font-family: 'JetBrains Mono', monospace; }
  .td-red { color: #bc8cff !important; font-weight: 700; font-family: 'JetBrains Mono', monospace; }

  /* tip / warn 박스 */
  .tip-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ffa657; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .tip-box strong { color: #e6edf3; }
  .tip-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .warn-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ff7b72; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .warn-box strong { color: #e6edf3; }
  .warn-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ff7b72; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }

  .ic { font-family: 'JetBrains Mono', monospace; font-size: 0.88em; padding: 1px 6px; background: #1c2128; border: 1px solid #30363d; border-radius: 4px; color: #ffa657; }
  .post-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 32px; padding-top: 20px; border-top: 1px solid #21262d; }
  .post-tag { padding: 3px 10px; background: rgba(255,166,87,0.08); border: 1px solid rgba(255,166,87,0.25); border-radius: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; }
  .post-tag::before { content: '#'; opacity: 0.6; }
  @media (max-width: 600px) { .toc-list { grid-template-columns: 1fr; } }
&lt;/style&gt;

&lt;div class=&quot;post-wrap&quot;&gt;
  &lt;div class=&quot;post-badge&quot;&gt;코딩테스트 · JavaScript&lt;/div&gt;

  &lt;p&gt;JS 코테에서 &lt;strong&gt;map / filter / reduce&lt;/strong&gt;는 거의 매번 나와요.&lt;br&gt;
  세 가지를 확실히 이해하고 체이닝까지 자유자재로 쓸 수 있으면&lt;br&gt;
  JS 코테 절반은 먹고 들어갑니다!&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
    &lt;span class=&quot;toc-title&quot;&gt;// TABLE OF CONTENTS&lt;/span&gt;
    &lt;div class=&quot;toc-list&quot;&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;01&lt;/span&gt; 세 함수 핵심 요약&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;02&lt;/span&gt; map — 변환&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;03&lt;/span&gt; filter — 필터링&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;04&lt;/span&gt; reduce — 축약&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;05&lt;/span&gt; 체이닝 — 연결해서 쓰기&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;06&lt;/span&gt; 실전 코테 패턴&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;07&lt;/span&gt; forEach vs map 차이&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;08&lt;/span&gt; 한눈에 비교&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 01. 핵심 요약 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;01&lt;/span&gt; 세 함수 핵심 요약&lt;/h2&gt;

  &lt;div class=&quot;flow-viz&quot;&gt;
    &lt;span class=&quot;fv-title&quot;&gt;// 세 함수의 역할&lt;/span&gt;
    &lt;div class=&quot;fv-row&quot;&gt;
      &lt;span class=&quot;fv-item fi-arr&quot;&gt;[1, 2, 3, 4, 5]&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-map&quot;&gt;map(×2)&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-res&quot;&gt;[2, 4, 6, 8, 10]&lt;/span&gt;
      &lt;span class=&quot;fv-label&quot;&gt;← 모든 요소 변환, 길이 동일&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;fv-row&quot;&gt;
      &lt;span class=&quot;fv-item fi-arr&quot;&gt;[1, 2, 3, 4, 5]&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-fil&quot;&gt;filter(짝수)&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-res&quot;&gt;[2, 4]&lt;/span&gt;
      &lt;span class=&quot;fv-label&quot;&gt;← 조건에 맞는 요소만, 길이 줄어듦&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;fv-row&quot;&gt;
      &lt;span class=&quot;fv-item fi-arr&quot;&gt;[1, 2, 3, 4, 5]&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-red&quot;&gt;reduce(합산)&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-res&quot;&gt;15&lt;/span&gt;
      &lt;span class=&quot;fv-label&quot;&gt;← 하나의 값으로 축약&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 02. map --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;02&lt;/span&gt; map — 모든 요소를 변환&lt;/h2&gt;

  &lt;p&gt;&lt;span class=&quot;ic&quot;&gt;map&lt;/span&gt;은 배열의 &lt;strong&gt;모든 요소를 변환해서 새 배열을 반환&lt;/strong&gt;해요.&lt;br&gt;
  원본 배열은 변경되지 않고, &lt;strong&gt;항상 같은 길이의 새 배열&lt;/strong&gt;이 나와요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;map 기본 &amp; 활용&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;// 기본 문법&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// array.map((현재값, 인덱스, 원본배열) =&gt; 변환값)&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; nums = [&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;5&lt;/span&gt;];&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 모든 요소에 ×2&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; doubled = nums.&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;(n =&gt; n * &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [2, 4, 6, 8, 10]&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 객체 배열에서 특정 필드 추출&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; users = [&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ name: &lt;span class=&quot;str&quot;&gt;'Alice'&lt;/span&gt;, age: &lt;span class=&quot;num&quot;&gt;25&lt;/span&gt; },&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ name: &lt;span class=&quot;str&quot;&gt;'Bob'&lt;/span&gt;,   age: &lt;span class=&quot;num&quot;&gt;30&lt;/span&gt; },&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ name: &lt;span class=&quot;str&quot;&gt;'Carol'&lt;/span&gt;, age: &lt;span class=&quot;num&quot;&gt;22&lt;/span&gt; },&lt;br&gt;
];&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; names = users.&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;(user =&gt; user.name);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// ['Alice', 'Bob', 'Carol']&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; ageNext = users.&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;(user =&gt; ({&lt;br&gt;
&amp;nbsp;&amp;nbsp;...user,&lt;br&gt;
&amp;nbsp;&amp;nbsp;age: user.age + &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;cm&quot;&gt;// 나이 +1&lt;/span&gt;&lt;br&gt;
}));&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [{name:'Alice', age:26}, ...]&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 인덱스 활용&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; indexed = nums.&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;((n, i) =&gt; &lt;span class=&quot;str&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;num&quot;&gt;${i}&lt;/span&gt;&lt;span class=&quot;str&quot;&gt;번째: &lt;/span&gt;&lt;span class=&quot;num&quot;&gt;${n}&lt;/span&gt;&lt;span class=&quot;str&quot;&gt;`&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// ['0번째: 1', '1번째: 2', ...]&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  map의 핵심&lt;/span&gt;
    &lt;strong&gt;입력 배열 길이 = 출력 배열 길이&lt;/strong&gt; 항상 동일!&lt;br&gt;
    원본 배열을 변경하지 않고 &lt;strong&gt;새 배열을 반환&lt;/strong&gt;해요.&lt;br&gt;
    반환값이 없으면 &lt;span class=&quot;ic&quot;&gt;undefined&lt;/span&gt;로 채워지니 주의.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 03. filter --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;03&lt;/span&gt; filter — 조건에 맞는 요소만&lt;/h2&gt;

  &lt;p&gt;&lt;span class=&quot;ic&quot;&gt;filter&lt;/span&gt;는 &lt;strong&gt;조건(true/false)에 맞는 요소만 골라서 새 배열을 반환&lt;/strong&gt;해요.&lt;br&gt;
  원본 배열보다 &lt;strong&gt;길이가 같거나 짧아져요.&lt;/strong&gt;&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;filter 기본 &amp; 활용&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;// 기본 문법&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// array.filter((현재값, 인덱스, 원본배열) =&gt; true/false)&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; nums = [&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;5&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;6&lt;/span&gt;];&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 짝수만&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; evens = nums.&lt;span class=&quot;fil&quot;&gt;filter&lt;/span&gt;(n =&gt; n % &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt; === &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [2, 4, 6]&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 3 이상만&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; over3 = nums.&lt;span class=&quot;fil&quot;&gt;filter&lt;/span&gt;(n =&gt; n &gt;= &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [3, 4, 5, 6]&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 객체 배열 필터링&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; users = [&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ name: &lt;span class=&quot;str&quot;&gt;'Alice'&lt;/span&gt;, age: &lt;span class=&quot;num&quot;&gt;25&lt;/span&gt;, active: &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;  },&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ name: &lt;span class=&quot;str&quot;&gt;'Bob'&lt;/span&gt;,   age: &lt;span class=&quot;num&quot;&gt;17&lt;/span&gt;, active: &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt; },&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ name: &lt;span class=&quot;str&quot;&gt;'Carol'&lt;/span&gt;, age: &lt;span class=&quot;num&quot;&gt;30&lt;/span&gt;, active: &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;  },&lt;br&gt;
];&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 성인 + 활성 유저만&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; activeAdults = users.&lt;span class=&quot;fil&quot;&gt;filter&lt;/span&gt;(u =&gt; u.age &gt;= &lt;span class=&quot;num&quot;&gt;18&lt;/span&gt; &amp;&amp; u.active);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [{name:'Alice',...}, {name:'Carol',...}]&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 중복 제거 (indexOf 활용)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; arr = [&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;4&lt;/span&gt;];&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; unique = arr.&lt;span class=&quot;fil&quot;&gt;filter&lt;/span&gt;((v, i, self) =&gt; self.indexOf(v) === i);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [1, 2, 3, 4]&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 04. reduce --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;04&lt;/span&gt; reduce — 하나의 값으로 축약&lt;/h2&gt;

  &lt;p&gt;&lt;span class=&quot;ic&quot;&gt;reduce&lt;/span&gt;는 배열을 &lt;strong&gt;하나의 값(숫자, 문자열, 객체, 배열 등)으로 축약&lt;/strong&gt;해요.&lt;br&gt;
  세 함수 중 가장 강력하지만 처음엔 헷갈리는 녀석이에요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;reduce 기본 — 동작 원리&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;// 기본 문법&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// array.reduce((누적값, 현재값, 인덱스, 원본배열) =&gt; 새누적값, 초기값)&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; nums = [&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;5&lt;/span&gt;];&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 합계&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; sum = nums.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((acc, cur) =&gt; acc + cur, &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 0+1=1 → 1+2=3 → 3+3=6 → 6+4=10 → 10+5=15&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 결과: 15&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 최댓값&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; max = nums.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((acc, cur) =&gt; acc &gt; cur ? acc : cur);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 결과: 5&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 곱 (초기값 1)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; product = nums.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((acc, cur) =&gt; acc * cur, &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 결과: 120&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;reduce 심화 — 다양한 활용&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;// 객체로 그룹핑 (가장 많이 쓰는 패턴!)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; fruits = [&lt;span class=&quot;str&quot;&gt;'apple'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'banana'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'apple'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'cherry'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'banana'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'apple'&lt;/span&gt;];&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; count = fruits.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((acc, cur) =&gt; {&lt;br&gt;
&amp;nbsp;&amp;nbsp;acc[cur] = (acc[cur] || &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;) + &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; acc;&lt;br&gt;
}, {});&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// { apple: 3, banana: 2, cherry: 1 }&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 2차원 배열 → 1차원 (flat)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; nested = [[&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;], [&lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;4&lt;/span&gt;], [&lt;span class=&quot;num&quot;&gt;5&lt;/span&gt;]];&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; flat = nested.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((acc, cur) =&gt; acc.concat(cur), []);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// [1, 2, 3, 4, 5]&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 객체 배열 → 특정 키로 객체 변환&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; users = [&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ id: &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, name: &lt;span class=&quot;str&quot;&gt;'Alice'&lt;/span&gt; },&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ id: &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, name: &lt;span class=&quot;str&quot;&gt;'Bob'&lt;/span&gt;   },&lt;br&gt;
];&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; userMap = users.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((acc, user) =&gt; {&lt;br&gt;
&amp;nbsp;&amp;nbsp;acc[user.id] = user;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; acc;&lt;br&gt;
}, {});&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// { 1: {id:1, name:'Alice'}, 2: {id:2, name:'Bob'} }&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 파이프라인 (함수 순차 실행)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; pipeline = [x =&gt; x + &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, x =&gt; x * &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, x =&gt; x - &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;];&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; result = pipeline.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((acc, fn) =&gt; &lt;span class=&quot;fn&quot;&gt;fn&lt;/span&gt;(acc), &lt;span class=&quot;num&quot;&gt;5&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 5 → +1=6 → ×2=12 → -3=9&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ 초기값 꼭 넣으세요!&lt;/span&gt;
    초기값 없이 빈 배열에 reduce 하면 &lt;strong&gt;TypeError 발생!&lt;/strong&gt;&lt;br&gt;
    항상 &lt;span class=&quot;ic&quot;&gt;.reduce((acc, cur) =&gt; ..., 초기값)&lt;/span&gt; 형태로 쓰는 게 안전해요.&lt;br&gt;
    초기값이 숫자면 &lt;span class=&quot;ic&quot;&gt;0&lt;/span&gt;, 배열이면 &lt;span class=&quot;ic&quot;&gt;[]&lt;/span&gt;, 객체면 &lt;span class=&quot;ic&quot;&gt;{}&lt;/span&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 05. 체이닝 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;05&lt;/span&gt; 체이닝 — 연결해서 쓰기&lt;/h2&gt;

  &lt;p&gt;세 함수를 &lt;strong&gt;체이닝(chaining)&lt;/strong&gt;으로 연결하면 복잡한 작업도 한 줄에 처리할 수 있어요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;체이닝 패턴&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; students = [&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ name: &lt;span class=&quot;str&quot;&gt;'Alice'&lt;/span&gt;, score: &lt;span class=&quot;num&quot;&gt;85&lt;/span&gt;, pass: &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;  },&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ name: &lt;span class=&quot;str&quot;&gt;'Bob'&lt;/span&gt;,   score: &lt;span class=&quot;num&quot;&gt;42&lt;/span&gt;, pass: &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt; },&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ name: &lt;span class=&quot;str&quot;&gt;'Carol'&lt;/span&gt;, score: &lt;span class=&quot;num&quot;&gt;91&lt;/span&gt;, pass: &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;  },&lt;br&gt;
&amp;nbsp;&amp;nbsp;{ name: &lt;span class=&quot;str&quot;&gt;'Dave'&lt;/span&gt;,  score: &lt;span class=&quot;num&quot;&gt;73&lt;/span&gt;, pass: &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;  },&lt;br&gt;
];&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 합격자 이름만 추출 (filter → map)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; passNames = students&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;fil&quot;&gt;filter&lt;/span&gt;(s =&gt; s.pass)           &lt;span class=&quot;cm&quot;&gt;// 합격자만&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;(s =&gt; s.name);             &lt;span class=&quot;cm&quot;&gt;// 이름 추출&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// ['Alice', 'Carol', 'Dave']&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 합격자 점수 합계 (filter → map → reduce)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; passTotal = students&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;fil&quot;&gt;filter&lt;/span&gt;(s =&gt; s.pass)           &lt;span class=&quot;cm&quot;&gt;// 합격자만&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;(s =&gt; s.score)             &lt;span class=&quot;cm&quot;&gt;// 점수만&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((acc, cur) =&gt; acc + cur, &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;); &lt;span class=&quot;cm&quot;&gt;// 합산&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 85 + 91 + 73 = 249&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 합격자 평균 점수&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; passScores = students.&lt;span class=&quot;fil&quot;&gt;filter&lt;/span&gt;(s =&gt; s.pass).&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;(s =&gt; s.score);&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; avg = passScores.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((a, b) =&gt; a + b, &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;) / passScores.length;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 83&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 06. 실전 코테 패턴 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;06&lt;/span&gt; 실전 코테 패턴&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;코테 자주 나오는 패턴&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 패턴 1: 문자열 뒤집기 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;str&quot;&gt;'hello'&lt;/span&gt;.split(&lt;span class=&quot;str&quot;&gt;''&lt;/span&gt;).&lt;span class=&quot;fn&quot;&gt;reverse&lt;/span&gt;().&lt;span class=&quot;fn&quot;&gt;join&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;''&lt;/span&gt;); &lt;span class=&quot;cm&quot;&gt;// 'olleh'&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 패턴 2: 특정 조건 개수 세기 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; arr = [&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;5&lt;/span&gt;];&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; evenCnt = arr.&lt;span class=&quot;fil&quot;&gt;filter&lt;/span&gt;(n =&gt; n % &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt; === &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;).length; &lt;span class=&quot;cm&quot;&gt;// 2&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 패턴 3: 배열 합계 / 평균 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; sum = arr.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((a, b) =&gt; a + b, &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;);       &lt;span class=&quot;cm&quot;&gt;// 15&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; avg = sum / arr.length;                           &lt;span class=&quot;cm&quot;&gt;// 3&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 패턴 4: 최댓값 / 최솟값 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; max = arr.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((a, b) =&gt; Math.&lt;span class=&quot;fn&quot;&gt;max&lt;/span&gt;(a, b));  &lt;span class=&quot;cm&quot;&gt;// 5&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; min = arr.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((a, b) =&gt; Math.&lt;span class=&quot;fn&quot;&gt;min&lt;/span&gt;(a, b));  &lt;span class=&quot;cm&quot;&gt;// 1&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// 또는&lt;/span&gt;&lt;br&gt;
Math.&lt;span class=&quot;fn&quot;&gt;max&lt;/span&gt;(...arr); &lt;span class=&quot;cm&quot;&gt;// 5 (스프레드 연산자)&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 패턴 5: 중복 제거 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; dup = [&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;];&lt;br&gt;
[...&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;Set&lt;/span&gt;(dup)];                                     &lt;span class=&quot;cm&quot;&gt;// [1, 2, 3]&lt;/span&gt;&lt;br&gt;
dup.&lt;span class=&quot;fil&quot;&gt;filter&lt;/span&gt;((v, i, s) =&gt; s.indexOf(v) === i);          &lt;span class=&quot;cm&quot;&gt;// [1, 2, 3]&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 패턴 6: 빈도수 계산 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; words = [&lt;span class=&quot;str&quot;&gt;'a'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'b'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'a'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'c'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'b'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'a'&lt;/span&gt;];&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; freq = words.&lt;span class=&quot;red&quot;&gt;reduce&lt;/span&gt;((acc, w) =&gt; {&lt;br&gt;
&amp;nbsp;&amp;nbsp;acc[w] = (acc[w] || &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;) + &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; acc;&lt;br&gt;
}, {});&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// { a: 3, b: 2, c: 1 }&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 패턴 7: 객체 배열 정렬 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; sorted = students&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;fil&quot;&gt;filter&lt;/span&gt;(s =&gt; s.pass)&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;fn&quot;&gt;sort&lt;/span&gt;((a, b) =&gt; b.score - a.score)  &lt;span class=&quot;cm&quot;&gt;// 점수 내림차순&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;.&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;(s =&gt; s.name);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// ['Carol', 'Alice', 'Dave']&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 07. forEach vs map --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;07&lt;/span&gt; forEach vs map — 헷갈리는 차이&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;forEach vs map 차이&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;// forEach — 반환값 없음 (undefined)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; result1 = [&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;].&lt;span class=&quot;fn&quot;&gt;forEach&lt;/span&gt;(n =&gt; n * &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// result1 = undefined ← 새 배열 안 만들어짐!&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// map — 새 배열 반환&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; result2 = [&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;].&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;(n =&gt; n * &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// result2 = [2, 4, 6] ← 새 배열!&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// forEach 는 부수효과(side effect)가 목적일 때&lt;/span&gt;&lt;br&gt;
[&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;].&lt;span class=&quot;fn&quot;&gt;forEach&lt;/span&gt;(n =&gt; console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(n));  &lt;span class=&quot;good&quot;&gt;// ✅ 출력용&lt;/span&gt;&lt;br&gt;
[&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;].&lt;span class=&quot;fn&quot;&gt;forEach&lt;/span&gt;(n =&gt; arr.&lt;span class=&quot;fn&quot;&gt;push&lt;/span&gt;(n * &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;));  &lt;span class=&quot;good&quot;&gt;// ✅ 외부 배열 채울 때&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;// map 은 변환 결과를 사용할 때&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; doubled = [&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;].&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;(n =&gt; n * &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;);  &lt;span class=&quot;good&quot;&gt;// ✅&lt;/span&gt;&lt;br&gt;
[&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;].&lt;span class=&quot;map&quot;&gt;map&lt;/span&gt;(n =&gt; console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(n));          &lt;span class=&quot;bad&quot;&gt;// ❌ 반환값 무시, forEach 써야&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 08. 한눈에 비교 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;08&lt;/span&gt; 한눈에 비교&lt;/h2&gt;

  &lt;table class=&quot;compare-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;구분&lt;/th&gt;&lt;th&gt;map&lt;/th&gt;&lt;th&gt;filter&lt;/th&gt;&lt;th&gt;reduce&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;목적&lt;/td&gt;
      &lt;td class=&quot;td-map&quot;&gt;변환&lt;/td&gt;
      &lt;td class=&quot;td-fil&quot;&gt;필터링&lt;/td&gt;
      &lt;td class=&quot;td-red&quot;&gt;축약&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;반환값&lt;/td&gt;
      &lt;td&gt;새 배열&lt;/td&gt;
      &lt;td&gt;새 배열&lt;/td&gt;
      &lt;td&gt;단일 값 (뭐든 가능)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;결과 길이&lt;/td&gt;
      &lt;td class=&quot;td-map&quot;&gt;원본과 동일&lt;/td&gt;
      &lt;td class=&quot;td-fil&quot;&gt;같거나 짧음&lt;/td&gt;
      &lt;td class=&quot;td-red&quot;&gt;하나&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;콜백 반환&lt;/td&gt;
      &lt;td&gt;변환된 값&lt;/td&gt;
      &lt;td&gt;true / false&lt;/td&gt;
      &lt;td&gt;새 누적값&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;초기값&lt;/td&gt;
      &lt;td&gt;없음&lt;/td&gt;
      &lt;td&gt;없음&lt;/td&gt;
      &lt;td class=&quot;td-red&quot;&gt;필수 권장!&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;주요 사용&lt;/td&gt;
      &lt;td&gt;데이터 변환&lt;/td&gt;
      &lt;td&gt;조건 필터&lt;/td&gt;
      &lt;td&gt;합계, 그룹핑&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;✅ CONCLUSION&lt;/span&gt;
    &lt;strong&gt;map&lt;/strong&gt; → 모든 요소 변환, 길이 동일&lt;br&gt;
    &lt;strong&gt;filter&lt;/strong&gt; → 조건 맞는 것만, 길이 줄어듦&lt;br&gt;
    &lt;strong&gt;reduce&lt;/strong&gt; → 하나의 값으로 축약, 가장 강력&lt;br&gt;&lt;br&gt;
    코테에서 &lt;strong&gt;filter → map → reduce 체이닝&lt;/strong&gt; 패턴을 자유자재로 쓸 수 있으면&lt;br&gt;
    JS 배열 관련 문제는 거의 다 해결할 수 있어요!
  &lt;/div&gt;

  &lt;div class=&quot;post-tags&quot;&gt;
    &lt;span class=&quot;post-tag&quot;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;map&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;filter&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;reduce&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;배열메서드&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;코딩테스트&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;체이닝&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;</description>
      <category>  코딩테스트/JavaScript</category>
      <category>Filter</category>
      <category>Map</category>
      <category>reduce</category>
      <author>woohaha</author>
      <guid isPermaLink="true">https://story2248.tistory.com/22</guid>
      <comments>https://story2248.tistory.com/22#entry22comment</comments>
      <pubDate>Fri, 29 May 2026 18:15:58 +0900</pubDate>
    </item>
    <item>
      <title>[개발 도구 및 환경] 전자정부프레임워크</title>
      <link>https://story2248.tistory.com/21</link>
      <description>&lt;style&gt;
  .post-wrap {
    background: #0d1117; color: #e6edf3;
    font-family: 'Pretendard', -apple-system, sans-serif;
    padding: 0; line-height: 1.8;
  }
  .post-wrap * { box-sizing: border-box; }
  .post-wrap p { margin: 0 0 16px; color: #8b949e; font-size: 15px; }
  .post-wrap p strong { color: #e6edf3; }
  .post-divider { border: none; border-top: 1px solid #21262d; margin: 32px 0; }

  .post-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px;
    background: rgba(63,185,80,0.1); border: 1px solid rgba(63,185,80,0.3);
    border-radius: 20px; font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: #3fb950; margin-bottom: 20px;
  }
  .post-badge::before { content: '⚙️'; }

  /* 목차 */
  .toc { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 24px 0; }
  .toc-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .toc-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .toc-item { font-size: 13px; color: #8b949e; padding: 3px 0; display: flex; align-items: center; gap: 8px; }
  .toc-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; }

  .post-wrap h2 {
    font-size: 18px; font-weight: 600; color: #e6edf3;
    margin: 32px 0 16px; padding-bottom: 8px;
    border-bottom: 1px solid #21262d;
    display: flex; align-items: center; gap: 8px;
  }
  .post-wrap h2 .num {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #3fb950; background: rgba(63,185,80,0.1);
    border: 1px solid rgba(63,185,80,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h3 {
    font-size: 15px; font-weight: 600; color: #8b949e;
    margin: 20px 0 10px; font-family: 'JetBrains Mono', monospace;
  }
  .post-wrap h3::before { content: '// '; color: #484f58; }

  /* 전체 흐름 시각화 */
  .flow-full {
    background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; padding: 24px; margin: 20px 0;
    font-family: 'JetBrains Mono', monospace;
  }
  .ff-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 20px; display: block; }
  .ff-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
  .ff-item {
    padding: 8px 16px; border-radius: 6px; font-size: 12px; font-weight: 700;
    text-align: center; flex-shrink: 0;
  }
  .fi-client  { background: rgba(88,166,255,0.15);  color: #58a6ff;  border: 1px solid rgba(88,166,255,0.3); }
  .fi-ctrl    { background: rgba(63,185,80,0.15);   color: #3fb950;  border: 1px solid rgba(63,185,80,0.3); }
  .fi-svc     { background: rgba(188,140,255,0.15); color: #bc8cff;  border: 1px solid rgba(188,140,255,0.3); }
  .fi-mapper  { background: rgba(255,166,87,0.15);  color: #ffa657;  border: 1px solid rgba(255,166,87,0.3); }
  .fi-xml     { background: rgba(255,123,114,0.15); color: #ff7b72;  border: 1px solid rgba(255,123,114,0.3); }
  .fi-db      { background: rgba(57,211,83,0.15);   color: #39d353;  border: 1px solid rgba(57,211,83,0.3); }
  .fi-vo      { background: rgba(121,192,255,0.15); color: #79c0ff;  border: 1px solid rgba(121,192,255,0.3); }
  .ff-arrow   { color: #30363d; font-size: 18px; font-family: 'JetBrains Mono', monospace; }
  .ff-label   { font-size: 10px; color: #484f58; font-family: 'Pretendard', sans-serif; }

  /* 레이어 카드 */
  .layer-card {
    border-radius: 8px; padding: 18px 20px; margin: 16px 0;
    border: 1px solid #30363d;
  }
  .lc-header { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
  .lc-badge {
    font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 700;
    padding: 3px 10px; border-radius: 5px; flex-shrink: 0;
  }
  .lc-title { font-size: 15px; font-weight: 600; color: #e6edf3; }
  .lc-desc  { font-size: 13px; color: #8b949e; line-height: 1.7; margin-bottom: 12px; }
  .lc-desc strong { color: #e6edf3; }

  /* 코드 블럭 */
  .code-block { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; margin: 12px 0; overflow: hidden; }
  .code-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; background: #161b22; border-bottom: 1px solid #30363d; }
  .code-lang { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; letter-spacing: 0.05em; }
  .code-dots { display: flex; gap: 5px; }
  .code-dots span { width: 10px; height: 10px; border-radius: 50%; }
  .code-dots span:nth-child(1) { background: #ff5f56; }
  .code-dots span:nth-child(2) { background: #ffbd2e; }
  .code-dots span:nth-child(3) { background: #27c93f; }
  .code-body { padding: 18px 22px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.85; overflow-x: auto; color: #e6edf3; }
  .kw  { color: #ff7b72; }
  .fn  { color: #d2a8ff; }
  .cm  { color: #484f58; font-style: italic; }
  .str { color: #a5d6ff; }
  .num { color: #79c0ff; }
  .ann { color: #3fb950; font-weight: 600; } /* 어노테이션 */
  .cls { color: #ffa657; }
  .tag { color: #7ee787; }
  .attr{ color: #79c0ff; }

  /* tip / warn / info 박스 */
  .tip-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #3fb950; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .tip-box strong { color: #e6edf3; }
  .tip-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .warn-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ffa657; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .warn-box strong { color: #e6edf3; }
  .warn-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .info-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #58a6ff; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .info-box strong { color: #e6edf3; }
  .info-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }

  /* 비교 테이블 */
  .compare-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
  .compare-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 10px 14px; border: 1px solid #30363d; letter-spacing: 0.05em; text-align: left; }
  .compare-table td { padding: 10px 14px; border: 1px solid #30363d; color: #e6edf3; }
  .compare-table tr:nth-child(even) td { background: #161b22; }

  .ic { font-family: 'JetBrains Mono', monospace; font-size: 0.88em; padding: 1px 6px; background: #1c2128; border: 1px solid #30363d; border-radius: 4px; color: #ffa657; }
  .post-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 32px; padding-top: 20px; border-top: 1px solid #21262d; }
  .post-tag { padding: 3px 10px; background: rgba(63,185,80,0.08); border: 1px solid rgba(63,185,80,0.25); border-radius: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; }
  .post-tag::before { content: '#'; opacity: 0.6; }
  @media (max-width: 600px) { .toc-list { grid-template-columns: 1fr; } }
&lt;/style&gt;

&lt;div class=&quot;post-wrap&quot;&gt;
  &lt;div class=&quot;post-badge&quot;&gt;개발 도구 · 환경&lt;/div&gt;

  &lt;p&gt;전자정부프레임워크(eGovFrame) 기반 프로젝트에서&lt;br&gt;
  &lt;strong&gt;요청이 들어오면 어떤 순서로 처리되는지&lt;/strong&gt; 한 번에 정리해봤어요.&lt;br&gt;
  Controller → Service → Mapper → XML → DB → 다시 돌아오는 전체 흐름이에요.&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
    &lt;span class=&quot;toc-title&quot;&gt;// TABLE OF CONTENTS&lt;/span&gt;
    &lt;div class=&quot;toc-list&quot;&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;01&lt;/span&gt; 전체 요청/응답 흐름&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;02&lt;/span&gt; VO / DTO 란?&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;03&lt;/span&gt; Controller&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;04&lt;/span&gt; Service / ServiceImpl&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;05&lt;/span&gt; Mapper (DAO)&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;06&lt;/span&gt; MyBatis XML Mapper&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;07&lt;/span&gt; 전체 예제 코드&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;08&lt;/span&gt; 핵심 어노테이션 정리&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 01. 전체 흐름 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;01&lt;/span&gt; 전체 요청 / 응답 흐름&lt;/h2&gt;

  &lt;p&gt;사용자가 버튼을 누르면 서버에서 이런 순서로 처리돼요.&lt;/p&gt;

  &lt;div class=&quot;flow-full&quot;&gt;
    &lt;span class=&quot;ff-title&quot;&gt;// 요청 흐름 (Request)&lt;/span&gt;
    &lt;div class=&quot;ff-row&quot;&gt;
      &lt;span class=&quot;ff-item fi-client&quot;&gt;브라우저 요청&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-ctrl&quot;&gt;Controller&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-svc&quot;&gt;Service&lt;br&gt;&lt;small style=&quot;font-size:10px;&quot;&gt;(Interface)&lt;/small&gt;&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-svc&quot;&gt;ServiceImpl&lt;br&gt;&lt;small style=&quot;font-size:10px;&quot;&gt;(구현체)&lt;/small&gt;&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-mapper&quot;&gt;Mapper&lt;br&gt;&lt;small style=&quot;font-size:10px;&quot;&gt;(Interface)&lt;/small&gt;&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-xml&quot;&gt;XML&lt;br&gt;&lt;small style=&quot;font-size:10px;&quot;&gt;(SQL)&lt;/small&gt;&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-db&quot;&gt;DB&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;ff-row&quot; style=&quot;margin-top:12px;&quot;&gt;
      &lt;span class=&quot;ff-item fi-client&quot;&gt;브라우저 응답&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;←&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-ctrl&quot;&gt;Controller&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;←&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-svc&quot;&gt;ServiceImpl&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;←&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-mapper&quot;&gt;Mapper&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;←&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-xml&quot;&gt;XML&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;←&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-db&quot;&gt;DB&lt;/span&gt;
    &lt;/div&gt;
    &lt;div style=&quot;margin-top:14px; padding-top:12px; border-top:1px solid #21262d; font-size:11px; color:#484f58; font-family:'Pretendard',sans-serif;&quot;&gt;
      ※ 데이터는 VO(Value Object)에 담겨 각 레이어 사이를 이동해요.
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;info-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  레이어드 아키텍처&lt;/span&gt;
    이 구조를 &lt;strong&gt;레이어드 아키텍처(Layered Architecture)&lt;/strong&gt;라고 해요.&lt;br&gt;
    각 레이어는 바로 아래 레이어에만 의존하고, 역할이 명확히 분리돼 있어요.&lt;br&gt;
    &lt;strong&gt;Controller(화면 처리) → Service(비즈니스 로직) → Mapper(DB 처리)&lt;/strong&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 02. VO / DTO --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;02&lt;/span&gt; VO / DTO — 데이터를 담는 그릇&lt;/h2&gt;

  &lt;p&gt;레이어 간에 데이터를 주고받을 때 사용하는 &lt;strong&gt;데이터 전달 객체&lt;/strong&gt;예요.&lt;/p&gt;

  &lt;div class=&quot;layer-card&quot; style=&quot;background:#161b22;&quot;&gt;
    &lt;div class=&quot;lc-header&quot;&gt;
      &lt;span class=&quot;lc-badge&quot; style=&quot;background:rgba(121,192,255,0.15);color:#79c0ff;border:1px solid rgba(121,192,255,0.3);&quot;&gt;VO / DTO&lt;/span&gt;
      &lt;span class=&quot;lc-title&quot;&gt;Value Object / Data Transfer Object&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;lc-desc&quot;&gt;
      &lt;strong&gt;VO (Value Object)&lt;/strong&gt; — DB 테이블 컬럼과 매핑되는 객체. getter/setter 보유.&lt;br&gt;
      &lt;strong&gt;DTO (Data Transfer Object)&lt;/strong&gt; — 레이어 간 데이터 전달용. VO와 혼용하는 경우 많음.&lt;br&gt;
      전자정부프레임워크에서는 보통 &lt;strong&gt;VO 하나로 통일&lt;/strong&gt;해서 씀.
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;EmpVO.java — VO 예시&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;kw&quot;&gt;public class&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; {&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// DB 컬럼과 매핑되는 필드&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; String empId;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// EMP_ID&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; String empNm;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// EMP_NM&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; String deptCd;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// DEPT_CD&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;int&lt;/span&gt;    salary;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// SALARY&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 검색 조건용 필드 (DB에 없어도 OK)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; String searchNm;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; String searchDept;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// getter / setter&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; String &lt;span class=&quot;fn&quot;&gt;getEmpId&lt;/span&gt;() { &lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; empId; }&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;public void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;setEmpId&lt;/span&gt;(String empId) { &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;.empId = empId; }&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// ... 나머지 getter/setter 생략&lt;/span&gt;&lt;br&gt;
}
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  camelCase ↔ snake_case 자동 변환&lt;/span&gt;
    MyBatis는 DB 컬럼명 &lt;span class=&quot;ic&quot;&gt;EMP_NM&lt;/span&gt;을 VO 필드 &lt;span class=&quot;ic&quot;&gt;empNm&lt;/span&gt;으로 &lt;strong&gt;자동 매핑&lt;/strong&gt;해줘요.&lt;br&gt;
    설정에서 &lt;span class=&quot;ic&quot;&gt;mapUnderscoreToCamelCase=true&lt;/span&gt;로 설정하면 돼요.&lt;br&gt;
    이 설정 없으면 XML에서 &lt;span class=&quot;ic&quot;&gt;resultMap&lt;/span&gt;으로 수동 매핑해야 해요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 03. Controller --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;03&lt;/span&gt; Controller — 요청의 시작점&lt;/h2&gt;

  &lt;div class=&quot;layer-card&quot; style=&quot;background:#161b22;&quot;&gt;
    &lt;div class=&quot;lc-header&quot;&gt;
      &lt;span class=&quot;lc-badge&quot; style=&quot;background:rgba(63,185,80,0.15);color:#3fb950;border:1px solid rgba(63,185,80,0.3);&quot;&gt;Controller&lt;/span&gt;
      &lt;span class=&quot;lc-title&quot;&gt;요청 수신 → Service 호출 → 결과 반환&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;lc-desc&quot;&gt;
      &lt;strong&gt;역할:&lt;/strong&gt; HTTP 요청을 받아 Service를 호출하고 결과를 View(JSP)나 JSON으로 반환.&lt;br&gt;
      비즈니스 로직은 절대 Controller에 넣으면 안 돼요 → Service에 위임!
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;EmpController.java&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;ann&quot;&gt;@Controller&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;ann&quot;&gt;@RequestMapping&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;/emp&quot;&lt;/span&gt;)&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;public class&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpController&lt;/span&gt; {&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;ann&quot;&gt;@Autowired&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpService&lt;/span&gt; empService; &lt;span class=&quot;cm&quot;&gt;// Service 주입&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 사원 목록 조회 (JSP 반환)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;ann&quot;&gt;@RequestMapping&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;/list.do&quot;&lt;/span&gt;)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; String &lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO, &lt;span class=&quot;cls&quot;&gt;ModelMap&lt;/span&gt; model) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt;&amp;gt; list = empService.&lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model.&lt;span class=&quot;fn&quot;&gt;addAttribute&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;empList&quot;&lt;/span&gt;, list);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;str&quot;&gt;&quot;emp/empList&quot;&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;// JSP 경로&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;}&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 사원 저장 (JSON 반환)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;ann&quot;&gt;@RequestMapping&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;/insert.do&quot;&lt;/span&gt;)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;ann&quot;&gt;@ResponseBody&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;Map&lt;/span&gt;&amp;lt;String, Object&amp;gt; &lt;span class=&quot;fn&quot;&gt;insertEmp&lt;/span&gt;(&lt;span class=&quot;ann&quot;&gt;@RequestBody&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;Map&lt;/span&gt;&amp;lt;String, Object&amp;gt; result = &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;HashMap&lt;/span&gt;&amp;lt;&amp;gt;();&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;try&lt;/span&gt; {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;empService.&lt;span class=&quot;fn&quot;&gt;insertEmp&lt;/span&gt;(empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.&lt;span class=&quot;fn&quot;&gt;put&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;result&quot;&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;&quot;success&quot;&lt;/span&gt;);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;span class=&quot;kw&quot;&gt;catch&lt;/span&gt; (&lt;span class=&quot;cls&quot;&gt;Exception&lt;/span&gt; e) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.&lt;span class=&quot;fn&quot;&gt;put&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;result&quot;&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;&quot;fail&quot;&lt;/span&gt;);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; result;&lt;br&gt;
&amp;nbsp;&amp;nbsp;}&lt;br&gt;
}
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 04. Service / ServiceImpl --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;04&lt;/span&gt; Service / ServiceImpl — 비즈니스 로직&lt;/h2&gt;

  &lt;div class=&quot;layer-card&quot; style=&quot;background:#161b22;&quot;&gt;
    &lt;div class=&quot;lc-header&quot;&gt;
      &lt;span class=&quot;lc-badge&quot; style=&quot;background:rgba(188,140,255,0.15);color:#bc8cff;border:1px solid rgba(188,140,255,0.3);&quot;&gt;Service&lt;/span&gt;
      &lt;span class=&quot;lc-title&quot;&gt;Interface + 구현체로 분리하는 이유&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;lc-desc&quot;&gt;
      &lt;strong&gt;Service (Interface)&lt;/strong&gt; — 무엇을 할 수 있는지 선언만 해요.&lt;br&gt;
      &lt;strong&gt;ServiceImpl (구현체)&lt;/strong&gt; — 실제 비즈니스 로직을 구현해요.&lt;br&gt;
      인터페이스로 분리하면 &lt;strong&gt;구현체 교체, 테스트, 트랜잭션 AOP 적용&lt;/strong&gt;이 쉬워요.
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;EmpService.java (Interface)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;kw&quot;&gt;public interface&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpService&lt;/span&gt; {&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 무엇을 할지만 선언 — 구현은 ServiceImpl에서&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt;&amp;gt; &lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;selectEmpDetail&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;insertEmp&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;updateEmp&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;deleteEmp&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
}
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;EmpServiceImpl.java (구현체)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;ann&quot;&gt;@Service&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;empService&quot;&lt;/span&gt;)&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;public class&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpServiceImpl&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpService&lt;/span&gt; {&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;ann&quot;&gt;@Autowired&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpMapper&lt;/span&gt; empMapper; &lt;span class=&quot;cm&quot;&gt;// Mapper 주입&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;ann&quot;&gt;@Override&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt;&amp;gt; &lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; empMapper.&lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;}&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;ann&quot;&gt;@Override&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;ann&quot;&gt;@Transactional&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;// 트랜잭션 적용!&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;public void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;insertEmp&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 비즈니스 로직 — 여러 작업을 하나의 트랜잭션으로&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;empMapper.&lt;span class=&quot;fn&quot;&gt;insertEmp&lt;/span&gt;(empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;empMapper.&lt;span class=&quot;fn&quot;&gt;insertEmpHist&lt;/span&gt;(empVO); &lt;span class=&quot;cm&quot;&gt;// 이력도 함께 저장&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;}&lt;br&gt;
}
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  @Transactional 위치&lt;/span&gt;
    &lt;span class=&quot;ic&quot;&gt;@Transactional&lt;/span&gt;은 &lt;strong&gt;ServiceImpl 메서드&lt;/strong&gt;에 붙여요.&lt;br&gt;
    붙이면 해당 메서드 안의 모든 DB 작업이 하나의 트랜잭션으로 묶여요.&lt;br&gt;
    예외 발생 시 자동 ROLLBACK, 정상 종료 시 자동 COMMIT!
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 05. Mapper --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;05&lt;/span&gt; Mapper (DAO) — DB 연결 인터페이스&lt;/h2&gt;

  &lt;div class=&quot;layer-card&quot; style=&quot;background:#161b22;&quot;&gt;
    &lt;div class=&quot;lc-header&quot;&gt;
      &lt;span class=&quot;lc-badge&quot; style=&quot;background:rgba(255,166,87,0.15);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;Mapper&lt;/span&gt;
      &lt;span class=&quot;lc-title&quot;&gt;SQL 실행을 담당하는 인터페이스&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;lc-desc&quot;&gt;
      MyBatis에서 &lt;strong&gt;Mapper Interface&lt;/strong&gt;는 선언만 하면 돼요.&lt;br&gt;
      실제 SQL은 XML 파일에 작성하고, MyBatis가 자동으로 연결해줘요.&lt;br&gt;
      메서드명 = XML의 &lt;strong&gt;id값&lt;/strong&gt;과 일치해야 해요.
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;EmpMapper.java (Interface)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;ann&quot;&gt;@Mapper&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;// MyBatis Mapper 선언&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;public interface&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpMapper&lt;/span&gt; {&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 메서드명 = XML의 id와 반드시 일치!&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt;&amp;gt; &lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;selectEmpDetail&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;insertEmp&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;insertEmpHist&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;updateEmp&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;deleteEmp&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
}
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ Mapper vs DAO&lt;/span&gt;
    옛날 방식은 &lt;strong&gt;DAO 클래스&lt;/strong&gt;를 직접 만들어서 SqlSession으로 SQL을 호출했어요.&lt;br&gt;
    MyBatis 3.x 이후부터는 &lt;strong&gt;Mapper Interface&lt;/strong&gt;만 선언하면 MyBatis가 자동으로 구현체를 만들어줘요.&lt;br&gt;
    전자정부프레임워크 구버전 프로젝트에서는 아직 DAO 방식을 쓰는 경우도 있어요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 06. MyBatis XML --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;06&lt;/span&gt; MyBatis XML Mapper — 실제 SQL 작성&lt;/h2&gt;

  &lt;div class=&quot;layer-card&quot; style=&quot;background:#161b22;&quot;&gt;
    &lt;div class=&quot;lc-header&quot;&gt;
      &lt;span class=&quot;lc-badge&quot; style=&quot;background:rgba(255,123,114,0.15);color:#ff7b72;border:1px solid rgba(255,123,114,0.3);&quot;&gt;XML&lt;/span&gt;
      &lt;span class=&quot;lc-title&quot;&gt;SQL을 Java 코드와 분리해서 관리&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;lc-desc&quot;&gt;
      SQL을 Java 코드 밖에 XML로 관리해서 &lt;strong&gt;SQL 변경 시 Java 코드를 건드리지 않아도 돼요.&lt;/strong&gt;&lt;br&gt;
      namespace = Mapper 인터페이스 경로, id = 메서드명과 일치해야 해요.
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;EmpMapper.xml&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tag&quot;&gt;&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- namespace = Mapper 인터페이스 전체 경로 --&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tag&quot;&gt;&amp;lt;mapper&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;namespace&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;com.example.emp.mapper.EmpMapper&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- 목록 조회 --&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;select&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;selectEmpList&quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;parameterType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;EmpVO&quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;resultType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;EmpVO&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT EMP_ID, EMP_NM, DEPT_CD, SALARY&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM   EMP&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE  1=1&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;if&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;test&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;searchNm != null and searchNm != ''&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AND EMP_NM LIKE '%' || #{searchNm} || '%'&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;if&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;test&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;searchDept != null and searchDept != ''&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AND DEPT_CD = #{searchDept}&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ORDER BY EMP_ID&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- 단건 조회 --&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;select&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;selectEmpDetail&quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;parameterType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;EmpVO&quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;resultType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;EmpVO&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT EMP_ID, EMP_NM, DEPT_CD, SALARY&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM   EMP&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE  EMP_ID = #{empId}&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- 등록 --&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;insert&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;insertEmp&quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;parameterType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;EmpVO&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT INTO EMP (EMP_ID, EMP_NM, DEPT_CD, SALARY)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VALUES (#{empId}, #{empNm}, #{deptCd}, #{salary})&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/insert&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- 수정 --&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;update&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;updateEmp&quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;parameterType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;EmpVO&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UPDATE EMP&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;set&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;if&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;test&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;empNm != null&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;EMP_NM = #{empNm},&lt;span class=&quot;tag&quot;&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;if&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;test&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;salary != 0&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;SALARY = #{salary},&lt;span class=&quot;tag&quot;&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/set&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE EMP_ID = #{empId}&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/update&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- 삭제 --&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;delete&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;deleteEmp&quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;parameterType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;EmpVO&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DELETE FROM EMP WHERE EMP_ID = #{empId}&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/delete&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;tag&quot;&gt;&amp;lt;/mapper&amp;gt;&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;info-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  #{} vs ${} 차이&lt;/span&gt;
    &lt;strong&gt;#{empId}&lt;/strong&gt; → PreparedStatement 방식. &lt;strong&gt;SQL Injection 방지!&lt;/strong&gt; ← 항상 이걸 써야 해요&lt;br&gt;
    &lt;strong&gt;${empId}&lt;/strong&gt; → 값을 문자열로 직접 삽입. SQL Injection 위험! ORDER BY 컬럼명 등 특수한 경우에만 사용
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 07. 전체 예제 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;07&lt;/span&gt; 전체 예제 — 조회 요청 흐름 추적&lt;/h2&gt;

  &lt;p&gt;&lt;strong&gt;&quot;사원 목록 조회&quot; 요청이 들어왔을 때&lt;/strong&gt; 각 레이어를 거치는 순서를 코드로 추적해볼게요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;요청 흐름 추적 — Step by Step&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;/* ① 브라우저 → /emp/list.do 요청 */&lt;/span&gt;&lt;br&gt;
GET /emp/list.do?searchNm=홍&amp;searchDept=10&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;/* ② Controller — 요청 파라미터 자동 바인딩 */&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;ann&quot;&gt;@RequestMapping&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;/list.do&quot;&lt;/span&gt;)&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; String &lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO, &lt;span class=&quot;cls&quot;&gt;ModelMap&lt;/span&gt; model) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// empVO.searchNm = &quot;홍&quot;, empVO.searchDept = &quot;10&quot; 자동 세팅&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt;&amp;gt; list = empService.&lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(empVO); &lt;span class=&quot;cm&quot;&gt;// ③ Service 호출&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;model.&lt;span class=&quot;fn&quot;&gt;addAttribute&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;empList&quot;&lt;/span&gt;, list);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;str&quot;&gt;&quot;emp/empList&quot;&lt;/span&gt;;&lt;br&gt;
}&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;/* ③ ServiceImpl — 비즈니스 로직 */&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt;&amp;gt; &lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt; empMapper.&lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(empVO); &lt;span class=&quot;cm&quot;&gt;// ④ Mapper 호출&lt;/span&gt;&lt;br&gt;
}&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;/* ④ MyBatis — Mapper Interface → XML SQL 실행 */&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 동적 쿼리 생성 (searchNm, searchDept 조건 추가됨)&lt;/span&gt;&lt;br&gt;
SELECT EMP_ID, EMP_NM, DEPT_CD, SALARY&lt;br&gt;
FROM   EMP&lt;br&gt;
WHERE  1=1&lt;br&gt;
AND    EMP_NM LIKE &lt;span class=&quot;str&quot;&gt;'%홍%'&lt;/span&gt;&lt;br&gt;
AND    DEPT_CD = &lt;span class=&quot;str&quot;&gt;'10'&lt;/span&gt;&lt;br&gt;
ORDER BY EMP_ID&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;/* ⑤ DB 실행 → 결과 → EmpVO 리스트로 자동 매핑 */&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;/* ⑥ Controller → ModelMap에 담아 JSP로 전달 */&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;/* ⑦ JSP에서 ${empList}로 렌더링 → 브라우저 응답 */&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 08. 핵심 어노테이션 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;08&lt;/span&gt; 핵심 어노테이션 정리&lt;/h2&gt;

  &lt;table class=&quot;compare-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;어노테이션&lt;/th&gt;&lt;th&gt;위치&lt;/th&gt;&lt;th&gt;역할&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#3fb950;&quot;&gt;@Controller&lt;/td&gt;&lt;td&gt;Controller 클래스&lt;/td&gt;&lt;td&gt;Spring MVC 컨트롤러 등록&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#3fb950;&quot;&gt;@RequestMapping&lt;/td&gt;&lt;td&gt;Controller 메서드&lt;/td&gt;&lt;td&gt;URL 매핑&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#3fb950;&quot;&gt;@ResponseBody&lt;/td&gt;&lt;td&gt;Controller 메서드&lt;/td&gt;&lt;td&gt;반환값을 JSON으로 직렬화&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#3fb950;&quot;&gt;@RequestBody&lt;/td&gt;&lt;td&gt;파라미터&lt;/td&gt;&lt;td&gt;요청 Body(JSON)를 VO로 변환&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#bc8cff;&quot;&gt;@Service&lt;/td&gt;&lt;td&gt;ServiceImpl 클래스&lt;/td&gt;&lt;td&gt;Spring Bean으로 등록&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#bc8cff;&quot;&gt;@Transactional&lt;/td&gt;&lt;td&gt;ServiceImpl 메서드&lt;/td&gt;&lt;td&gt;트랜잭션 적용&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#ffa657;&quot;&gt;@Mapper&lt;/td&gt;&lt;td&gt;Mapper 인터페이스&lt;/td&gt;&lt;td&gt;MyBatis Mapper 등록&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#58a6ff;&quot;&gt;@Autowired&lt;/td&gt;&lt;td&gt;필드/생성자&lt;/td&gt;&lt;td&gt;Spring Bean 자동 주입 (DI)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#58a6ff;&quot;&gt;@Component&lt;/td&gt;&lt;td&gt;일반 클래스&lt;/td&gt;&lt;td&gt;Spring Bean으로 등록 (범용)&lt;/td&gt;&lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;✅ CONCLUSION&lt;/span&gt;
    &lt;strong&gt;Controller&lt;/strong&gt; → URL 받아서 Service 호출&lt;br&gt;
    &lt;strong&gt;Service(Interface)&lt;/strong&gt; → 무엇을 할지 선언&lt;br&gt;
    &lt;strong&gt;ServiceImpl&lt;/strong&gt; → 실제 비즈니스 로직 + 트랜잭션&lt;br&gt;
    &lt;strong&gt;Mapper(Interface)&lt;/strong&gt; → SQL 메서드 선언&lt;br&gt;
    &lt;strong&gt;XML&lt;/strong&gt; → 실제 SQL 작성&lt;br&gt;
    &lt;strong&gt;VO&lt;/strong&gt; → 레이어 간 데이터 이동 그릇&lt;br&gt;&lt;br&gt;
    이 흐름을 머릿속에 그릴 수 있으면 전자정부프레임워크 프로젝트 어디서든 길을 잃지 않아요!
  &lt;/div&gt;


  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 09. 프로시저 / 펑션 호출 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;09&lt;/span&gt; MyBatis에서 프로시저 / 펑션 호출&lt;/h2&gt;

  &lt;p&gt;복잡한 비즈니스 로직이 DB 프로시저로 구현된 경우&lt;br&gt;
  MyBatis XML에서 &lt;span class=&quot;ic&quot;&gt;statementType=&quot;CALLABLE&quot;&lt;/span&gt;로 호출할 수 있어요.&lt;/p&gt;

  &lt;h3&gt;Oracle 프로시저 호출&lt;/h3&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;EmpMapper.xml — 프로시저 호출&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- Oracle 프로시저 호출 --&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tag&quot;&gt;&amp;lt;update&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;callSalaryUpdate&quot;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class=&quot;attr&quot;&gt;parameterType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;EmpVO&quot;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class=&quot;attr&quot;&gt;statementType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;CALLABLE&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;{CALL SP_SALARY_UPDATE(&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#{deptCd,    mode=IN,  jdbcType=VARCHAR},&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#{rate,      mode=IN,  jdbcType=NUMERIC},&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#{resultCnt, mode=OUT, jdbcType=NUMERIC}&lt;br&gt;
&amp;nbsp;&amp;nbsp;)}&lt;br&gt;
&lt;span class=&quot;tag&quot;&gt;&amp;lt;/update&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;&amp;lt;!-- Oracle 펑션 호출 --&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tag&quot;&gt;&amp;lt;select&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;callGetDeptName&quot;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class=&quot;attr&quot;&gt;parameterType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;EmpVO&quot;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class=&quot;attr&quot;&gt;statementType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;CALLABLE&quot;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class=&quot;attr&quot;&gt;resultType&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&quot;String&quot;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;{#{deptNm, mode=OUT, jdbcType=VARCHAR} =&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CALL FN_GET_DEPT_NAME(#{deptCd, mode=IN, jdbcType=VARCHAR})}&lt;br&gt;
&lt;span class=&quot;tag&quot;&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;EmpMapper.java — 프로시저 메서드 선언&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;ann&quot;&gt;@Mapper&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;public interface&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpMapper&lt;/span&gt; {&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 프로시저 호출 — OUT 파라미터는 VO에 담겨 반환&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;callSalaryUpdate&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 펑션 호출&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;callGetDeptName&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO);&lt;br&gt;
}
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;EmpServiceImpl.java — 프로시저 결과 사용&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;ann&quot;&gt;@Transactional&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;public void&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;processSalaryUpdate&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO) {&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 프로시저 호출&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;empMapper.&lt;span class=&quot;fn&quot;&gt;callSalaryUpdate&lt;/span&gt;(empVO);&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// OUT 파라미터 값은 호출 후 VO에 자동으로 세팅됨&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;int&lt;/span&gt; resultCnt = empVO.&lt;span class=&quot;fn&quot;&gt;getResultCnt&lt;/span&gt;();&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;System.out.println&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;처리건수: &quot;&lt;/span&gt; + resultCnt);&lt;br&gt;
}
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  OUT 파라미터 처리 핵심&lt;/span&gt;
    프로시저의 &lt;strong&gt;OUT 파라미터는 VO 필드에 자동으로 세팅&lt;/strong&gt;돼요.&lt;br&gt;
    VO에 OUT 파라미터 받을 필드를 미리 선언해두면 MyBatis가 자동으로 채워줘요.&lt;br&gt;
    &lt;span class=&quot;ic&quot;&gt;mode=OUT&lt;/span&gt;으로 선언한 필드는 &lt;strong&gt;호출 후 VO.getXxx()로 꺼내쓰면&lt;/strong&gt; 됩니다.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 10. 넥사크로에서 호출하는 흐름 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;10&lt;/span&gt; 넥사크로 → Spring → DB 전체 흐름&lt;/h2&gt;

  &lt;p&gt;ERP 넥사크로 화면에서 조회 버튼을 누르면 어떤 경로로 흘러가는지 정리해봤어요.&lt;/p&gt;

  &lt;div class=&quot;flow-full&quot;&gt;
    &lt;span class=&quot;ff-title&quot;&gt;// 넥사크로 → Spring MVC → MyBatis → DB 전체 흐름&lt;/span&gt;
    &lt;div class=&quot;ff-row&quot;&gt;
      &lt;span class=&quot;ff-item fi-client&quot;&gt;넥사크로&lt;br&gt;&lt;small style=&quot;font-size:10px;&quot;&gt;transaction()&lt;/small&gt;&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-ctrl&quot;&gt;JSP or&lt;br&gt;Controller&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-svc&quot;&gt;Service&lt;br&gt;Impl&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-mapper&quot;&gt;Mapper&lt;br&gt;Interface&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-xml&quot;&gt;XML&lt;br&gt;(SQL/SP)&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item fi-db&quot;&gt;DB&lt;br&gt;(Oracle)&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;넥사크로 → JSP 트랜잭션 → Controller 흐름&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;/* ① 넥사크로 화면 — transaction() 호출 */&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;btn_search_onclick&lt;/span&gt;(obj, e) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;fn&quot;&gt;transaction&lt;/span&gt;(&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;selectList&quot;&lt;/span&gt;,&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;/emp/list.do&quot;&lt;/span&gt;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// Controller URL&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;ds_search&quot;&lt;/span&gt;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 보낼 Dataset (검색조건)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;ds_list&quot;&lt;/span&gt;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 받을 Dataset (결과)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;callback_selectList&quot;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 응답 후 콜백함수&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;);&lt;br&gt;
}&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;/* ② Controller — 넥사크로 Dataset 파싱 */&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;ann&quot;&gt;@RequestMapping&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;/emp/list.do&quot;&lt;/span&gt;)&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;ModelAndView&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(&lt;span class=&quot;cls&quot;&gt;HttpServletRequest&lt;/span&gt; request,&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;HttpServletResponse&lt;/span&gt; response) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 넥사크로 Dataset → VO 변환&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;NexacroUtil&lt;/span&gt; nexacroUtil = &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;NexacroUtil&lt;/span&gt;(request, response);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;DataSet&lt;/span&gt; ds_search = nexacroUtil.&lt;span class=&quot;fn&quot;&gt;getDataSet&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;ds_search&quot;&lt;/span&gt;);&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt; empVO = &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt;();&lt;br&gt;
&amp;nbsp;&amp;nbsp;empVO.&lt;span class=&quot;fn&quot;&gt;setSearchNm&lt;/span&gt;(ds_search.&lt;span class=&quot;fn&quot;&gt;getString&lt;/span&gt;(&lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;&quot;SEARCH_NM&quot;&lt;/span&gt;));&lt;br&gt;
&amp;nbsp;&amp;nbsp;empVO.&lt;span class=&quot;fn&quot;&gt;setSearchDept&lt;/span&gt;(ds_search.&lt;span class=&quot;fn&quot;&gt;getString&lt;/span&gt;(&lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;&quot;DEPT_CD&quot;&lt;/span&gt;));&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// Service 호출&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cls&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span class=&quot;cls&quot;&gt;EmpVO&lt;/span&gt;&amp;gt; list = empService.&lt;span class=&quot;fn&quot;&gt;selectEmpList&lt;/span&gt;(empVO);&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 결과 → 넥사크로 Dataset으로 변환 후 응답&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;nexacroUtil.&lt;span class=&quot;fn&quot;&gt;addDataSet&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;ds_list&quot;&lt;/span&gt;, list);&lt;br&gt;
&amp;nbsp;&amp;nbsp;nexacroUtil.&lt;span class=&quot;fn&quot;&gt;sendData&lt;/span&gt;();&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return null&lt;/span&gt;;&lt;br&gt;
}&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;/* ③ ServiceImpl → Mapper → XML → DB → 결과 반환 */&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;/* ④ 넥사크로 콜백에서 ds_list 자동 바인딩 → Grid 표시 */&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;넥사크로에서 프로시저 결과 받기&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;/* 넥사크로 — 저장 버튼 클릭 */&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;btn_save_onclick&lt;/span&gt;(obj, e) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;fn&quot;&gt;transaction&lt;/span&gt;(&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;saveSalary&quot;&lt;/span&gt;,&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;/emp/salaryUpdate.do&quot;&lt;/span&gt;,&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;ds_param&quot;&lt;/span&gt;,&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// 보낼 Dataset (deptCd, rate)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;ds_result&quot;&lt;/span&gt;, &lt;span class=&quot;cm&quot;&gt;// 받을 Dataset (처리건수 등)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;str&quot;&gt;&quot;callback_save&quot;&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;);&lt;br&gt;
}&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;/* 콜백 — 프로시저 OUT 파라미터 결과 확인 */&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;callback_save&lt;/span&gt;(id, errorCode, errorMsg) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;if&lt;/span&gt; (errorCode &amp;lt; &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;) {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;alert&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&quot;저장 실패: &quot;&lt;/span&gt; + errorMsg);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;return&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;}&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;// ds_result에서 처리건수 확인&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;var&lt;/span&gt; resultCnt = &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;.ds_result.&lt;span class=&quot;fn&quot;&gt;getColumn&lt;/span&gt;(&lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;&quot;RESULT_CNT&quot;&lt;/span&gt;);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;alert&lt;/span&gt;(resultCnt + &lt;span class=&quot;str&quot;&gt;&quot;건 처리 완료!&quot;&lt;/span&gt;);&lt;br&gt;
}
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ 넥사크로 + Spring 주요 주의사항&lt;/span&gt;
    &lt;strong&gt;넥사크로 Dataset 컬럼명&lt;/strong&gt;은 대문자 언더스코어 (&lt;span class=&quot;ic&quot;&gt;EMP_NM&lt;/span&gt;) 형태가 많아요.&lt;br&gt;
    Java VO는 카멜케이스 (&lt;span class=&quot;ic&quot;&gt;empNm&lt;/span&gt;) → 변환 코드 또는 MyBatis 설정 필요.&lt;br&gt;
    &lt;strong&gt;NexacroUtil&lt;/strong&gt;은 프로젝트마다 커스텀 유틸이라 코드가 다를 수 있어요.&lt;br&gt;
    트랜잭션은 반드시 &lt;strong&gt;ServiceImpl에 @Transactional&lt;/strong&gt;로 걸어야 프로시저 오류 시 롤백돼요.
  &lt;/div&gt;

    &lt;div class=&quot;post-tags&quot;&gt;
    &lt;span class=&quot;post-tag&quot;&gt;Spring&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;MyBatis&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;전자정부프레임워크&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;Controller&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;Service&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;Mapper&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;VO&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;MVC패턴&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;eGovFrame&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;!-- 추가: 프로시저/펑션 호출 섹션 --&gt;</description>
      <category>  개발 공부/개발 도구 및 환경</category>
      <author>woohaha</author>
      <guid isPermaLink="true">https://story2248.tistory.com/21</guid>
      <comments>https://story2248.tistory.com/21#entry21comment</comments>
      <pubDate>Mon, 25 May 2026 13:40:58 +0900</pubDate>
    </item>
    <item>
      <title>[Network] TCP vs UDP 완전 정복 - 3-Way Handshake부터 실사용 예시까지</title>
      <link>https://story2248.tistory.com/20</link>
      <description>&lt;style&gt;
  .post-wrap {
    background: #0d1117; color: #e6edf3;
    font-family: 'Pretendard', -apple-system, sans-serif;
    padding: 0; line-height: 1.8;
  }
  .post-wrap * { box-sizing: border-box; }
  .post-wrap p { margin: 0 0 16px; color: #8b949e; font-size: 15px; }
  .post-wrap p strong { color: #e6edf3; }
  .post-divider { border: none; border-top: 1px solid #21262d; margin: 32px 0; }

  .post-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px;
    background: rgba(88,166,255,0.1); border: 1px solid rgba(88,166,255,0.3);
    border-radius: 20px; font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: #58a6ff; margin-bottom: 20px;
  }
  .post-badge::before { content: ' '; }

  /* 목차 */
  .toc { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 24px 0; }
  .toc-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .toc-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .toc-item { font-size: 13px; color: #8b949e; padding: 3px 0; display: flex; align-items: center; gap: 8px; }
  .toc-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; }

  .post-wrap h2 {
    font-size: 18px; font-weight: 600; color: #e6edf3;
    margin: 32px 0 16px; padding-bottom: 8px;
    border-bottom: 1px solid #21262d;
    display: flex; align-items: center; gap: 8px;
  }
  .post-wrap h2 .num {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #58a6ff; background: rgba(88,166,255,0.1);
    border: 1px solid rgba(88,166,255,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h3 {
    font-size: 15px; font-weight: 600; color: #8b949e;
    margin: 20px 0 10px; font-family: 'JetBrains Mono', monospace;
  }
  .post-wrap h3::before { content: '// '; color: #484f58; }

  /* 핵심 요약 */
  .summary-box {
    background: #161b22; border: 1px solid #30363d;
    border-left: 3px solid #58a6ff; border-radius: 6px;
    padding: 20px 24px; margin: 24px 0;
    font-family: 'JetBrains Mono', monospace; font-size: 13px;
    color: #8b949e; line-height: 2;
  }
  .summary-box .sl-title { font-size: 11px; color: #58a6ff; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .sl-row { display: flex; align-items: baseline; gap: 12px; margin-bottom: 6px; }
  .sl-kw { font-size: 12px; font-weight: 700; padding: 1px 10px; border-radius: 4px; flex-shrink: 0; min-width: 60px; text-align: center; }
  .kw-tcp { background: rgba(88,166,255,0.1); color: #58a6ff; border: 1px solid rgba(88,166,255,0.3); }
  .kw-udp { background: rgba(255,166,87,0.1); color: #ffa657; border: 1px solid rgba(255,166,87,0.3); }
  .sl-desc { color: #e6edf3; font-size: 13px; font-family: 'Pretendard', sans-serif; }

  /* 핸드셰이크 시각화 */
  .handshake {
    background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; padding: 20px 24px; margin: 16px 0;
    font-family: 'JetBrains Mono', monospace;
  }
  .hs-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 20px; display: block; }
  .hs-parties { display: flex; justify-content: space-between; margin-bottom: 16px; }
  .hs-party { font-size: 12px; font-weight: 700; padding: 6px 20px; border-radius: 6px; }
  .hp-client { background: rgba(88,166,255,0.15); color: #58a6ff; border: 1px solid rgba(88,166,255,0.3); }
  .hp-server { background: rgba(63,185,80,0.15); color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .hs-step {
    display: flex; align-items: center; margin-bottom: 10px;
    font-size: 12px;
  }
  .hs-msg {
    flex: 1; text-align: center; position: relative;
    padding: 6px 12px; border-radius: 5px; font-weight: 600;
  }
  .hm-syn  { background: rgba(88,166,255,0.12); color: #58a6ff; border: 1px solid rgba(88,166,255,0.3); }
  .hm-ack  { background: rgba(63,185,80,0.12);  color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .hm-fin  { background: rgba(255,123,114,0.12);color: #ff7b72; border: 1px solid rgba(255,123,114,0.3); }
  .hm-data { background: rgba(188,140,255,0.12);color: #bc8cff; border: 1px solid rgba(188,140,255,0.3); }
  .hs-arrow-r { color: #58a6ff; margin: 0 8px; font-size: 16px; }
  .hs-arrow-l { color: #3fb950; margin: 0 8px; font-size: 16px; }
  .hs-desc { font-size: 10px; color: #484f58; margin-left: 10px; font-family: 'Pretendard', sans-serif; }

  /* UDP 시각화 */
  .udp-viz {
    background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; padding: 20px 24px; margin: 16px 0;
    font-family: 'JetBrains Mono', monospace;
  }
  .uv-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 16px; display: block; }
  .uv-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
  .uv-item { padding: 6px 14px; border-radius: 5px; font-size: 12px; font-weight: 600; }
  .ui-send { background: rgba(255,166,87,0.15); color: #ffa657; border: 1px solid rgba(255,166,87,0.3); }
  .ui-lost { background: rgba(255,123,114,0.15); color: #ff7b72; border: 1px solid rgba(255,123,114,0.3); }
  .ui-recv { background: rgba(63,185,80,0.15);  color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .uv-arrow { color: #484f58; font-size: 16px; }

  /* 코드 블럭 */
  .code-block { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; margin: 12px 0; overflow: hidden; }
  .code-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; background: #161b22; border-bottom: 1px solid #30363d; }
  .code-lang { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; letter-spacing: 0.05em; }
  .code-dots { display: flex; gap: 5px; }
  .code-dots span { width: 10px; height: 10px; border-radius: 50%; }
  .code-dots span:nth-child(1) { background: #ff5f56; }
  .code-dots span:nth-child(2) { background: #ffbd2e; }
  .code-dots span:nth-child(3) { background: #27c93f; }
  .code-body { padding: 18px 22px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.85; overflow-x: auto; color: #e6edf3; }
  .kw { color: #ff7b72; } .fn { color: #d2a8ff; }
  .cm { color: #484f58; font-style: italic; } .str { color: #a5d6ff; }
  .num { color: #79c0ff; }
  .tcp { color: #58a6ff; font-weight: 600; }
  .udp { color: #ffa657; font-weight: 600; }

  /* 비교 카드 */
  .compare-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 16px 0; }
  .ccard { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px 18px; }
  .ccard-title { font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 700; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid #21262d; }
  .ccard ul { margin: 0; padding: 0; list-style: none; }
  .ccard ul li { font-size: 13px; color: #8b949e; padding: 4px 0; border-bottom: 1px solid #21262d; line-height: 1.6; display: flex; gap: 8px; }
  .ccard ul li:last-child { border-bottom: none; }
  .ccard ul li::before { content: '·'; color: #484f58; flex-shrink: 0; }

  /* 비교 테이블 */
  .compare-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
  .compare-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 10px 14px; border: 1px solid #30363d; letter-spacing: 0.05em; text-align: left; }
  .compare-table td { padding: 10px 14px; border: 1px solid #30363d; color: #e6edf3; }
  .compare-table tr:nth-child(even) td { background: #161b22; }
  .td-tcp { color: #58a6ff !important; font-weight: 700; }
  .td-udp { color: #ffa657 !important; font-weight: 700; }

  /* 사용 예시 카드 */
  .use-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 16px 0; }
  .use-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 14px 16px; }
  .use-title { font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 700; margin-bottom: 8px; }
  .use-items { display: flex; flex-wrap: wrap; gap: 5px; }
  .use-item { font-size: 11px; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono', monospace; }

  /* tip / warn 박스 */
  .tip-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #58a6ff; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .tip-box strong { color: #e6edf3; }
  .tip-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .warn-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ffa657; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .warn-box strong { color: #e6edf3; }
  .warn-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }

  .ic { font-family: 'JetBrains Mono', monospace; font-size: 0.88em; padding: 1px 6px; background: #1c2128; border: 1px solid #30363d; border-radius: 4px; color: #ffa657; }
  .post-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 32px; padding-top: 20px; border-top: 1px solid #21262d; }
  .post-tag { padding: 3px 10px; background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.25); border-radius: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; }
  .post-tag::before { content: '#'; opacity: 0.6; }
  @media (max-width: 600px) {
    .toc-list { grid-template-columns: 1fr; }
    .compare-cards { grid-template-columns: 1fr; }
    .use-grid { grid-template-columns: 1fr; }
  }
&lt;/style&gt;

&lt;div class=&quot;post-wrap&quot;&gt;
  &lt;div class=&quot;post-badge&quot;&gt;CS 면접 대비 · 네트워크&lt;/div&gt;

  &lt;p&gt;면접에서 &lt;strong&gt;&quot;TCP와 UDP의 차이를 설명해주세요&quot;&lt;/strong&gt;는 네트워크 단골 질문이에요.&lt;br&gt;
  개념부터 3-Way Handshake, 실제 사용 예시까지 한 번에 정리해드릴게요.&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
    &lt;span class=&quot;toc-title&quot;&gt;// TABLE OF CONTENTS&lt;/span&gt;
    &lt;div class=&quot;toc-list&quot;&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;01&lt;/span&gt; TCP / UDP 핵심 요약&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;02&lt;/span&gt; TCP — 연결 지향&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;03&lt;/span&gt; 3-Way Handshake&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;04&lt;/span&gt; 4-Way Handshake (연결 종료)&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;05&lt;/span&gt; TCP 특징 — 흐름/혼잡 제어&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;06&lt;/span&gt; UDP — 비연결 지향&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;07&lt;/span&gt; TCP vs UDP 비교&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;08&lt;/span&gt; 면접 답변 정리&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 01. 핵심 요약 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;01&lt;/span&gt; TCP / UDP 핵심 요약&lt;/h2&gt;

  &lt;div class=&quot;summary-box&quot;&gt;
    &lt;span class=&quot;sl-title&quot;&gt;⚡ 핵심 한 줄 요약&lt;/span&gt;
    &lt;div class=&quot;sl-row&quot;&gt;
      &lt;span class=&quot;sl-kw kw-tcp&quot;&gt;TCP&lt;/span&gt;
      &lt;span class=&quot;sl-desc&quot;&gt;&lt;strong&gt;신뢰성 보장&lt;/strong&gt; — 연결 후 순서 보장, 재전송, 느리지만 정확&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;sl-row&quot;&gt;
      &lt;span class=&quot;sl-kw kw-udp&quot;&gt;UDP&lt;/span&gt;
      &lt;span class=&quot;sl-desc&quot;&gt;&lt;strong&gt;속도 우선&lt;/strong&gt; — 연결 없이 전송, 손실 가능, 빠르지만 불확실&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 02. TCP --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;02&lt;/span&gt; TCP — 연결 지향 프로토콜&lt;/h2&gt;

  &lt;p&gt;TCP(Transmission Control Protocol)는 데이터를 &lt;strong&gt;정확하게, 순서대로&lt;/strong&gt; 전달해요.&lt;br&gt;
  통신 전 연결을 맺고, 통신 후 연결을 끊는 과정이 있어요.&lt;/p&gt;

  &lt;div class=&quot;compare-cards&quot;&gt;
    &lt;div class=&quot;ccard&quot;&gt;
      &lt;div class=&quot;ccard-title&quot; style=&quot;color:#58a6ff;&quot;&gt;TCP 특징&lt;/div&gt;
      &lt;ul&gt;
        &lt;li&gt;연결 지향 (3-Way Handshake)&lt;/li&gt;
        &lt;li&gt;데이터 순서 보장&lt;/li&gt;
        &lt;li&gt;손실 시 자동 재전송&lt;/li&gt;
        &lt;li&gt;흐름 제어 / 혼잡 제어&lt;/li&gt;
        &lt;li&gt;1:1 통신 (유니캐스트)&lt;/li&gt;
        &lt;li&gt;속도 상대적으로 느림&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;div class=&quot;ccard&quot;&gt;
      &lt;div class=&quot;ccard-title&quot; style=&quot;color:#58a6ff;&quot;&gt;TCP 헤더 구조&lt;/div&gt;
      &lt;ul&gt;
        &lt;li&gt;출발지 포트 (16bit)&lt;/li&gt;
        &lt;li&gt;목적지 포트 (16bit)&lt;/li&gt;
        &lt;li&gt;시퀀스 번호 — 순서 보장&lt;/li&gt;
        &lt;li&gt;확인 응답 번호 (ACK)&lt;/li&gt;
        &lt;li&gt;플래그 (SYN/ACK/FIN/RST)&lt;/li&gt;
        &lt;li&gt;체크섬 — 오류 검출&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 03. 3-Way Handshake --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;03&lt;/span&gt; 3-Way Handshake — 연결 수립&lt;/h2&gt;

  &lt;p&gt;TCP 연결을 맺을 때 &lt;strong&gt;3번의 메시지를 주고받는 과정&lt;/strong&gt;이에요.&lt;br&gt;
  서로의 존재를 확인하고 통신 준비가 됐음을 확인해요.&lt;/p&gt;

  &lt;div class=&quot;handshake&quot;&gt;
    &lt;span class=&quot;hs-title&quot;&gt;// 3-Way Handshake (연결 수립)&lt;/span&gt;
    &lt;div class=&quot;hs-parties&quot;&gt;
      &lt;span class=&quot;hs-party hp-client&quot;&gt;Client&lt;/span&gt;
      &lt;span class=&quot;hs-party hp-server&quot;&gt;Server&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;hs-step&quot;&gt;
      &lt;span class=&quot;hs-msg hm-syn&quot;&gt;SYN&lt;/span&gt;
      &lt;span class=&quot;hs-arrow-r&quot;&gt;────────────→&lt;/span&gt;
      &lt;span class=&quot;hs-desc&quot;&gt;① &quot;나 연결하고 싶어!&quot; (SYN=1, seq=x)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;hs-step&quot;&gt;
      &lt;span class=&quot;hs-arrow-l&quot;&gt;←────────────&lt;/span&gt;
      &lt;span class=&quot;hs-msg hm-ack&quot;&gt;SYN + ACK&lt;/span&gt;
      &lt;span class=&quot;hs-desc&quot;&gt;② &quot;OK! 나도 준비됐어!&quot; (SYN=1, ACK=x+1, seq=y)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;hs-step&quot;&gt;
      &lt;span class=&quot;hs-msg hm-ack&quot;&gt;ACK&lt;/span&gt;
      &lt;span class=&quot;hs-arrow-r&quot;&gt;────────────→&lt;/span&gt;
      &lt;span class=&quot;hs-desc&quot;&gt;③ &quot;확인! 이제 통신하자!&quot; (ACK=y+1)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div style=&quot;margin-top:14px; padding-top:12px; border-top:1px solid #21262d; font-size:11px; color:#484f58; font-family:'Pretendard',sans-serif;&quot;&gt;
      ✅ 연결 수립 완료 → 데이터 전송 시작
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  SYN / ACK 플래그 의미&lt;/span&gt;
    &lt;strong&gt;SYN (Synchronize)&lt;/strong&gt; = 연결 요청. 시퀀스 번호 동기화&lt;br&gt;
    &lt;strong&gt;ACK (Acknowledgment)&lt;/strong&gt; = 수신 확인. &quot;잘 받았어!&quot; 응답&lt;br&gt;
    &lt;strong&gt;FIN (Finish)&lt;/strong&gt; = 연결 종료 요청&lt;br&gt;
    &lt;strong&gt;RST (Reset)&lt;/strong&gt; = 강제 연결 초기화
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 04. 4-Way Handshake --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;04&lt;/span&gt; 4-Way Handshake — 연결 종료&lt;/h2&gt;

  &lt;p&gt;TCP 연결을 끊을 때는 &lt;strong&gt;4번의 메시지&lt;/strong&gt;가 필요해요.&lt;br&gt;
  양쪽 모두 데이터 전송이 끝났음을 확인해야 하기 때문이에요.&lt;/p&gt;

  &lt;div class=&quot;handshake&quot;&gt;
    &lt;span class=&quot;hs-title&quot;&gt;// 4-Way Handshake (연결 종료)&lt;/span&gt;
    &lt;div class=&quot;hs-parties&quot;&gt;
      &lt;span class=&quot;hs-party hp-client&quot;&gt;Client&lt;/span&gt;
      &lt;span class=&quot;hs-party hp-server&quot;&gt;Server&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;hs-step&quot;&gt;
      &lt;span class=&quot;hs-msg hm-fin&quot;&gt;FIN&lt;/span&gt;
      &lt;span class=&quot;hs-arrow-r&quot;&gt;────────────→&lt;/span&gt;
      &lt;span class=&quot;hs-desc&quot;&gt;① &quot;나 이제 보낼 거 없어&quot; (FIN)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;hs-step&quot;&gt;
      &lt;span class=&quot;hs-arrow-l&quot;&gt;←────────────&lt;/span&gt;
      &lt;span class=&quot;hs-msg hm-ack&quot;&gt;ACK&lt;/span&gt;
      &lt;span class=&quot;hs-desc&quot;&gt;② &quot;알겠어, 잠깐만&quot; (ACK) — 서버 아직 보낼 수 있음&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;hs-step&quot;&gt;
      &lt;span class=&quot;hs-arrow-l&quot;&gt;←────────────&lt;/span&gt;
      &lt;span class=&quot;hs-msg hm-fin&quot;&gt;FIN&lt;/span&gt;
      &lt;span class=&quot;hs-desc&quot;&gt;③ &quot;나도 다 보냈어&quot; (FIN)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;hs-step&quot;&gt;
      &lt;span class=&quot;hs-msg hm-ack&quot;&gt;ACK&lt;/span&gt;
      &lt;span class=&quot;hs-arrow-r&quot;&gt;────────────→&lt;/span&gt;
      &lt;span class=&quot;hs-desc&quot;&gt;④ &quot;확인! 연결 끊자&quot; (ACK)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div style=&quot;margin-top:14px; padding-top:12px; border-top:1px solid #21262d; font-size:11px; color:#484f58; font-family:'Pretendard',sans-serif;&quot;&gt;
      ✅ 연결 종료 완료 → TIME_WAIT 상태 후 완전 종료
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ TIME_WAIT 상태&lt;/span&gt;
    클라이언트는 마지막 ACK를 보낸 후 바로 종료하지 않고 &lt;strong&gt;TIME_WAIT 상태&lt;/strong&gt;로 잠시 대기해요.&lt;br&gt;
    이유: 마지막 ACK가 유실됐을 때 서버가 FIN을 재전송하면 받아서 처리하기 위함.&lt;br&gt;
    TIME_WAIT 기간: 보통 &lt;strong&gt;2 × MSL (Maximum Segment Lifetime)&lt;/strong&gt; = 약 60~120초
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 05. 흐름/혼잡 제어 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;05&lt;/span&gt; TCP 특징 — 흐름 제어 &amp; 혼잡 제어&lt;/h2&gt;

  &lt;h3&gt;흐름 제어 (Flow Control)&lt;/h3&gt;
  &lt;p&gt;수신측이 처리할 수 있는 속도보다 &lt;strong&gt;송신측이 너무 빠르게 보내는 것을 방지&lt;/strong&gt;해요.&lt;br&gt;
  수신 버퍼 크기를 알려주는 &lt;strong&gt;윈도우 크기(Window Size)&lt;/strong&gt;로 제어해요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;흐름 제어 / 혼잡 제어 개념&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 흐름 제어 (Flow Control) ━━━&lt;/span&gt;&lt;br&gt;
수신측 버퍼: &lt;span class=&quot;num&quot;&gt;65535&lt;/span&gt; bytes (Window Size)&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 수신측이 &quot;나 이만큼만 받을 수 있어&quot;라고 알림&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 송신측은 Window Size 이내에서만 전송&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 슬라이딩 윈도우 방식으로 동적 조절&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 혼잡 제어 (Congestion Control) ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 네트워크 자체가 혼잡할 때 전송량 줄이기&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 패킷 손실 감지 → 전송 속도 감소&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 알고리즘: Slow Start → Congestion Avoidance&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 처음엔 천천히 → 점점 빠르게 → 손실 발생 → 다시 줄이기&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 06. UDP --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;06&lt;/span&gt; UDP — 비연결 지향 프로토콜&lt;/h2&gt;

  &lt;p&gt;UDP(User Datagram Protocol)는 연결 없이 &lt;strong&gt;일단 보내고 보는&lt;/strong&gt; 방식이에요.&lt;br&gt;
  손실이 발생해도 재전송하지 않아서 빠르지만 신뢰성이 없어요.&lt;/p&gt;

  &lt;div class=&quot;udp-viz&quot;&gt;
    &lt;span class=&quot;uv-title&quot;&gt;// UDP 전송 — 확인 없이 그냥 보냄&lt;/span&gt;
    &lt;div class=&quot;uv-row&quot;&gt;
      &lt;span class=&quot;uv-item ui-send&quot;&gt;패킷1 전송&lt;/span&gt;
      &lt;span class=&quot;uv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;uv-item ui-recv&quot;&gt;수신 ✅&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;uv-row&quot;&gt;
      &lt;span class=&quot;uv-item ui-send&quot;&gt;패킷2 전송&lt;/span&gt;
      &lt;span class=&quot;uv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;uv-item ui-lost&quot;&gt;손실  &lt;/span&gt;
      &lt;span style=&quot;font-size:11px;color:#484f58;font-family:'Pretendard',sans-serif;&quot;&gt;← 재전송 없음, 그냥 무시&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;uv-row&quot;&gt;
      &lt;span class=&quot;uv-item ui-send&quot;&gt;패킷3 전송&lt;/span&gt;
      &lt;span class=&quot;uv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;uv-item ui-recv&quot;&gt;수신 ✅&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;compare-cards&quot;&gt;
    &lt;div class=&quot;ccard&quot;&gt;
      &lt;div class=&quot;ccard-title&quot; style=&quot;color:#ffa657;&quot;&gt;UDP 특징&lt;/div&gt;
      &lt;ul&gt;
        &lt;li&gt;비연결 지향 (핸드셰이크 없음)&lt;/li&gt;
        &lt;li&gt;데이터 순서 보장 안 됨&lt;/li&gt;
        &lt;li&gt;손실 시 재전송 없음&lt;/li&gt;
        &lt;li&gt;빠른 전송 속도&lt;/li&gt;
        &lt;li&gt;1:1 / 1:N(멀티캐스트) 가능&lt;/li&gt;
        &lt;li&gt;헤더 크기 작음 (8byte)&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;div class=&quot;ccard&quot;&gt;
      &lt;div class=&quot;ccard-title&quot; style=&quot;color:#ffa657;&quot;&gt;UDP 헤더 구조&lt;/div&gt;
      &lt;ul&gt;
        &lt;li&gt;출발지 포트 (16bit)&lt;/li&gt;
        &lt;li&gt;목적지 포트 (16bit)&lt;/li&gt;
        &lt;li&gt;길이 (16bit)&lt;/li&gt;
        &lt;li&gt;체크섬 (16bit)&lt;/li&gt;
        &lt;li&gt;총 8byte — TCP(20byte+)보다 훨씬 작음&lt;/li&gt;
        &lt;li&gt;오버헤드 최소화&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 07. TCP vs UDP 비교 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;07&lt;/span&gt; TCP vs UDP 한눈에 비교&lt;/h2&gt;

  &lt;table class=&quot;compare-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;구분&lt;/th&gt;&lt;th&gt;TCP&lt;/th&gt;&lt;th&gt;UDP&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;연결 방식&lt;/td&gt;
      &lt;td class=&quot;td-tcp&quot;&gt;연결 지향 (3-Way Handshake)&lt;/td&gt;
      &lt;td class=&quot;td-udp&quot;&gt;비연결 지향&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;신뢰성&lt;/td&gt;
      &lt;td class=&quot;td-tcp&quot;&gt;보장 (재전송, 순서 보장)&lt;/td&gt;
      &lt;td class=&quot;td-udp&quot;&gt;미보장&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;속도&lt;/td&gt;
      &lt;td&gt;상대적으로 느림&lt;/td&gt;
      &lt;td class=&quot;td-udp&quot;&gt;빠름&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;헤더 크기&lt;/td&gt;
      &lt;td&gt;20~60 byte&lt;/td&gt;
      &lt;td class=&quot;td-udp&quot;&gt;8 byte&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;흐름 제어&lt;/td&gt;
      &lt;td class=&quot;td-tcp&quot;&gt;있음&lt;/td&gt;
      &lt;td&gt;없음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;혼잡 제어&lt;/td&gt;
      &lt;td class=&quot;td-tcp&quot;&gt;있음&lt;/td&gt;
      &lt;td&gt;없음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;통신 방식&lt;/td&gt;
      &lt;td&gt;1:1 (유니캐스트)&lt;/td&gt;
      &lt;td class=&quot;td-udp&quot;&gt;1:1 / 1:N 가능&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;사용 예시&lt;/td&gt;
      &lt;td&gt;HTTP, HTTPS, FTP, 이메일&lt;/td&gt;
      &lt;td class=&quot;td-udp&quot;&gt;스트리밍, 게임, DNS, VoIP&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

  &lt;h3&gt;실제 사용 예시&lt;/h3&gt;
  &lt;div class=&quot;use-grid&quot;&gt;
    &lt;div class=&quot;use-card&quot;&gt;
      &lt;div class=&quot;use-title&quot; style=&quot;color:#58a6ff;&quot;&gt;TCP 사용 — 정확성이 중요할 때&lt;/div&gt;
      &lt;div class=&quot;use-items&quot;&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(88,166,255,0.1);color:#58a6ff;border:1px solid rgba(88,166,255,0.3);&quot;&gt;HTTP/HTTPS&lt;/span&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(88,166,255,0.1);color:#58a6ff;border:1px solid rgba(88,166,255,0.3);&quot;&gt;FTP&lt;/span&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(88,166,255,0.1);color:#58a6ff;border:1px solid rgba(88,166,255,0.3);&quot;&gt;이메일(SMTP)&lt;/span&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(88,166,255,0.1);color:#58a6ff;border:1px solid rgba(88,166,255,0.3);&quot;&gt;파일 다운로드&lt;/span&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(88,166,255,0.1);color:#58a6ff;border:1px solid rgba(88,166,255,0.3);&quot;&gt;SSH&lt;/span&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(88,166,255,0.1);color:#58a6ff;border:1px solid rgba(88,166,255,0.3);&quot;&gt;DB 통신&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;use-card&quot;&gt;
      &lt;div class=&quot;use-title&quot; style=&quot;color:#ffa657;&quot;&gt;UDP 사용 — 속도가 중요할 때&lt;/div&gt;
      &lt;div class=&quot;use-items&quot;&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(255,166,87,0.1);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;YouTube 스트리밍&lt;/span&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(255,166,87,0.1);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;온라인 게임&lt;/span&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(255,166,87,0.1);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;DNS 조회&lt;/span&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(255,166,87,0.1);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;VoIP (인터넷 전화)&lt;/span&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(255,166,87,0.1);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;화상회의&lt;/span&gt;
        &lt;span class=&quot;use-item&quot; style=&quot;background:rgba(255,166,87,0.1);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;DHCP&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  UDP인데 왜 유튜브는 끊기지 않나요?&lt;/span&gt;
    UDP는 패킷이 손실돼도 재전송 없이 그냥 넘어가요.&lt;br&gt;
    스트리밍에서 1~2 프레임 손실은 &lt;strong&gt;화질 저하나 잠깐 끊기는 정도&lt;/strong&gt;고 치명적이지 않아요.&lt;br&gt;
    반면 TCP면 손실 시 재전송 대기 → &lt;strong&gt;전체적인 지연(버퍼링)이 더 심해져요.&lt;/strong&gt;&lt;br&gt;
    최근 HTTP/3는 UDP 기반의 &lt;strong&gt;QUIC 프로토콜&lt;/strong&gt;을 사용해요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 08. 면접 답변 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;08&lt;/span&gt; 면접 답변 정리&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;면접 예상 Q&amp;A&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;Q. TCP와 UDP의 차이를 설명해주세요.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tcp&quot;&gt;&quot;TCP는 연결 지향 프로토콜로 3-Way Handshake를 통해&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tcp&quot;&gt;연결을 맺고 데이터의 신뢰성과 순서를 보장합니다.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;udp&quot;&gt;UDP는 비연결 지향으로 연결 과정 없이 데이터를 전송하며&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;udp&quot;&gt;속도가 빠르지만 신뢰성은 보장하지 않습니다.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tcp&quot;&gt;HTTP, 파일 전송처럼 정확성이 중요할 때는 TCP를,&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;udp&quot;&gt;스트리밍, 게임처럼 속도가 중요할 때는 UDP를 사용합니다.&quot;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;Q. 3-Way Handshake를 설명해주세요.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tcp&quot;&gt;&quot;클라이언트가 SYN을 보내고, 서버가 SYN+ACK로 응답하면&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tcp&quot;&gt;클라이언트가 ACK를 보내 연결을 수립합니다.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tcp&quot;&gt;이 과정으로 양측이 서로 통신 준비가 됐음을 확인해요.&quot;&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;✅ CONCLUSION&lt;/span&gt;
    &lt;strong&gt;TCP = 신뢰성 / UDP = 속도&lt;/strong&gt;로 기억하세요.&lt;br&gt;
    면접에서 3-Way Handshake 순서 (SYN → SYN+ACK → ACK)와&lt;br&gt;
    각각의 사용 사례(HTTP vs 스트리밍)까지 말할 수 있으면 충분해요.&lt;br&gt;
    여유가 있다면 &lt;strong&gt;TIME_WAIT, 흐름 제어, HTTP/3의 QUIC&lt;/strong&gt;까지 언급하면 플러스!
  &lt;/div&gt;

  &lt;div class=&quot;post-tags&quot;&gt;
    &lt;span class=&quot;post-tag&quot;&gt;네트워크&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;TCP&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;UDP&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;3-Way Handshake&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;4-Way Handshake&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;CS면접&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;흐름제어&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;혼잡제어&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;</description>
      <category>  CS 면접 대비/네크워크</category>
      <author>woohaha</author>
      <guid isPermaLink="true">https://story2248.tistory.com/20</guid>
      <comments>https://story2248.tistory.com/20#entry20comment</comments>
      <pubDate>Sat, 23 May 2026 08:07:23 +0900</pubDate>
    </item>
    <item>
      <title>[SQL] BEGIN/END 다중쿼리 완전 정복 - 트랜잭션, 커서, 프로시저</title>
      <link>https://story2248.tistory.com/19</link>
      <description>&lt;style&gt;
  .post-wrap {
    background: #0d1117; color: #e6edf3;
    font-family: 'Pretendard', -apple-system, sans-serif;
    padding: 0; line-height: 1.8;
  }
  .post-wrap * { box-sizing: border-box; }
  .post-wrap p { margin: 0 0 16px; color: #8b949e; font-size: 15px; }
  .post-wrap p strong { color: #e6edf3; }
  .post-divider { border: none; border-top: 1px solid #21262d; margin: 32px 0; }

  .post-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px;
    background: rgba(88,166,255,0.1); border: 1px solid rgba(88,166,255,0.3);
    border-radius: 20px; font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: #58a6ff; margin-bottom: 20px;
  }
  .post-badge::before { content: ' ️'; }

  /* 목차 */
  .toc { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 24px 0; }
  .toc-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .toc-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .toc-item { font-size: 13px; color: #8b949e; padding: 3px 0; display: flex; align-items: center; gap: 8px; }
  .toc-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; }

  .post-wrap h2 {
    font-size: 18px; font-weight: 600; color: #e6edf3;
    margin: 32px 0 16px; padding-bottom: 8px;
    border-bottom: 1px solid #21262d;
    display: flex; align-items: center; gap: 8px;
  }
  .post-wrap h2 .num {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #58a6ff; background: rgba(88,166,255,0.1);
    border: 1px solid rgba(88,166,255,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h3 {
    font-size: 15px; font-weight: 600; color: #8b949e;
    margin: 20px 0 10px; font-family: 'JetBrains Mono', monospace;
  }
  .post-wrap h3::before { content: '// '; color: #484f58; }

  /* DB 뱃지 */
  .db-badges { display: flex; gap: 6px; margin-bottom: 14px; flex-wrap: wrap; }
  .db-badge { font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 5px; }
  .db-ora { background: rgba(255,166,87,0.15); color: #ffa657; border: 1px solid rgba(255,166,87,0.3); }
  .db-my  { background: rgba(63,185,80,0.15);  color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .db-ms  { background: rgba(88,166,255,0.15); color: #58a6ff; border: 1px solid rgba(88,166,255,0.3); }
  .db-all { background: rgba(188,140,255,0.15);color: #bc8cff; border: 1px solid rgba(188,140,255,0.3); }

  /* 코드 블럭 */
  .code-block { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; margin: 12px 0; overflow: hidden; }
  .code-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; background: #161b22; border-bottom: 1px solid #30363d; }
  .code-lang { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; letter-spacing: 0.05em; }
  .code-dots { display: flex; gap: 5px; }
  .code-dots span { width: 10px; height: 10px; border-radius: 50%; }
  .code-dots span:nth-child(1) { background: #ff5f56; }
  .code-dots span:nth-child(2) { background: #ffbd2e; }
  .code-dots span:nth-child(3) { background: #27c93f; }
  .code-body { padding: 18px 22px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.85; overflow-x: auto; color: #e6edf3; }
  .kw  { color: #ff7b72; } .fn  { color: #d2a8ff; }
  .cm  { color: #484f58; font-style: italic; } .str { color: #a5d6ff; }
  .num { color: #79c0ff; }
  .ora { color: #ffa657; font-weight: 600; }
  .my  { color: #3fb950; font-weight: 600; }
  .ms  { color: #58a6ff; font-weight: 600; }
  .good { color: #3fb950; } .bad { color: #ff7b72; }

  /* 흐름 시각화 */
  .flow-box { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 16px 0; font-family: 'JetBrains Mono', monospace; }
  .fb-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 14px; display: block; }
  .fb-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
  .fb-item { padding: 7px 14px; border-radius: 6px; font-size: 12px; font-weight: 600; }
  .fi-start  { background: rgba(63,185,80,0.15);  color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .fi-work   { background: rgba(88,166,255,0.15); color: #58a6ff; border: 1px solid rgba(88,166,255,0.3); }
  .fi-commit { background: rgba(63,185,80,0.15);  color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .fi-roll   { background: rgba(255,123,114,0.15);color: #ff7b72; border: 1px solid rgba(255,123,114,0.3); }
  .fi-save   { background: rgba(255,166,87,0.15); color: #ffa657; border: 1px solid rgba(255,166,87,0.3); }
  .fb-arrow  { color: #484f58; font-size: 16px; }
  .fb-label  { font-size: 11px; color: #484f58; font-family: 'Pretendard', sans-serif; }

  /* 비교 테이블 */
  .compare-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
  .compare-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 10px 14px; border: 1px solid #30363d; letter-spacing: 0.05em; text-align: left; }
  .compare-table td { padding: 10px 14px; border: 1px solid #30363d; color: #e6edf3; }
  .compare-table tr:nth-child(even) td { background: #161b22; }
  .td-good { color: #3fb950 !important; font-weight: 700; }
  .td-bad  { color: #ff7b72 !important; }

  /* tip / warn 박스 */
  .tip-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #58a6ff; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .tip-box strong { color: #e6edf3; }
  .tip-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .warn-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ffa657; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .warn-box strong { color: #e6edf3; }
  .warn-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }

  .ic { font-family: 'JetBrains Mono', monospace; font-size: 0.88em; padding: 1px 6px; background: #1c2128; border: 1px solid #30363d; border-radius: 4px; color: #ffa657; }
  .post-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 32px; padding-top: 20px; border-top: 1px solid #21262d; }
  .post-tag { padding: 3px 10px; background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.25); border-radius: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; }
  .post-tag::before { content: '#'; opacity: 0.6; }
  @media (max-width: 600px) { .toc-list { grid-template-columns: 1fr; } }
&lt;/style&gt;

&lt;div class=&quot;post-wrap&quot;&gt;
  &lt;div class=&quot;post-badge&quot;&gt;SQL · 코딩테스트 / 실무&lt;/div&gt;

  &lt;p&gt;SQL에서 여러 쿼리를 묶어서 실행하거나, 조건에 따라 분기하거나,&lt;br&gt;
  반복 처리하는 방법들이 있어요.&lt;br&gt;
  &lt;strong&gt;BEGIN, 다중 DML, 프로시저 기초&lt;/strong&gt;까지 한 번에 정리해드릴게요.&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
    &lt;span class=&quot;toc-title&quot;&gt;// TABLE OF CONTENTS&lt;/span&gt;
    &lt;div class=&quot;toc-list&quot;&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;01&lt;/span&gt; BEGIN / END 블록&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;02&lt;/span&gt; 다중 DML 트랜잭션&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;03&lt;/span&gt; SAVEPOINT 부분 롤백&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;04&lt;/span&gt; IF / CASE 조건 분기&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;05&lt;/span&gt; LOOP / WHILE 반복&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;06&lt;/span&gt; 커서 (CURSOR)&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;07&lt;/span&gt; 프로시저 기초&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;08&lt;/span&gt; DB별 문법 비교&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 01. BEGIN / END --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;01&lt;/span&gt; BEGIN / END 블록&lt;/h2&gt;

  &lt;p&gt;여러 SQL 문장을 &lt;strong&gt;하나의 블록으로 묶는&lt;/strong&gt; 구조예요.&lt;br&gt;
  트랜잭션 제어, 조건 분기, 예외 처리 등을 함께 쓸 수 있어요.&lt;/p&gt;

  &lt;div class=&quot;db-badges&quot;&gt;
    &lt;span class=&quot;db-badge db-ora&quot;&gt;Oracle — BEGIN..END (PL/SQL)&lt;/span&gt;
    &lt;span class=&quot;db-badge db-my&quot;&gt;MySQL — BEGIN..END&lt;/span&gt;
    &lt;span class=&quot;db-badge db-ms&quot;&gt;MSSQL — BEGIN..END&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;BEGIN / END 기본 구조&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ Oracle PL/SQL ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE - &lt;span class=&quot;num&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ID = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE + &lt;span class=&quot;num&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ID = &lt;span class=&quot;str&quot;&gt;'B'&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;COMMIT&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;EXCEPTION&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHEN OTHERS THEN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;ROLLBACK&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;DBMS_OUTPUT.PUT_LINE&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'오류: '&lt;/span&gt; || &lt;span class=&quot;fn&quot;&gt;SQLERRM&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ MySQL ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;START TRANSACTION&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE - &lt;span class=&quot;num&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ID = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE + &lt;span class=&quot;num&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ID = &lt;span class=&quot;str&quot;&gt;'B'&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;COMMIT&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ MSSQL ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN TRANSACTION&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN TRY&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE - &lt;span class=&quot;num&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ID = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE + &lt;span class=&quot;num&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ID = &lt;span class=&quot;str&quot;&gt;'B'&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;COMMIT TRANSACTION&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END TRY&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN CATCH&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;ROLLBACK TRANSACTION&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END CATCH&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  DB별 예외 처리 키워드&lt;/span&gt;
    &lt;strong&gt;Oracle&lt;/strong&gt; → &lt;span class=&quot;ic&quot;&gt;EXCEPTION WHEN OTHERS THEN&lt;/span&gt;&lt;br&gt;
    &lt;strong&gt;MySQL&lt;/strong&gt; → &lt;span class=&quot;ic&quot;&gt;DECLARE ... HANDLER FOR SQLEXCEPTION&lt;/span&gt; (프로시저 내)&lt;br&gt;
    &lt;strong&gt;MSSQL&lt;/strong&gt; → &lt;span class=&quot;ic&quot;&gt;BEGIN TRY ... END TRY / BEGIN CATCH ... END CATCH&lt;/span&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 02. 다중 DML 트랜잭션 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;02&lt;/span&gt; 다중 DML 트랜잭션 — 묶어서 처리&lt;/h2&gt;

  &lt;p&gt;여러 INSERT / UPDATE / DELETE를 &lt;strong&gt;하나의 트랜잭션으로 묶어&lt;/strong&gt;&lt;br&gt;
  전부 성공하거나 전부 실패하게 만드는 패턴이에요.&lt;/p&gt;

  &lt;div class=&quot;flow-box&quot;&gt;
    &lt;span class=&quot;fb-title&quot;&gt;// 다중 DML 트랜잭션 흐름&lt;/span&gt;
    &lt;div class=&quot;fb-row&quot;&gt;
      &lt;span class=&quot;fb-item fi-start&quot;&gt;BEGIN&lt;/span&gt;
      &lt;span class=&quot;fb-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fb-item fi-work&quot;&gt;INSERT&lt;/span&gt;
      &lt;span class=&quot;fb-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fb-item fi-work&quot;&gt;UPDATE&lt;/span&gt;
      &lt;span class=&quot;fb-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fb-item fi-work&quot;&gt;DELETE&lt;/span&gt;
      &lt;span class=&quot;fb-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fb-item fi-commit&quot;&gt;COMMIT ✅&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;fb-row&quot; style=&quot;margin-top:6px;&quot;&gt;
      &lt;span class=&quot;fb-item fi-start&quot;&gt;BEGIN&lt;/span&gt;
      &lt;span class=&quot;fb-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fb-item fi-work&quot;&gt;INSERT&lt;/span&gt;
      &lt;span class=&quot;fb-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fb-item fi-work&quot;&gt;UPDATE&lt;/span&gt;
      &lt;span class=&quot;fb-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fb-item&quot; style=&quot;background:rgba(255,123,114,0.15);color:#ff7b72;border:1px solid rgba(255,123,114,0.3);&quot;&gt;ERROR!&lt;/span&gt;
      &lt;span class=&quot;fb-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fb-item fi-roll&quot;&gt;ROLLBACK ❌&lt;/span&gt;
      &lt;span class=&quot;fb-label&quot;&gt;← 전부 취소&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;실무 패턴 — 주문 처리 (Oracle PL/SQL)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;kw&quot;&gt;DECLARE&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;V_ORDER_ID  NUMBER;&lt;br&gt;
&amp;nbsp;&amp;nbsp;V_STOCK     NUMBER;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 1. 재고 확인&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; STOCK_QTY &lt;span class=&quot;kw&quot;&gt;INTO&lt;/span&gt; V_STOCK&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   PRODUCT&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  PROD_CD = &lt;span class=&quot;str&quot;&gt;'P001'&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FOR UPDATE&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- 배타락 획득&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;IF&lt;/span&gt; V_STOCK &lt; &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;THEN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;RAISE_APPLICATION_ERROR&lt;/span&gt;(&lt;span class=&quot;num&quot;&gt;-20001&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'재고 부족'&lt;/span&gt;);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;END IF&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 2. 주문 생성&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;INSERT INTO&lt;/span&gt; ORDERS (ORDER_ID, PROD_CD, QTY, STATUS)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;VALUES&lt;/span&gt; (SEQ_ORDER.NEXTVAL, &lt;span class=&quot;str&quot;&gt;'P001'&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'주문완료'&lt;/span&gt;);&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 3. 재고 차감&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; PRODUCT&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt;    STOCK_QTY = STOCK_QTY - &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  PROD_CD = &lt;span class=&quot;str&quot;&gt;'P001'&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 4. 주문 이력 기록&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;INSERT INTO&lt;/span&gt; ORDER_HIST (PROD_CD, ACTION, ACT_DT)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;VALUES&lt;/span&gt; (&lt;span class=&quot;str&quot;&gt;'P001'&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;'주문'&lt;/span&gt;, SYSDATE);&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;COMMIT&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- 전부 성공 시 확정&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;EXCEPTION&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHEN OTHERS THEN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;ROLLBACK&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- 하나라도 실패하면 전부 취소&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;DBMS_OUTPUT.PUT_LINE&lt;/span&gt;(&lt;span class=&quot;fn&quot;&gt;SQLERRM&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 03. SAVEPOINT --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;03&lt;/span&gt; SAVEPOINT — 부분 롤백&lt;/h2&gt;

  &lt;p&gt;트랜잭션 중간에 &lt;strong&gt;체크포인트를 설정&lt;/strong&gt;해서 특정 지점까지만 롤백할 수 있어요.&lt;/p&gt;

  &lt;div class=&quot;db-badges&quot;&gt;
    &lt;span class=&quot;db-badge db-ora&quot;&gt;Oracle&lt;/span&gt;
    &lt;span class=&quot;db-badge db-my&quot;&gt;MySQL&lt;/span&gt;
    &lt;span class=&quot;db-badge db-ms&quot;&gt;MSSQL — SAVE TRANSACTION&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;SAVEPOINT 활용&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;-- Oracle / MySQL&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;INSERT INTO&lt;/span&gt; LOG_TABLE &lt;span class=&quot;kw&quot;&gt;VALUES&lt;/span&gt; (&lt;span class=&quot;str&quot;&gt;'작업시작'&lt;/span&gt;, SYSDATE);&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SAVEPOINT&lt;/span&gt; SP1; &lt;span class=&quot;cm&quot;&gt;-- 체크포인트 1 설정&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; EMP &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; SALARY = SALARY * &lt;span class=&quot;num&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; DEPT_CD = &lt;span class=&quot;str&quot;&gt;'10'&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SAVEPOINT&lt;/span&gt; SP2; &lt;span class=&quot;cm&quot;&gt;-- 체크포인트 2 설정&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; EMP &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; SALARY = SALARY * &lt;span class=&quot;num&quot;&gt;1.2&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; DEPT_CD = &lt;span class=&quot;str&quot;&gt;'20'&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 20번 부서 업데이트만 취소하고 싶을 때&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;ROLLBACK TO&lt;/span&gt; SP2; &lt;span class=&quot;cm&quot;&gt;-- SP2 이후만 취소 (SP1까지는 유지)&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;COMMIT&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- INSERT + 10번 부서 UPDATE만 반영&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- MSSQL은 문법이 다름&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN TRANSACTION&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;ms&quot;&gt;SAVE TRANSACTION&lt;/span&gt; SP1&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; EMP &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; SALARY = SALARY * &lt;span class=&quot;num&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; DEPT_CD = &lt;span class=&quot;str&quot;&gt;'10'&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;ms&quot;&gt;ROLLBACK TRANSACTION&lt;/span&gt; SP1 &lt;span class=&quot;cm&quot;&gt;-- SP1으로 부분 롤백&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;COMMIT&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 04. IF / CASE 조건 분기 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;04&lt;/span&gt; IF / CASE — 조건 분기&lt;/h2&gt;

  &lt;p&gt;PL/SQL / T-SQL에서 &lt;strong&gt;조건에 따라 다른 SQL을 실행&lt;/strong&gt;하는 구조예요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;IF / CASE 조건 분기 — DB별 비교&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ Oracle PL/SQL ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;DECLARE&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;V_SCORE NUMBER := &lt;span class=&quot;num&quot;&gt;85&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;V_GRADE VARCHAR2(&lt;span class=&quot;num&quot;&gt;10&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;IF&lt;/span&gt; V_SCORE &gt;= &lt;span class=&quot;num&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;THEN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;V_GRADE := &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;ELSIF&lt;/span&gt; V_SCORE &gt;= &lt;span class=&quot;num&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;THEN&lt;/span&gt;   &lt;span class=&quot;cm&quot;&gt;-- ELSE IF 아닌 ELSIF!&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;V_GRADE := &lt;span class=&quot;str&quot;&gt;'B'&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;ELSIF&lt;/span&gt; V_SCORE &gt;= &lt;span class=&quot;num&quot;&gt;70&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;THEN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;V_GRADE := &lt;span class=&quot;str&quot;&gt;'C'&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;ELSE&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;V_GRADE := &lt;span class=&quot;str&quot;&gt;'F'&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;END IF&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;DBMS_OUTPUT.PUT_LINE&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'등급: '&lt;/span&gt; || V_GRADE);&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ MySQL ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;IF&lt;/span&gt; V_SCORE &gt;= &lt;span class=&quot;num&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;THEN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; V_GRADE = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;ELSEIF&lt;/span&gt; V_SCORE &gt;= &lt;span class=&quot;num&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;THEN&lt;/span&gt;   &lt;span class=&quot;cm&quot;&gt;-- ELSEIF (붙여서)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; V_GRADE = &lt;span class=&quot;str&quot;&gt;'B'&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;ELSE&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; V_GRADE = &lt;span class=&quot;str&quot;&gt;'F'&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END IF&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ MSSQL ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;IF&lt;/span&gt; @SCORE &gt;= &lt;span class=&quot;num&quot;&gt;90&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; @GRADE = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;ELSE IF&lt;/span&gt; @SCORE &gt;= &lt;span class=&quot;num&quot;&gt;80&lt;/span&gt;    &lt;span class=&quot;cm&quot;&gt;-- ELSE IF (띄어서)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; @GRADE = &lt;span class=&quot;str&quot;&gt;'B'&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;ELSE&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; @GRADE = &lt;span class=&quot;str&quot;&gt;'F'&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ DB별 키워드 차이&lt;/span&gt;
    &lt;strong&gt;Oracle&lt;/strong&gt; → &lt;span class=&quot;ic&quot;&gt;ELSIF&lt;/span&gt; (E 없음)&lt;br&gt;
    &lt;strong&gt;MySQL&lt;/strong&gt; → &lt;span class=&quot;ic&quot;&gt;ELSEIF&lt;/span&gt; (공백 없음)&lt;br&gt;
    &lt;strong&gt;MSSQL&lt;/strong&gt; → &lt;span class=&quot;ic&quot;&gt;ELSE IF&lt;/span&gt; (공백 있음)&lt;br&gt;
    헷갈리는 포인트니까 꼭 기억해두세요!
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 05. LOOP / WHILE --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;05&lt;/span&gt; LOOP / WHILE — 반복 처리&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;반복문 — DB별 비교&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ Oracle — 3가지 반복문 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 1. LOOP (무한 루프 + EXIT)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;DECLARE&lt;/span&gt; I NUMBER := &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;LOOP&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;DBMS_OUTPUT.PUT_LINE&lt;/span&gt;(I);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;I := I + &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;EXIT WHEN&lt;/span&gt; I &gt; &lt;span class=&quot;num&quot;&gt;5&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- 탈출 조건&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;END LOOP&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 2. WHILE LOOP&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;DECLARE&lt;/span&gt; I NUMBER := &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHILE&lt;/span&gt; I &lt;= &lt;span class=&quot;num&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;LOOP&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;DBMS_OUTPUT.PUT_LINE&lt;/span&gt;(I);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;I := I + &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;END LOOP&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 3. FOR LOOP (범위 반복)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FOR&lt;/span&gt; I &lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;..&lt;span class=&quot;num&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;LOOP&lt;/span&gt;   &lt;span class=&quot;cm&quot;&gt;-- 1부터 5까지&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;DBMS_OUTPUT.PUT_LINE&lt;/span&gt;(I);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;END LOOP&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ MySQL / MSSQL — WHILE ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- MySQL&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; @I = &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHILE&lt;/span&gt; @I &lt;= &lt;span class=&quot;num&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;DO&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;INSERT INTO&lt;/span&gt; TEST &lt;span class=&quot;kw&quot;&gt;VALUES&lt;/span&gt; (@I);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; @I = @I + &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END WHILE&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- MSSQL&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;DECLARE&lt;/span&gt; @I INT = &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHILE&lt;/span&gt; @I &lt;= &lt;span class=&quot;num&quot;&gt;5&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;INSERT INTO&lt;/span&gt; TEST &lt;span class=&quot;kw&quot;&gt;VALUES&lt;/span&gt; (@I)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; @I = @I + &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 06. 커서 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;06&lt;/span&gt; 커서 (CURSOR) — 행 단위 처리&lt;/h2&gt;

  &lt;p&gt;SELECT 결과를 &lt;strong&gt;한 행씩 순서대로 처리&lt;/strong&gt;할 때 사용해요.&lt;br&gt;
  보통 집합 연산으로 처리 불가능할 때 커서를 써요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;커서 — Oracle PL/SQL&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;kw&quot;&gt;DECLARE&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 커서 선언&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;CURSOR&lt;/span&gt; C_EMP &lt;span class=&quot;kw&quot;&gt;IS&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_ID, EMP_NM, SALARY&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  DEPT_CD = &lt;span class=&quot;str&quot;&gt;'10'&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;V_EMP_ID  EMP.EMP_ID%TYPE;&lt;br&gt;
&amp;nbsp;&amp;nbsp;V_EMP_NM  EMP.EMP_NM%TYPE;&lt;br&gt;
&amp;nbsp;&amp;nbsp;V_SALARY  EMP.SALARY%TYPE;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;OPEN&lt;/span&gt; C_EMP; &lt;span class=&quot;cm&quot;&gt;-- 커서 열기&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;LOOP&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FETCH&lt;/span&gt; C_EMP &lt;span class=&quot;kw&quot;&gt;INTO&lt;/span&gt; V_EMP_ID, V_EMP_NM, V_SALARY; &lt;span class=&quot;cm&quot;&gt;-- 한 행씩 가져오기&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;EXIT WHEN&lt;/span&gt; C_EMP%NOTFOUND; &lt;span class=&quot;cm&quot;&gt;-- 더 이상 행 없으면 탈출&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 행별 처리 로직&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; EMP&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt;    SALARY = V_SALARY * &lt;span class=&quot;num&quot;&gt;1.1&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  EMP_ID = V_EMP_ID;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;END LOOP&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;CLOSE&lt;/span&gt; C_EMP; &lt;span class=&quot;cm&quot;&gt;-- 커서 닫기&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;COMMIT&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- FOR 커서 (더 간단한 방식)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FOR&lt;/span&gt; REC &lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt; (&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_ID, SALARY &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; EMP &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; DEPT_CD = &lt;span class=&quot;str&quot;&gt;'10'&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;LOOP&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; EMP &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; SALARY = REC.SALARY * &lt;span class=&quot;num&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; EMP_ID = REC.EMP_ID;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;END LOOP&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- OPEN/FETCH/CLOSE 자동 처리!&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;COMMIT&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ 커서 성능 주의&lt;/span&gt;
    커서는 행 단위로 처리하므로 &lt;strong&gt;대량 데이터에는 매우 느려요.&lt;/strong&gt;&lt;br&gt;
    가능하면 &lt;strong&gt;집합 기반 SQL(UPDATE/INSERT SELECT)&lt;/strong&gt;로 대체하는 게 훨씬 빨라요.&lt;br&gt;
    커서는 행별로 다른 로직이 필요할 때만 사용하세요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 07. 프로시저 기초 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;07&lt;/span&gt; 프로시저 기초 (Stored Procedure)&lt;/h2&gt;

  &lt;p&gt;자주 쓰는 SQL 로직을 &lt;strong&gt;DB에 저장&lt;/strong&gt;해두고 이름으로 호출하는 방식이에요.&lt;br&gt;
  ERP 개발에서 복잡한 마감 처리나 배치 작업에 많이 사용해요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;프로시저 생성 &amp; 호출 — DB별 비교&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ Oracle ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;CREATE OR REPLACE PROCEDURE&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;SP_SALARY_UPDATE&lt;/span&gt; (&lt;br&gt;
&amp;nbsp;&amp;nbsp;P_DEPT_CD  &lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt;  VARCHAR2,    &lt;span class=&quot;cm&quot;&gt;-- 입력 파라미터&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;P_RATE     &lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt;  NUMBER,&lt;br&gt;
&amp;nbsp;&amp;nbsp;P_CNT      &lt;span class=&quot;kw&quot;&gt;OUT&lt;/span&gt; NUMBER      &lt;span class=&quot;cm&quot;&gt;-- 출력 파라미터&lt;/span&gt;&lt;br&gt;
)&lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; EMP&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt;    SALARY = SALARY * (&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt; + P_RATE / &lt;span class=&quot;num&quot;&gt;100&lt;/span&gt;)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  DEPT_CD = P_DEPT_CD;&lt;br&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;P_CNT := SQL%ROWCOUNT; &lt;span class=&quot;cm&quot;&gt;-- 영향받은 행 수&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;COMMIT&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;EXCEPTION&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHEN OTHERS THEN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;ROLLBACK&lt;/span&gt;;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;RAISE&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 호출&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;DECLARE&lt;/span&gt; V_CNT NUMBER;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;SP_SALARY_UPDATE&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'10'&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;10&lt;/span&gt;, V_CNT);&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;DBMS_OUTPUT.PUT_LINE&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'처리건수: '&lt;/span&gt; || V_CNT);&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;;&lt;br&gt;
/&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ MySQL ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;DELIMITER&lt;/span&gt; //&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;CREATE PROCEDURE&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;SP_SALARY_UPDATE&lt;/span&gt;(&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt;  P_DEPT_CD VARCHAR(&lt;span class=&quot;num&quot;&gt;10&lt;/span&gt;),&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt;  P_RATE    DECIMAL(&lt;span class=&quot;num&quot;&gt;5&lt;/span&gt;,&lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;),&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;OUT&lt;/span&gt; P_CNT     INT&lt;br&gt;
)&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; EMP&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt;    SALARY = SALARY * (&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt; + P_RATE / &lt;span class=&quot;num&quot;&gt;100&lt;/span&gt;)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  DEPT_CD = P_DEPT_CD;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; P_CNT = ROW_COUNT();&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;//&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;DELIMITER&lt;/span&gt; ;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 호출&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;CALL&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;SP_SALARY_UPDATE&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;'10'&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;10&lt;/span&gt;, @CNT);&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; @CNT;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ MSSQL ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;CREATE PROCEDURE&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;SP_SALARY_UPDATE&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;@DEPT_CD VARCHAR(&lt;span class=&quot;num&quot;&gt;10&lt;/span&gt;),&lt;br&gt;
&amp;nbsp;&amp;nbsp;@RATE    DECIMAL(&lt;span class=&quot;num&quot;&gt;5&lt;/span&gt;,&lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;),&lt;br&gt;
&amp;nbsp;&amp;nbsp;@CNT     INT &lt;span class=&quot;kw&quot;&gt;OUTPUT&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; EMP&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt;    SALARY = SALARY * (&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt; + @RATE / &lt;span class=&quot;num&quot;&gt;100&lt;/span&gt;)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  DEPT_CD = @DEPT_CD&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; @CNT = @@ROWCOUNT&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;END&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 호출&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;EXEC&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;SP_SALARY_UPDATE&lt;/span&gt; &lt;span class=&quot;str&quot;&gt;'10'&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;10&lt;/span&gt;, @CNT &lt;span class=&quot;kw&quot;&gt;OUTPUT&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 08. DB별 비교 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;08&lt;/span&gt; DB별 문법 한눈에 비교&lt;/h2&gt;

  &lt;table class=&quot;compare-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;구분&lt;/th&gt;&lt;th&gt;Oracle&lt;/th&gt;&lt;th&gt;MySQL&lt;/th&gt;&lt;th&gt;MSSQL&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;블록 시작&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;BEGIN&lt;/td&gt;
      &lt;td&gt;BEGIN&lt;/td&gt;
      &lt;td&gt;BEGIN&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;변수 선언&lt;/td&gt;
      &lt;td&gt;DECLARE 섹션&lt;/td&gt;
      &lt;td&gt;DECLARE 안에서&lt;/td&gt;
      &lt;td&gt;DECLARE @변수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;변수 할당&lt;/td&gt;
      &lt;td&gt;V := 값&lt;/td&gt;
      &lt;td&gt;SET V = 값&lt;/td&gt;
      &lt;td&gt;SET @V = 값&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;조건 분기&lt;/td&gt;
      &lt;td&gt;IF / ELSIF / END IF&lt;/td&gt;
      &lt;td&gt;IF / ELSEIF / END IF&lt;/td&gt;
      &lt;td&gt;IF / ELSE IF / END&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;반복문&lt;/td&gt;
      &lt;td&gt;LOOP / WHILE / FOR&lt;/td&gt;
      &lt;td&gt;WHILE DO / END WHILE&lt;/td&gt;
      &lt;td&gt;WHILE BEGIN END&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;예외 처리&lt;/td&gt;
      &lt;td&gt;EXCEPTION WHEN&lt;/td&gt;
      &lt;td&gt;DECLARE HANDLER&lt;/td&gt;
      &lt;td&gt;TRY / CATCH&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;프로시저 호출&lt;/td&gt;
      &lt;td&gt;프로시저명()&lt;/td&gt;
      &lt;td&gt;CALL 프로시저명()&lt;/td&gt;
      &lt;td&gt;EXEC 프로시저명&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;출력&lt;/td&gt;
      &lt;td&gt;DBMS_OUTPUT.PUT_LINE&lt;/td&gt;
      &lt;td&gt;SELECT '값'&lt;/td&gt;
      &lt;td&gt;PRINT '값'&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;✅ CONCLUSION&lt;/span&gt;
    &lt;strong&gt;BEGIN/END&lt;/strong&gt;로 여러 SQL을 묶고, &lt;strong&gt;COMMIT/ROLLBACK&lt;/strong&gt;으로 트랜잭션을 제어해요.&lt;br&gt;
    조건 분기는 &lt;strong&gt;IF/ELSIF&lt;/strong&gt;, 반복은 &lt;strong&gt;LOOP/WHILE/FOR&lt;/strong&gt;, 행별 처리는 &lt;strong&gt;커서&lt;/strong&gt;를 사용해요.&lt;br&gt;
    자주 쓰는 로직은 &lt;strong&gt;프로시저&lt;/strong&gt;로 저장해두면 재사용성이 높아져요.&lt;br&gt;
    ERP 개발에서는 Oracle PL/SQL로 복잡한 마감/정산 로직을 많이 구현하니 익혀두면 유용해요!
  &lt;/div&gt;

  &lt;div class=&quot;post-tags&quot;&gt;
    &lt;span class=&quot;post-tag&quot;&gt;SQL&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;PL/SQL&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;BEGIN&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;트랜잭션&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;SAVEPOINT&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;커서&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;프로시저&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;Oracle&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;MySQL&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;MSSQL&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;</description>
      <category>  코딩테스트/SQL</category>
      <author>woohaha</author>
      <guid isPermaLink="true">https://story2248.tistory.com/19</guid>
      <comments>https://story2248.tistory.com/19#entry19comment</comments>
      <pubDate>Mon, 18 May 2026 19:42:17 +0900</pubDate>
    </item>
    <item>
      <title>[SQL] 서브쿼리 완전 정복 - 스칼라/인라인뷰/EXISTS/상관 서브쿼리 한 번에</title>
      <link>https://story2248.tistory.com/18</link>
      <description>&lt;style&gt;
  .post-wrap {
    background: #0d1117; color: #e6edf3;
    font-family: 'Pretendard', -apple-system, sans-serif;
    padding: 0; line-height: 1.8;
  }
  .post-wrap * { box-sizing: border-box; }
  .post-wrap p { margin: 0 0 16px; color: #8b949e; font-size: 15px; }
  .post-wrap p strong { color: #e6edf3; }
  .post-divider { border: none; border-top: 1px solid #21262d; margin: 32px 0; }

  .post-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px;
    background: rgba(88,166,255,0.1); border: 1px solid rgba(88,166,255,0.3);
    border-radius: 20px; font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: #58a6ff; margin-bottom: 20px;
  }
  .post-badge::before { content: ' ️'; }

  /* 목차 */
  .toc { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 24px 0; }
  .toc-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .toc-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .toc-item { font-size: 13px; color: #8b949e; padding: 3px 0; display: flex; align-items: center; gap: 8px; }
  .toc-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; }

  .post-wrap h2 {
    font-size: 18px; font-weight: 600; color: #e6edf3;
    margin: 32px 0 16px; padding-bottom: 8px;
    border-bottom: 1px solid #21262d;
    display: flex; align-items: center; gap: 8px;
  }
  .post-wrap h2 .num {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #58a6ff; background: rgba(88,166,255,0.1);
    border: 1px solid rgba(88,166,255,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h3 {
    font-size: 15px; font-weight: 600; color: #8b949e;
    margin: 20px 0 10px; font-family: 'JetBrains Mono', monospace;
  }
  .post-wrap h3::before { content: '// '; color: #484f58; }

  /* 위치 뱃지 */
  .pos-badges { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
  .pos-badge { font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 5px; }
  .pb-select { background: rgba(88,166,255,0.15); color: #58a6ff; border: 1px solid rgba(88,166,255,0.3); }
  .pb-from   { background: rgba(63,185,80,0.15);  color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .pb-where  { background: rgba(188,140,255,0.15);color: #bc8cff; border: 1px solid rgba(188,140,255,0.3); }
  .pb-having { background: rgba(255,166,87,0.15); color: #ffa657; border: 1px solid rgba(255,166,87,0.3); }
  .pb-all    { background: rgba(57,211,83,0.15);  color: #39d353; border: 1px solid rgba(57,211,83,0.3); }

  /* 결과 테이블 */
  .result-table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 13px; }
  .result-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 8px 14px; border: 1px solid #30363d; text-align: left; }
  .result-table td { padding: 8px 14px; border: 1px solid #30363d; color: #e6edf3; font-family: 'JetBrains Mono', monospace; font-size: 12px; }
  .result-table tr:nth-child(even) td { background: #161b22; }
  .hi { color: #3fb950 !important; font-weight: 700; }
  .lo { color: #ff7b72 !important; }

  /* 코드 블럭 */
  .code-block { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; margin: 12px 0; overflow: hidden; }
  .code-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; background: #161b22; border-bottom: 1px solid #30363d; }
  .code-lang { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; letter-spacing: 0.05em; }
  .code-dots { display: flex; gap: 5px; }
  .code-dots span { width: 10px; height: 10px; border-radius: 50%; }
  .code-dots span:nth-child(1) { background: #ff5f56; }
  .code-dots span:nth-child(2) { background: #ffbd2e; }
  .code-dots span:nth-child(3) { background: #27c93f; }
  .code-body { padding: 18px 22px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.85; overflow-x: auto; color: #e6edf3; }
  .kw  { color: #ff7b72; } .fn  { color: #d2a8ff; }
  .cm  { color: #484f58; font-style: italic; } .str { color: #a5d6ff; }
  .num { color: #79c0ff; }
  .sub { color: #3fb950; } /* 서브쿼리 강조 */
  .good { color: #3fb950; } .bad { color: #ff7b72; }

  /* 비교 카드 */
  .compare-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 16px 0; }
  .ccard { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px 18px; }
  .ccard-title { font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 700; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid #21262d; }
  .ccard ul { margin: 0; padding: 0; list-style: none; }
  .ccard ul li { font-size: 13px; color: #8b949e; padding: 4px 0; border-bottom: 1px solid #21262d; line-height: 1.6; display: flex; gap: 8px; }
  .ccard ul li:last-child { border-bottom: none; }
  .ccard ul li::before { content: '·'; color: #484f58; flex-shrink: 0; }

  /* tip / warn 박스 */
  .tip-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #58a6ff; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .tip-box strong { color: #e6edf3; }
  .tip-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .warn-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ffa657; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .warn-box strong { color: #e6edf3; }
  .warn-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }

  /* 비교 테이블 */
  .compare-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
  .compare-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 10px 14px; border: 1px solid #30363d; letter-spacing: 0.05em; text-align: left; }
  .compare-table td { padding: 10px 14px; border: 1px solid #30363d; color: #e6edf3; }
  .compare-table tr:nth-child(even) td { background: #161b22; }
  .td-good { color: #3fb950 !important; font-weight: 700; }
  .td-bad  { color: #ff7b72 !important; }

  .ic { font-family: 'JetBrains Mono', monospace; font-size: 0.88em; padding: 1px 6px; background: #1c2128; border: 1px solid #30363d; border-radius: 4px; color: #ffa657; }
  .post-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 32px; padding-top: 20px; border-top: 1px solid #21262d; }
  .post-tag { padding: 3px 10px; background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.25); border-radius: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; }
  .post-tag::before { content: '#'; opacity: 0.6; }
  @media (max-width: 600px) {
    .toc-list { grid-template-columns: 1fr; }
    .compare-cards { grid-template-columns: 1fr; }
  }
&lt;/style&gt;

&lt;div class=&quot;post-wrap&quot;&gt;
  &lt;div class=&quot;post-badge&quot;&gt;SQL · 코딩테스트&lt;/div&gt;

  &lt;p&gt;서브쿼리는 &lt;strong&gt;쿼리 안에 또 다른 쿼리&lt;/strong&gt;를 넣는 것이에요.&lt;br&gt;
  위치에 따라 스칼라, 인라인 뷰, 중첩 서브쿼리로 나뉘고&lt;br&gt;
  각각 쓰임새가 달라요. 코테와 실무 모두 자주 나오니 확실히 정리해봐요!&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
    &lt;span class=&quot;toc-title&quot;&gt;// TABLE OF CONTENTS&lt;/span&gt;
    &lt;div class=&quot;toc-list&quot;&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;01&lt;/span&gt; 서브쿼리란?&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;02&lt;/span&gt; 스칼라 서브쿼리 (SELECT)&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;03&lt;/span&gt; 인라인 뷰 (FROM)&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;04&lt;/span&gt; 중첩 서브쿼리 (WHERE)&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;05&lt;/span&gt; IN / NOT IN&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;06&lt;/span&gt; EXISTS / NOT EXISTS&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;07&lt;/span&gt; 상관 서브쿼리&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;08&lt;/span&gt; 서브쿼리 vs JOIN&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 01. 서브쿼리란? --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;01&lt;/span&gt; 서브쿼리란?&lt;/h2&gt;

  &lt;p&gt;서브쿼리는 &lt;strong&gt;SQL 문장 안에 포함된 또 다른 SELECT 문&lt;/strong&gt;이에요.&lt;br&gt;
  위치에 따라 이름과 동작 방식이 달라져요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;서브쿼리 위치별 종류&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_NM,
       (&lt;span class=&quot;sub&quot;&gt;SELECT DEPT_NM FROM DEPT WHERE DEPT_CD = E.DEPT_CD&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt; DEPT_NM  &lt;span class=&quot;cm&quot;&gt;-- ① 스칼라&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;  (&lt;span class=&quot;sub&quot;&gt;SELECT * FROM EMP WHERE SALARY &gt; 3000000&lt;/span&gt;) E                     &lt;span class=&quot;cm&quot;&gt;-- ② 인라인뷰&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; EMP_ID &lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt; (&lt;span class=&quot;sub&quot;&gt;SELECT EMP_ID FROM AWARD WHERE YEAR = 2024&lt;/span&gt;);             &lt;span class=&quot;cm&quot;&gt;-- ③ 중첩(WHERE)&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;pos-badges&quot;&gt;
    &lt;span class=&quot;pos-badge pb-select&quot;&gt;① SELECT절 → 스칼라 서브쿼리&lt;/span&gt;
    &lt;span class=&quot;pos-badge pb-from&quot;&gt;② FROM절 → 인라인 뷰&lt;/span&gt;
    &lt;span class=&quot;pos-badge pb-where&quot;&gt;③ WHERE절 → 중첩 서브쿼리&lt;/span&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 02. 스칼라 서브쿼리 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;02&lt;/span&gt; 스칼라 서브쿼리 — SELECT절&lt;/h2&gt;

  &lt;p&gt;&lt;strong&gt;단 하나의 값(1행 1열)&lt;/strong&gt;을 반환하는 서브쿼리예요.&lt;br&gt;
  SELECT절에 넣어서 컬럼처럼 사용해요.&lt;/p&gt;

  &lt;div class=&quot;pos-badges&quot;&gt;&lt;span class=&quot;pos-badge pb-select&quot;&gt;SELECT절&lt;/span&gt;&lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;스칼라 서브쿼리&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;-- 사원별 부서명 조회 (JOIN 대신 스칼라 서브쿼리)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; E.EMP_NM,&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; E.SALARY,&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; D.DEPT_NM&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   DEPT D&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  D.DEPT_CD = E.DEPT_CD&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt; DEPT_NM,&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;AVG&lt;/span&gt;(SALARY) &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; EMP&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt; AVG_SAL  &lt;span class=&quot;cm&quot;&gt;-- 전체 평균 급여&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP E;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 결과: 각 행마다 서브쿼리 1번씩 실행&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- ⚠️ 행이 많으면 성능 주의!&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ 스칼라 서브쿼리 주의사항&lt;/span&gt;
    반드시 &lt;strong&gt;1행 1열&lt;/strong&gt;만 반환해야 해요. 2행 이상 반환 시 오류 발생!&lt;br&gt;
    결과가 없으면 &lt;strong&gt;NULL&lt;/strong&gt;을 반환해요.&lt;br&gt;
    각 행마다 서브쿼리가 실행되므로 &lt;strong&gt;데이터가 많으면 JOIN으로 대체&lt;/strong&gt;하는 게 성능상 유리해요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 03. 인라인 뷰 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;03&lt;/span&gt; 인라인 뷰 — FROM절&lt;/h2&gt;

  &lt;p&gt;FROM절에 서브쿼리를 넣어 &lt;strong&gt;가상 테이블처럼 사용&lt;/strong&gt;하는 방식이에요.&lt;br&gt;
  복잡한 쿼리를 단계별로 처리할 때 유용해요.&lt;/p&gt;

  &lt;div class=&quot;pos-badges&quot;&gt;&lt;span class=&quot;pos-badge pb-from&quot;&gt;FROM절&lt;/span&gt;&lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;인라인 뷰&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;-- 부서별 평균 급여보다 많이 받는 사원 조회&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; E.EMP_NM, E.SALARY, T.AVG_SAL&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP E&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;JOIN&lt;/span&gt;  (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; DEPT_CD,&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;fn&quot;&gt;AVG&lt;/span&gt;(SALARY) &lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt; AVG_SAL&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;GROUP BY&lt;/span&gt; DEPT_CD&lt;/span&gt;) T &lt;span class=&quot;kw&quot;&gt;ON&lt;/span&gt; E.DEPT_CD = T.DEPT_CD&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; E.SALARY &gt; T.AVG_SAL&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;ORDER BY&lt;/span&gt; E.DEPT_CD;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- Oracle에서는 인라인뷰에 ORDER BY 사용 시 ROWNUM과 함께 쓰는 경우 많음&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; *&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;  (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_NM, SALARY,&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;ROW_NUMBER&lt;/span&gt;() &lt;span class=&quot;kw&quot;&gt;OVER&lt;/span&gt;(&lt;span class=&quot;kw&quot;&gt;ORDER BY&lt;/span&gt; SALARY &lt;span class=&quot;kw&quot;&gt;DESC&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt; RN&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP&lt;/span&gt;) T&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; T.RN &lt;= &lt;span class=&quot;num&quot;&gt;3&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- 급여 TOP 3&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  NOTE&lt;/span&gt;
    인라인 뷰는 &lt;strong&gt;반드시 별칭(Alias)&lt;/strong&gt;을 붙여야 해요.&lt;br&gt;
    WITH절(CTE)로 대체하면 가독성이 더 좋아요.&lt;br&gt;
    &lt;span class=&quot;ic&quot;&gt;FROM (서브쿼리) T&lt;/span&gt; ← T가 인라인뷰 별칭
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 04. 중첩 서브쿼리 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;04&lt;/span&gt; 중첩 서브쿼리 — WHERE절&lt;/h2&gt;

  &lt;p&gt;WHERE 조건에 서브쿼리를 넣어 &lt;strong&gt;동적으로 조건값을 생성&lt;/strong&gt;하는 방식이에요.&lt;/p&gt;

  &lt;div class=&quot;pos-badges&quot;&gt;&lt;span class=&quot;pos-badge pb-where&quot;&gt;WHERE절&lt;/span&gt;&lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;중첩 서브쿼리 — 단일행 / 다중행&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 단일행 서브쿼리 (=, &gt;, &lt; 사용) ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 전체 평균 급여보다 많이 받는 사원&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_NM, SALARY&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  SALARY &gt; (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;AVG&lt;/span&gt;(SALARY) &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; EMP&lt;/span&gt;);&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 다중행 서브쿼리 (IN, ANY, ALL 사용) ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 2024년 수상 경력 있는 사원&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_NM&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  EMP_ID &lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt; (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_ID &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; AWARD &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; YEAR = &lt;span class=&quot;num&quot;&gt;2024&lt;/span&gt;&lt;/span&gt;);&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- ANY: 하나라도 만족하면 TRUE&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  SALARY &gt; &lt;span class=&quot;kw&quot;&gt;ANY&lt;/span&gt; (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; SALARY &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; EMP &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; DEPT_CD = &lt;span class=&quot;str&quot;&gt;'10'&lt;/span&gt;&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- = MIN(서브쿼리 결과) 보다 큰 값&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- ALL: 모두 만족해야 TRUE&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  SALARY &gt; &lt;span class=&quot;kw&quot;&gt;ALL&lt;/span&gt; (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; SALARY &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; EMP &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; DEPT_CD = &lt;span class=&quot;str&quot;&gt;'10'&lt;/span&gt;&lt;/span&gt;);&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- = MAX(서브쿼리 결과) 보다 큰 값&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 05. IN / NOT IN --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;05&lt;/span&gt; IN / NOT IN&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;IN / NOT IN 활용&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;-- IN: 서브쿼리 결과 중 하나라도 일치하면&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_NM&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  DEPT_CD &lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt; (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; DEPT_CD &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; DEPT &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; LOC = &lt;span class=&quot;str&quot;&gt;'서울'&lt;/span&gt;&lt;/span&gt;);&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- NOT IN: 서브쿼리 결과에 없는 것만&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_NM&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  EMP_ID &lt;span class=&quot;kw&quot;&gt;NOT IN&lt;/span&gt; (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_ID &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; RETIRED_EMP&lt;/span&gt;);&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- ⚠️ NOT IN + NULL 함정!&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 서브쿼리 결과에 NULL이 하나라도 있으면 NOT IN은 항상 FALSE!&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  EMP_ID &lt;span class=&quot;kw&quot;&gt;NOT IN&lt;/span&gt; (&lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;)  &lt;span class=&quot;cm&quot;&gt;-- ← 결과 없음! NULL 때문에&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 해결: NOT EXISTS 사용 또는 IS NOT NULL 조건 추가&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ NOT IN + NULL 함정&lt;/span&gt;
    서브쿼리 결과에 &lt;strong&gt;NULL이 포함되면 NOT IN은 항상 빈 결과&lt;/strong&gt;를 반환해요.&lt;br&gt;
    NULL과의 비교는 항상 UNKNOWN이기 때문이에요.&lt;br&gt;
    &lt;strong&gt;해결책: NOT EXISTS를 사용하거나 WHERE 컬럼 IS NOT NULL 추가&lt;/strong&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 06. EXISTS / NOT EXISTS --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;06&lt;/span&gt; EXISTS / NOT EXISTS&lt;/h2&gt;

  &lt;p&gt;서브쿼리 결과가 &lt;strong&gt;존재하는지 여부&lt;/strong&gt;만 확인하는 방식이에요.&lt;br&gt;
  행이 존재하면 TRUE, 없으면 FALSE를 반환해요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;EXISTS / NOT EXISTS&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;-- EXISTS: 주문이 있는 고객만 조회&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; C.CUST_NM&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   CUSTOMER C&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  &lt;span class=&quot;kw&quot;&gt;EXISTS&lt;/span&gt; (&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;cm&quot;&gt;-- 값이 뭐든 상관없음, 존재 여부만 체크&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   ORDERS O&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  O.CUST_ID = C.CUST_ID&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; );&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- NOT EXISTS: 한 번도 주문 안 한 고객&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; C.CUST_NM&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   CUSTOMER C&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  &lt;span class=&quot;kw&quot;&gt;NOT EXISTS&lt;/span&gt; (&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   ORDERS O&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  O.CUST_ID = C.CUST_ID&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; );
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;compare-cards&quot;&gt;
    &lt;div class=&quot;ccard&quot;&gt;
      &lt;div class=&quot;ccard-title&quot; style=&quot;color:#bc8cff;&quot;&gt;IN&lt;/div&gt;
      &lt;ul&gt;
        &lt;li&gt;서브쿼리 결과를 전부 가져온 후 비교&lt;/li&gt;
        &lt;li&gt;결과가 적을 때 유리&lt;/li&gt;
        &lt;li&gt;NULL 포함 시 NOT IN 주의&lt;/li&gt;
        &lt;li&gt;외부 쿼리와 독립적으로 실행&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;div class=&quot;ccard&quot;&gt;
      &lt;div class=&quot;ccard-title&quot; style=&quot;color:#3fb950;&quot;&gt;EXISTS&lt;/div&gt;
      &lt;ul&gt;
        &lt;li&gt;조건 만족하는 행 발견하는 즉시 중단&lt;/li&gt;
        &lt;li&gt;결과가 많을 때 유리 (Early Exit)&lt;/li&gt;
        &lt;li&gt;NULL 문제 없음&lt;/li&gt;
        &lt;li&gt;외부 쿼리 각 행마다 실행 (상관 서브쿼리)&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 07. 상관 서브쿼리 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;07&lt;/span&gt; 상관 서브쿼리 (Correlated Subquery)&lt;/h2&gt;

  &lt;p&gt;외부 쿼리의 컬럼을 &lt;strong&gt;서브쿼리 안에서 참조&lt;/strong&gt;하는 방식이에요.&lt;br&gt;
  외부 쿼리의 각 행마다 서브쿼리가 실행돼요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;상관 서브쿼리 패턴&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;-- 같은 부서에서 자신보다 급여가 높은 사람이 있는 사원&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; E1.EMP_NM, E1.SALARY, E1.DEPT_CD&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP E1&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  &lt;span class=&quot;kw&quot;&gt;EXISTS&lt;/span&gt; (&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP E2&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  E2.DEPT_CD = E1.DEPT_CD  &lt;span class=&quot;cm&quot;&gt;-- 외부 쿼리 참조!&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;AND&lt;/span&gt;    E2.SALARY &gt; E1.SALARY&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; );&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 부서별 최고 급여자 조회 (상관 서브쿼리)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_NM, DEPT_CD, SALARY&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP E1&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  SALARY = (&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;MAX&lt;/span&gt;(SALARY)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP E2&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  E2.DEPT_CD = E1.DEPT_CD&lt;/span&gt;  &lt;span class=&quot;cm&quot;&gt;-- 외부 행의 DEPT_CD 참조&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- UPDATE에서의 상관 서브쿼리&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; EMP E&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt;    DEPT_NM = (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; DEPT_NM &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; DEPT D &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; D.DEPT_CD = E.DEPT_CD&lt;/span&gt;);
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  상관 서브쿼리 vs 일반 서브쿼리&lt;/span&gt;
    &lt;strong&gt;일반 서브쿼리&lt;/strong&gt; = 독립적으로 실행, 결과를 외부 쿼리에 전달 (1번만 실행)&lt;br&gt;
    &lt;strong&gt;상관 서브쿼리&lt;/strong&gt; = 외부 행마다 반복 실행 (N번 실행) → 데이터 많으면 느림&lt;br&gt;
    상관 서브쿼리는 &lt;strong&gt;JOIN이나 윈도우 함수로 대체&lt;/strong&gt;하면 성능이 좋아지는 경우가 많아요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 08. 서브쿼리 vs JOIN --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;08&lt;/span&gt; 서브쿼리 vs JOIN — 언제 뭘 쓸까?&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;같은 결과, 다른 방식 비교&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 서브쿼리 방식 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; EMP_NM&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  DEPT_CD &lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt; (&lt;span class=&quot;sub&quot;&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; DEPT_CD &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; DEPT &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; LOC = &lt;span class=&quot;str&quot;&gt;'서울'&lt;/span&gt;&lt;/span&gt;);&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ JOIN 방식 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; E.EMP_NM&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt;   EMP E&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;JOIN&lt;/span&gt;   DEPT D &lt;span class=&quot;kw&quot;&gt;ON&lt;/span&gt; E.DEPT_CD = D.DEPT_CD&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt;  D.LOC = &lt;span class=&quot;str&quot;&gt;'서울'&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 서브쿼리가 유리한 경우 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 존재 여부만 확인할 때 (EXISTS)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 집계 결과를 조건으로 쓸 때 (AVG, MAX 등)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 가독성이 중요할 때&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ JOIN이 유리한 경우 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 연결된 테이블의 컬럼도 SELECT해야 할 때&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 대용량 데이터 조회 성능이 중요할 때&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 옵티마이저가 더 효율적인 실행계획 세울 수 있음&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;table class=&quot;compare-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;구분&lt;/th&gt;&lt;th&gt;서브쿼리&lt;/th&gt;&lt;th&gt;JOIN&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;가독성&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;직관적, 단계적으로 이해 쉬움&lt;/td&gt;
      &lt;td&gt;익숙해지면 간결&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;성능&lt;/td&gt;
      &lt;td&gt;상관 서브쿼리는 N번 실행&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;보통 더 빠름 (옵티마이저 최적화)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;연결 컬럼 조회&lt;/td&gt;
      &lt;td class=&quot;td-bad&quot;&gt;스칼라로만 가능 (1열 제한)&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;여러 컬럼 자유롭게 조회&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;존재 여부 확인&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;EXISTS로 빠르게 처리&lt;/td&gt;
      &lt;td&gt;DISTINCT 필요한 경우 있음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;집계 조건&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;HAVING 없이 WHERE에서 처리 가능&lt;/td&gt;
      &lt;td&gt;별도 처리 필요&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;✅ CONCLUSION&lt;/span&gt;
    서브쿼리는 &lt;strong&gt;위치(SELECT/FROM/WHERE)와 반환 형태(단일행/다중행)&lt;/strong&gt;로 구분해요.&lt;br&gt;
    EXISTS는 NULL 안전하고 Early Exit으로 IN보다 대용량에서 유리해요.&lt;br&gt;
    상관 서브쿼리는 편리하지만 성능 주의 → &lt;strong&gt;JOIN이나 윈도우 함수로 대체&lt;/strong&gt; 고려!&lt;br&gt;
    코테에서는 &lt;strong&gt;인라인 뷰 + 집계 서브쿼리&lt;/strong&gt; 패턴이 특히 자주 나와요.
  &lt;/div&gt;

  &lt;div class=&quot;post-tags&quot;&gt;
    &lt;span class=&quot;post-tag&quot;&gt;SQL&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;서브쿼리&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;스칼라서브쿼리&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;인라인뷰&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;EXISTS&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;상관서브쿼리&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;코딩테스트&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;</description>
      <category>  코딩테스트/SQL</category>
      <category>서브쿼리</category>
      <author>woohaha</author>
      <guid isPermaLink="true">https://story2248.tistory.com/18</guid>
      <comments>https://story2248.tistory.com/18#entry18comment</comments>
      <pubDate>Wed, 13 May 2026 19:51:53 +0900</pubDate>
    </item>
    <item>
      <title>[DB] 정규화 완전 정복 - 1NF부터 BCNF까지, 반정규화까지 한 번에</title>
      <link>https://story2248.tistory.com/17</link>
      <description>&lt;style&gt;
  .post-wrap {
    background: #0d1117; color: #e6edf3;
    font-family: 'Pretendard', -apple-system, sans-serif;
    padding: 0; line-height: 1.8;
  }
  .post-wrap * { box-sizing: border-box; }
  .post-wrap p { margin: 0 0 16px; color: #8b949e; font-size: 15px; }
  .post-wrap p strong { color: #e6edf3; }
  .post-divider { border: none; border-top: 1px solid #21262d; margin: 32px 0; }

  .post-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px;
    background: rgba(188,140,255,0.1); border: 1px solid rgba(188,140,255,0.3);
    border-radius: 20px; font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: #bc8cff; margin-bottom: 20px;
  }
  .post-badge::before { content: ' ️'; }

  /* 목차 */
  .toc { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 24px 0; }
  .toc-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .toc-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .toc-item { font-size: 13px; color: #8b949e; padding: 3px 0; display: flex; align-items: center; gap: 8px; }
  .toc-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #bc8cff; }

  .post-wrap h2 {
    font-size: 18px; font-weight: 600; color: #e6edf3;
    margin: 32px 0 16px; padding-bottom: 8px;
    border-bottom: 1px solid #21262d;
    display: flex; align-items: center; gap: 8px;
  }
  .post-wrap h2 .num {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #bc8cff; background: rgba(188,140,255,0.1);
    border: 1px solid rgba(188,140,255,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h3 {
    font-size: 15px; font-weight: 600; color: #8b949e;
    margin: 20px 0 10px; font-family: 'JetBrains Mono', monospace;
  }
  .post-wrap h3::before { content: '// '; color: #484f58; }

  /* 테이블 */
  .db-table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 13px; }
  .db-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 8px 14px; border: 1px solid #30363d; text-align: left; letter-spacing: 0.05em; }
  .db-table td { padding: 8px 14px; border: 1px solid #30363d; color: #e6edf3; font-family: 'JetBrains Mono', monospace; font-size: 12px; }
  .db-table tr:nth-child(even) td { background: #161b22; }
  .dup { color: #ff7b72 !important; }
  .bad-dep { color: #ffa657 !important; }
  .ok { color: #3fb950 !important; }
  .pk { color: #58a6ff !important; font-weight: 700; }

  /* 정규화 단계 카드 */
  .nf-card {
    background: #161b22; border: 1px solid #30363d;
    border-radius: 8px; padding: 18px 20px; margin: 16px 0;
  }
  .nf-header { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
  .nf-badge {
    font-family: 'JetBrains Mono', monospace; font-size: 20px; font-weight: 700;
    padding: 4px 16px; border-radius: 6px; flex-shrink: 0;
  }
  .nf-title { font-size: 15px; font-weight: 600; color: #e6edf3; }
  .nf-rule {
    font-size: 13px; color: #8b949e; margin-bottom: 14px;
    padding: 10px 14px; background: #0d1117; border-radius: 6px;
    border-left: 3px solid;
    font-family: 'Pretendard', sans-serif; line-height: 1.7;
  }
  .nf-rule strong { color: #e6edf3; }

  /* 화살표 변환 */
  .transform {
    display: flex; align-items: center; gap: 12px; margin: 16px 0; flex-wrap: wrap;
  }
  .transform-arrow {
    font-size: 24px; color: #3fb950; font-weight: 700; flex-shrink: 0;
  }
  .transform-label {
    font-family: 'JetBrains Mono', monospace; font-size: 11px;
    color: #3fb950; display: block; margin-bottom: 6px;
  }
  .bad-label {
    font-family: 'JetBrains Mono', monospace; font-size: 11px;
    color: #ff7b72; display: block; margin-bottom: 6px;
  }

  /* tip / warn 박스 */
  .tip-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #bc8cff; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .tip-box strong { color: #e6edf3; }
  .tip-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #bc8cff; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .warn-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ffa657; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .warn-box strong { color: #e6edf3; }
  .warn-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .info-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #58a6ff; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .info-box strong { color: #e6edf3; }
  .info-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }

  /* 비교 테이블 */
  .compare-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
  .compare-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 10px 14px; border: 1px solid #30363d; letter-spacing: 0.05em; text-align: left; }
  .compare-table td { padding: 10px 14px; border: 1px solid #30363d; color: #e6edf3; }
  .compare-table tr:nth-child(even) td { background: #161b22; }
  .td-good { color: #3fb950 !important; font-weight: 700; }
  .td-bad  { color: #ff7b72 !important; }

  .ic { font-family: 'JetBrains Mono', monospace; font-size: 0.88em; padding: 1px 6px; background: #1c2128; border: 1px solid #30363d; border-radius: 4px; color: #ffa657; }
  .post-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 32px; padding-top: 20px; border-top: 1px solid #21262d; }
  .post-tag { padding: 3px 10px; background: rgba(188,140,255,0.08); border: 1px solid rgba(188,140,255,0.25); border-radius: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #bc8cff; }
  .post-tag::before { content: '#'; opacity: 0.6; }
  @media (max-width: 600px) { .toc-list { grid-template-columns: 1fr; } .transform { flex-direction: column; } }
&lt;/style&gt;

&lt;div class=&quot;post-wrap&quot;&gt;
  &lt;div class=&quot;post-badge&quot;&gt;CS 면접 대비 · 데이터베이스&lt;/div&gt;

  &lt;p&gt;DB 설계 면접에서 &lt;strong&gt;&quot;정규화가 뭔가요?&quot;&lt;/strong&gt;는 단골 질문이에요.&lt;br&gt;
  1NF부터 BCNF까지, 왜 하는지부터 실제 예시까지 한 번에 정리해드릴게요.&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
    &lt;span class=&quot;toc-title&quot;&gt;// TABLE OF CONTENTS&lt;/span&gt;
    &lt;div class=&quot;toc-list&quot;&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;01&lt;/span&gt; 정규화란? — 왜 해야 하나&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;02&lt;/span&gt; 이상현상 3가지&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;03&lt;/span&gt; 1NF — 원자값&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;04&lt;/span&gt; 2NF — 부분 함수 종속 제거&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;05&lt;/span&gt; 3NF — 이행 함수 종속 제거&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;06&lt;/span&gt; BCNF&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;07&lt;/span&gt; 반정규화&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;08&lt;/span&gt; 면접 답변 정리&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 01. 정규화란? --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;01&lt;/span&gt; 정규화란?&lt;/h2&gt;

  &lt;p&gt;정규화는 &lt;strong&gt;데이터 중복을 최소화하고 데이터 무결성을 보장&lt;/strong&gt;하기 위해&lt;br&gt;
  테이블을 올바른 구조로 분리하는 과정이에요.&lt;br&gt;
  1NF → 2NF → 3NF → BCNF 순서로 단계적으로 적용해요.&lt;/p&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  정규화가 필요한 이유&lt;/span&gt;
    정규화 안 된 테이블은 &lt;strong&gt;이상현상(Anomaly)&lt;/strong&gt;이 발생해요.&lt;br&gt;
    데이터 중복 → 업데이트 시 일부만 변경 → 데이터 불일치&lt;br&gt;
    → 시스템 신뢰성 하락 → 서비스 장애 원인이 돼요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 02. 이상현상 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;02&lt;/span&gt; 이상현상 3가지&lt;/h2&gt;

  &lt;p&gt;정규화가 안 된 아래 테이블을 예시로 설명할게요.&lt;/p&gt;

  &lt;div style=&quot;margin-bottom:6px;font-family:'JetBrains Mono',monospace;font-size:11px;color:#ff7b72;&quot;&gt;// 정규화 전 — 문제 있는 테이블&lt;/div&gt;
  &lt;table class=&quot;db-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;주문ID&lt;/th&gt;&lt;th&gt;고객ID&lt;/th&gt;&lt;th&gt;고객명&lt;/th&gt;&lt;th&gt;고객주소&lt;/th&gt;&lt;th&gt;상품코드&lt;/th&gt;&lt;th&gt;상품명&lt;/th&gt;&lt;th&gt;수량&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td&gt;C01&lt;/td&gt;&lt;td class=&quot;dup&quot;&gt;홍길동&lt;/td&gt;&lt;td class=&quot;dup&quot;&gt;서울&lt;/td&gt;&lt;td&gt;P01&lt;/td&gt;&lt;td class=&quot;dup&quot;&gt;노트북&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1002&lt;/td&gt;&lt;td&gt;C01&lt;/td&gt;&lt;td class=&quot;dup&quot;&gt;홍길동&lt;/td&gt;&lt;td class=&quot;dup&quot;&gt;서울&lt;/td&gt;&lt;td&gt;P02&lt;/td&gt;&lt;td&gt;마우스&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1003&lt;/td&gt;&lt;td&gt;C02&lt;/td&gt;&lt;td&gt;김철수&lt;/td&gt;&lt;td&gt;부산&lt;/td&gt;&lt;td&gt;P01&lt;/td&gt;&lt;td class=&quot;dup&quot;&gt;노트북&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;nf-card&quot; style=&quot;margin-top:16px;&quot;&gt;
    &lt;div style=&quot;font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700;color:#ff7b72;margin-bottom:12px;&quot;&gt;① 삽입 이상 (Insertion Anomaly)&lt;/div&gt;
    &lt;p style=&quot;margin:0;font-size:13px;&quot;&gt;새 고객을 등록하려면 &lt;strong&gt;주문도 함께 있어야 해요.&lt;/strong&gt;&lt;br&gt;주문 없이는 고객 정보만 넣을 수가 없어요.&lt;/p&gt;
  &lt;/div&gt;

  &lt;div class=&quot;nf-card&quot;&gt;
    &lt;div style=&quot;font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700;color:#ffa657;margin-bottom:12px;&quot;&gt;② 갱신 이상 (Update Anomaly)&lt;/div&gt;
    &lt;p style=&quot;margin:0;font-size:13px;&quot;&gt;홍길동 주소가 바뀌면 &lt;strong&gt;2개 행을 모두 수정&lt;/strong&gt;해야 해요.&lt;br&gt;하나라도 빠뜨리면 같은 사람의 주소가 달라지는 불일치 발생.&lt;/p&gt;
  &lt;/div&gt;

  &lt;div class=&quot;nf-card&quot;&gt;
    &lt;div style=&quot;font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700;color:#bc8cff;margin-bottom:12px;&quot;&gt;③ 삭제 이상 (Deletion Anomaly)&lt;/div&gt;
    &lt;p style=&quot;margin:0;font-size:13px;&quot;&gt;1003 주문을 삭제하면 &lt;strong&gt;김철수 고객 정보도 함께 사라져요.&lt;/strong&gt;&lt;br&gt;주문 기록과 고객 기록이 결합돼 있기 때문이에요.&lt;/p&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 03. 1NF --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;03&lt;/span&gt; 1NF — 원자값 (Atomic Value)&lt;/h2&gt;

  &lt;div class=&quot;nf-card&quot;&gt;
    &lt;div class=&quot;nf-header&quot;&gt;
      &lt;span class=&quot;nf-badge&quot; style=&quot;background:rgba(88,166,255,0.15);color:#58a6ff;border:1px solid rgba(88,166,255,0.3);&quot;&gt;1NF&lt;/span&gt;
      &lt;span class=&quot;nf-title&quot;&gt;제1 정규형&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;nf-rule&quot; style=&quot;border-left-color:#58a6ff;&quot;&gt;
      &lt;strong&gt;모든 컬럼의 값은 원자값(더 이상 분해할 수 없는 단일 값)이어야 해요.&lt;/strong&gt;&lt;br&gt;
      반복 그룹이나 다중값 컬럼이 있으면 1NF 위반이에요.
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;bad-label&quot;&gt;// 1NF 위반 — 다중값 컬럼&lt;/div&gt;
  &lt;table class=&quot;db-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;주문ID&lt;/th&gt;&lt;th&gt;고객명&lt;/th&gt;&lt;th&gt;주문상품 (다중값!)&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td&gt;홍길동&lt;/td&gt;&lt;td class=&quot;dup&quot;&gt;노트북, 마우스, 키보드&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1002&lt;/td&gt;&lt;td&gt;김철수&lt;/td&gt;&lt;td&gt;노트북&lt;/td&gt;&lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;transform&quot;&gt;
    &lt;div style=&quot;flex:1;&quot;&gt;
      &lt;div class=&quot;transform-label&quot;&gt;// 1NF 적용 후 — 원자값으로 분리&lt;/div&gt;
      &lt;table class=&quot;db-table&quot;&gt;
        &lt;tr&gt;&lt;th&gt;주문ID&lt;/th&gt;&lt;th&gt;고객명&lt;/th&gt;&lt;th&gt;주문상품&lt;/th&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td&gt;홍길동&lt;/td&gt;&lt;td class=&quot;ok&quot;&gt;노트북&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td&gt;홍길동&lt;/td&gt;&lt;td class=&quot;ok&quot;&gt;마우스&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td&gt;홍길동&lt;/td&gt;&lt;td class=&quot;ok&quot;&gt;키보드&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1002&lt;/td&gt;&lt;td&gt;김철수&lt;/td&gt;&lt;td class=&quot;ok&quot;&gt;노트북&lt;/td&gt;&lt;/tr&gt;
      &lt;/table&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 04. 2NF --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;04&lt;/span&gt; 2NF — 부분 함수 종속 제거&lt;/h2&gt;

  &lt;div class=&quot;nf-card&quot;&gt;
    &lt;div class=&quot;nf-header&quot;&gt;
      &lt;span class=&quot;nf-badge&quot; style=&quot;background:rgba(63,185,80,0.15);color:#3fb950;border:1px solid rgba(63,185,80,0.3);&quot;&gt;2NF&lt;/span&gt;
      &lt;span class=&quot;nf-title&quot;&gt;제2 정규형&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;nf-rule&quot; style=&quot;border-left-color:#3fb950;&quot;&gt;
      1NF를 만족하면서 &lt;strong&gt;모든 비기본키 컬럼이 기본키 전체에 완전 함수 종속&lt;/strong&gt;되어야 해요.&lt;br&gt;
      기본키의 일부에만 종속되는 &lt;strong&gt;부분 함수 종속&lt;/strong&gt;을 제거해야 해요.&lt;br&gt;
      ※ 기본키가 단일 컬럼이면 2NF는 자동으로 만족돼요.
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;bad-label&quot;&gt;// 2NF 위반 — PK: (주문ID, 상품코드) 복합키인데 부분 종속 발생&lt;/div&gt;
  &lt;table class=&quot;db-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;주문ID (PK)&lt;/th&gt;&lt;th&gt;상품코드 (PK)&lt;/th&gt;&lt;th&gt;수량&lt;/th&gt;&lt;th class=&quot;bad-dep&quot;&gt;상품명 ⚠️&lt;/th&gt;&lt;th class=&quot;bad-dep&quot;&gt;고객명 ⚠️&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td class=&quot;pk&quot;&gt;P01&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;노트북 (상품코드에만 종속)&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;홍길동 (주문ID에만 종속)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td class=&quot;pk&quot;&gt;P02&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;마우스&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;홍길동&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1002&lt;/td&gt;&lt;td class=&quot;pk&quot;&gt;P01&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;노트북&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;김철수&lt;/td&gt;&lt;/tr&gt;
  &lt;/table&gt;

  &lt;div style=&quot;font-family:'JetBrains Mono',monospace;font-size:11px;color:#3fb950;margin:16px 0 8px;&quot;&gt;// 2NF 적용 후 — 테이블 분리&lt;/div&gt;
  &lt;div style=&quot;display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:12px 0;&quot;&gt;
    &lt;div&gt;
      &lt;div style=&quot;font-family:'JetBrains Mono',monospace;font-size:10px;color:#58a6ff;margin-bottom:5px;&quot;&gt;주문_상품 테이블&lt;/div&gt;
      &lt;table class=&quot;db-table&quot;&gt;
        &lt;tr&gt;&lt;th&gt;주문ID&lt;/th&gt;&lt;th&gt;상품코드&lt;/th&gt;&lt;th&gt;수량&lt;/th&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td class=&quot;pk&quot;&gt;P01&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td class=&quot;pk&quot;&gt;P02&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1002&lt;/td&gt;&lt;td class=&quot;pk&quot;&gt;P01&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
      &lt;/table&gt;
    &lt;/div&gt;
    &lt;div&gt;
      &lt;div style=&quot;font-family:'JetBrains Mono',monospace;font-size:10px;color:#58a6ff;margin-bottom:5px;&quot;&gt;상품 테이블&lt;/div&gt;
      &lt;table class=&quot;db-table&quot;&gt;
        &lt;tr&gt;&lt;th&gt;상품코드&lt;/th&gt;&lt;th&gt;상품명&lt;/th&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;P01&lt;/td&gt;&lt;td class=&quot;ok&quot;&gt;노트북&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;P02&lt;/td&gt;&lt;td class=&quot;ok&quot;&gt;마우스&lt;/td&gt;&lt;/tr&gt;
      &lt;/table&gt;
    &lt;/div&gt;
    &lt;div&gt;
      &lt;div style=&quot;font-family:'JetBrains Mono',monospace;font-size:10px;color:#58a6ff;margin-bottom:5px;&quot;&gt;주문 테이블&lt;/div&gt;
      &lt;table class=&quot;db-table&quot;&gt;
        &lt;tr&gt;&lt;th&gt;주문ID&lt;/th&gt;&lt;th&gt;고객명&lt;/th&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td class=&quot;ok&quot;&gt;홍길동&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1002&lt;/td&gt;&lt;td class=&quot;ok&quot;&gt;김철수&lt;/td&gt;&lt;/tr&gt;
      &lt;/table&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 05. 3NF --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;05&lt;/span&gt; 3NF — 이행 함수 종속 제거&lt;/h2&gt;

  &lt;div class=&quot;nf-card&quot;&gt;
    &lt;div class=&quot;nf-header&quot;&gt;
      &lt;span class=&quot;nf-badge&quot; style=&quot;background:rgba(188,140,255,0.15);color:#bc8cff;border:1px solid rgba(188,140,255,0.3);&quot;&gt;3NF&lt;/span&gt;
      &lt;span class=&quot;nf-title&quot;&gt;제3 정규형&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;nf-rule&quot; style=&quot;border-left-color:#bc8cff;&quot;&gt;
      2NF를 만족하면서 &lt;strong&gt;기본키가 아닌 컬럼이 다른 비기본키 컬럼에 종속&lt;/strong&gt;되면 안 돼요.&lt;br&gt;
      &lt;strong&gt;A → B → C&lt;/strong&gt; 형태의 이행 함수 종속을 제거해야 해요.
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;bad-label&quot;&gt;// 3NF 위반 — 주문ID → 고객ID → 고객주소 (이행 종속)&lt;/div&gt;
  &lt;table class=&quot;db-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;주문ID (PK)&lt;/th&gt;&lt;th&gt;고객ID&lt;/th&gt;&lt;th class=&quot;bad-dep&quot;&gt;고객주소 ⚠️&lt;/th&gt;&lt;th&gt;주문일&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td&gt;C01&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;서울 (고객ID에 종속)&lt;/td&gt;&lt;td&gt;2024-01-15&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1002&lt;/td&gt;&lt;td&gt;C01&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;서울 (중복!)&lt;/td&gt;&lt;td&gt;2024-02-01&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1003&lt;/td&gt;&lt;td&gt;C02&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;부산&lt;/td&gt;&lt;td&gt;2024-02-10&lt;/td&gt;&lt;/tr&gt;
  &lt;/table&gt;

  &lt;div style=&quot;font-family:'JetBrains Mono',monospace;font-size:11px;color:#3fb950;margin:16px 0 8px;&quot;&gt;// 3NF 적용 후 — 이행 종속 제거&lt;/div&gt;
  &lt;div style=&quot;display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:12px 0;&quot;&gt;
    &lt;div&gt;
      &lt;div style=&quot;font-family:'JetBrains Mono',monospace;font-size:10px;color:#58a6ff;margin-bottom:5px;&quot;&gt;주문 테이블&lt;/div&gt;
      &lt;table class=&quot;db-table&quot;&gt;
        &lt;tr&gt;&lt;th&gt;주문ID&lt;/th&gt;&lt;th&gt;고객ID&lt;/th&gt;&lt;th&gt;주문일&lt;/th&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1001&lt;/td&gt;&lt;td&gt;C01&lt;/td&gt;&lt;td&gt;2024-01-15&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1002&lt;/td&gt;&lt;td&gt;C01&lt;/td&gt;&lt;td&gt;2024-02-01&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;1003&lt;/td&gt;&lt;td&gt;C02&lt;/td&gt;&lt;td&gt;2024-02-10&lt;/td&gt;&lt;/tr&gt;
      &lt;/table&gt;
    &lt;/div&gt;
    &lt;div&gt;
      &lt;div style=&quot;font-family:'JetBrains Mono',monospace;font-size:10px;color:#58a6ff;margin-bottom:5px;&quot;&gt;고객 테이블&lt;/div&gt;
      &lt;table class=&quot;db-table&quot;&gt;
        &lt;tr&gt;&lt;th&gt;고객ID&lt;/th&gt;&lt;th&gt;고객주소&lt;/th&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;C01&lt;/td&gt;&lt;td class=&quot;ok&quot;&gt;서울&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;C02&lt;/td&gt;&lt;td class=&quot;ok&quot;&gt;부산&lt;/td&gt;&lt;/tr&gt;
      &lt;/table&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 06. BCNF --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;06&lt;/span&gt; BCNF — 보이스-코드 정규형&lt;/h2&gt;

  &lt;div class=&quot;nf-card&quot;&gt;
    &lt;div class=&quot;nf-header&quot;&gt;
      &lt;span class=&quot;nf-badge&quot; style=&quot;background:rgba(255,166,87,0.15);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;BCNF&lt;/span&gt;
      &lt;span class=&quot;nf-title&quot;&gt;Boyce-Codd 정규형&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;nf-rule&quot; style=&quot;border-left-color:#ffa657;&quot;&gt;
      3NF보다 강한 조건. &lt;strong&gt;모든 결정자(Determinant)는 후보키여야 해요.&lt;/strong&gt;&lt;br&gt;
      3NF를 만족해도 BCNF를 위반하는 경우가 있어요.
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;bad-label&quot;&gt;// BCNF 위반 예시 — 학생-과목-교수 테이블&lt;/div&gt;
  &lt;table class=&quot;db-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;학생ID (PK)&lt;/th&gt;&lt;th&gt;과목명 (PK)&lt;/th&gt;&lt;th class=&quot;bad-dep&quot;&gt;담당교수 ⚠️&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;S01&lt;/td&gt;&lt;td class=&quot;pk&quot;&gt;데이터베이스&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;김교수 (과목명에 종속, but 교수가 과목 결정)&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;S02&lt;/td&gt;&lt;td class=&quot;pk&quot;&gt;데이터베이스&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;김교수&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;pk&quot;&gt;S01&lt;/td&gt;&lt;td class=&quot;pk&quot;&gt;알고리즘&lt;/td&gt;&lt;td class=&quot;bad-dep&quot;&gt;이교수&lt;/td&gt;&lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;info-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  NOTE&lt;/span&gt;
    담당교수 → 과목명으로 결정되는데, 담당교수는 후보키가 아니에요.&lt;br&gt;
    이 경우 &lt;strong&gt;교수-과목 테이블&lt;/strong&gt;을 분리해서 BCNF를 만족시켜요.&lt;br&gt;
    실무에서 3NF까지는 꼭 하고, BCNF는 상황에 따라 적용해요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 07. 반정규화 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;07&lt;/span&gt; 반정규화 (De-normalization)&lt;/h2&gt;

  &lt;p&gt;정규화를 하면 테이블이 많아지고 &lt;strong&gt;JOIN이 늘어나 조회 성능이 떨어질 수 있어요.&lt;/strong&gt;&lt;br&gt;
  이럴 때 의도적으로 중복을 허용해서 성능을 높이는 것이 &lt;strong&gt;반정규화&lt;/strong&gt;예요.&lt;/p&gt;

  &lt;table class=&quot;compare-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;구분&lt;/th&gt;&lt;th&gt;정규화&lt;/th&gt;&lt;th&gt;반정규화&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;목적&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;데이터 무결성 / 중복 제거&lt;/td&gt;
      &lt;td&gt;조회 성능 향상&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;JOIN&lt;/td&gt;
      &lt;td&gt;많음&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;적음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;중복&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;없음&lt;/td&gt;
      &lt;td&gt;의도적 허용&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;수정 비용&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;낮음 (한 곳만 수정)&lt;/td&gt;
      &lt;td class=&quot;td-bad&quot;&gt;높음 (여러 곳 수정)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;사용 상황&lt;/td&gt;
      &lt;td&gt;OLTP (트랜잭션 중심)&lt;/td&gt;
      &lt;td class=&quot;td-good&quot;&gt;OLAP (분석/조회 중심)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ 반정규화 적용 기준&lt;/span&gt;
    반정규화는 &lt;strong&gt;성능 문제가 실제로 발생한 후&lt;/strong&gt; 고려해야 해요.&lt;br&gt;
    처음부터 반정규화하면 데이터 불일치 위험이 높아요.&lt;br&gt;
    &lt;strong&gt;정규화 먼저 → 성능 측정 → 필요한 부분만 반정규화&lt;/strong&gt;가 올바른 순서예요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 08. 면접 답변 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;08&lt;/span&gt; 정규화 단계 한눈에 정리&lt;/h2&gt;

  &lt;table class=&quot;compare-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;정규형&lt;/th&gt;&lt;th&gt;조건&lt;/th&gt;&lt;th&gt;제거 대상&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#58a6ff;font-weight:700;&quot;&gt;1NF&lt;/td&gt;
      &lt;td&gt;모든 컬럼이 원자값&lt;/td&gt;
      &lt;td&gt;다중값, 반복 그룹&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#3fb950;font-weight:700;&quot;&gt;2NF&lt;/td&gt;
      &lt;td&gt;1NF + 완전 함수 종속&lt;/td&gt;
      &lt;td&gt;부분 함수 종속&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#bc8cff;font-weight:700;&quot;&gt;3NF&lt;/td&gt;
      &lt;td&gt;2NF + 비이행 종속&lt;/td&gt;
      &lt;td&gt;이행 함수 종속&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;font-family:'JetBrains Mono',monospace;color:#ffa657;font-weight:700;&quot;&gt;BCNF&lt;/td&gt;
      &lt;td&gt;3NF + 결정자 = 후보키&lt;/td&gt;
      &lt;td&gt;후보키가 아닌 결정자&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;✅ 면접 답변 핵심&lt;/span&gt;
    &lt;strong&gt;&quot;정규화는 데이터 중복을 제거하고 무결성을 보장하기 위해 테이블을 분리하는 과정입니다.&lt;br&gt;
    1NF(원자값) → 2NF(부분 종속 제거) → 3NF(이행 종속 제거) 순으로 적용하며,&lt;br&gt;
    실무에서는 3NF까지 적용하고, 조회 성능이 필요한 경우 반정규화를 고려합니다.&quot;&lt;/strong&gt;
  &lt;/div&gt;

  &lt;div class=&quot;post-tags&quot;&gt;
    &lt;span class=&quot;post-tag&quot;&gt;데이터베이스&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;정규화&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;1NF&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;2NF&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;3NF&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;BCNF&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;반정규화&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;CS면접&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;</description>
      <category>  CS 면접 대비/데이터베이스</category>
      <category>1NF</category>
      <category>2NF</category>
      <category>3NF</category>
      <category>반정규화</category>
      <category>정규화</category>
      <author>woohaha</author>
      <guid isPermaLink="true">https://story2248.tistory.com/17</guid>
      <comments>https://story2248.tistory.com/17#entry17comment</comments>
      <pubDate>Tue, 12 May 2026 18:14:39 +0900</pubDate>
    </item>
    <item>
      <title>[ERP] 구매/영업/재고 모듈 완전 정복 - P2P&amp;middot;O2C 흐름부터 재고 평가까지</title>
      <link>https://story2248.tistory.com/16</link>
      <description>&lt;style&gt;
  .post-wrap {
    background: #0d1117; color: #e6edf3;
    font-family: 'Pretendard', -apple-system, sans-serif;
    padding: 0; line-height: 1.8;
  }
  .post-wrap * { box-sizing: border-box; }
  .post-wrap p { margin: 0 0 16px; color: #8b949e; font-size: 15px; }
  .post-wrap p strong { color: #e6edf3; }
  .post-divider { border: none; border-top: 1px solid #21262d; margin: 32px 0; }

  .post-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px;
    background: rgba(63,185,80,0.1); border: 1px solid rgba(63,185,80,0.3);
    border-radius: 20px; font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: #3fb950; margin-bottom: 20px;
  }
  .post-badge::before { content: ' '; }

  /* 목차 */
  .toc { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 24px 0; }
  .toc-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .toc-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .toc-item { font-size: 13px; color: #8b949e; padding: 3px 0; display: flex; align-items: center; gap: 8px; }
  .toc-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; }

  .post-wrap h2 {
    font-size: 18px; font-weight: 600; color: #e6edf3;
    margin: 32px 0 16px; padding-bottom: 8px;
    border-bottom: 1px solid #21262d;
    display: flex; align-items: center; gap: 8px;
  }
  .post-wrap h2 .num {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #3fb950; background: rgba(63,185,80,0.1);
    border: 1px solid rgba(63,185,80,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h2 .num-blue {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #58a6ff; background: rgba(88,166,255,0.1);
    border: 1px solid rgba(88,166,255,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h2 .num-purple {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #bc8cff; background: rgba(188,140,255,0.1);
    border: 1px solid rgba(188,140,255,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h3 {
    font-size: 15px; font-weight: 600; color: #8b949e;
    margin: 20px 0 10px; font-family: 'JetBrains Mono', monospace;
  }
  .post-wrap h3::before { content: '// '; color: #484f58; }

  /* 전체 연계 흐름 */
  .full-flow {
    background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; padding: 20px 24px; margin: 20px 0;
    font-family: 'JetBrains Mono', monospace;
  }
  .ff-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 16px; display: block; }
  .ff-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
  .ff-item { padding: 8px 14px; border-radius: 6px; font-size: 12px; font-weight: 600; text-align: center; }
  .ff-buy    { background: rgba(63,185,80,0.15);  color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .ff-inv    { background: rgba(255,166,87,0.15); color: #ffa657; border: 1px solid rgba(255,166,87,0.3); }
  .ff-sal    { background: rgba(88,166,255,0.15); color: #58a6ff; border: 1px solid rgba(88,166,255,0.3); }
  .ff-acc    { background: rgba(188,140,255,0.15);color: #bc8cff; border: 1px solid rgba(188,140,255,0.3); }
  .ff-arrow  { color: #30363d; font-size: 18px; }
  .ff-label  { font-size: 10px; color: #484f58; font-family: 'Pretendard', sans-serif; margin-left: 4px; }

  /* 용어 카드 */
  .term-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 16px 0; }
  .term-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 14px 16px; }
  .term-name { font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 700; margin-bottom: 6px; }
  .term-desc { font-size: 13px; color: #8b949e; line-height: 1.65; }
  .term-desc strong { color: #e6edf3; }
  .term-example { margin-top: 8px; padding: 6px 10px; background: #0d1117; border-radius: 4px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; }
  .term-example::before { content: 'ex) '; color: #3fb950; }

  /* 타임라인 */
  .timeline { position: relative; margin: 20px 0; padding-left: 20px; }
  .tl-item { position: relative; padding: 0 0 0 20px; }
  .tl-dot { position: absolute; left: -25px; top: 16px; width: 10px; height: 10px; border-radius: 50%; border: 2px solid #0d1117; }
  .tl-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px 20px; margin-bottom: 10px; }
  .tl-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
  .tl-badge { font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 4px; flex-shrink: 0; }
  .tl-title { font-size: 14px; font-weight: 600; color: #e6edf3; }
  .tl-desc { font-size: 13px; color: #8b949e; line-height: 1.7; }
  .tl-tags { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 8px; }
  .tl-tag { font-family: 'JetBrains Mono', monospace; font-size: 10px; padding: 1px 7px; border-radius: 4px; background: #1c2128; border: 1px solid #30363d; color: #484f58; }

  /* 재고 수량 흐름 */
  .stock-flow {
    background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; padding: 18px 22px; margin: 16px 0;
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
  }
  .sf-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 14px; display: block; }
  .sf-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
  .sf-label { color: #484f58; min-width: 80px; font-size: 11px; }
  .sf-val { font-weight: 700; }
  .sf-plus  { color: #3fb950; }
  .sf-minus { color: #ff7b72; }
  .sf-eq    { color: #ffa657; }
  .sf-op    { color: #484f58; margin: 0 4px; }

  /* 코드 블럭 */
  .code-block { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; margin: 12px 0; overflow: hidden; }
  .code-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; background: #161b22; border-bottom: 1px solid #30363d; }
  .code-lang { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; letter-spacing: 0.05em; }
  .code-dots { display: flex; gap: 5px; }
  .code-dots span { width: 10px; height: 10px; border-radius: 50%; }
  .code-dots span:nth-child(1) { background: #ff5f56; }
  .code-dots span:nth-child(2) { background: #ffbd2e; }
  .code-dots span:nth-child(3) { background: #27c93f; }
  .code-body { padding: 18px 22px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.85; overflow-x: auto; color: #e6edf3; }
  .kw { color: #ff7b72; } .fn { color: #d2a8ff; }
  .cm { color: #484f58; font-style: italic; } .str { color: #a5d6ff; }
  .num { color: #79c0ff; }

  /* tip / warn / info 박스 */
  .tip-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #3fb950; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .tip-box strong { color: #e6edf3; }
  .tip-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .warn-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ff7b72; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .warn-box strong { color: #e6edf3; }
  .warn-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ff7b72; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .info-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #58a6ff; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .info-box strong { color: #e6edf3; }
  .info-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }

  /* 모듈 구분 배너 */
  .module-banner {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 20px; border-radius: 8px; margin: 28px 0 16px;
    font-family: 'JetBrains Mono', monospace; font-size: 14px; font-weight: 700;
  }
  .mb-buy    { background: rgba(63,185,80,0.08);  border: 1px solid rgba(63,185,80,0.2);  color: #3fb950; }
  .mb-sal    { background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.2); color: #58a6ff; }
  .mb-inv    { background: rgba(255,166,87,0.08); border: 1px solid rgba(255,166,87,0.2); color: #ffa657; }
  .mb-icon   { font-size: 18px; }

  /* 비교 테이블 */
  .compare-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
  .compare-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 10px 14px; border: 1px solid #30363d; letter-spacing: 0.05em; text-align: left; }
  .compare-table td { padding: 10px 14px; border: 1px solid #30363d; color: #e6edf3; }
  .compare-table tr:nth-child(even) td { background: #161b22; }

  .ic { font-family: 'JetBrains Mono', monospace; font-size: 0.88em; padding: 1px 6px; background: #1c2128; border: 1px solid #30363d; border-radius: 4px; color: #ffa657; }
  .post-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 32px; padding-top: 20px; border-top: 1px solid #21262d; }
  .post-tag { padding: 3px 10px; background: rgba(63,185,80,0.08); border: 1px solid rgba(63,185,80,0.25); border-radius: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; }
  .post-tag::before { content: '#'; opacity: 0.6; }
  @media (max-width: 600px) {
    .term-grid { grid-template-columns: 1fr; }
    .toc-list { grid-template-columns: 1fr; }
  }
&lt;/style&gt;

&lt;div class=&quot;post-wrap&quot;&gt;
  &lt;div class=&quot;post-badge&quot;&gt;ERP · 구매 / 영업 / 재고 모듈&lt;/div&gt;

  &lt;p&gt;ERP의 꽃은 &lt;strong&gt;모듈 간 연계&lt;/strong&gt;예요.&lt;br&gt;
  구매 → 재고 → 영업이 어떻게 연결되는지 흐름을 이해하면&lt;br&gt;
  개발할 때 전체 그림이 보이고 훨씬 수월해져요.&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
    &lt;span class=&quot;toc-title&quot;&gt;// TABLE OF CONTENTS&lt;/span&gt;
    &lt;div class=&quot;toc-list&quot;&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;01&lt;/span&gt; 전체 모듈 연계 흐름&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;02&lt;/span&gt; 구매 모듈 — P2P 흐름&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;03&lt;/span&gt; 구매 모듈 — 핵심 용어&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;04&lt;/span&gt; 재고 모듈 — 재고 관리&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;05&lt;/span&gt; 재고 모듈 — 핵심 용어&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;06&lt;/span&gt; 영업 모듈 — O2C 흐름&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;07&lt;/span&gt; 영업 모듈 — 핵심 용어&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;08&lt;/span&gt; 재고 평가 방법&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;09&lt;/span&gt; 모듈 간 전표 자동화&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;10&lt;/span&gt; 개발 시 주의사항&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 01. 전체 연계 흐름 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;01&lt;/span&gt; 전체 모듈 연계 흐름&lt;/h2&gt;

  &lt;p&gt;ERP에서 구매/재고/영업은 독립된 모듈이지만 &lt;strong&gt;데이터가 서로 연결&lt;/strong&gt;돼요.&lt;br&gt;
  한 모듈의 처리 결과가 다음 모듈의 입력이 되는 구조예요.&lt;/p&gt;

  &lt;div class=&quot;full-flow&quot;&gt;
    &lt;span class=&quot;ff-title&quot;&gt;// 구매 → 재고 → 영업 → 회계 연계 흐름&lt;/span&gt;
    &lt;div class=&quot;ff-row&quot;&gt;
      &lt;span class=&quot;ff-item ff-buy&quot;&gt;발주 (구매)&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item ff-buy&quot;&gt;입고&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item ff-inv&quot;&gt;재고 증가  &lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item ff-sal&quot;&gt;수주 (영업)&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item ff-sal&quot;&gt;출하&lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item ff-inv&quot;&gt;재고 감소  &lt;/span&gt;
      &lt;span class=&quot;ff-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;ff-item ff-acc&quot;&gt;회계 전표&lt;/span&gt;
    &lt;/div&gt;
    &lt;div style=&quot;margin-top:12px; font-size:11px; color:#484f58; font-family:'Pretendard',sans-serif;&quot;&gt;
      ※ 입고 → 매입채무 발생 / 출하 → 매출채권 발생 → 회계 모듈 자동 연동
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 구매 모듈 배너 --&gt;
  &lt;div class=&quot;module-banner mb-buy&quot;&gt;
    &lt;span class=&quot;mb-icon&quot;&gt; &lt;/span&gt;
    구매 모듈 (Procurement)
  &lt;/div&gt;

  &lt;!-- 02. 구매 P2P 흐름 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;02&lt;/span&gt; 구매 모듈 — P2P 흐름&lt;/h2&gt;

  &lt;p&gt;Purchase to Pay. &lt;strong&gt;구매 요청부터 대금 지급까지&lt;/strong&gt;의 전체 흐름이에요.&lt;/p&gt;

  &lt;div class=&quot;timeline&quot;&gt;
    &lt;div style=&quot;position:absolute; left:0; top:0; bottom:0; width:2px; background:linear-gradient(to bottom, #3fb950, #58a6ff, #bc8cff, #ffa657, #ff7b72); border-radius:2px;&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#3fb950;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(63,185,80,0.1);color:#3fb950;border:1px solid rgba(63,185,80,0.3);&quot;&gt;STEP 1&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;구매 요청 (PR — Purchase Request)&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;현장/부서에서 필요한 물품 구매를 요청.&lt;br&gt;&lt;strong&gt;요청자 → 구매팀&lt;/strong&gt;으로 승인 흐름. 예산 체크 포함.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;PR&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;구매요청&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;예산체크&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#58a6ff;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(88,166,255,0.1);color:#58a6ff;border:1px solid rgba(88,166,255,0.3);&quot;&gt;STEP 2&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;견적 / 협력사 선정&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;협력사에 견적 요청(RFQ). 가격/납기/품질 비교 후 선정.&lt;br&gt;중견기업은 &lt;strong&gt;승인된 협력사(Approved Vendor)&lt;/strong&gt; 목록에서만 선정.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;RFQ&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;견적&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;협력사&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#bc8cff;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(188,140,255,0.1);color:#bc8cff;border:1px solid rgba(188,140,255,0.3);&quot;&gt;STEP 3&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;발주 (PO — Purchase Order)&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;협력사에 정식으로 주문. &lt;strong&gt;발주서(PO) 발행.&lt;/strong&gt;&lt;br&gt;품목/수량/단가/납기일 확정. 계약의 효력 발생.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;PO&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;발주서&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;납기일&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#ffa657;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(255,166,87,0.1);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;STEP 4&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;입고 (GR — Goods Receipt)&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;물품 수령 후 수량/품질 검사. &lt;strong&gt;입고 처리 시 재고 자동 증가.&lt;/strong&gt;&lt;br&gt;PO와 수량 대조(3-Way Matching).&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;GR&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;입고검사&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;재고증가&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#ff7b72;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(255,123,114,0.1);color:#ff7b72;border:1px solid rgba(255,123,114,0.3);&quot;&gt;STEP 5&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;세금계산서 수취 &amp; 지급&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;협력사로부터 세금계산서 수취 → 매입채무 확정.&lt;br&gt;&lt;strong&gt;지급기일&lt;/strong&gt;에 맞춰 계좌이체/어음 지급 → 채무 소멸.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;매입채무&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;세금계산서&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;지급&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;!-- 03. 구매 용어 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;03&lt;/span&gt; 구매 모듈 — 핵심 용어&lt;/h2&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#3fb950;&quot;&gt;단가 계약&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;협력사와 &lt;strong&gt;특정 기간/물품의 단가를 미리 계약.&lt;/strong&gt;&lt;br&gt;발주 시 단가 계약에서 자동 적용. 매번 견적 불필요.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;A사 볼트 2024년 연간 단가: 500원/개&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#58a6ff;&quot;&gt;3-Way Matching&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;PO(발주) - GR(입고) - Invoice(세금계산서) &lt;strong&gt;3개를 대조&lt;/strong&gt;해서 일치 시에만 지급 승인.&lt;br&gt;부정 지급 방지의 핵심.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;반품 (Return)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;입고 후 불량/오배송 발견 시 협력사에 반품.&lt;br&gt;&lt;strong&gt;반품 처리 시 재고 자동 감소 + 매입채무 취소.&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ffa657;&quot;&gt;MRP (자재소요계획)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;생산 계획 기반으로 &lt;strong&gt;필요 자재를 자동 계산&lt;/strong&gt;해 발주 제안.&lt;br&gt;제조업 ERP의 핵심 기능.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;완제품 100개 생산 → 부품 A 300개 필요 → 자동 발주 제안&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ff7b72;&quot;&gt;발주 잔량&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;발주는 했지만 아직 &lt;strong&gt;입고되지 않은 수량.&lt;/strong&gt;&lt;br&gt;발주량 - 입고량 = 발주잔량. 재고 예측에 사용.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#39d353;&quot;&gt;검수 (Inspection)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;입고 물품의 &lt;strong&gt;수량·품질 확인 절차.&lt;/strong&gt;&lt;br&gt;전수검사/샘플검사 선택. 불합격 시 반품 처리.&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 재고 모듈 배너 --&gt;
  &lt;div class=&quot;module-banner mb-inv&quot;&gt;
    &lt;span class=&quot;mb-icon&quot;&gt; &lt;/span&gt;
    재고 모듈 (Inventory)
  &lt;/div&gt;

  &lt;!-- 04. 재고 관리 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;04&lt;/span&gt; 재고 모듈 — 재고 수량 흐름&lt;/h2&gt;

  &lt;p&gt;재고는 &lt;strong&gt;입고되면 늘고, 출고되면 줄어요.&lt;/strong&gt;&lt;br&gt;
  ERP에서는 모든 재고 변동이 자동으로 기록돼야 해요.&lt;/p&gt;

  &lt;div class=&quot;stock-flow&quot;&gt;
    &lt;span class=&quot;sf-title&quot;&gt;// 재고 수량 계산 구조&lt;/span&gt;
    &lt;div class=&quot;sf-row&quot;&gt;
      &lt;span class=&quot;sf-label&quot;&gt;기초재고&lt;/span&gt;
      &lt;span class=&quot;sf-val sf-eq&quot;&gt;100개&lt;/span&gt;
      &lt;span class=&quot;sf-op&quot;&gt;+&lt;/span&gt;
      &lt;span class=&quot;sf-label&quot;&gt;입고&lt;/span&gt;
      &lt;span class=&quot;sf-val sf-plus&quot;&gt;+50개&lt;/span&gt;
      &lt;span class=&quot;sf-op&quot;&gt;-&lt;/span&gt;
      &lt;span class=&quot;sf-label&quot;&gt;출고&lt;/span&gt;
      &lt;span class=&quot;sf-val sf-minus&quot;&gt;-30개&lt;/span&gt;
      &lt;span class=&quot;sf-op&quot;&gt;-&lt;/span&gt;
      &lt;span class=&quot;sf-label&quot;&gt;반품출고&lt;/span&gt;
      &lt;span class=&quot;sf-val sf-minus&quot;&gt;-5개&lt;/span&gt;
      &lt;span class=&quot;sf-op&quot;&gt;+&lt;/span&gt;
      &lt;span class=&quot;sf-label&quot;&gt;반품입고&lt;/span&gt;
      &lt;span class=&quot;sf-val sf-plus&quot;&gt;+3개&lt;/span&gt;
      &lt;span class=&quot;sf-op&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;sf-val sf-eq&quot;&gt;기말재고 118개&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;재고 이동 유형&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;-- 재고 증가 이동&lt;/span&gt;&lt;br&gt;
구매 입고&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 협력사로부터 입고&lt;/span&gt;&lt;br&gt;
생산 입고&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 생산 완료 제품 입고&lt;/span&gt;&lt;br&gt;
판매 반품&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 고객이 반품한 물품 입고&lt;/span&gt;&lt;br&gt;
재고 조정(+) &lt;span class=&quot;cm&quot;&gt;-- 실사 후 수량 증가 조정&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 재고 감소 이동&lt;/span&gt;&lt;br&gt;
판매 출고&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 고객에게 출하&lt;/span&gt;&lt;br&gt;
생산 출고&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 생산에 원자재 투입&lt;/span&gt;&lt;br&gt;
구매 반품&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 협력사에 반품&lt;/span&gt;&lt;br&gt;
폐기&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- 불량/유효기간 만료&lt;/span&gt;&lt;br&gt;
재고 조정(-) &lt;span class=&quot;cm&quot;&gt;-- 실사 후 수량 감소 조정&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 재고 이동 (증감 없음)&lt;/span&gt;&lt;br&gt;
창고 간 이동&amp;nbsp;&lt;span class=&quot;cm&quot;&gt;-- A창고 → B창고 (회사 내부)&lt;/span&gt;&lt;br&gt;
로케이션 이동 &lt;span class=&quot;cm&quot;&gt;-- 같은 창고 내 위치 변경&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;!-- 05. 재고 용어 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;05&lt;/span&gt; 재고 모듈 — 핵심 용어&lt;/h2&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ffa657;&quot;&gt;안전재고 (Safety Stock)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;수요 변동·납기 지연에 대비한 &lt;strong&gt;최소 보유 재고량.&lt;/strong&gt;&lt;br&gt;안전재고 이하로 떨어지면 발주 알림 발생.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;안전재고 50개 → 재고 45개 → 발주 알림!&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#3fb950;&quot;&gt;재주문점 (ROP)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;발주를 트리거하는 재고 수준.&lt;br&gt;&lt;strong&gt;일평균 사용량 × 리드타임 + 안전재고&lt;/strong&gt;로 계산.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;일 10개 사용, 리드타임 5일 → ROP = 50+안전재고&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#58a6ff;&quot;&gt;로케이션 (Location)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;창고 내 &lt;strong&gt;물품이 보관된 위치.&lt;/strong&gt;&lt;br&gt;동-열-단 체계로 관리. 바코드/QR로 조회.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;A동-3열-2단 → A-03-02&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;LOT / 배치 관리&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;동일 생산 배치를 &lt;strong&gt;LOT 번호로 묶어 추적.&lt;/strong&gt;&lt;br&gt;불량 발생 시 해당 LOT 전체 추적 가능. 식품/의약품 필수.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;LOT: 20241215-001 → 불량 발생 시 해당 LOT 전량 회수&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ff7b72;&quot;&gt;재고 실사 (Physical Inventory)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;실제 재고 수량과 &lt;strong&gt;시스템 수량을 대조&lt;/strong&gt;하는 작업.&lt;br&gt;차이 발생 시 재고 조정 처리. 보통 월/분기/연 1회.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#39d353;&quot;&gt;가용재고&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;&lt;strong&gt;실제 사용 가능한 재고량.&lt;/strong&gt;&lt;br&gt;현재고 - 예약재고(출하 예정) + 발주잔량(입고 예정).&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;현재고 100 - 예약 30 + 발주잔량 50 = 가용 120&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 영업 모듈 배너 --&gt;
  &lt;div class=&quot;module-banner mb-sal&quot;&gt;
    &lt;span class=&quot;mb-icon&quot;&gt; &lt;/span&gt;
    영업 모듈 (Sales)
  &lt;/div&gt;

  &lt;!-- 06. 영업 O2C 흐름 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;06&lt;/span&gt; 영업 모듈 — O2C 흐름&lt;/h2&gt;

  &lt;p&gt;Order to Cash. &lt;strong&gt;수주부터 대금 수령까지&lt;/strong&gt;의 전체 흐름이에요.&lt;/p&gt;

  &lt;div class=&quot;timeline&quot;&gt;
    &lt;div style=&quot;position:absolute; left:0; top:0; bottom:0; width:2px; background:linear-gradient(to bottom, #58a6ff, #3fb950, #bc8cff, #ffa657, #ff7b72); border-radius:2px;&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#58a6ff;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(88,166,255,0.1);color:#58a6ff;border:1px solid rgba(88,166,255,0.3);&quot;&gt;STEP 1&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;견적 (Quotation)&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;고객 요청에 따라 &lt;strong&gt;가격/납기/조건을 제시.&lt;/strong&gt;&lt;br&gt;견적 유효기간 내 수주 전환 가능.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;견적서&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;유효기간&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;단가&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#3fb950;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(63,185,80,0.1);color:#3fb950;border:1px solid rgba(63,185,80,0.3);&quot;&gt;STEP 2&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;수주 (SO — Sales Order)&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;고객의 정식 주문 접수. &lt;strong&gt;재고 예약 발생.&lt;/strong&gt;&lt;br&gt;가용재고 확인 후 납기 확정. 신용 한도 체크.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;SO&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;재고예약&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;신용한도&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#bc8cff;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(188,140,255,0.1);color:#bc8cff;border:1px solid rgba(188,140,255,0.3);&quot;&gt;STEP 3&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;출하 지시 / 피킹&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;창고에 출하 지시. &lt;strong&gt;피킹(Picking) = 재고 위치에서 물품 꺼내기.&lt;/strong&gt;&lt;br&gt;패킹(Packing) → 운송장 출력.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;피킹&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;패킹&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;운송장&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#ffa657;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(255,166,87,0.1);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;STEP 4&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;출하 (Delivery)&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;물품 발송. &lt;strong&gt;출하 처리 시 재고 자동 감소.&lt;/strong&gt;&lt;br&gt;매출 전표 자동 생성 → 회계 연동.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;출하&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;재고감소&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;매출전표&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#ff7b72;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(255,123,114,0.1);color:#ff7b72;border:1px solid rgba(255,123,114,0.3);&quot;&gt;STEP 5&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;세금계산서 발행 &amp; 수금&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;전자세금계산서 발행 → 매출채권 확정.&lt;br&gt;&lt;strong&gt;수금 처리&lt;/strong&gt; 시 매출채권 소멸. 미수금 관리 중요.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;매출채권&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;수금&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;미수금&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;!-- 07. 영업 용어 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;07&lt;/span&gt; 영업 모듈 — 핵심 용어&lt;/h2&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#58a6ff;&quot;&gt;고객 (Customer)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;영업 거래처. &lt;strong&gt;신용한도, 결제조건, 담당 영업사원&lt;/strong&gt; 등을 관리.&lt;br&gt;고객 코드로 모든 거래 이력 추적.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#3fb950;&quot;&gt;신용한도 (Credit Limit)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;고객의 &lt;strong&gt;최대 외상 허용 금액.&lt;/strong&gt;&lt;br&gt;수주 시 자동 체크. 한도 초과 시 수주 보류 또는 경고.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;신용한도 5천만원 → 미수금 4천+신규 2천 = 초과!&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;납기 (Delivery Date)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;고객에게 약속한 &lt;strong&gt;물품 도착 날짜.&lt;/strong&gt;&lt;br&gt;납기 준수율이 영업 KPI. 재고/생산 계획과 연동.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ffa657;&quot;&gt;수주 잔량&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;수주는 됐지만 아직 &lt;strong&gt;출하되지 않은 수량.&lt;/strong&gt;&lt;br&gt;수주량 - 출하량 = 수주잔량. 생산/재고 계획 기준.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ff7b72;&quot;&gt;반품 (Sales Return)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;고객이 불량/오배송 등으로 반품.&lt;br&gt;&lt;strong&gt;반품 입고 처리 → 재고 증가 + 매출 취소 전표.&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#39d353;&quot;&gt;채권 연령 분석&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;미수금을 &lt;strong&gt;30일/60일/90일 이상&lt;/strong&gt;으로 구분 관리.&lt;br&gt;장기 미수금은 대손 처리 위험 → 집중 관리 대상.&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 08. 재고 평가 방법 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;08&lt;/span&gt; 재고 평가 방법&lt;/h2&gt;

  &lt;p&gt;같은 품목도 &lt;strong&gt;언제 얼마에 샀냐&lt;/strong&gt;에 따라 재고 단가가 달라져요.&lt;br&gt;
  평가 방법에 따라 매출원가와 재고자산 금액이 달라지므로 중요해요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;재고 평가 방법 비교 (볼트 100개 예시)&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;-- 입고 내역&lt;/span&gt;&lt;br&gt;
1월: &lt;span class=&quot;num&quot;&gt;50&lt;/span&gt;개 @ &lt;span class=&quot;num&quot;&gt;1,000&lt;/span&gt;원 = &lt;span class=&quot;num&quot;&gt;50,000&lt;/span&gt;원&lt;br&gt;
2월: &lt;span class=&quot;num&quot;&gt;50&lt;/span&gt;개 @ &lt;span class=&quot;num&quot;&gt;1,200&lt;/span&gt;원 = &lt;span class=&quot;num&quot;&gt;60,000&lt;/span&gt;원&lt;br&gt;
총 &lt;span class=&quot;num&quot;&gt;100&lt;/span&gt;개, 총 &lt;span class=&quot;num&quot;&gt;110,000&lt;/span&gt;원&lt;br&gt;
3월에 &lt;span class=&quot;num&quot;&gt;60&lt;/span&gt;개 출고&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 선입선출법 (FIFO) ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 먼저 들어온 것부터 먼저 나감&lt;/span&gt;&lt;br&gt;
출고원가: 1월 50개(50,000) + 2월 10개(12,000) = &lt;span class=&quot;num&quot;&gt;62,000&lt;/span&gt;원&lt;br&gt;
기말재고: 2월 40개 × 1,200 = &lt;span class=&quot;num&quot;&gt;48,000&lt;/span&gt;원&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 총평균법 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 전체 평균 단가로 계산&lt;/span&gt;&lt;br&gt;
평균단가: 110,000 ÷ 100개 = &lt;span class=&quot;num&quot;&gt;1,100&lt;/span&gt;원&lt;br&gt;
출고원가: 60개 × 1,100 = &lt;span class=&quot;num&quot;&gt;66,000&lt;/span&gt;원&lt;br&gt;
기말재고: 40개 × 1,100 = &lt;span class=&quot;num&quot;&gt;44,000&lt;/span&gt;원&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 이동평균법 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 입고 때마다 평균 재계산&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 실시간 단가 관리. ERP에서 가장 많이 사용&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;info-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  실무에서는&lt;/span&gt;
    국내 중견기업은 대부분 &lt;strong&gt;총평균법 또는 이동평균법&lt;/strong&gt;을 사용해요.&lt;br&gt;
    선입선출법은 식품/의약품처럼 유통기한 관리가 필요한 업종에서 주로 사용해요.&lt;br&gt;
    재고 평가 방법은 &lt;strong&gt;회계 정책으로 정해두면 함부로 바꾸기 어려워요.&lt;/strong&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 09. 모듈 간 전표 자동화 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;09&lt;/span&gt; 모듈 간 전표 자동 생성&lt;/h2&gt;

  &lt;p&gt;ERP의 핵심 가치는 &lt;strong&gt;업무 처리 시 회계 전표가 자동으로 생성&lt;/strong&gt;되는 것이에요.&lt;/p&gt;

  &lt;div class=&quot;compare-table&quot; style=&quot;font-size:13px;&quot;&gt;
    &lt;table style=&quot;width:100%; border-collapse:collapse;&quot;&gt;
      &lt;tr&gt;
        &lt;th style=&quot;background:#1c2128;color:#8b949e;font-family:'JetBrains Mono',monospace;font-size:11px;padding:10px 14px;border:1px solid #30363d;text-align:left;&quot;&gt;모듈 처리&lt;/th&gt;
        &lt;th style=&quot;background:#1c2128;color:#8b949e;font-family:'JetBrains Mono',monospace;font-size:11px;padding:10px 14px;border:1px solid #30363d;text-align:left;&quot;&gt;자동 생성 전표&lt;/th&gt;
        &lt;th style=&quot;background:#1c2128;color:#8b949e;font-family:'JetBrains Mono',monospace;font-size:11px;padding:10px 14px;border:1px solid #30363d;text-align:left;&quot;&gt;차변&lt;/th&gt;
        &lt;th style=&quot;background:#1c2128;color:#8b949e;font-family:'JetBrains Mono',monospace;font-size:11px;padding:10px 14px;border:1px solid #30363d;text-align:left;&quot;&gt;대변&lt;/th&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#3fb950;font-weight:700;&quot;&gt;구매 입고&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#e6edf3;&quot;&gt;매입 전표&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#58a6ff;font-family:'JetBrains Mono',monospace;&quot;&gt;재고자산&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#ff7b72;font-family:'JetBrains Mono',monospace;&quot;&gt;매입채무&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr style=&quot;background:#161b22;&quot;&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#3fb950;font-weight:700;&quot;&gt;구매 반품&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#e6edf3;&quot;&gt;역분개 전표&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#58a6ff;font-family:'JetBrains Mono',monospace;&quot;&gt;매입채무&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#ff7b72;font-family:'JetBrains Mono',monospace;&quot;&gt;재고자산&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#58a6ff;font-weight:700;&quot;&gt;판매 출하&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#e6edf3;&quot;&gt;매출 전표&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#58a6ff;font-family:'JetBrains Mono',monospace;&quot;&gt;매출채권&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#ff7b72;font-family:'JetBrains Mono',monospace;&quot;&gt;매출 / 부가세&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr style=&quot;background:#161b22;&quot;&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#58a6ff;font-weight:700;&quot;&gt;판매 반품&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#e6edf3;&quot;&gt;역분개 전표&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#58a6ff;font-family:'JetBrains Mono',monospace;&quot;&gt;매출 / 부가세&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#ff7b72;font-family:'JetBrains Mono',monospace;&quot;&gt;매출채권&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#ffa657;font-weight:700;&quot;&gt;재고 폐기&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#e6edf3;&quot;&gt;재고 조정 전표&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#58a6ff;font-family:'JetBrains Mono',monospace;&quot;&gt;재고폐기손실&lt;/td&gt;
        &lt;td style=&quot;padding:10px 14px;border:1px solid #30363d;color:#ff7b72;font-family:'JetBrains Mono',monospace;&quot;&gt;재고자산&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/table&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 10. 개발 주의사항 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;10&lt;/span&gt; 개발 시 핵심 주의사항&lt;/h2&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  재고 수량 동시성 문제&lt;/span&gt;
    동시에 여러 사용자가 같은 품목을 출고 처리하면 &lt;strong&gt;재고가 마이너스&lt;/strong&gt;가 될 수 있어요.&lt;br&gt;
    출고 처리 시 &lt;strong&gt;FOR UPDATE(배타락)&lt;/strong&gt;로 해당 재고 행을 잠그고 처리해야 해요.&lt;br&gt;
    재고 수량은 절대 마이너스가 되면 안 돼요 → 출고 전 가용재고 체크 필수!
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  이력 관리 필수&lt;/span&gt;
    모든 재고 변동은 &lt;strong&gt;이동 이력 테이블&lt;/strong&gt;에 기록해야 해요.&lt;br&gt;
    현재 수량만 저장하면 &quot;언제 왜 줄었냐&quot;를 추적할 수 없어요.&lt;br&gt;
    LOT 관리 품목은 LOT 별 수량까지 이력 추적이 필요해요.
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  모듈 간 트랜잭션 처리&lt;/span&gt;
    출하 처리 시 재고 감소 + 매출 전표 생성이 &lt;strong&gt;하나의 트랜잭션&lt;/strong&gt;으로 처리돼야 해요.&lt;br&gt;
    재고는 줄었는데 전표가 안 생기거나, 전표는 생겼는데 재고가 안 줄면 데이터 불일치 발생.&lt;br&gt;
    모듈 간 연계 처리는 반드시 &lt;strong&gt;COMMIT / ROLLBACK으로 묶어서&lt;/strong&gt; 처리하세요.
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;✅ CONCLUSION&lt;/span&gt;
    구매/재고/영업은 &lt;strong&gt;데이터가 서로 연결된 하나의 흐름&lt;/strong&gt;이에요.&lt;br&gt;
    구매 입고 → 재고 증가 → 영업 출하 → 재고 감소 → 회계 전표 자동 생성.&lt;br&gt;
    개발할 때 이 흐름을 항상 머릿속에 그리면 어떤 모듈을 개발해도 길을 잃지 않아요!
  &lt;/div&gt;

  &lt;div class=&quot;post-tags&quot;&gt;
    &lt;span class=&quot;post-tag&quot;&gt;ERP&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;구매모듈&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;영업모듈&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;재고모듈&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;P2P&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;O2C&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;재고평가&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;MRP&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;LOT관리&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;</description>
      <category>  개발 공부/ERP</category>
      <author>woohaha</author>
      <guid isPermaLink="true">https://story2248.tistory.com/16</guid>
      <comments>https://story2248.tistory.com/16#entry16comment</comments>
      <pubDate>Mon, 11 May 2026 20:08:31 +0900</pubDate>
    </item>
    <item>
      <title>[DB] 트랜잭션 완전 정복 - ACID, 격리 수준, 데드락까지 한 번에</title>
      <link>https://story2248.tistory.com/15</link>
      <description>&lt;style&gt;
  .post-wrap {
    background: #0d1117; color: #e6edf3;
    font-family: 'Pretendard', -apple-system, sans-serif;
    padding: 0; line-height: 1.8;
  }
  .post-wrap * { box-sizing: border-box; }
  .post-wrap p { margin: 0 0 16px; color: #8b949e; font-size: 15px; }
  .post-wrap p strong { color: #e6edf3; }
  .post-divider { border: none; border-top: 1px solid #21262d; margin: 32px 0; }

  .post-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px;
    background: rgba(188,140,255,0.1); border: 1px solid rgba(188,140,255,0.3);
    border-radius: 20px; font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: #bc8cff; margin-bottom: 20px;
  }
  .post-badge::before { content: ' ️'; }

  /* 목차 */
  .toc { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 24px 0; }
  .toc-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .toc-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .toc-item { font-size: 13px; color: #8b949e; padding: 3px 0; display: flex; align-items: center; gap: 8px; }
  .toc-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #bc8cff; }

  .post-wrap h2 {
    font-size: 18px; font-weight: 600; color: #e6edf3;
    margin: 32px 0 16px; padding-bottom: 8px;
    border-bottom: 1px solid #21262d;
    display: flex; align-items: center; gap: 8px;
  }
  .post-wrap h2 .num {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #bc8cff; background: rgba(188,140,255,0.1);
    border: 1px solid rgba(188,140,255,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h3 {
    font-size: 15px; font-weight: 600; color: #8b949e;
    margin: 20px 0 10px; font-family: 'JetBrains Mono', monospace;
  }
  .post-wrap h3::before { content: '// '; color: #484f58; }

  /* ACID 카드 */
  .acid-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin: 20px 0; }
  .acid-card {
    background: #161b22; border: 1px solid #30363d;
    border-radius: 8px; padding: 18px 20px;
  }
  .acid-letter {
    font-family: 'JetBrains Mono', monospace; font-size: 32px; font-weight: 700;
    margin-bottom: 4px; display: block;
  }
  .acid-name { font-size: 13px; font-weight: 600; color: #8b949e; margin-bottom: 10px; font-family: 'JetBrains Mono', monospace; }
  .acid-desc { font-size: 13px; color: #8b949e; line-height: 1.7; }
  .acid-desc strong { color: #e6edf3; }
  .acid-example { margin-top: 10px; padding: 8px 12px; background: #0d1117; border-radius: 5px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; line-height: 1.7; }
  .acid-example::before { content: 'ex) '; color: #bc8cff; }

  /* 트랜잭션 흐름 */
  .tx-flow {
    background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; padding: 20px 24px; margin: 16px 0;
    font-family: 'JetBrains Mono', monospace;
  }
  .tx-flow .viz-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 16px; display: block; }
  .tx-step {
    display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
  }
  .tx-node {
    padding: 6px 14px; border-radius: 6px; font-size: 12px; font-weight: 600; flex-shrink: 0;
  }
  .tn-start  { background: rgba(63,185,80,0.15);  color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .tn-work   { background: rgba(88,166,255,0.15); color: #58a6ff; border: 1px solid rgba(88,166,255,0.3); }
  .tn-commit { background: rgba(63,185,80,0.15);  color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .tn-roll   { background: rgba(255,123,114,0.15);color: #ff7b72; border: 1px solid rgba(255,123,114,0.3); }
  .tn-err    { background: rgba(255,166,87,0.15); color: #ffa657; border: 1px solid rgba(255,166,87,0.3); }
  .tx-arrow  { color: #484f58; font-size: 14px; }
  .tx-desc   { font-size: 11px; color: #484f58; }

  /* 격리 수준 테이블 */
  .iso-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
  .iso-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 10px 14px; border: 1px solid #30363d; letter-spacing: 0.05em; text-align: center; }
  .iso-table td { padding: 10px 14px; border: 1px solid #30363d; color: #e6edf3; text-align: center; font-size: 13px; }
  .iso-table tr:nth-child(even) td { background: #161b22; }
  .iso-table td:first-child { text-align: left; font-family: 'JetBrains Mono', monospace; font-weight: 700; }
  .ok  { color: #3fb950 !important; font-weight: 700; }
  .ng  { color: #ff7b72 !important; font-weight: 700; }
  .best { background: rgba(63,185,80,0.08) !important; }

  /* 문제 현상 카드 */
  .problem-grid { display: grid; grid-template-columns: repeat(1, 1fr); gap: 10px; margin: 16px 0; }
  .problem-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 14px 18px; }
  .problem-name { font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 700; margin-bottom: 8px; }
  .problem-desc { font-size: 13px; color: #8b949e; line-height: 1.65; }
  .problem-desc strong { color: #e6edf3; }

  /* 코드 블럭 */
  .code-block { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; margin: 12px 0; overflow: hidden; }
  .code-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; background: #161b22; border-bottom: 1px solid #30363d; }
  .code-lang { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #bc8cff; letter-spacing: 0.05em; }
  .code-dots { display: flex; gap: 5px; }
  .code-dots span { width: 10px; height: 10px; border-radius: 50%; }
  .code-dots span:nth-child(1) { background: #ff5f56; }
  .code-dots span:nth-child(2) { background: #ffbd2e; }
  .code-dots span:nth-child(3) { background: #27c93f; }
  .code-body { padding: 18px 22px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.85; overflow-x: auto; color: #e6edf3; }
  .kw { color: #ff7b72; } .fn { color: #d2a8ff; }
  .cm { color: #484f58; font-style: italic; } .str { color: #a5d6ff; }
  .num { color: #79c0ff; } .good { color: #3fb950; } .bad { color: #ff7b72; }
  .tx1 { color: #58a6ff; font-weight: 600; }
  .tx2 { color: #ffa657; font-weight: 600; }

  /* tip / warn 박스 */
  .tip-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #bc8cff; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .tip-box strong { color: #e6edf3; }
  .tip-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #bc8cff; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .warn-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ffa657; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .warn-box strong { color: #e6edf3; }
  .warn-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ffa657; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }

  .ic { font-family: 'JetBrains Mono', monospace; font-size: 0.88em; padding: 1px 6px; background: #1c2128; border: 1px solid #30363d; border-radius: 4px; color: #ffa657; }
  .post-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 32px; padding-top: 20px; border-top: 1px solid #21262d; }
  .post-tag { padding: 3px 10px; background: rgba(188,140,255,0.08); border: 1px solid rgba(188,140,255,0.25); border-radius: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #bc8cff; }
  .post-tag::before { content: '#'; opacity: 0.6; }
  @media (max-width: 600px) {
    .acid-grid { grid-template-columns: 1fr; }
    .toc-list { grid-template-columns: 1fr; }
  }
&lt;/style&gt;

&lt;div class=&quot;post-wrap&quot;&gt;
  &lt;div class=&quot;post-badge&quot;&gt;CS 면접 대비 · 데이터베이스&lt;/div&gt;

  &lt;p&gt;DB 면접에서 &lt;strong&gt;&quot;트랜잭션이 뭔가요?&quot;&lt;/strong&gt;, &lt;strong&gt;&quot;ACID가 뭔가요?&quot;&lt;/strong&gt;는 거의 필수 질문이에요.&lt;br&gt;
  개념부터 격리 수준, 실제 SQL까지 한 번에 정리해드릴게요.&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
    &lt;span class=&quot;toc-title&quot;&gt;// TABLE OF CONTENTS&lt;/span&gt;
    &lt;div class=&quot;toc-list&quot;&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;01&lt;/span&gt; 트랜잭션이란?&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;02&lt;/span&gt; ACID 특성&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;03&lt;/span&gt; COMMIT / ROLLBACK&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;04&lt;/span&gt; 격리 수준 4단계&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;05&lt;/span&gt; 동시성 문제 3가지&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;06&lt;/span&gt; 락(Lock) 개념&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;07&lt;/span&gt; 데드락&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;08&lt;/span&gt; 면접 답변 정리&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 01. 트랜잭션이란? --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;01&lt;/span&gt; 트랜잭션이란?&lt;/h2&gt;

  &lt;p&gt;트랜잭션은 &lt;strong&gt;하나의 논리적 작업 단위&lt;/strong&gt;예요.&lt;br&gt;
  여러 DB 작업을 묶어서 &lt;strong&gt;전부 성공하거나 전부 실패&lt;/strong&gt;하게 만드는 것이 핵심이에요.&lt;/p&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  가장 유명한 예시 — 계좌 이체&lt;/span&gt;
    A 계좌에서 100만원 출금 → B 계좌에 100만원 입금&lt;br&gt;&lt;br&gt;
    만약 출금은 됐는데 입금 중 오류가 나면? → &lt;strong&gt;돈이 사라짐!&lt;/strong&gt;&lt;br&gt;
    트랜잭션으로 묶으면 → 입금 실패 시 출금도 자동 취소(ROLLBACK)
  &lt;/div&gt;

  &lt;div class=&quot;tx-flow&quot;&gt;
    &lt;span class=&quot;viz-title&quot;&gt;// 트랜잭션 흐름&lt;/span&gt;
    &lt;div class=&quot;tx-step&quot;&gt;
      &lt;span class=&quot;tx-node tn-start&quot;&gt;BEGIN&lt;/span&gt;
      &lt;span class=&quot;tx-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;tx-node tn-work&quot;&gt;UPDATE (출금)&lt;/span&gt;
      &lt;span class=&quot;tx-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;tx-node tn-work&quot;&gt;UPDATE (입금)&lt;/span&gt;
      &lt;span class=&quot;tx-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;tx-node tn-commit&quot;&gt;COMMIT ✅&lt;/span&gt;
      &lt;span class=&quot;tx-desc&quot;&gt;← 모두 성공 시&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tx-step&quot; style=&quot;margin-top:8px;&quot;&gt;
      &lt;span class=&quot;tx-node tn-start&quot;&gt;BEGIN&lt;/span&gt;
      &lt;span class=&quot;tx-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;tx-node tn-work&quot;&gt;UPDATE (출금)&lt;/span&gt;
      &lt;span class=&quot;tx-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;tx-node tn-err&quot;&gt;ERROR 발생&lt;/span&gt;
      &lt;span class=&quot;tx-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;tx-node tn-roll&quot;&gt;ROLLBACK ❌&lt;/span&gt;
      &lt;span class=&quot;tx-desc&quot;&gt;← 실패 시 전부 취소&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 02. ACID --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;02&lt;/span&gt; ACID 특성&lt;/h2&gt;

  &lt;p&gt;트랜잭션이 보장해야 하는 4가지 특성이에요. 면접에서 하나씩 설명할 수 있어야 해요.&lt;/p&gt;

  &lt;div class=&quot;acid-grid&quot;&gt;
    &lt;div class=&quot;acid-card&quot;&gt;
      &lt;span class=&quot;acid-letter&quot; style=&quot;color:#58a6ff;&quot;&gt;A&lt;/span&gt;
      &lt;div class=&quot;acid-name&quot;&gt;Atomicity — 원자성&lt;/div&gt;
      &lt;div class=&quot;acid-desc&quot;&gt;트랜잭션 내 작업은 &lt;strong&gt;전부 성공하거나 전부 실패&lt;/strong&gt;해야 해요.&lt;br&gt;&quot;쪼갤 수 없다&quot;는 원자의 특성에서 유래.&lt;/div&gt;
      &lt;div class=&quot;acid-example&quot;&gt;계좌이체: 출금+입금 모두 성공 OR 모두 취소&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;acid-card&quot;&gt;
      &lt;span class=&quot;acid-letter&quot; style=&quot;color:#3fb950;&quot;&gt;C&lt;/span&gt;
      &lt;div class=&quot;acid-name&quot;&gt;Consistency — 일관성&lt;/div&gt;
      &lt;div class=&quot;acid-desc&quot;&gt;트랜잭션 전후로 DB가 &lt;strong&gt;항상 일관된 상태&lt;/strong&gt;를 유지해야 해요.&lt;br&gt;무결성 제약조건(PK, FK, NOT NULL 등)을 항상 만족.&lt;/div&gt;
      &lt;div class=&quot;acid-example&quot;&gt;이체 후 A+B 잔액 합계는 이체 전과 동일해야 함&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;acid-card&quot;&gt;
      &lt;span class=&quot;acid-letter&quot; style=&quot;color:#bc8cff;&quot;&gt;I&lt;/span&gt;
      &lt;div class=&quot;acid-name&quot;&gt;Isolation — 격리성&lt;/div&gt;
      &lt;div class=&quot;acid-desc&quot;&gt;동시에 실행되는 트랜잭션이 &lt;strong&gt;서로 영향을 주지 않아야&lt;/strong&gt; 해요.&lt;br&gt;격리 수준(Isolation Level)으로 조절 가능.&lt;/div&gt;
      &lt;div class=&quot;acid-example&quot;&gt;A가 이체 중일 때 B는 중간 상태를 보면 안 됨&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;acid-card&quot;&gt;
      &lt;span class=&quot;acid-letter&quot; style=&quot;color:#ffa657;&quot;&gt;D&lt;/span&gt;
      &lt;div class=&quot;acid-name&quot;&gt;Durability — 지속성&lt;/div&gt;
      &lt;div class=&quot;acid-desc&quot;&gt;COMMIT된 트랜잭션은 &lt;strong&gt;시스템 장애가 나도 영구적으로 반영&lt;/strong&gt;돼야 해요.&lt;br&gt;WAL(Write-Ahead Logging)로 보장.&lt;/div&gt;
      &lt;div class=&quot;acid-example&quot;&gt;COMMIT 후 서버 다운돼도 데이터는 보존됨&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 03. COMMIT / ROLLBACK / SAVEPOINT --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;03&lt;/span&gt; COMMIT / ROLLBACK / SAVEPOINT&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;트랜잭션 제어 명령어&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;-- 트랜잭션 시작 (Oracle은 자동 시작, MySQL은 명시적)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- MySQL/MSSQL&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- Oracle은 첫 DML부터 자동으로 트랜잭션 시작&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 작업 수행&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE - &lt;span class=&quot;num&quot;&gt;1000000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ACCT_ID = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE + &lt;span class=&quot;num&quot;&gt;1000000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ACCT_ID = &lt;span class=&quot;str&quot;&gt;'B'&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 성공 시: 영구 반영&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;COMMIT;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 실패 시: 전체 취소&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;bad&quot;&gt;ROLLBACK;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- SAVEPOINT: 부분 롤백 지점 설정&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SAVEPOINT&lt;/span&gt; SP1;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE - &lt;span class=&quot;num&quot;&gt;500000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ACCT_ID = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SAVEPOINT&lt;/span&gt; SP2;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE + &lt;span class=&quot;num&quot;&gt;500000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ACCT_ID = &lt;span class=&quot;str&quot;&gt;'C'&lt;/span&gt;;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- SP2 이후만 취소 (SP1까지의 작업은 유지)&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;ROLLBACK TO&lt;/span&gt; SP2;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;COMMIT;&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;-- SP1~SP2 작업만 반영&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ AUTO COMMIT 주의&lt;/span&gt;
    MySQL은 기본적으로 &lt;strong&gt;AUTO COMMIT = ON&lt;/strong&gt;이에요.&lt;br&gt;
    즉, DML 실행 즉시 자동 COMMIT → ROLLBACK 불가!&lt;br&gt;
    트랜잭션을 직접 제어하려면 &lt;span class=&quot;ic&quot;&gt;SET autocommit = 0;&lt;/span&gt; 또는 &lt;span class=&quot;ic&quot;&gt;BEGIN;&lt;/span&gt;으로 시작해야 해요.&lt;br&gt;
    Oracle은 AUTO COMMIT이 기본 OFF예요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 04. 격리 수준 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;04&lt;/span&gt; 격리 수준 4단계 (Isolation Level)&lt;/h2&gt;

  &lt;p&gt;트랜잭션 간의 &lt;strong&gt;격리 정도&lt;/strong&gt;를 4단계로 설정할 수 있어요.&lt;br&gt;
  격리 수준이 높을수록 &lt;strong&gt;정합성 ↑, 동시성 ↓&lt;/strong&gt;이에요.&lt;/p&gt;

  &lt;table class=&quot;iso-table&quot;&gt;
    &lt;tr&gt;
      &lt;th&gt;격리 수준&lt;/th&gt;
      &lt;th&gt;Dirty Read&lt;/th&gt;
      &lt;th&gt;Non-Repeatable Read&lt;/th&gt;
      &lt;th&gt;Phantom Read&lt;/th&gt;
      &lt;th&gt;성능&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;READ UNCOMMITTED&lt;/td&gt;
      &lt;td class=&quot;ng&quot;&gt;발생&lt;/td&gt;
      &lt;td class=&quot;ng&quot;&gt;발생&lt;/td&gt;
      &lt;td class=&quot;ng&quot;&gt;발생&lt;/td&gt;
      &lt;td class=&quot;ok&quot;&gt;최고&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;READ COMMITTED&lt;/td&gt;
      &lt;td class=&quot;ok&quot;&gt;방지&lt;/td&gt;
      &lt;td class=&quot;ng&quot;&gt;발생&lt;/td&gt;
      &lt;td class=&quot;ng&quot;&gt;발생&lt;/td&gt;
      &lt;td class=&quot;ok&quot;&gt;높음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr class=&quot;best&quot;&gt;
      &lt;td&gt;REPEATABLE READ ★&lt;/td&gt;
      &lt;td class=&quot;ok&quot;&gt;방지&lt;/td&gt;
      &lt;td class=&quot;ok&quot;&gt;방지&lt;/td&gt;
      &lt;td class=&quot;ng&quot;&gt;발생&lt;/td&gt;
      &lt;td&gt;보통&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SERIALIZABLE&lt;/td&gt;
      &lt;td class=&quot;ok&quot;&gt;방지&lt;/td&gt;
      &lt;td class=&quot;ok&quot;&gt;방지&lt;/td&gt;
      &lt;td class=&quot;ok&quot;&gt;방지&lt;/td&gt;
      &lt;td class=&quot;ng&quot;&gt;최저&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  DB별 기본 격리 수준&lt;/span&gt;
    &lt;strong&gt;MySQL InnoDB&lt;/strong&gt; → REPEATABLE READ (기본값)&lt;br&gt;
    &lt;strong&gt;Oracle&lt;/strong&gt; → READ COMMITTED (기본값)&lt;br&gt;
    &lt;strong&gt;MSSQL&lt;/strong&gt; → READ COMMITTED (기본값)&lt;br&gt;
    대부분의 서비스는 READ COMMITTED 또는 REPEATABLE READ를 사용해요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 05. 동시성 문제 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;05&lt;/span&gt; 동시성 문제 3가지&lt;/h2&gt;

  &lt;div class=&quot;problem-grid&quot;&gt;
    &lt;div class=&quot;problem-card&quot;&gt;
      &lt;div class=&quot;problem-name&quot; style=&quot;color:#ff7b72;&quot;&gt;① Dirty Read — 더티 리드&lt;/div&gt;
      &lt;div class=&quot;problem-desc&quot;&gt;
        아직 &lt;strong&gt;COMMIT 안 된 데이터를 다른 트랜잭션이 읽는 것&lt;/strong&gt;이에요.
      &lt;/div&gt;
      &lt;div class=&quot;code-block&quot; style=&quot;margin-top:10px;&quot;&gt;
        &lt;div class=&quot;code-body&quot; style=&quot;padding:12px 16px;&quot;&gt;
&lt;span class=&quot;tx1&quot;&gt;TX1:&lt;/span&gt; UPDATE BALANCE = 5000 (아직 COMMIT 안 함)&lt;br&gt;
&lt;span class=&quot;tx2&quot;&gt;TX2:&lt;/span&gt; SELECT BALANCE → &lt;span class=&quot;bad&quot;&gt;5000 읽음&lt;/span&gt; (Dirty Read!)&lt;br&gt;
&lt;span class=&quot;tx1&quot;&gt;TX1:&lt;/span&gt; ROLLBACK → BALANCE 원래대로&lt;br&gt;
&lt;span class=&quot;tx2&quot;&gt;TX2:&lt;/span&gt; &lt;span class=&quot;bad&quot;&gt;5000이라는 잘못된 값으로 작업함&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&quot;problem-card&quot;&gt;
      &lt;div class=&quot;problem-name&quot; style=&quot;color:#ffa657;&quot;&gt;② Non-Repeatable Read — 반복 불가 읽기&lt;/div&gt;
      &lt;div class=&quot;problem-desc&quot;&gt;
        같은 트랜잭션 내에서 같은 쿼리를 두 번 실행했는데 &lt;strong&gt;결과가 달라지는 것&lt;/strong&gt;이에요.
      &lt;/div&gt;
      &lt;div class=&quot;code-block&quot; style=&quot;margin-top:10px;&quot;&gt;
        &lt;div class=&quot;code-body&quot; style=&quot;padding:12px 16px;&quot;&gt;
&lt;span class=&quot;tx1&quot;&gt;TX1:&lt;/span&gt; SELECT BALANCE → &lt;span class=&quot;good&quot;&gt;3000&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tx2&quot;&gt;TX2:&lt;/span&gt; UPDATE BALANCE = 5000 → COMMIT&lt;br&gt;
&lt;span class=&quot;tx1&quot;&gt;TX1:&lt;/span&gt; SELECT BALANCE → &lt;span class=&quot;bad&quot;&gt;5000&lt;/span&gt; (값이 바뀜!)&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 같은 TX1인데 두 번 조회 결과가 다름&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&quot;problem-card&quot;&gt;
      &lt;div class=&quot;problem-name&quot; style=&quot;color:#bc8cff;&quot;&gt;③ Phantom Read — 팬텀 리드&lt;/div&gt;
      &lt;div class=&quot;problem-desc&quot;&gt;
        같은 트랜잭션 내에서 같은 조건으로 조회했는데 &lt;strong&gt;없던 행이 생기거나 사라지는 것&lt;/strong&gt;이에요.
      &lt;/div&gt;
      &lt;div class=&quot;code-block&quot; style=&quot;margin-top:10px;&quot;&gt;
        &lt;div class=&quot;code-body&quot; style=&quot;padding:12px 16px;&quot;&gt;
&lt;span class=&quot;tx1&quot;&gt;TX1:&lt;/span&gt; SELECT COUNT(*) WHERE AGE &gt; 20 → &lt;span class=&quot;good&quot;&gt;5건&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tx2&quot;&gt;TX2:&lt;/span&gt; INSERT (AGE=25) → COMMIT&lt;br&gt;
&lt;span class=&quot;tx1&quot;&gt;TX1:&lt;/span&gt; SELECT COUNT(*) WHERE AGE &gt; 20 → &lt;span class=&quot;bad&quot;&gt;6건&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 없던 행이 생김 (Phantom!)&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 06. 락 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;06&lt;/span&gt; 락(Lock) 개념&lt;/h2&gt;

  &lt;p&gt;동시성 문제를 해결하기 위해 DB는 &lt;strong&gt;락(Lock)&lt;/strong&gt;을 사용해요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;락 종류 및 사용법&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 공유락 (Shared Lock / S-Lock) ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 읽기 락. 여러 트랜잭션이 동시에 읽기 가능&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 공유락 걸린 동안 쓰기(배타락) 불가&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; * &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ACCT_ID = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;LOCK IN SHARE MODE&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- MySQL&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; * &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ACCT_ID = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;FOR SHARE&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- MySQL 8.0+&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 배타락 (Exclusive Lock / X-Lock) ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 쓰기 락. 해당 row를 혼자 독점&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 다른 트랜잭션의 읽기/쓰기 모두 대기&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; * &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ACCT_ID = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;FOR UPDATE&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- 배타락&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ FOR UPDATE 실무 활용 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 잔액 조회 후 차감할 때 중간에 다른 TX가 끼어들지 못하게&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;BEGIN&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; BALANCE &lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ACCT_ID = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;FOR UPDATE&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; ACCOUNT &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; BALANCE = BALANCE - &lt;span class=&quot;num&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; ACCT_ID = &lt;span class=&quot;str&quot;&gt;'A'&lt;/span&gt;;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;COMMIT&lt;/span&gt;;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 07. 데드락 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;07&lt;/span&gt; 데드락 (DeadLock)&lt;/h2&gt;

  &lt;p&gt;두 트랜잭션이 &lt;strong&gt;서로 상대방의 락이 풀리기를 기다리는 무한 대기 상태&lt;/strong&gt;예요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;데드락 발생 시나리오&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;tx1&quot;&gt;TX1:&lt;/span&gt; A 락 획득 → B 락 요청 (대기 중...)&lt;br&gt;
&lt;span class=&quot;tx2&quot;&gt;TX2:&lt;/span&gt; B 락 획득 → A 락 요청 (대기 중...)&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- TX1은 TX2가 B를 놓길 기다리고&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- TX2는 TX1이 A를 놓길 기다림&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- → 영원히 대기 = DeadLock!&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 예방 방법 ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 1. 항상 같은 순서로 락 획득&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;tx1&quot;&gt;TX1:&lt;/span&gt; A 락 → B 락 (순서 고정)&lt;br&gt;
&lt;span class=&quot;tx2&quot;&gt;TX2:&lt;/span&gt; A 락 → B 락 (같은 순서)&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 2. 락 타임아웃 설정&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; innodb_lock_wait_timeout = &lt;span class=&quot;num&quot;&gt;30&lt;/span&gt;; &lt;span class=&quot;cm&quot;&gt;-- 30초 후 자동 포기&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 3. 트랜잭션을 짧게 유지&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 락 보유 시간을 최소화&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;⚠️ DB의 데드락 감지&lt;/span&gt;
    DB는 &lt;strong&gt;데드락을 자동으로 감지&lt;/strong&gt;하고, 두 트랜잭션 중 하나를 강제 종료(ROLLBACK)해요.&lt;br&gt;
    어떤 트랜잭션을 종료할지는 DB가 판단해요 (보통 더 적은 작업을 한 쪽).&lt;br&gt;
    애플리케이션 코드에서는 데드락 오류를 잡아서 &lt;strong&gt;재시도 로직&lt;/strong&gt;을 구현해야 해요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 08. 면접 답변 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;08&lt;/span&gt; 면접 답변 정리&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;면접 예상 Q&amp;A&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;Q. 트랜잭션이란 무엇인가요?&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;&quot;DB에서 하나의 논리적 작업 단위로, 여러 쿼리를 묶어&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;전부 성공하거나 전부 실패하게 보장하는 것입니다.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;ACID 특성을 만족해야 하며 COMMIT/ROLLBACK으로 제어합니다.&quot;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;Q. ACID를 설명해주세요.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;&quot;A(원자성): 전부 성공 or 전부 실패&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;C(일관성): 트랜잭션 전후 DB 무결성 유지&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;I(격리성): 동시 트랜잭션 간 독립성 보장&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;D(지속성): COMMIT 후 장애에도 영구 보존&quot;&lt;/span&gt;&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;Q. 격리 수준에 대해 설명해주세요.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;&quot;READ UNCOMMITTED → READ COMMITTED → REPEATABLE READ → SERIALIZABLE&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;순으로 격리 수준이 높아지며, 정합성은 높아지고 동시성은 낮아집니다.&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;good&quot;&gt;MySQL InnoDB 기본값은 REPEATABLE READ입니다.&quot;&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;✅ CONCLUSION&lt;/span&gt;
    트랜잭션은 &lt;strong&gt;ACID + COMMIT/ROLLBACK + 격리 수준&lt;/strong&gt; 세트로 기억하세요.&lt;br&gt;
    면접에서 Dirty Read / Non-Repeatable Read / Phantom Read 세 가지 동시성 문제까지 언급하면&lt;br&gt;
    확실히 차별화됩니다. 데드락 방지법도 알아두면 플러스!
  &lt;/div&gt;

  &lt;div class=&quot;post-tags&quot;&gt;
    &lt;span class=&quot;post-tag&quot;&gt;데이터베이스&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;트랜잭션&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;ACID&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;격리수준&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;DeadLock&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;CS면접&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;COMMIT&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;ROLLBACK&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;</description>
      <category>  CS 면접 대비/데이터베이스</category>
      <author>woohaha</author>
      <guid isPermaLink="true">https://story2248.tistory.com/15</guid>
      <comments>https://story2248.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 10 May 2026 11:50:21 +0900</pubDate>
    </item>
    <item>
      <title>[ERP] 회계 모듈 완전 정복 - 전표부터 재무제표까지 업무 흐름 총정리</title>
      <link>https://story2248.tistory.com/14</link>
      <description>&lt;style&gt;
  .post-wrap {
    background: #0d1117; color: #e6edf3;
    font-family: 'Pretendard', -apple-system, sans-serif;
    padding: 0; line-height: 1.8;
  }
  .post-wrap * { box-sizing: border-box; }
  .post-wrap p { margin: 0 0 16px; color: #8b949e; font-size: 15px; }
  .post-wrap p strong { color: #e6edf3; }
  .post-divider { border: none; border-top: 1px solid #21262d; margin: 32px 0; }

  .post-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px;
    background: rgba(63,185,80,0.1); border: 1px solid rgba(63,185,80,0.3);
    border-radius: 20px; font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: #3fb950; margin-bottom: 20px;
  }
  .post-badge::before { content: ' '; }

  /* 목차 */
  .toc { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 18px 22px; margin: 24px 0; }
  .toc-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 12px; display: block; }
  .toc-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .toc-item { font-size: 13px; color: #8b949e; padding: 3px 0; display: flex; align-items: center; gap: 8px; }
  .toc-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; }

  .post-wrap h2 {
    font-size: 18px; font-weight: 600; color: #e6edf3;
    margin: 32px 0 16px; padding-bottom: 8px;
    border-bottom: 1px solid #21262d;
    display: flex; align-items: center; gap: 8px;
  }
  .post-wrap h2 .num {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: #3fb950; background: rgba(63,185,80,0.1);
    border: 1px solid rgba(63,185,80,0.3);
    border-radius: 4px; padding: 1px 7px;
  }
  .post-wrap h3 {
    font-size: 15px; font-weight: 600; color: #8b949e;
    margin: 24px 0 10px; font-family: 'JetBrains Mono', monospace;
  }
  .post-wrap h3::before { content: '// '; color: #484f58; }

  /* 용어 카드 */
  .term-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 16px 0; }
  .term-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 14px 16px; }
  .term-name { font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 700; margin-bottom: 6px; }
  .term-desc { font-size: 13px; color: #8b949e; line-height: 1.65; }
  .term-desc strong { color: #e6edf3; }
  .term-example { margin-top: 8px; padding: 6px 10px; background: #0d1117; border-radius: 4px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #484f58; }
  .term-example::before { content: 'ex) '; color: #3fb950; }

  /* 전표 구조 */
  .journal-table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 13px; }
  .journal-table th { background: #1c2128; color: #8b949e; font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 8px 14px; border: 1px solid #30363d; letter-spacing: 0.05em; text-align: left; }
  .journal-table td { padding: 8px 14px; border: 1px solid #30363d; color: #e6edf3; font-size: 13px; }
  .journal-table tr:nth-child(even) td { background: #161b22; }
  .td-debit  { color: #58a6ff !important; font-family: 'JetBrains Mono', monospace; font-weight: 700; }
  .td-credit { color: #ff7b72 !important; font-family: 'JetBrains Mono', monospace; font-weight: 700; }
  .td-amount { font-family: 'JetBrains Mono', monospace; text-align: right; }
  .td-total  { background: #1c2128 !important; font-family: 'JetBrains Mono', monospace; font-weight: 700; }

  /* 흐름 시각화 */
  .flow-viz { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 20px 24px; margin: 16px 0; font-family: 'JetBrains Mono', monospace; }
  .fv-title { font-size: 10px; color: #484f58; letter-spacing: 0.1em; margin-bottom: 16px; display: block; }
  .fv-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
  .fv-item { padding: 8px 16px; border-radius: 6px; font-size: 12px; font-weight: 500; }
  .fi-a { background: rgba(63,185,80,0.15);  color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
  .fi-b { background: rgba(88,166,255,0.15); color: #58a6ff; border: 1px solid rgba(88,166,255,0.3); }
  .fi-c { background: rgba(188,140,255,0.15);color: #bc8cff; border: 1px solid rgba(188,140,255,0.3); }
  .fi-d { background: rgba(255,166,87,0.15); color: #ffa657; border: 1px solid rgba(255,166,87,0.3); }
  .fi-e { background: rgba(255,123,114,0.15);color: #ff7b72; border: 1px solid rgba(255,123,114,0.3); }
  .fv-arrow { color: #484f58; font-size: 16px; }

  /* 타임라인 */
  .timeline { position: relative; margin: 20px 0; padding-left: 20px; }
  .timeline::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px; background: linear-gradient(to bottom, #3fb950, #58a6ff, #bc8cff, #ffa657, #ff7b72, #39d353); border-radius: 2px; }
  .tl-item { position: relative; padding: 0 0 0 20px; }
  .tl-dot { position: absolute; left: -25px; top: 16px; width: 10px; height: 10px; border-radius: 50%; border: 2px solid #0d1117; }
  .tl-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px 20px; margin-bottom: 10px; }
  .tl-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
  .tl-badge { font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 4px; flex-shrink: 0; }
  .tl-title { font-size: 14px; font-weight: 600; color: #e6edf3; }
  .tl-desc { font-size: 13px; color: #8b949e; line-height: 1.7; }
  .tl-tags { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 8px; }
  .tl-tag { font-family: 'JetBrains Mono', monospace; font-size: 10px; padding: 1px 7px; border-radius: 4px; background: #1c2128; border: 1px solid #30363d; color: #484f58; }

  /* tip / warn / info 박스 */
  .tip-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #3fb950; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .tip-box strong { color: #e6edf3; }
  .tip-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .warn-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #ff7b72; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .warn-box strong { color: #e6edf3; }
  .warn-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #ff7b72; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }
  .info-box { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #58a6ff; border-radius: 6px; padding: 16px 20px; margin: 16px 0; font-size: 14px; color: #8b949e; line-height: 1.75; }
  .info-box strong { color: #e6edf3; }
  .info-box .tip-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #58a6ff; margin-bottom: 8px; display: block; letter-spacing: 0.08em; }

  /* 코드 블럭 */
  .code-block { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; margin: 12px 0; overflow: hidden; }
  .code-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; background: #161b22; border-bottom: 1px solid #30363d; }
  .code-lang { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; letter-spacing: 0.05em; }
  .code-dots { display: flex; gap: 5px; }
  .code-dots span { width: 10px; height: 10px; border-radius: 50%; }
  .code-dots span:nth-child(1) { background: #ff5f56; }
  .code-dots span:nth-child(2) { background: #ffbd2e; }
  .code-dots span:nth-child(3) { background: #27c93f; }
  .code-body { padding: 18px 22px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.85; overflow-x: auto; color: #e6edf3; }
  .kw { color: #ff7b72; } .fn { color: #d2a8ff; }
  .cm { color: #484f58; font-style: italic; } .str { color: #a5d6ff; }
  .dr { color: #58a6ff; font-weight: 700; } .cr { color: #ff7b72; font-weight: 700; }

  .ic { font-family: 'JetBrains Mono', monospace; font-size: 0.88em; padding: 1px 6px; background: #1c2128; border: 1px solid #30363d; border-radius: 4px; color: #ffa657; }
  .post-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 32px; padding-top: 20px; border-top: 1px solid #21262d; }
  .post-tag { padding: 3px 10px; background: rgba(63,185,80,0.08); border: 1px solid rgba(63,185,80,0.25); border-radius: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3fb950; }
  .post-tag::before { content: '#'; opacity: 0.6; }
  @media (max-width: 600px) {
    .term-grid { grid-template-columns: 1fr; }
    .toc-list { grid-template-columns: 1fr; }
  }
&lt;/style&gt;

&lt;div class=&quot;post-wrap&quot;&gt;
  &lt;div class=&quot;post-badge&quot;&gt;ERP · 회계 모듈&lt;/div&gt;

  &lt;p&gt;인사/급여 모듈에 이어 이번엔 &lt;strong&gt;ERP 회계 모듈&lt;/strong&gt;을 정리해봤어요.&lt;br&gt;
  회계는 전표, 계정과목, 결산 같은 용어가 낯설어서 처음엔 어렵게 느껴지는데,&lt;br&gt;
  &lt;strong&gt;흐름을 먼저 이해하면&lt;/strong&gt; 개발이 훨씬 쉬워져요.&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
    &lt;span class=&quot;toc-title&quot;&gt;// TABLE OF CONTENTS&lt;/span&gt;
    &lt;div class=&quot;toc-list&quot;&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;01&lt;/span&gt; 회계 기본 개념&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;02&lt;/span&gt; 계정과목 체계&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;03&lt;/span&gt; 전표 (분개) 구조&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;04&lt;/span&gt; 전표 유형&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;05&lt;/span&gt; 매입/매출 흐름&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;06&lt;/span&gt; 월마감 &amp; 결산 흐름&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;07&lt;/span&gt; 재무제표 종류&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;08&lt;/span&gt; 부가세(VAT) 처리&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;09&lt;/span&gt; 원가 모듈 연동&lt;/div&gt;
      &lt;div class=&quot;toc-item&quot;&gt;&lt;span class=&quot;toc-num&quot;&gt;10&lt;/span&gt; 개발 시 주의사항&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 01. 회계 기본 개념 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;01&lt;/span&gt; 회계 기본 개념&lt;/h2&gt;

  &lt;p&gt;회계는 기업의 &lt;strong&gt;모든 경제적 거래를 기록하고 보고&lt;/strong&gt;하는 시스템이에요.&lt;br&gt;
  ERP 회계 모듈은 이 모든 거래를 &lt;strong&gt;자동으로 전표로 변환&lt;/strong&gt;하는 게 핵심이에요.&lt;/p&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#3fb950;&quot;&gt;복식부기&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;모든 거래를 &lt;strong&gt;차변(Debit)과 대변(Credit)&lt;/strong&gt; 양쪽에 동시 기록.&lt;br&gt;차변 합계 = 대변 합계 → 항상 균형 유지.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;현금 수령 → 차변: 현금 / 대변: 매출&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#58a6ff;&quot;&gt;차변 (Debit)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;전표의 왼쪽. &lt;strong&gt;자산 증가 / 부채·자본 감소 / 비용 발생&lt;/strong&gt;을 기록.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;현금 받음 → 차변: 현금(자산↑)&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ff7b72;&quot;&gt;대변 (Credit)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;전표의 오른쪽. &lt;strong&gt;자산 감소 / 부채·자본 증가 / 수익 발생&lt;/strong&gt;을 기록.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;현금 받음 → 대변: 매출(수익↑)&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;발생주의&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;현금 수수 시점이 아닌 &lt;strong&gt;거래 발생 시점&lt;/strong&gt;에 인식.&lt;br&gt;중견기업 이상은 모두 발생주의 적용.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;12월 납품 → 1월 수금이어도 12월 매출&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;info-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  차변/대변 쉽게 외우기&lt;/span&gt;
    &lt;strong&gt;차변&lt;/strong&gt; = 돈 나갈 때 / 자산 늘 때 / 비용 생길 때&lt;br&gt;
    &lt;strong&gt;대변&lt;/strong&gt; = 돈 들어올 때 / 부채 늘 때 / 수익 생길 때&lt;br&gt;
    헷갈리면 &lt;strong&gt;&quot;차변은 왼쪽, 자산·비용은 차변에서 증가&quot;&lt;/strong&gt;로 기억하세요.
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 02. 계정과목 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;02&lt;/span&gt; 계정과목 체계&lt;/h2&gt;

  &lt;p&gt;모든 거래는 &lt;strong&gt;계정과목(Account)&lt;/strong&gt;으로 분류돼요.&lt;br&gt;
  ERP에서는 &lt;strong&gt;계정과목 코드 체계&lt;/strong&gt;를 처음에 잘 설계해야 나중에 고생이 없어요.&lt;/p&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;계정과목 분류 체계&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 재무상태표 계정 (B/S) ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;dr&quot;&gt;자산 (Assets)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;유동자산: 현금, 보통예금, 매출채권, 재고자산&lt;br&gt;
&amp;nbsp;&amp;nbsp;비유동자산: 건물, 기계장치, 토지, 소프트웨어&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cr&quot;&gt;부채 (Liabilities)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;유동부채: 매입채무, 미지급금, 예수금, 단기차입금&lt;br&gt;
&amp;nbsp;&amp;nbsp;비유동부채: 장기차입금, 퇴직급여충당부채&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cr&quot;&gt;자본 (Equity)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;자본금, 이익잉여금, 기타포괄손익&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;━━━ 손익계산서 계정 (P&amp;L) ━━━&lt;/span&gt;&lt;br&gt;
&lt;span class=&quot;cr&quot;&gt;수익 (Revenue)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;매출, 영업외수익, 이자수익&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;dr&quot;&gt;비용 (Expense)&lt;/span&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;매출원가, 급여, 임차료, 감가상각비, 이자비용
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#3fb950;&quot;&gt;매출채권 (AR)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;물건을 팔았는데 아직 못 받은 돈.&lt;strong&gt; 외상매출금.&lt;/strong&gt;&lt;br&gt;Accounts Receivable. 자산 계정.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;납품 완료, 대금은 30일 후 수령 예정&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#58a6ff;&quot;&gt;매입채무 (AP)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;물건을 샀는데 아직 안 낸 돈.&lt;strong&gt; 외상매입금.&lt;/strong&gt;&lt;br&gt;Accounts Payable. 부채 계정.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;원자재 구매, 대금은 다음달 지급 예정&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;미지급금&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;영업 외 거래에서 발생한 미지급액.&lt;br&gt;매입채무는 영업 거래, 미지급금은 &lt;strong&gt;그 외 거래&lt;/strong&gt;.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;PC 구입 후 미지급, 광고비 미지급&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ffa657;&quot;&gt;선급금 / 선수금&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;&lt;strong&gt;선급금&lt;/strong&gt; = 먼저 준 돈 (자산) / &lt;strong&gt;선수금&lt;/strong&gt; = 먼저 받은 돈 (부채).&lt;br&gt;계약금이 대표적인 예.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;계약금 10% 선수령 → 선수금(부채)&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ff7b72;&quot;&gt;예수금&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;직원 급여에서 공제해 보관 중인 돈.&lt;br&gt;&lt;strong&gt;소득세, 4대보험&lt;/strong&gt; 등. 나중에 국세청/공단에 납부.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;급여 공제 소득세 → 예수금(부채)&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#39d353;&quot;&gt;감가상각&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;건물·기계 등 유형자산의 가치를 &lt;strong&gt;내용연수에 걸쳐 비용으로 배분.&lt;/strong&gt;&lt;br&gt;현금 지출 없는 비용.&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;5천만원 기계 → 5년 정액법 → 연 1천만원 비용&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 03. 전표 구조 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;03&lt;/span&gt; 전표 (분개) 구조&lt;/h2&gt;

  &lt;p&gt;모든 거래는 &lt;strong&gt;전표(Journal Entry)&lt;/strong&gt;로 기록돼요.&lt;br&gt;
  차변 합계와 대변 합계가 반드시 일치해야 해요.&lt;/p&gt;

  &lt;h3&gt;예시 1 — 제품 외상 판매 (100만원)&lt;/h3&gt;
  &lt;table class=&quot;journal-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;구분&lt;/th&gt;&lt;th&gt;계정과목&lt;/th&gt;&lt;th&gt;적요&lt;/th&gt;&lt;th&gt;차변&lt;/th&gt;&lt;th&gt;대변&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;td-debit&quot;&gt;차변&lt;/td&gt;&lt;td&gt;매출채권&lt;/td&gt;&lt;td&gt;A사 제품 판매&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;1,000,000&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;td-credit&quot;&gt;대변&lt;/td&gt;&lt;td&gt;매출&lt;/td&gt;&lt;td&gt;A사 제품 판매&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;-&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;909,091&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;td-credit&quot;&gt;대변&lt;/td&gt;&lt;td&gt;부가세예수금&lt;/td&gt;&lt;td&gt;VAT 10%&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;-&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;90,909&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;td-total&quot; colspan=&quot;3&quot;&gt;합계&lt;/td&gt;&lt;td class=&quot;td-amount td-total&quot;&gt;1,000,000&lt;/td&gt;&lt;td class=&quot;td-amount td-total&quot;&gt;1,000,000&lt;/td&gt;&lt;/tr&gt;
  &lt;/table&gt;

  &lt;h3&gt;예시 2 — 급여 지급 (300만원)&lt;/h3&gt;
  &lt;table class=&quot;journal-table&quot;&gt;
    &lt;tr&gt;&lt;th&gt;구분&lt;/th&gt;&lt;th&gt;계정과목&lt;/th&gt;&lt;th&gt;적요&lt;/th&gt;&lt;th&gt;차변&lt;/th&gt;&lt;th&gt;대변&lt;/th&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;td-debit&quot;&gt;차변&lt;/td&gt;&lt;td&gt;급여&lt;/td&gt;&lt;td&gt;3월 급여&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;3,000,000&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;td-credit&quot;&gt;대변&lt;/td&gt;&lt;td&gt;예수금(소득세)&lt;/td&gt;&lt;td&gt;원천세&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;-&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;150,000&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;td-credit&quot;&gt;대변&lt;/td&gt;&lt;td&gt;예수금(4대보험)&lt;/td&gt;&lt;td&gt;보험료&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;-&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;270,000&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;td-credit&quot;&gt;대변&lt;/td&gt;&lt;td&gt;보통예금&lt;/td&gt;&lt;td&gt;실수령액 이체&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;-&lt;/td&gt;&lt;td class=&quot;td-amount&quot;&gt;2,580,000&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td class=&quot;td-total&quot; colspan=&quot;3&quot;&gt;합계&lt;/td&gt;&lt;td class=&quot;td-amount td-total&quot;&gt;3,000,000&lt;/td&gt;&lt;td class=&quot;td-amount td-total&quot;&gt;3,000,000&lt;/td&gt;&lt;/tr&gt;
  &lt;/table&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 04. 전표 유형 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;04&lt;/span&gt; 전표 유형&lt;/h2&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#3fb950;&quot;&gt;일반전표&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;일반적인 회계 거래 기록.&lt;br&gt;수동으로 입력하거나 타 모듈에서 &lt;strong&gt;자동 생성.&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#58a6ff;&quot;&gt;매입전표&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;구매/매입 시 발생하는 전표.&lt;br&gt;구매 모듈에서 &lt;strong&gt;입고 처리 시 자동 생성.&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;매출전표&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;판매/매출 시 발생하는 전표.&lt;br&gt;영업 모듈에서 &lt;strong&gt;출하 처리 시 자동 생성.&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ffa657;&quot;&gt;급여전표&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;인사 모듈 급여 마감 후 &lt;strong&gt;자동 생성.&lt;/strong&gt;&lt;br&gt;부서별 급여 비용 배분까지 처리.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ff7b72;&quot;&gt;결산전표&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;월/연 결산 시 수동 또는 자동 생성.&lt;br&gt;감가상각, 충당금, 선급비용 안분 등.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#39d353;&quot;&gt;역분개 전표&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;잘못된 전표를 취소할 때 &lt;strong&gt;반대 방향으로 생성.&lt;/strong&gt;&lt;br&gt;삭제 대신 역분개로 이력 보존.&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 05. 매입/매출 흐름 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;05&lt;/span&gt; 매입 / 매출 흐름&lt;/h2&gt;

  &lt;h3&gt;매출 흐름 (Order to Cash)&lt;/h3&gt;
  &lt;div class=&quot;flow-viz&quot;&gt;
    &lt;span class=&quot;fv-title&quot;&gt;// 매출 O2C 흐름&lt;/span&gt;
    &lt;div class=&quot;fv-row&quot;&gt;
      &lt;span class=&quot;fv-item fi-a&quot;&gt;수주 (영업)&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-b&quot;&gt;출하/납품&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-c&quot;&gt;세금계산서 발행&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-d&quot;&gt;매출 전표 생성&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-e&quot;&gt;대금 수령 → 채권 소멸&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;h3&gt;매입 흐름 (Purchase to Pay)&lt;/h3&gt;
  &lt;div class=&quot;flow-viz&quot;&gt;
    &lt;span class=&quot;fv-title&quot;&gt;// 매입 P2P 흐름&lt;/span&gt;
    &lt;div class=&quot;fv-row&quot;&gt;
      &lt;span class=&quot;fv-item fi-a&quot;&gt;발주 (구매)&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-b&quot;&gt;입고 처리&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-c&quot;&gt;세금계산서 수취&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-d&quot;&gt;매입 전표 생성&lt;/span&gt;
      &lt;span class=&quot;fv-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;fv-item fi-e&quot;&gt;대금 지급 → 채무 소멸&lt;/span&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#3fb950;&quot;&gt;세금계산서&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;VAT 포함 거래의 증빙 서류.&lt;strong&gt; 전자세금계산서&lt;/strong&gt; 의무 발행(일정 규모 이상).&lt;br&gt;공급가액 + 부가세 명시.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#58a6ff;&quot;&gt;계산서&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;VAT 면세 거래 증빙. 면세 품목(농산물, 의료 등).&lt;br&gt;부가세 없이 공급가액만.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;채권 관리 (AR)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;매출 후 수금 관리. &lt;strong&gt;채권 잔액 = 매출액 - 수금액.&lt;/strong&gt;&lt;br&gt;연령 분석(30일/60일/90일 이상)으로 부실채권 관리.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ffa657;&quot;&gt;채무 관리 (AP)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;매입 후 지급 관리. &lt;strong&gt;지급 기일 관리&lt;/strong&gt;가 핵심.&lt;br&gt;어음, 현금, 계좌이체 등 지급 수단 다양.&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 06. 월마감 &amp; 결산 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;06&lt;/span&gt; 월마감 &amp; 결산 흐름&lt;/h2&gt;

  &lt;div class=&quot;timeline&quot;&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#3fb950;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(63,185,80,0.1);color:#3fb950;border:1px solid rgba(63,185,80,0.3);&quot;&gt;STEP 1&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;전표 입력 마감&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;해당 월의 모든 거래 전표 입력 완료 확인.&lt;br&gt;&lt;strong&gt;마감 후 해당 월 전표 수정/추가 불가.&lt;/strong&gt; 오류 발견 시 역분개로 처리.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;전표마감&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;기간잠금&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#58a6ff;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(88,166,255,0.1);color:#58a6ff;border:1px solid rgba(88,166,255,0.3);&quot;&gt;STEP 2&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;결산 조정 전표&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;감가상각비 계상, 선급비용 안분, 충당금 설정 등 &lt;strong&gt;결산 조정 전표 생성.&lt;/strong&gt;&lt;br&gt;대부분 자동 계산 후 검토.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;감가상각&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;선급비용안분&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;충당금&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#bc8cff;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(188,140,255,0.1);color:#bc8cff;border:1px solid rgba(188,140,255,0.3);&quot;&gt;STEP 3&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;부가세 신고 자료&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;분기별 부가세 신고(1/4/7/10월). 매출세액 - 매입세액 = 납부세액.&lt;br&gt;&lt;strong&gt;전자세금계산서 합계표&lt;/strong&gt; 홈택스 연동.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;부가세신고&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;홈택스연동&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#ffa657;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(255,166,87,0.1);color:#ffa657;border:1px solid rgba(255,166,87,0.3);&quot;&gt;STEP 4&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;재무제표 생성&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;재무상태표(B/S), 손익계산서(P&amp;L), 현금흐름표 자동 생성.&lt;br&gt;&lt;strong&gt;경영진 보고용&lt;/strong&gt; 및 외부 감사 자료.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;재무상태표&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;손익계산서&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;감사&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;tl-item&quot;&gt;
      &lt;div class=&quot;tl-dot&quot; style=&quot;background:#ff7b72;&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;tl-card&quot;&gt;
        &lt;div class=&quot;tl-header&quot;&gt;
          &lt;span class=&quot;tl-badge&quot; style=&quot;background:rgba(255,123,114,0.1);color:#ff7b72;border:1px solid rgba(255,123,114,0.3);&quot;&gt;STEP 5&lt;/span&gt;
          &lt;span class=&quot;tl-title&quot;&gt;월마감 확정&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;tl-desc&quot;&gt;월마감 확정 후 &lt;strong&gt;해당 월 완전 잠금.&lt;/strong&gt;&lt;br&gt;다음 달 시작 잔액(이월잔액) 자동 생성.&lt;/div&gt;
        &lt;div class=&quot;tl-tags&quot;&gt;&lt;span class=&quot;tl-tag&quot;&gt;월마감&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;이월잔액&lt;/span&gt;&lt;span class=&quot;tl-tag&quot;&gt;기간잠금&lt;/span&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 07. 재무제표 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;07&lt;/span&gt; 재무제표 종류&lt;/h2&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#3fb950;&quot;&gt;재무상태표 (B/S)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;특정 시점의 &lt;strong&gt;자산 = 부채 + 자본&lt;/strong&gt; 현황.&lt;br&gt;Balance Sheet. &quot;이 회사 재산이 얼마냐?&quot;&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;2024-12-31 기준 자산 100억, 부채 60억, 자본 40억&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#58a6ff;&quot;&gt;손익계산서 (P&amp;L)&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;일정 기간의 &lt;strong&gt;수익 - 비용 = 이익&lt;/strong&gt; 현황.&lt;br&gt;Profit &amp; Loss. &quot;이 기간에 얼마 벌었냐?&quot;&lt;/div&gt;
      &lt;div class=&quot;term-example&quot;&gt;2024년 매출 50억 - 비용 42억 = 영업이익 8억&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;현금흐름표&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;실제 &lt;strong&gt;현금의 유입/유출&lt;/strong&gt; 현황.&lt;br&gt;이익이 나도 현금이 없으면 부도 → 흑자도산 방지.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ffa657;&quot;&gt;자본변동표&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;자본의 변동 내역. 배당, 유상증자, 당기순이익 등.&lt;br&gt;중견기업 이상은 &lt;strong&gt;4가지 재무제표 모두 작성 의무.&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 08. 부가세 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;08&lt;/span&gt; 부가세(VAT) 처리&lt;/h2&gt;

  &lt;div class=&quot;code-block&quot;&gt;
    &lt;div class=&quot;code-header&quot;&gt;
      &lt;div class=&quot;code-dots&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
      &lt;span class=&quot;code-lang&quot;&gt;부가세 계산 구조&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&quot;code-body&quot;&gt;
&lt;span class=&quot;cm&quot;&gt;-- 매출 시 (부가세 10% 포함)&lt;/span&gt;&lt;br&gt;
공급가액: &lt;span class=&quot;num&quot;&gt;1,000,000&lt;/span&gt;원&lt;br&gt;
부가세:   &amp;nbsp;&amp;nbsp;&lt;span class=&quot;num&quot;&gt;100,000&lt;/span&gt;원 (&lt;span class=&quot;cr&quot;&gt;매출세액&lt;/span&gt; — 국가에 납부)&lt;br&gt;
합계:   &lt;span class=&quot;num&quot;&gt;1,100,000&lt;/span&gt;원 (거래처로부터 수령)&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 매입 시 (부가세 10% 포함)&lt;/span&gt;&lt;br&gt;
공급가액: &lt;span class=&quot;num&quot;&gt;500,000&lt;/span&gt;원&lt;br&gt;
부가세:   &amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;num&quot;&gt;50,000&lt;/span&gt;원 (&lt;span class=&quot;dr&quot;&gt;매입세액&lt;/span&gt; — 환급 가능)&lt;br&gt;
합계:   &amp;nbsp;&amp;nbsp;&lt;span class=&quot;num&quot;&gt;550,000&lt;/span&gt;원 (거래처에 지급)&lt;br&gt;&lt;br&gt;
&lt;span class=&quot;cm&quot;&gt;-- 분기 부가세 납부액&lt;/span&gt;&lt;br&gt;
납부세액 = &lt;span class=&quot;cr&quot;&gt;매출세액&lt;/span&gt; - &lt;span class=&quot;dr&quot;&gt;매입세액&lt;/span&gt;&lt;br&gt;
납부세액 = &lt;span class=&quot;num&quot;&gt;100,000&lt;/span&gt; - &lt;span class=&quot;num&quot;&gt;50,000&lt;/span&gt; = &lt;span class=&quot;num&quot;&gt;50,000&lt;/span&gt;원 납부
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ff7b72;&quot;&gt;부가세 예수금&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;매출 시 거래처로부터 받은 부가세.&lt;strong&gt; 부채 계정.&lt;/strong&gt;&lt;br&gt;분기별로 국세청에 납부해야 하는 돈.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#58a6ff;&quot;&gt;부가세 대급금&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;매입 시 거래처에 지급한 부가세.&lt;strong&gt; 자산 계정.&lt;/strong&gt;&lt;br&gt;매출세액에서 공제(환급)받을 수 있는 돈.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#3fb950;&quot;&gt;과세 / 면세&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;&lt;strong&gt;과세&lt;/strong&gt; = VAT 10% 부과 (대부분 재화·용역)&lt;br&gt;&lt;strong&gt;면세&lt;/strong&gt; = VAT 없음 (농산물, 의료, 교육 등)&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ffa657;&quot;&gt;영세율&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;수출 거래. VAT 0% 적용.&lt;strong&gt; 매입세액은 전액 환급.&lt;/strong&gt;&lt;br&gt;수출 기업에서 자주 나오는 개념.&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 09. 원가 모듈 연동 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;09&lt;/span&gt; 원가 모듈 연동&lt;/h2&gt;

  &lt;p&gt;중견기업 이상은 &lt;strong&gt;원가 모듈&lt;/strong&gt;이 회계 모듈과 연동돼요.&lt;br&gt;
  제품 하나를 만드는 데 얼마가 들었는지를 정확하게 계산하는 게 목적이에요.&lt;/p&gt;

  &lt;div class=&quot;term-grid&quot;&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#3fb950;&quot;&gt;직접재료비&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;제품 생산에 직접 들어가는 원자재 비용.&lt;br&gt;&lt;strong&gt;구매 → 입고 → 생산 투입&lt;/strong&gt; 흐름으로 추적.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#58a6ff;&quot;&gt;직접노무비&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;생산 작업자 인건비.&lt;strong&gt; 작업시간 × 시간당 임률.&lt;/strong&gt;&lt;br&gt;인사 모듈 급여와 연동.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#bc8cff;&quot;&gt;제조간접비&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;전력비, 감가상각비 등 직접 추적 어려운 비용.&lt;br&gt;&lt;strong&gt;배부 기준&lt;/strong&gt;(작업시간, 기계시간)으로 제품에 안분.&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;term-card&quot;&gt;
      &lt;div class=&quot;term-name&quot; style=&quot;color:#ffa657;&quot;&gt;매출원가&lt;/div&gt;
      &lt;div class=&quot;term-desc&quot;&gt;판매된 제품의 원가.&lt;br&gt;매출 - 매출원가 = &lt;strong&gt;매출총이익.&lt;/strong&gt;&lt;br&gt;재고 평가 방법(선입선출, 평균법)에 따라 달라짐.&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;post-divider&quot;&gt;

  &lt;!-- 10. 개발 주의사항 --&gt;
  &lt;h2&gt;&lt;span class=&quot;num&quot;&gt;10&lt;/span&gt; 개발 시 핵심 주의사항&lt;/h2&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  전표 수정 이력 반드시 보존&lt;/span&gt;
    확정된 전표는 &lt;strong&gt;삭제가 아닌 역분개&lt;/strong&gt;로 처리해야 해요.&lt;br&gt;
    전표 이력이 없으면 감사 시 문제가 돼요.&lt;br&gt;
    &lt;strong&gt;전표 상태(임시/확정/역분개)&lt;/strong&gt;를 플래그로 관리하고, 확정 후에는 수정 불가 로직 필수.
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  차변 합계 = 대변 합계 검증&lt;/span&gt;
    전표 저장 시 &lt;strong&gt;반드시 차대 밸런스 체크&lt;/strong&gt; 로직 넣어야 해요.&lt;br&gt;
    차변 합계 ≠ 대변 합계이면 저장 자체를 막아야 합니다.&lt;br&gt;
    회계 데이터 정합성의 가장 기본 원칙이에요.
  &lt;/div&gt;

  &lt;div class=&quot;warn-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;  기간 잠금 처리&lt;/span&gt;
    마감된 회계 기간에는 &lt;strong&gt;전표 입력/수정 불가&lt;/strong&gt; 처리해야 해요.&lt;br&gt;
    PERIOD_LOCK 테이블로 기간별 잠금 상태를 관리하고,&lt;br&gt;
    전표 저장 시 해당 기간 잠금 여부를 반드시 체크해야 해요.
  &lt;/div&gt;

  &lt;div class=&quot;tip-box&quot;&gt;
    &lt;span class=&quot;tip-label&quot;&gt;✅ CONCLUSION&lt;/span&gt;
    회계 모듈 핵심은 &lt;strong&gt;전표 → 원장 → 재무제표&lt;/strong&gt; 흐름이에요.&lt;br&gt;
    타 모듈(인사/구매/영업)과 자동 전표 연동이 ERP 회계의 핵심 가치고,&lt;br&gt;
    &lt;strong&gt;차대 밸런스 / 전표 이력 / 기간 잠금&lt;/strong&gt; 세 가지는 개발 시 절대 빠트리면 안 돼요!
  &lt;/div&gt;

  &lt;div class=&quot;post-tags&quot;&gt;
    &lt;span class=&quot;post-tag&quot;&gt;ERP&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;회계모듈&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;전표&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;분개&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;계정과목&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;부가세&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;결산&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;재무제표&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;매입채무&lt;/span&gt;
    &lt;span class=&quot;post-tag&quot;&gt;매출채권&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;</description>
      <category>  개발 공부/ERP</category>
      <author>woohaha</author>
      <guid isPermaLink="true">https://story2248.tistory.com/14</guid>
      <comments>https://story2248.tistory.com/14#entry14comment</comments>
      <pubDate>Sat, 9 May 2026 21:01:56 +0900</pubDate>
    </item>
  </channel>
</rss>