<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>보초 개발자</title>
    <link>https://yowon25.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 17 Aug 2026 16:11:59 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>안녕 하리</managingEditor>
    <image>
      <title>보초 개발자</title>
      <url>https://tistory1.daumcdn.net/tistory/5302521/attach/6fa0df360a5744828678a1ea48d77ed2</url>
      <link>https://yowon25.tistory.com</link>
    </image>
    <item>
      <title>Codex 와 Claude CLI 명령어 비교</title>
      <link>https://yowon25.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 AI 코딩 에이전트를 쓸 때,&lt;br /&gt;프롬프트를 길게 쓰는 것보다 필요한 순간에 /review 한 번, 길어지면 /compact 한 번, 위험해 보이면 권한 제어 한 번이 더 큰 차이를 만든다.&lt;/p&gt;
&lt;p data-end=&quot;625&quot; data-start=&quot;533&quot; data-ke-size=&quot;size16&quot;&gt;아래는 &lt;b&gt;현업에서 가장 자주 쓰이는 커맨드 중심으로&lt;/b&gt; Codex와 Claude Code를 &amp;ldquo;기능 단위&amp;rdquo;로 비교한 글이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 410px;&quot; border=&quot;1&quot; data-end=&quot;2041&quot; data-start=&quot;365&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;height: 35px; width: 17.3256%;&quot;&gt;&lt;b&gt;목적(실무에서 자주)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 35px; width: 17.3256%;&quot;&gt;&lt;b&gt;Codex (OpenAI)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 35px; width: 23.7209%;&quot;&gt;&lt;b&gt;Claude Code (Anthropic)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 35px; width: 41.5116%;&quot;&gt;&lt;b&gt;차이 포인트&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;561&quot; data-start=&quot;451&quot;&gt;
&lt;td style=&quot;height: 19px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;463&quot; data-start=&quot;451&quot;&gt;세션/대화 초기화&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;472&quot; data-start=&quot;463&quot;&gt;/new&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;483&quot; data-start=&quot;472&quot;&gt;/clear&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;561&quot; data-start=&quot;483&quot;&gt;Codex는 &amp;ldquo;새 대화&amp;rdquo; 개념&lt;br /&gt;Claude는 &amp;ldquo;기록 삭제&amp;rdquo; 개념&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot; data-end=&quot;691&quot; data-start=&quot;562&quot;&gt;
&lt;td style=&quot;height: 35px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;577&quot; data-start=&quot;562&quot;&gt;대화 압축(토큰 절약)&lt;/td&gt;
&lt;td style=&quot;height: 35px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;590&quot; data-start=&quot;577&quot;&gt;/compact&lt;/td&gt;
&lt;td style=&quot;height: 35px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;618&quot; data-start=&quot;590&quot;&gt;/compact [instructions]&lt;/td&gt;
&lt;td style=&quot;height: 35px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;691&quot; data-start=&quot;618&quot;&gt;Claude는 압축 시 &amp;ldquo;포커스 지시&amp;rdquo; 인자를 공식 지원&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot; data-end=&quot;782&quot; data-start=&quot;692&quot;&gt;
&lt;td style=&quot;height: 17px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;700&quot; data-start=&quot;692&quot;&gt;모델 변경&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;711&quot; data-start=&quot;700&quot;&gt;/model&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;722&quot; data-start=&quot;711&quot;&gt;/model&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;782&quot; data-start=&quot;722&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot; data-end=&quot;913&quot; data-start=&quot;783&quot;&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;794&quot; data-start=&quot;783&quot;&gt;상태/토큰 확인&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;806&quot; data-start=&quot;794&quot;&gt;/status&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;818&quot; data-start=&quot;806&quot;&gt;/status&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;913&quot; data-start=&quot;818&quot;&gt;Codex는 &amp;ldquo;세션 설정+토큰&amp;rdquo; 요약&lt;br /&gt;Claude는 &amp;ldquo;Settings(Status 탭)&amp;rdquo; 성격&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1015&quot; data-start=&quot;914&quot;&gt;
&lt;td style=&quot;height: 19px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;925&quot; data-start=&quot;914&quot;&gt;코드 리뷰 요청&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;937&quot; data-start=&quot;925&quot;&gt;/review&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;949&quot; data-start=&quot;937&quot;&gt;/review&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;1015&quot; data-start=&quot;949&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot; data-end=&quot;1153&quot; data-start=&quot;1016&quot;&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1032&quot; data-start=&quot;1016&quot;&gt;변경사항(diff) 확인&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1042&quot; data-start=&quot;1032&quot;&gt;/diff&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1056&quot; data-start=&quot;1042&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;1153&quot; data-start=&quot;1056&quot;&gt;Codex는 git diff를 바로 보여줌&lt;br /&gt;Claude는 보통 review로 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1250&quot; data-start=&quot;1154&quot;&gt;
&lt;td style=&quot;height: 19px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1161&quot; data-start=&quot;1154&quot;&gt;되돌리기&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1171&quot; data-start=&quot;1161&quot;&gt;/undo&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1183&quot; data-start=&quot;1171&quot;&gt;/rewind&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;1250&quot; data-start=&quot;1183&quot;&gt;둘 다 롤백 계열이지만 명령/범위 표현이 다름&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot; data-end=&quot;1403&quot; data-start=&quot;1251&quot;&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1270&quot; data-start=&quot;1251&quot;&gt;&amp;ldquo;이 파일 봐&amp;rdquo; 컨텍스트 고정&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1283&quot; data-start=&quot;1270&quot;&gt;/mention&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1306&quot; data-start=&quot;1283&quot;&gt;(명령 대신 @파일 참조가 핵심)&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;1403&quot; data-start=&quot;1306&quot;&gt;Codex는 slash로 파일 attach&lt;br /&gt;Claude는 @ 파일 참조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot; data-end=&quot;1551&quot; data-start=&quot;1404&quot;&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1420&quot; data-start=&quot;1404&quot;&gt;실행 권한/안전 설정&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1435&quot; data-start=&quot;1420&quot;&gt;/approvals&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1464&quot; data-start=&quot;1435&quot;&gt;/permissions, /sandbox&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;1551&quot; data-start=&quot;1464&quot;&gt;Codex는 &amp;ldquo;승인 정책&amp;rdquo; 중심&lt;br /&gt;Claude는 &amp;ldquo;권한/샌드박스&amp;rdquo; 메뉴가 분리&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot; data-end=&quot;1682&quot; data-start=&quot;1552&quot;&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1567&quot; data-start=&quot;1552&quot;&gt;MCP 도구 관리/확인&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1576&quot; data-start=&quot;1567&quot;&gt;/mcp&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1585&quot; data-start=&quot;1576&quot;&gt;/mcp&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;1682&quot; data-start=&quot;1585&quot;&gt;Codex는 &amp;ldquo;연결된 MCP 목록 확인&amp;rdquo; 성격&lt;br /&gt;Claude는 &amp;ldquo;연결+OAuth 관리&amp;rdquo;까지 포함&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot; data-end=&quot;1815&quot; data-start=&quot;1683&quot;&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1700&quot; data-start=&quot;1683&quot;&gt;프로젝트 가이드 파일 생성&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1724&quot; data-start=&quot;1700&quot;&gt;/init &amp;rarr; AGENTS.md&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1748&quot; data-start=&quot;1724&quot;&gt;/init &amp;rarr; CLAUDE.md&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;1815&quot; data-start=&quot;1748&quot;&gt;&amp;ldquo;프로젝트 메모리 파일&amp;rdquo; 이름/철학이 다름&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1919&quot; data-start=&quot;1816&quot;&gt;
&lt;td style=&quot;height: 19px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1821&quot; data-start=&quot;1816&quot;&gt;종료&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 17.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1842&quot; data-start=&quot;1821&quot;&gt;/exit or /quit&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 23.7209%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1852&quot; data-start=&quot;1842&quot;&gt;/exit&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 41.5116%;&quot; data-col-size=&quot;md&quot; data-end=&quot;1919&quot; data-start=&quot;1852&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;625&quot; data-start=&quot;533&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Honey Tip!</category>
      <author>안녕 하리</author>
      <guid isPermaLink="true">https://yowon25.tistory.com/62</guid>
      <comments>https://yowon25.tistory.com/62#entry62comment</comments>
      <pubDate>Sun, 21 Dec 2025 23:12:35 +0900</pubDate>
    </item>
    <item>
      <title>Gatling을 활용한 부하 테스트</title>
      <link>https://yowon25.tistory.com/61</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 점검을 위한 성능 테스트를 준비하면서 여러 도구를 검토해봤다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;h3 data-ke-size=&quot;size23&quot;&gt;고민했던 도구들&lt;/h3&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;div&gt; 
 &lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt; 
  &lt;tbody&gt; 
   &lt;tr&gt; 
    &lt;td&gt;&lt;b&gt;도구&lt;/b&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;b&gt;JMeter&lt;/b&gt;&lt;/td&gt; 
    &lt;td&gt;• GUI 지원&lt;br&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;•&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;다양한 플러그인 확장&lt;/td&gt; 
    &lt;td&gt;• XML 파일로 Git 관리 어려움&lt;br&gt;• 무겁고 리소스를 많이 소모함&lt;br&gt;• CI/CD 통합 복잡&lt;/td&gt; 
    &lt;td&gt;❌&lt;/td&gt; 
   &lt;/tr&gt; 
   &lt;tr&gt; 
    &lt;td&gt;&lt;b&gt;nGrinder&lt;/b&gt;&lt;/td&gt; 
    &lt;td&gt;• 웹 UI 제공&lt;br&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;•&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;리포트 기능&lt;br&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;•&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;분산테스트 지원&lt;/td&gt; 
    &lt;td&gt;• 별도 서버 구축 필요&lt;br&gt;• Controller + Agent 관리 부담&lt;br&gt;• 로컬 실행 불편&lt;/td&gt; 
    &lt;td&gt;❌&lt;/td&gt; 
   &lt;/tr&gt; 
   &lt;tr&gt; 
    &lt;td&gt;&lt;b&gt;Gatling&lt;/b&gt;&lt;/td&gt; 
    &lt;td&gt;• 코드 기반 (Java/Kotlin)&lt;br&gt;• Git으로 버전 관리&lt;br&gt;• 리포팅 및 분석 기능&lt;br&gt;• 로컬 실행 간편&lt;br&gt;• CI/CD 통합 쉬움&lt;br&gt;• 비동기 방식으로 고성능&lt;/td&gt; 
    &lt;td&gt;• 초기 학습 곡선&lt;/td&gt; 
    &lt;td&gt;✅&lt;/td&gt; 
   &lt;/tr&gt; 
  &lt;/tbody&gt; 
 &lt;/table&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 시작하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Gatling 공식 GitHub에서 제공하는 샘플 프로젝트 클론&lt;/p&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;java&quot; style=&quot;color: #abb2bf; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;git clone https://github.com/gatling/gatling-gradle-plugin-demo-java.git
cd gatling-gradle-plugin-demo-java

### 프로젝트 구조 확인
gatling-gradle-plugin-demo-java/
├── build.gradle.kts              # Gradle 설정
├── src/
│   └── gatling/
│       ├── java/                 # 시뮬레이션 코드
│       │   └── example/
│       │       └── BasicSimulation.java
│       └── resources/            # 데이터 파일, 설정
│           └── gatling.conf
└── gradle/&lt;/code&gt;&lt;/pre&gt; 
  &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;초기 설정 파헤치기&lt;/h3&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;java&quot; style=&quot;color: #abb2bf; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package computerdatabase;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;

public class BasicSimulation extends Simulation {

  // 1️⃣ 가상 사용자 수를 시스템 프로퍼티로 받기
  // Reference: https://docs.gatling.io/guides/passing-parameters/
  private static final int vu = Integer.getInteger(&quot;vu&quot;, 1);

  // 2️⃣ HTTP 프로토콜 설정
  // Reference: https://docs.gatling.io/reference/script/protocols/http/protocol/
  private static final HttpProtocolBuilder httpProtocol = http
      .baseUrl(&quot;https://api-ecomm.gatling.io&quot;)
      .acceptHeader(&quot;application/json&quot;)
      .userAgentHeader(
          &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36&quot;);

  // 3️⃣ 시나리오 정의
  // Reference: https://docs.gatling.io/reference/script/core/scenario/
  private static final ScenarioBuilder scenario = scenario(&quot;Scenario&quot;)
      .exec(http(&quot;Session&quot;).get(&quot;/session&quot;));

  // 4️⃣ 테스트 성공 기준 (Assertion)
  // Reference: https://docs.gatling.io/reference/script/core/assertions/
  private static final Assertion assertion = global().failedRequests().count().lt(1L);

  // 5️⃣ 부하 주입 프로파일 설정 및 실행
  // Reference: https://docs.gatling.io/reference/script/core/injection/
  {
    setUp(scenario.injectOpen(atOnceUsers(vu)))
        .assertions(assertion)
        .protocols(httpProtocol);
  }
}&lt;/code&gt;&lt;/pre&gt; 
  &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;1️⃣ 동적 사용자 수 설정&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;reasonml&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;private static final int vu = Integer.getInteger(&quot;vu&quot;, 1);&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;vu: Virtual Users (가상 사용자 수)&lt;/li&gt;&lt;li&gt;커맨드라인에서 -Dvu=100 처럼 값을 전달하지 않으면 기본값 1 사용&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;2️⃣ HTTP 프로토콜 설정&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;reasonml&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;private static final HttpProtocolBuilder httpProtocol = http
    .baseUrl(&quot;https://api-ecomm.gatling.io&quot;)      // 기본 URL
    .acceptHeader(&quot;application/json&quot;)              // JSON 응답 허용
    .userAgentHeader(&quot;Mozilla/5.0 ...&quot;);          // User-Agent 설정&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;div&gt; 
 &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
  &lt;li&gt;이 설정은 모든 HTTP 요청에 공통으로 적용&lt;/li&gt; 
 &lt;/ul&gt; 
&lt;/div&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;3️⃣ 시나리오 정의&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;processing&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;private static final ScenarioBuilder scenario = scenario(&quot;Scenario&quot;)
    .exec(http(&quot;Session&quot;).get(&quot;/session&quot;));&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;scenario(&quot;Scenario&quot;): 시나리오 이름&lt;/li&gt;&lt;li&gt;.exec(): 실행할 액션 정의&lt;/li&gt;&lt;li&gt;http(&quot;Session&quot;): 요청 이름 (리포트에 표시)&lt;/li&gt;&lt;li&gt;.get(&quot;/session&quot;): GET 요청&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;4️⃣ 성공 기준 정의 (Assertion)&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;axapta&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;private static final Assertion assertion = global().failedRequests().count().lt(1L);&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;전체 실패한 요청 수가 1개 미만이어야 테스트 성공&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;5️⃣ 부하 주입 및 실행&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;clojure&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;{
  setUp(scenario.injectOpen(atOnceUsers(vu)))
      .assertions(assertion)
      .protocols(httpProtocol);
}&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;atOnceUsers(vu): 모든 사용자를 동시에 시작&lt;/li&gt;&lt;/ul&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 실행하기&lt;/h3&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 실행&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;jboss-cli&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;./gradlew gatlingRun&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용자 수 조정해서 실행&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;routeros&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;./gradlew gatlingRun -Dvu=50&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;더 나아가기&lt;/h3&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 환경별 설정&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;java&quot; style=&quot;color: #abb2bf; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class ApiLoadTest extends Simulation {
  
  // 커맨드라인으로 모든 설정 제어
  private static final int VU = Integer.getInteger(&quot;vu&quot;, 10);
  private static final int DURATION = Integer.getInteger(&quot;duration&quot;, 60);
  private static final String ENV = System.getProperty(&quot;env&quot;, &quot;dev&quot;);
  
  private static final HttpProtocolBuilder httpProtocol = http
      .baseUrl(getBaseUrl(ENV))
      .acceptHeader(&quot;application/json&quot;);
  
  private static String getBaseUrl(String env) {
    return switch(env) {
      case &quot;prod&quot; -&amp;gt; &quot;https://api.prod.com&quot;;
      case &quot;cbt&quot; -&amp;gt; &quot;https://api.cbt.com&quot;;
      default -&amp;gt; &quot;https://api.dev.com&quot;;
    };
  }
}&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행:&lt;/b&gt;&lt;/p&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;java&quot; style=&quot;color: #abb2bf; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;./gradlew gatlingRun -Dvu=100 -Dduration=300 -Denv=prod&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Injection Profile&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;java&quot; style=&quot;color: #abb2bf; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//   가장 많이 쓰는 패턴
setUp(
  scenario.injectOpen(
    nothingFor(5),                    // 워밍업
    rampUsers(VU).during(60),         // 점진적 증가
    constantUsersPerSec(10).during(DURATION - 120)  // 일정 부하 유지
  )
).protocols(httpProtocol);

// 피크 트래픽 테스트
setUp(
  scenario.injectOpen(
    rampUsersPerSec(10).to(100).during(180),  // 3분간 증가
    constantUsersPerSec(100).during(600)      // 10분간 피크 유지
  )
).protocols(httpProtocol);

// 스트레스 테스트
setUp(
  scenario.injectOpen(
    incrementUsersPerSec(10)
      .times(5)
      .eachLevelLasting(60)
      .separatedByRampsLasting(30)
      .startingFrom(10)
  )
).protocols(httpProtocol);&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 인증 토큰 재사용&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;java&quot; style=&quot;color: #abb2bf; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private static final ScenarioBuilder scenario = scenario(&quot;Auth Flow&quot;)
  // 로그인해서 토큰 저장
  .exec(http(&quot;Login&quot;)
    .post(&quot;/api/login&quot;)
    .body(StringBody(&quot;{\&quot;username\&quot;:\&quot;#{username}\&quot;,\&quot;password\&quot;:\&quot;#{password}\&quot;}&quot;))
    .asJson()
    .check(jsonPath(&quot;$.token&quot;).saveAs(&quot;token&quot;)))  // 세션에 저장
  
  .pause(1, 3)
  
  // 저장한 토큰 재사용
  .exec(http(&quot;Get Data&quot;)
    .get(&quot;/api/data&quot;)
    .header(&quot;Authorization&quot;, &quot;Bearer #{token}&quot;)
    .check(status().is(200)));&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.현실적인 사용자 행동&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;java&quot; style=&quot;color: #abb2bf; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private static final ScenarioBuilder scenario = scenario(&quot;E-Commerce&quot;)
  .exec(http(&quot;Homepage&quot;).get(&quot;/&quot;))
  .pause(2, 5)  // 사람처럼 랜덤 대기
  
  .exec(http(&quot;Search&quot;).get(&quot;/search?q=laptop&quot;))
  .pause(3, 7)
  
  .feed(productFeeder)
  .exec(http(&quot;View Product&quot;).get(&quot;/products/#{productId}&quot;))
  .pause(5, 10)  // 상품 등을 살펴보는 시간
  
  .exec(http(&quot;Add to Cart&quot;).post(&quot;/cart&quot;)
    .body(StringBody(&quot;{\&quot;productId\&quot;: \&quot;#{productId}\&quot;}&quot;))
    .asJson())
  .pause(1, 2);&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. Assertions&lt;/h4&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre class=&quot;java&quot; style=&quot;color: #abb2bf; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;setUp(scenario.injectOpen(injection))
  .assertions(
    global().responseTime().max().lt(5000),           // 최대 응답시간 5초 미만
    global().responseTime().percentile(95).lt(3000),  // 95% 요청이 3초 미만
    global().successfulRequests().percent().gt(99)    // 성공률 99% 이상
  )
  .protocols(httpProtocol);&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과화면&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Gatling을 쓰기로 한 이유는 간단하다.&lt;br&gt;JMeter의 XML 지옥에서 벗어나고 싶었고, nGrinder는 서버 구축하는 것부터 부담스러웠다. 반면 Gatling은 코드로 관리되니까 Git으로 버전 관리도 되고, 로컬에서 바로 돌려볼 수 있어서 좋았다. 팀원들이랑 코드 리뷰하면서 시나리오를 함께 개선할 수 있다는 점도 마음에 들었다.&lt;br&gt;특히 우리 프로젝트가 Java/Kotlin 기반이라 코드 스타일을 통일할 수 있고, 시나리오를 객체지향적으로 깔끔하게 짤 수 있다는 게 큰 장점이었다.&lt;br&gt;리포팅 기능도 생각보다 훌륭해서 별도로 모니터링 툴을 붙이지 않아도 충분히 분석할 수 있었다.&lt;br&gt;앞으로는 Gatling을 주로 사용할 것 같다. 이제까지 이런 좋은 도구를 몰랐다니 좀 허탈하긴 한데... 그래도 이제라도 알게 돼서 다행이다!&lt;/p&gt;</description>
      <category>Honey Tip!</category>
      <author>안녕 하리</author>
      <guid isPermaLink="true">https://yowon25.tistory.com/61</guid>
      <comments>https://yowon25.tistory.com/61#entry61comment</comments>
      <pubDate>Wed, 19 Nov 2025 17:03:27 +0900</pubDate>
    </item>
    <item>
      <title>이미지와 데이터 동시 처리 시 발생하는 Multipart 문제 해결</title>
      <link>https://yowon25.tistory.com/49</link>
      <description>&lt;h2 data-end=&quot;115&quot; data-start=&quot;110&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-end=&quot;216&quot; data-start=&quot;117&quot; data-ke-size=&quot;size16&quot;&gt;진행 중인 프로젝트에서&lt;br /&gt;&lt;b&gt;이미지 파일(MultipartFile)&lt;/b&gt; 과 &lt;b&gt;일반 데이터(JSON)&lt;/b&gt; 를 동시에 전송받는 API를 개발해야 했다.&lt;/p&gt;
&lt;p data-end=&quot;301&quot; data-start=&quot;281&quot; data-ke-size=&quot;size16&quot;&gt;하지만 다음과 같은 오류가 발생했다...&lt;/p&gt;
&lt;pre id=&quot;code_1745891533892&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Resolved [org.springframework.web.bind.MissingServletRequestPartException: Required request part 'file' is not present]

/* 또는 */

Content type 'application/json' not supported&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;502&quot; data-start=&quot;497&quot; data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-end=&quot;145&quot; data-start=&quot;82&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot에서는 요청을 처리할 때, 요청의 &lt;b&gt;Content-Type&lt;/b&gt;에 따라 동작 방식이 달라진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;322&quot; data-start=&quot;147&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;234&quot; data-start=&quot;147&quot;&gt;&lt;b&gt;@RequestBody&lt;/b&gt; 는 요청 전체가 application/json 타입일 때만 사용된다.&lt;br /&gt;즉, 파일과 JSON을 함께 받을 수 없다.&lt;/li&gt;
&lt;li data-end=&quot;322&quot; data-start=&quot;235&quot;&gt;&lt;b&gt;@RequestPart&lt;/b&gt; 는 요청이 multipart/form-data 타입일 때, 요청 파트별로 파일이나 JSON을 따로 처리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 &lt;b&gt;MediaType.MULTIPART_FORM_DATA_VALUE&lt;/b&gt; 로 설정하고, &lt;b&gt;@RequestPart&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt; 를 사용&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745892309620&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 예시 코드 */
@PostMapping(value = &quot;/upload&quot;, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity&amp;lt;String&amp;gt; upload(
    @RequestPart(&quot;file&quot;, required = false) MultipartFile file,
    @RequestPart(&quot;data&quot;) MyRequestDto data
) {
    // 파일(선택)과 JSON 데이터(필수)를 정상적으로 받음
    return ResponseEntity.ok(&quot;업로드 성공&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;+ 추가 문제 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스트맨으로 API 테스트를 할 경우, form-data 형식으로 Content-Type을 &lt;b&gt;application/json&lt;/b&gt; 을 명시해준다면 정상적으로 동작합니다!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbs5YT/btsNCBTkQ3j/W97ivWQTbqjKm5PW1kNfhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbs5YT/btsNCBTkQ3j/W97ivWQTbqjKm5PW1kNfhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbs5YT/btsNCBTkQ3j/W97ivWQTbqjKm5PW1kNfhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcbs5YT%2FbtsNCBTkQ3j%2FW97ivWQTbqjKm5PW1kNfhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;832&quot; height=&quot;175&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만... Swagger에서는 &quot;Content-Type&amp;nbsp;'application/octet-stream'&amp;nbsp;is&amp;nbsp;not&amp;nbsp;supported&quot;라고&amp;nbsp;예외가&amp;nbsp;발생합니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application/octet-stream은 MIME 타입 중 하나로, 알 수 없는 이진 파일을 의미합니다. &lt;br /&gt;즉 전달하고자 하는 리소스의 유형이 명시되지 않아 인식할 수 없는 리소스인 경우에 해당 타입이 사용됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 swagger에서 분명히 multipart/form-data로 설정해서 보내고 있는데, 어째서 application/octet-stream 라는걸까..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인 결과, 전체 Content-Type은 multipart/form-data이지만, &lt;br /&gt;각 파트의 Content-Type은 null과 image/png였습니다. &lt;br /&gt;이를 통해 파트 중 타입이 누락된 부분이 null로 들어가게 되면서 어떤 타입인지 알 수가 없어 타입이 octet-stream으로 처리되고 있었다는 것을 알 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이 문제를 해결하기 위해서는 application/octet-stream 타입을 처리할 수 있는 메시지 컨버터를 추가해야 합니다!&lt;/p&gt;
&lt;pre id=&quot;code_1745897864262&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
    public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
    }

	// 해당 컨버터가 읽기 과정에서만 작동하도록 쓰기 과정은 false

    @Override
    public boolean canWrite(Class&amp;lt;?&amp;gt; clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Type type, Class&amp;lt;?&amp;gt; clazz, MediaType mediaType) {
        return false;
    }

    @Override
    protected boolean canWrite(MediaType mediaType) {
        return false;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://seondays.tistory.com/79&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seondays.tistory.com/79&lt;/a&gt;&lt;/p&gt;</description>
      <category>트러블슈팅</category>
      <author>안녕 하리</author>
      <guid isPermaLink="true">https://yowon25.tistory.com/49</guid>
      <comments>https://yowon25.tistory.com/49#entry49comment</comments>
      <pubDate>Tue, 29 Apr 2025 11:20:50 +0900</pubDate>
    </item>
    <item>
      <title>코딩테스트에서 자주 등장하는 확장 함수</title>
      <link>https://yowon25.tistory.com/48</link>
      <description>&lt;h3 data-end=&quot;245&quot; data-start=&quot;230&quot; data-ke-size=&quot;size23&quot;&gt;  숫자 관련 함수&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;550&quot; data-start=&quot;247&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;550&quot; data-start=&quot;307&quot;&gt;
&lt;tr data-end=&quot;371&quot; data-start=&quot;307&quot;&gt;
&lt;td data-end=&quot;323&quot; data-start=&quot;307&quot;&gt;ROUND(x, d)&lt;/td&gt;
&lt;td data-end=&quot;340&quot; data-start=&quot;323&quot;&gt;숫자를 d 자리까지 반올림 (default 0)&lt;/td&gt;
&lt;td data-end=&quot;361&quot; data-start=&quot;340&quot;&gt;ROUND(3.1415, 2)&lt;/td&gt;
&lt;td data-end=&quot;371&quot; data-start=&quot;361&quot;&gt;3.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;424&quot; data-start=&quot;372&quot;&gt;
&lt;td data-end=&quot;384&quot; data-start=&quot;372&quot;&gt;CEIL(x)&lt;/td&gt;
&lt;td data-end=&quot;403&quot; data-start=&quot;384&quot;&gt;x보다 크거나 같은 최소 정수&lt;/td&gt;
&lt;td data-end=&quot;417&quot; data-start=&quot;403&quot;&gt;CEIL(3.2)&lt;/td&gt;
&lt;td data-end=&quot;424&quot; data-start=&quot;417&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;479&quot; data-start=&quot;425&quot;&gt;
&lt;td data-end=&quot;438&quot; data-start=&quot;425&quot;&gt;FLOOR(x)&lt;/td&gt;
&lt;td data-end=&quot;457&quot; data-start=&quot;438&quot;&gt;x보다 작거나 같은 최대 정수&lt;/td&gt;
&lt;td data-end=&quot;472&quot; data-start=&quot;457&quot;&gt;FLOOR(3.9)&lt;/td&gt;
&lt;td data-end=&quot;479&quot; data-start=&quot;472&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;550&quot; data-start=&quot;480&quot;&gt;
&lt;td data-end=&quot;499&quot; data-start=&quot;480&quot;&gt;TRUNCATE(x, d)&lt;/td&gt;
&lt;td data-end=&quot;516&quot; data-start=&quot;499&quot;&gt;소수점 d 자리까지만 표시&lt;/td&gt;
&lt;td data-end=&quot;540&quot; data-start=&quot;516&quot;&gt;TRUNCATE(3.1415, 2)&lt;/td&gt;
&lt;td data-end=&quot;550&quot; data-start=&quot;540&quot;&gt;3.14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;573&quot; data-start=&quot;557&quot; data-ke-size=&quot;size23&quot;&gt;  문자열 관련 함수&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1180&quot; data-start=&quot;575&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;1180&quot; data-start=&quot;635&quot;&gt;
&lt;tr data-end=&quot;717&quot; data-start=&quot;635&quot;&gt;
&lt;td data-end=&quot;666&quot; data-start=&quot;635&quot;&gt;SUBSTRING(str, start, len)&lt;/td&gt;
&lt;td data-end=&quot;678&quot; data-start=&quot;666&quot;&gt;문자열 일부 추출&lt;/td&gt;
&lt;td data-end=&quot;707&quot; data-start=&quot;678&quot;&gt;SUBSTRING('Hello', 1, 2)&lt;/td&gt;
&lt;td data-end=&quot;717&quot; data-start=&quot;707&quot;&gt;'He'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;781&quot; data-start=&quot;718&quot;&gt;
&lt;td data-end=&quot;735&quot; data-start=&quot;718&quot;&gt;LEFT(str, n)&lt;/td&gt;
&lt;td data-end=&quot;749&quot; data-start=&quot;735&quot;&gt;왼쪽부터 n글자 추출&lt;/td&gt;
&lt;td data-end=&quot;770&quot; data-start=&quot;749&quot;&gt;LEFT('Hello', 3)&lt;/td&gt;
&lt;td data-end=&quot;781&quot; data-start=&quot;770&quot;&gt;'Hel'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;847&quot; data-start=&quot;782&quot;&gt;
&lt;td data-end=&quot;800&quot; data-start=&quot;782&quot;&gt;RIGHT(str, n)&lt;/td&gt;
&lt;td data-end=&quot;815&quot; data-start=&quot;800&quot;&gt;오른쪽부터 n글자 추출&lt;/td&gt;
&lt;td data-end=&quot;837&quot; data-start=&quot;815&quot;&gt;RIGHT('Hello', 2)&lt;/td&gt;
&lt;td data-end=&quot;847&quot; data-start=&quot;837&quot;&gt;'lo'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;916&quot; data-start=&quot;848&quot;&gt;
&lt;td data-end=&quot;876&quot; data-start=&quot;848&quot;&gt;CONCAT(str1, str2, ...)&lt;/td&gt;
&lt;td data-end=&quot;885&quot; data-start=&quot;876&quot;&gt;문자열 연결&lt;/td&gt;
&lt;td data-end=&quot;906&quot; data-start=&quot;885&quot;&gt;CONCAT('A', 'B')&lt;/td&gt;
&lt;td data-end=&quot;916&quot; data-start=&quot;906&quot;&gt;'AB'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1000&quot; data-start=&quot;917&quot;&gt;
&lt;td data-end=&quot;944&quot; data-start=&quot;917&quot;&gt;REPLACE(str, from, to)&lt;/td&gt;
&lt;td data-end=&quot;953&quot; data-start=&quot;944&quot;&gt;문자열 치환&lt;/td&gt;
&lt;td data-end=&quot;987&quot; data-start=&quot;953&quot;&gt;REPLACE('banana', 'na', 'oo')&lt;/td&gt;
&lt;td data-end=&quot;1000&quot; data-start=&quot;987&quot;&gt;'baooo'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1070&quot; data-start=&quot;1001&quot;&gt;
&lt;td data-end=&quot;1024&quot; data-start=&quot;1001&quot;&gt;INSTR(str, substr)&lt;/td&gt;
&lt;td data-end=&quot;1039&quot; data-start=&quot;1024&quot;&gt;부분 문자열 위치 반환&lt;/td&gt;
&lt;td data-end=&quot;1063&quot; data-start=&quot;1039&quot;&gt;INSTR('hello', 'e')&lt;/td&gt;
&lt;td data-end=&quot;1070&quot; data-start=&quot;1063&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1180&quot; data-start=&quot;1071&quot;&gt;
&lt;td data-end=&quot;1110&quot; data-start=&quot;1071&quot;&gt;SUBSTRING_INDEX(str, delim, count)&lt;/td&gt;
&lt;td data-end=&quot;1124&quot; data-start=&quot;1110&quot;&gt;구분자 기준으로 자름&lt;/td&gt;
&lt;td data-end=&quot;1166&quot; data-start=&quot;1124&quot;&gt;SUBSTRING_INDEX('서울 강남구 역삼동', ' ', 2)&lt;/td&gt;
&lt;td data-end=&quot;1180&quot; data-start=&quot;1166&quot;&gt;'서울 강남구'&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1202&quot; data-start=&quot;1187&quot; data-ke-size=&quot;size23&quot;&gt;  날짜 관련 함수&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1659&quot; data-start=&quot;1204&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;1659&quot; data-start=&quot;1264&quot;&gt;
&lt;tr data-end=&quot;1324&quot; data-start=&quot;1264&quot;&gt;
&lt;td data-end=&quot;1274&quot; data-start=&quot;1264&quot;&gt;NOW()&lt;/td&gt;
&lt;td data-end=&quot;1289&quot; data-start=&quot;1274&quot;&gt;현재 날짜와 시간 반환&lt;/td&gt;
&lt;td data-end=&quot;1299&quot; data-start=&quot;1289&quot;&gt;NOW()&lt;/td&gt;
&lt;td data-end=&quot;1324&quot; data-start=&quot;1299&quot;&gt;2025-04-25 14:32:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1380&quot; data-start=&quot;1325&quot;&gt;
&lt;td data-end=&quot;1339&quot; data-start=&quot;1325&quot;&gt;CURDATE()&lt;/td&gt;
&lt;td data-end=&quot;1350&quot; data-start=&quot;1339&quot;&gt;현재 날짜 반환&lt;/td&gt;
&lt;td data-end=&quot;1364&quot; data-start=&quot;1350&quot;&gt;CURDATE()&lt;/td&gt;
&lt;td data-end=&quot;1380&quot; data-start=&quot;1364&quot;&gt;2025-04-25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1473&quot; data-start=&quot;1381&quot;&gt;
&lt;td data-end=&quot;1411&quot; data-start=&quot;1381&quot;&gt;DATE_FORMAT(date, format)&lt;/td&gt;
&lt;td data-end=&quot;1422&quot; data-start=&quot;1411&quot;&gt;날짜 포맷 지정&lt;/td&gt;
&lt;td data-end=&quot;1457&quot; data-start=&quot;1422&quot;&gt;DATE_FORMAT(NOW(), '%Y-%m-%d')&lt;/td&gt;
&lt;td data-end=&quot;1473&quot; data-start=&quot;1457&quot;&gt;2025-04-25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1564&quot; data-start=&quot;1474&quot;&gt;
&lt;td data-end=&quot;1501&quot; data-start=&quot;1474&quot;&gt;DATEDIFF(date1, date2)&lt;/td&gt;
&lt;td data-end=&quot;1516&quot; data-start=&quot;1501&quot;&gt;날짜 차이 (일 단위)&lt;/td&gt;
&lt;td data-end=&quot;1557&quot; data-start=&quot;1516&quot;&gt;DATEDIFF('2025-05-01', '2025-04-25')&lt;/td&gt;
&lt;td data-end=&quot;1564&quot; data-start=&quot;1557&quot;&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1659&quot; data-start=&quot;1565&quot;&gt;
&lt;td data-end=&quot;1610&quot; data-start=&quot;1565&quot;&gt;YEAR(date) / MONTH(date) / DAY(date)&lt;/td&gt;
&lt;td data-end=&quot;1626&quot; data-start=&quot;1610&quot;&gt;연도 / 월 / 일 추출&lt;/td&gt;
&lt;td data-end=&quot;1649&quot; data-start=&quot;1626&quot;&gt;YEAR('2025-04-25')&lt;/td&gt;
&lt;td data-end=&quot;1659&quot; data-start=&quot;1649&quot;&gt;2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1688&quot; data-start=&quot;1666&quot; data-ke-size=&quot;size23&quot;&gt;⚙️ 조건 &amp;amp; NULL 처리 함수&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2076&quot; data-start=&quot;1690&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;2076&quot; data-start=&quot;1750&quot;&gt;
&lt;tr data-end=&quot;1840&quot; data-start=&quot;1750&quot;&gt;
&lt;td data-end=&quot;1784&quot; data-start=&quot;1750&quot;&gt;IF(expr, true_val, false_val)&lt;/td&gt;
&lt;td data-end=&quot;1793&quot; data-start=&quot;1784&quot;&gt;조건문 처리&lt;/td&gt;
&lt;td data-end=&quot;1828&quot; data-start=&quot;1793&quot;&gt;IF(score &amp;gt; 60, 'Pass', 'Fail')&lt;/td&gt;
&lt;td data-end=&quot;1840&quot; data-start=&quot;1828&quot;&gt;'Pass'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1909&quot; data-start=&quot;1841&quot;&gt;
&lt;td data-end=&quot;1865&quot; data-start=&quot;1841&quot;&gt;IFNULL(expr, value)&lt;/td&gt;
&lt;td data-end=&quot;1882&quot; data-start=&quot;1865&quot;&gt;NULL이면 대체 값 반환&lt;/td&gt;
&lt;td data-end=&quot;1902&quot; data-start=&quot;1882&quot;&gt;IFNULL(NULL, 0)&lt;/td&gt;
&lt;td data-end=&quot;1909&quot; data-start=&quot;1902&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2001&quot; data-start=&quot;1910&quot;&gt;
&lt;td data-end=&quot;1940&quot; data-start=&quot;1910&quot;&gt;COALESCE(val1, val2, ...)&lt;/td&gt;
&lt;td data-end=&quot;1962&quot; data-start=&quot;1940&quot;&gt;가장 먼저 NULL이 아닌 값 반환&lt;/td&gt;
&lt;td data-end=&quot;1992&quot; data-start=&quot;1962&quot;&gt;COALESCE(NULL, NULL, 'A')&lt;/td&gt;
&lt;td data-end=&quot;2001&quot; data-start=&quot;1992&quot;&gt;'A'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2076&quot; data-start=&quot;2002&quot;&gt;
&lt;td data-end=&quot;2011&quot; data-start=&quot;2002&quot;&gt;CASE&lt;/td&gt;
&lt;td data-end=&quot;2022&quot; data-start=&quot;2011&quot;&gt;복수 조건 처리&lt;/td&gt;
&lt;td data-end=&quot;2066&quot; data-start=&quot;2022&quot;&gt;CASE WHEN x &amp;gt; 0 THEN '양수' ELSE '음수' END&lt;/td&gt;
&lt;td data-end=&quot;2076&quot; data-start=&quot;2066&quot;&gt;'양수'&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;237&quot; data-start=&quot;205&quot; data-ke-size=&quot;size23&quot;&gt;  윈도우 함수 (Window Functions)&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;853&quot; data-start=&quot;239&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr data-end=&quot;419&quot; data-start=&quot;299&quot;&gt;
&lt;td data-end=&quot;316&quot; data-start=&quot;299&quot;&gt;ROW_NUMBER()&lt;/td&gt;
&lt;td data-end=&quot;338&quot; data-start=&quot;316&quot;&gt;파티션 내 순위 (중복 순위 없음)&lt;/td&gt;
&lt;td data-end=&quot;401&quot; data-start=&quot;338&quot;&gt;ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC)&lt;/td&gt;
&lt;td data-end=&quot;419&quot; data-start=&quot;401&quot;&gt;1, 2, 3, ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;509&quot; data-start=&quot;420&quot;&gt;
&lt;td data-end=&quot;431&quot; data-start=&quot;420&quot;&gt;RANK()&lt;/td&gt;
&lt;td data-end=&quot;453&quot; data-start=&quot;431&quot;&gt;동일 순위 허용, 다음 순위 건너뜀&lt;/td&gt;
&lt;td data-end=&quot;491&quot; data-start=&quot;453&quot;&gt;RANK() OVER (ORDER BY score DESC)&lt;/td&gt;
&lt;td data-end=&quot;509&quot; data-start=&quot;491&quot;&gt;1, 1, 3, ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;613&quot; data-start=&quot;510&quot;&gt;
&lt;td data-end=&quot;527&quot; data-start=&quot;510&quot;&gt;DENSE_RANK()&lt;/td&gt;
&lt;td data-end=&quot;551&quot; data-start=&quot;527&quot;&gt;동일 순위 허용, 다음 순위 안 건너뜀&lt;/td&gt;
&lt;td data-end=&quot;595&quot; data-start=&quot;551&quot;&gt;DENSE_RANK() OVER (ORDER BY score DESC)&lt;/td&gt;
&lt;td data-end=&quot;613&quot; data-start=&quot;595&quot;&gt;1, 1, 2, ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;691&quot; data-start=&quot;614&quot;&gt;
&lt;td data-end=&quot;627&quot; data-start=&quot;614&quot;&gt;NTILE(n)&lt;/td&gt;
&lt;td data-end=&quot;643&quot; data-start=&quot;627&quot;&gt;n등분한 그룹 번호 부여&lt;/td&gt;
&lt;td data-end=&quot;679&quot; data-start=&quot;643&quot;&gt;NTILE(4) OVER (ORDER BY salary)&lt;/td&gt;
&lt;td data-end=&quot;691&quot; data-start=&quot;679&quot;&gt;1~4 그룹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;771&quot; data-start=&quot;692&quot;&gt;
&lt;td data-end=&quot;709&quot; data-start=&quot;692&quot;&gt;LAG(expr, n)&lt;/td&gt;
&lt;td data-end=&quot;723&quot; data-start=&quot;709&quot;&gt;이전 n행의 값 참조&lt;/td&gt;
&lt;td data-end=&quot;762&quot; data-start=&quot;723&quot;&gt;LAG(score, 1) OVER (ORDER BY date)&lt;/td&gt;
&lt;td data-end=&quot;771&quot; data-start=&quot;762&quot;&gt;이전 점수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;853&quot; data-start=&quot;772&quot;&gt;
&lt;td data-end=&quot;790&quot; data-start=&quot;772&quot;&gt;LEAD(expr, n)&lt;/td&gt;
&lt;td&gt;다음 n행의 값 참조&lt;/td&gt;
&lt;td&gt;LEAD(score,&amp;nbsp;1)&amp;nbsp;OVER&amp;nbsp;(ORDER&amp;nbsp;BY&amp;nbsp;date)&lt;/td&gt;
&lt;td&gt;다음 점수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-end=&quot;2093&quot; data-start=&quot;2083&quot; data-ke-size=&quot;size18&quot;&gt;  활용 팁&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2321&quot; data-start=&quot;2095&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2156&quot; data-start=&quot;2095&quot;&gt;  &lt;b&gt;SUBSTRING_INDEX&lt;/b&gt; 는 주소 데이터에서 읍/면/동 단위로 자를 때 정말 자주 나와요!&lt;/li&gt;
&lt;li data-end=&quot;2206&quot; data-start=&quot;2157&quot;&gt;  &lt;b&gt;DATE_FORMAT&lt;/b&gt;은 날짜를 출력 포맷으로 맞춰야 하는 문제에서 필수!&lt;/li&gt;
&lt;li data-end=&quot;2261&quot; data-start=&quot;2207&quot;&gt;  &lt;b&gt;ROUND&lt;/b&gt;, &lt;b&gt;TRUNCATE&lt;/b&gt;는 평균 계산, 점수 조정에서 자주 등장합니다.&lt;/li&gt;
&lt;li data-end=&quot;2321&quot; data-start=&quot;2262&quot;&gt;  문자열 전처리 문제에서는 &lt;b&gt;LEFT / RIGHT / REPLACE&lt;/b&gt;는 거의 고정 단골이에요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Language/SQL</category>
      <author>안녕 하리</author>
      <guid isPermaLink="true">https://yowon25.tistory.com/48</guid>
      <comments>https://yowon25.tistory.com/48#entry48comment</comments>
      <pubDate>Fri, 25 Apr 2025 16:00:01 +0900</pubDate>
    </item>
    <item>
      <title>손쉽게 Swagger JSON 으로 Postman Collection 만들기</title>
      <link>https://yowon25.tistory.com/46</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Postman에서 제공하는 OpenAPI 문서를 활용하면, API 엔드포인트를 일일이 추가하지 않고도 자동으로 Postman Collection을 생성할 수 있음&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spring Boot 프로젝트에서 OpenAPI 문서를 기반으로 Postman Collection을 생성하는 방법&lt;/b&gt;을 작성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스웨거 설정이 되어있다고 가정&lt;br /&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Spring Boot 버전&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;사용해야 할 springdoc-openapi 버전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Spring Boot 2.7.x&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;springdoc-openapi-ui:1.7.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Spring Boot 3.x&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;springdoc-openapi-starter-webmvc-ui:2.0.2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;OpenAPI 문서 다운로드&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/api/swagger-ui/index.html?urls.primaryName=approval&quot;&gt;Swagger UI&lt;/a&gt; 접속&amp;nbsp;&amp;gt;&amp;nbsp;&lt;a href=&quot;http://localhost:8080/api/v3/api-docs/approval&quot;&gt;/api/v3/api-docs/approval&lt;/a&gt; 클릭&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/v3/api-docs/approval&quot;&gt;http://localhost:8080/v3/api-docs/approval&lt;/a&gt; 오른쪽 마우스 클릭 후, 다른 이름으로 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;포스트맨&amp;nbsp;호출로도&amp;nbsp;JSON&amp;nbsp;파일을&amp;nbsp;다운받을&amp;nbsp;수&amp;nbsp;있습니다!&lt;/p&gt;
&lt;pre id=&quot;code_1745478915281&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl --location 'http://localhost:8080/api/v3/api-docs/approval'&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oEomc/btsNxxbYmPa/nzZikaydbLdDmlRjWrQBXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oEomc/btsNxxbYmPa/nzZikaydbLdDmlRjWrQBXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oEomc/btsNxxbYmPa/nzZikaydbLdDmlRjWrQBXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoEomc%2FbtsNxxbYmPa%2FnzZikaydbLdDmlRjWrQBXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;327&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Postman API를 사용하여 컬렉션 자동 생성&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Postman API Key 발급하기
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://web.postman.co/settings/me/api-keys&quot;&gt;https://web.postman.co/settings/me/api-keys&lt;/a&gt; 접속 후, Generate API Key 클릭&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZLX9r/btsNyUcHCJ5/9yp24puSUVOPKDtju7kDV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZLX9r/btsNyUcHCJ5/9yp24puSUVOPKDtju7kDV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZLX9r/btsNyUcHCJ5/9yp24puSUVOPKDtju7kDV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZLX9r%2FbtsNyUcHCJ5%2F9yp24puSUVOPKDtju7kDV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;518&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성된 API Key 클립보드에 복사&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sobIb/btsNxYtE08e/UHVe0QsR4SPSX8X8tIqJ61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sobIb/btsNxYtE08e/UHVe0QsR4SPSX8X8tIqJ61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sobIb/btsNxYtE08e/UHVe0QsR4SPSX8X8tIqJ61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsobIb%2FbtsNxYtE08e%2FUHVe0QsR4SPSX8X8tIqJ61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;169&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Postman API를 사용하여 OpenAPI 문서를 가져와 컬렉션 생성&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;cURL을 사용한 컬렉션 자동 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745479146116&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl --location 'https://api.getpostman.com/import/openapi' \
--header 'X-Api-Key: ${발급받은 Postman API Key}' \
--header 'Content-Type: application/json' \
--form 'type=&quot;file&quot;' \
--form 'input=@&quot;${파일 경로 위치}&quot;'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1584&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1Vp6q/btsNxlv8OLU/z9IDhpxG7mpGsVhEzdWDy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1Vp6q/btsNxlv8OLU/z9IDhpxG7mpGsVhEzdWDy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1Vp6q/btsNxlv8OLU/z9IDhpxG7mpGsVhEzdWDy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1Vp6q%2FbtsNxlv8OLU%2Fz9IDhpxG7mpGsVhEzdWDy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1584&quot; height=&quot;614&quot; data-origin-width=&quot;1584&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Postman Collection &amp;nbsp;장점&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;API 문서 자동화&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;OpenAPI 문서를 기반으로 Postman Collection을 생성하여&amp;nbsp;&lt;b&gt;API 문서를 최신 상태로 유지 가능&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;테스트 자동화&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Postman에서 컬렉션을 활용하면 &lt;b&gt;API 테스트를 반복 수행할 수 있어 생산성 향상&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;손쉬운 협업&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;컬렉션을 팀과 공유&lt;/b&gt;하여 &lt;b&gt;테스트 및 개발 진행 가능&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약, 스프링 부트 3.x 버전이 아닌&amp;nbsp;&lt;b&gt; 2.7.x 버전을 사용&lt;/b&gt;하고 있다면 DTO의 @Schema(example = &quot;...&quot;) 값이 Swagger UI 및 OpenAPI JSON에 반영되지 않고 &lt;b&gt;기본 데이터 타입(&quot;&amp;lt;string&amp;gt;&quot;, &quot;&amp;lt;integer&amp;gt;&quot;)으로만 표시되는 문제&lt;/b&gt;가 발생&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;이를 해결하기 위해서 application.yml 설정 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745479260815&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;springdoc:
  api-docs:
    enabled: true
    resolve-schema-properties: true // 해당 부분 추가&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Honey Tip!</category>
      <author>안녕 하리</author>
      <guid isPermaLink="true">https://yowon25.tistory.com/46</guid>
      <comments>https://yowon25.tistory.com/46#entry46comment</comments>
      <pubDate>Thu, 24 Apr 2025 16:21:27 +0900</pubDate>
    </item>
    <item>
      <title>Unable to load io.netty.resolver.dns.macos 오류 해결 (Mac DNS 문제)</title>
      <link>https://yowon25.tistory.com/45</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;466c63f4-0dbe-4da9-8f01-e317c4d43c84&quot;&gt;어플리케이션 실행 시, &lt;b&gt;ERROR&lt;/b&gt; 로그 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Unable to load io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider, fallback to system defaults. This may result in incorrect DNS resolutions on MacOS. Check whether you have a dependency on 'io.netty:netty-resolver-dns-native-macos'. Use DEBUG level to see the full stack: java.lang.UnsatisfiedLinkError: failed to load the required native library&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2994&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP3lV8/btsNySy8zpT/7KHT52akhPZlWJJJ0Ikgvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP3lV8/btsNySy8zpT/7KHT52akhPZlWJJJ0Ikgvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP3lV8/btsNySy8zpT/7KHT52akhPZlWJJJ0Ikgvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP3lV8%2FbtsNySy8zpT%2F7KHT52akhPZlWJJJ0Ikgvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;44&quot; data-origin-width=&quot;2994&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;cf6962f1-a0df-4a97-80c3-b6b485157c9b&quot;&gt;&lt;b&gt;Netty에서 macOS 전용 DNS 네이티브 라이브러리를 불러오지 못해서 발생&lt;/b&gt;하는 문제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;951d01ad-215c-4eb2-85c9-780b74dd7d01&quot;&gt;특히 macOS 환경에서 DNS 리졸빙 이슈가 주로 발생함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;d580cdef-13ba-407a-836f-1e652fc9819b&quot;&gt;build.gradle 에 의존성 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745477479177&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Netty의 macOS용 네이티브 DNS 리졸버로 macOS 시스템에 최적화된 DNS 해석 기능을 제공하여, Java 기반의 DNS 리졸버보다 더 빠르고 효율적으로 DNS 해석을 처리
implementation &quot;io.netty:netty-resolver-dns-native-macos:4.1.107.Final:osx-aarch_64&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-macro-id=&quot;ff016df6-49b8-483f-a7b8-949e22c40f1c&quot; data-macro-body-type=&quot;RICH_TEXT&quot; data-macro-schema-version=&quot;1&quot; data-macro-name=&quot;info&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;만약 개발서버와 운영서버는 크라우드 환경인데 혹시 영향이 있지않을까❓&lt;/b&gt;&lt;/h3&gt;
&lt;span&gt;이 의존성은 macOS 시스템에서만 동작하는 네이티브 라이브러리이므로&lt;br /&gt;클라우드 환경이나 다른 운영체제(Linux, Windows)에서는 영향을 미치지 않고, 클라우드 환경에서는 Java의 기본 DNS 리졸버가 사용되므로 문제가 발생하지 않습니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>트러블슈팅</category>
      <author>안녕 하리</author>
      <guid isPermaLink="true">https://yowon25.tistory.com/45</guid>
      <comments>https://yowon25.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 24 Apr 2025 15:52:08 +0900</pubDate>
    </item>
    <item>
      <title>Spring JPA Open-in-View 경고 해결</title>
      <link>https://yowon25.tistory.com/44</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;9228c2e9-f27d-4032-a31e-cf0ced867da7&quot;&gt;어플리케이션 실행 시, &lt;b&gt;WARN &lt;/b&gt;로그 발생&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745476690903&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;5e788338-e576-4323-96e6-f7274adc5bff&quot;&gt;spring.jpa.open-in-view=true(기본값)&amp;nbsp;설정 시, &lt;b&gt;View 렌더링까지 영속성 컨텍스트(세션)가 열려 있음&lt;/b&gt;&lt;/li&gt;
&lt;li data-uuid=&quot;a3dd30dc-882c-402a-ac09-7576cd595a6c&quot;&gt;View에서 Lazy-loading을 사용하면 &lt;b&gt;View 자체가 DB 접근 주체&lt;/b&gt;가 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;b4bb641e-0841-4426-97d4-9fa827f88fc6&quot;&gt;이는 MVC 패턴 위배하며&amp;nbsp;&lt;b&gt;예측 불가능한 쿼리 실행과 N+1 문제&lt;/b&gt; 발생 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;6f9f6e72-58b6-4bc0-ba30-7a6d94685e8c&quot;&gt;application.yml 에 다음과 같이 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745476739917&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring
  jpa:
    open-in-view: false  // DB 커넥션을 트랜잭션 끝나면 바로 반환!&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;open-in-view: true&lt;/td&gt;
&lt;td&gt;open-in-view: false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View에서 Lazy 사용&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;N+1 발생 위험&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&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;tr&gt;
&lt;td&gt;유지보수 효율&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>트러블슈팅</category>
      <author>안녕 하리</author>
      <guid isPermaLink="true">https://yowon25.tistory.com/44</guid>
      <comments>https://yowon25.tistory.com/44#entry44comment</comments>
      <pubDate>Thu, 24 Apr 2025 15:49:05 +0900</pubDate>
    </item>
    <item>
      <title>Spring Data Redis: Repository store assignment 문제 해결</title>
      <link>https://yowon25.tistory.com/43</link>
      <description>&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;10694897-3b44-4565-bd39-9a1aa59ac5eb&quot;&gt;어플리케이션 실행 시,&lt;span&gt; 수많은 Spring Data Redis - Could not safely identify store assignment for repository candidate interface 로그 발생&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2398&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kVwai/btsNw3oOUGd/XhLc7BM87KEVreLihxxlZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kVwai/btsNw3oOUGd/XhLc7BM87KEVreLihxxlZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kVwai/btsNw3oOUGd/XhLc7BM87KEVreLihxxlZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkVwai%2FbtsNw3oOUGd%2FXhLc7BM87KEVreLihxxlZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2398&quot; height=&quot;560&quot; data-origin-width=&quot;2398&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;5865669c-8501-4971-97b6-0caade447b98&quot;&gt;Spring Boot가 Repository 인터페이스를 찾았는데&amp;nbsp;해당 인터페이스가&amp;nbsp;&lt;b&gt;Redis용인지 JPA용인지 판단을 못할 경우 &lt;/b&gt;경고 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;171d68ca-430f-4758-a8bf-ffcc2da8b07c&quot;&gt;특히 여러 DataStore(JPA + Redis 등)를 동시에 쓸 때 흔하게 발생함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;04bc787c-14b7-42ee-9dc0-e7a0b82ef9fc&quot;&gt;&lt;b&gt;Redis Entity에 선언되어 있는지 확인&lt;/b&gt;&lt;br /&gt;@RedisHash(&quot;bannerClick&quot;)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-uuid=&quot;cdd09676-aca2-4432-85d8-b5173b4a6fb6&quot;&gt;&lt;b&gt;Repository 인터페이스가 &lt;b&gt;명확하게 Redis 전용&lt;/b&gt;인지 확인&lt;/b&gt;&lt;br /&gt;public interface BannerClickRedisRepository extends CrudRepository&amp;lt;BannerClickRedisEntity, String&amp;gt; {&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-uuid=&quot;f004cf0a-a85e-4dfc-8fba-3550c2f81e7c&quot;&gt;&lt;b&gt;설정 클래스에 @EnableRedisRepositories 어노테이션 추가&lt;br /&gt;&lt;/b&gt;@EnableRedisRepositories(basePackages = &quot;&lt;a href=&quot;http://com.kakao.bs&quot;&gt;com.kakao.bs&lt;/a&gt;.restapi.display.repository&quot;)&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-macro-id=&quot;0302da1d-87c9-4c2d-b237-eb1614326788&quot; data-macro-body-type=&quot;RICH_TEXT&quot; data-macro-schema-version=&quot;1&quot; data-macro-name=&quot;info&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;basePackages를 명확하게 하기 위해 &lt;b&gt;Repository package 구조를 명확히 분리하는 것이 가장 좋음&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>트러블슈팅</category>
      <author>안녕 하리</author>
      <guid isPermaLink="true">https://yowon25.tistory.com/43</guid>
      <comments>https://yowon25.tistory.com/43#entry43comment</comments>
      <pubDate>Thu, 24 Apr 2025 15:32:44 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 제네릭의 out과 in 쉽게 이해하기</title>
      <link>https://yowon25.tistory.com/41</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;공변과 반공변&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 개발자라면 제네릭 타입의 와일드카드 &lt;code&gt;? extends T&lt;/code&gt;, &lt;code&gt;? super T&lt;/code&gt;를 다뤄본 적 있을 거예요.&lt;br /&gt;코틀린에서는 이 개념을 &lt;code&gt;out&lt;/code&gt;과 &lt;code&gt;in&lt;/code&gt; 키워드로 표현합니다.&lt;br /&gt;이번 글에서는 &lt;b&gt;공변성(Covariance)&lt;/b&gt;과 &lt;b&gt;반공변성(Contravariance)&lt;/b&gt;에 대해 &lt;b&gt;쉽고 간결하게&lt;/b&gt; 설명하겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  공변성 (Covariance) - &lt;code&gt;out&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공변&lt;/b&gt;이란, &lt;b&gt;타입 파라미터의 상속 관계가 제네릭 클래스까지 이어지는 것&lt;/b&gt;을 의미합니다.&lt;br /&gt;&lt;code&gt;out T&lt;/code&gt;는 &lt;b&gt;읽기 전용(Generic Producer)&lt;/b&gt;입니다.&lt;br /&gt;&lt;code&gt;T&lt;/code&gt; 타입의 값을 &lt;b&gt;반환(return)&lt;/b&gt;할 수는 있지만, &lt;b&gt;넣을 수는 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class Cage&amp;lt;T&amp;gt; {
	private val animals: MutableList&amp;lt;T&amp;gt; = mutableListOf()

	fun getFirst(): T {
		return animals.first()    
    }

	fun moveFrom(otherCage: Cage&amp;lt;out T&amp;gt;) {
		this.anumals.addAll(otherCage.animals)
	}
}


fun main() {
    val goldFishCage = Cage&amp;lt;GoldFish&amp;gt;()
    goldFishCage.put(GoldFish(&quot;금붕어&quot;))
    
    val cage = Cage&amp;lt;Fish&amp;gt;()
    cage.moveFrom(goldFishCage)
    
    val fish: Fish = cage.getFirst() // 읽기 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  반공변성 (Contravariance) - in&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;반공변&lt;/b&gt;이란,&amp;nbsp; &lt;b&gt;타입 파라미터의 상속 관계가 제네릭 클래스까지 &lt;u&gt;반대로&lt;/u&gt; 이어지는 것&lt;/b&gt;을 의미합니다.&lt;br /&gt;&lt;code&gt;in T&lt;/code&gt;는 &lt;b&gt;쓰기 전용(Generic Consumer)&lt;/b&gt;입니다.&lt;br /&gt;&lt;code&gt;T&lt;/code&gt; 타입의 값을 &lt;b&gt;넣을 수는 있지만&lt;/b&gt;, &lt;b&gt;꺼낼 수는 없습니다.&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class Cage&amp;lt;T&amp;gt; {
	private val animals: MutableList&amp;lt;T&amp;gt; = mutableListOf()

	fun getFirst(): T {
		return animals.first()    
    }
    
    fun put(animal: T) {
    	this.animals.add(animal)
    }

	fun moveFrom(otherCage: Cage&amp;lt;out T&amp;gt;) {
		this.anumals.addAll(otherCage.animals)
	}

	fun moveTo(otherCage: Cage&amp;lt;in T&amp;gt;) {
		otherCage.anumals.addAll(this.animals)
	}
}


fun main() {
    val fishCage = Cage&amp;lt;Fish&amp;gt;()
    
	val goldFishCage = Cage&amp;lt;GoldFish&amp;gt;()
    goldFishCage.put(GoldFish(&quot;금붕어&quot;))

    goldFishCage.moveTo(fishCage) // goldFishCage를 모두 fishCage로 옮겨라!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;✅ 공변과 반공변, 이렇게 기억하세요!&lt;/span&gt;&lt;/h3&gt;
&lt;table style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px; border-collapse: collapse; width: 81.2791%;&quot; border=&quot;1&quot; data-end=&quot;1833&quot; data-start=&quot;1595&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.5814%;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 12.6744%;&quot;&gt;키워드&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;타입 변환 방향&lt;/td&gt;
&lt;td style=&quot;width: 16.8605%;&quot;&gt;역할&lt;/td&gt;
&lt;td style=&quot;width: 16.3954%;&quot;&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1774&quot; data-start=&quot;1715&quot;&gt;
&lt;td style=&quot;width: 15.5814%;&quot; data-end=&quot;1723&quot; data-start=&quot;1715&quot;&gt;공변성&lt;/td&gt;
&lt;td style=&quot;width: 12.6744%;&quot; data-end=&quot;1732&quot; data-start=&quot;1723&quot;&gt;out&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%;&quot; data-end=&quot;1745&quot; data-start=&quot;1732&quot;&gt;자식 &amp;rarr; 부모로 변환&lt;/td&gt;
&lt;td style=&quot;width: 16.8605%;&quot; data-end=&quot;1759&quot; data-start=&quot;1745&quot;&gt;Producer&lt;/td&gt;
&lt;td style=&quot;width: 16.3954%;&quot; data-end=&quot;1774&quot; data-start=&quot;1759&quot;&gt;읽기 전용 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1833&quot; data-start=&quot;1775&quot;&gt;
&lt;td style=&quot;width: 15.5814%;&quot; data-end=&quot;1782&quot; data-start=&quot;1775&quot;&gt;반공변성&lt;/td&gt;
&lt;td style=&quot;width: 12.6744%;&quot; data-end=&quot;1791&quot; data-start=&quot;1782&quot;&gt;in&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%;&quot; data-end=&quot;1804&quot; data-start=&quot;1791&quot;&gt;부모 &amp;rarr; 자식로 변환&lt;/td&gt;
&lt;td style=&quot;width: 16.8605%;&quot; data-end=&quot;1818&quot; data-start=&quot;1804&quot;&gt;Consumer&lt;/td&gt;
&lt;td style=&quot;width: 16.3954%;&quot; data-end=&quot;1833&quot; data-start=&quot;1818&quot;&gt;쓰기 전용 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 코틀린에서는 변성을 주는 위치를 클라스 자체에 붙일 수 있고, 함수 또는 변수에만 붙일 수도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Language/Kotlin</category>
      <author>안녕 하리</author>
      <guid isPermaLink="true">https://yowon25.tistory.com/41</guid>
      <comments>https://yowon25.tistory.com/41#entry41comment</comments>
      <pubDate>Mon, 14 Apr 2025 09:28:21 +0900</pubDate>
    </item>
    <item>
      <title>GraphQL 살펴보기</title>
      <link>https://yowon25.tistory.com/38</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GraphQL이란&amp;nbsp;Facebook&lt;span&gt;이 개발한 &lt;/span&gt;API 쿼리 언어(Query Language)&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REST API와 달리, &lt;b&gt;클라이언트가 원하는 데이터만 선택해서 가져올 수 있음&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;REST API vs GraphQL 차이점&lt;/b&gt;비교 항목REST APIGraphQL
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;데이터 요청 방식&lt;/td&gt;
&lt;td&gt;여러 개의 엔드포인트를 호출&lt;/td&gt;
&lt;td&gt;단일 엔드포인트(/graphql&lt;span&gt;)에서 모든 요청을 처리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Over-fetching&lt;/td&gt;
&lt;td&gt;필요 없는 데이터까지 반환됨&lt;/td&gt;
&lt;td&gt;원하는 데이터만 선택 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Under-fetching&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&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;별도 API 문서 필요&lt;/td&gt;
&lt;td&gt;자체 문서화 (GraphiQL 등 지원)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;GraphQL 의존성 추가&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1742262354053&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
	...
    implementation 'org.springframework.boot:spring-boot-starter-graphql'
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;application.yml 파일 추가&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1742262391031&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  graphql:
    graphiql:
      enabled: true   # '/graphiql' 경로로 접근하면 웹 브라우저에서 GraphQL API 테스트 가능
    schema:
      printer:
        enabled: true  # 수행한 쿼리를 콘솔에 출력&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;GraphQL 스키마 정의&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GraphQL은 &lt;b&gt;스키마 기반으로 동작&lt;/b&gt;하므로, &lt;b&gt;schema.graphqls 파일을 생성&lt;/b&gt;하여 API 구조를 정의&lt;/li&gt;
&lt;li&gt;src/main/resources/graphql/schema.graphqls&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;GraphQL Resolver 구현 (Controller 역할)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GraphQL에서는 데이터 조회(Query), 데이터 변경(Mutation)을 담당하는&amp;nbsp;&lt;b&gt;리졸버(Resolver)&lt;/b&gt;가 필요&lt;/li&gt;
&lt;li&gt;역할설명어노테이션
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;데이터 조회&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;클라이언트의 Query 요청을 처리하여 데이터를 반환&lt;/td&gt;
&lt;td&gt;@QueryMapping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;데이터 생성/수정&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Mutation 요청을 처리하여 데이터를 추가/수정&lt;/td&gt;
&lt;td&gt;@MutationMapping&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;GraphQL 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/graphiql&quot;&gt;http://localhost:8080/graphiql&lt;/a&gt; 접속 후 query 테스트 진행&lt;/li&gt;
&lt;li&gt;Postman을 활용해서 API 호출&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Language/SQL</category>
      <author>안녕 하리</author>
      <guid isPermaLink="true">https://yowon25.tistory.com/38</guid>
      <comments>https://yowon25.tistory.com/38#entry38comment</comments>
      <pubDate>Tue, 18 Mar 2025 10:47:39 +0900</pubDate>
    </item>
  </channel>
</rss>