<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Dev on 42Class</title><link>https://42class.com/categories/dev/</link><description>Recent content in Dev on 42Class</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Sun, 05 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://42class.com/categories/dev/index.xml" rel="self" type="application/rss+xml"/><item><title>Meetnote 개발 회고 — 로컬 회의록 자동화 Obsidian 플러그인</title><link>https://42class.com/posts/meetnote-retrospective/</link><pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate><guid>https://42class.com/posts/meetnote-retrospective/</guid><description>&lt;h2 id="왜-만들었나"&gt;왜 만들었나
&lt;/h2&gt;&lt;p&gt;회의가 끝나고 나면 항상 같은 고민이 반복됐다. &amp;ldquo;누가 뭐라고 했더라?&amp;rdquo; 회의록을 작성하자니 회의에 집중이 안 되고, 녹음만 해두면 다시 듣기가 귀찮다. 기존 SaaS 서비스들은 비용도 문제지만, 회의 내용이 외부 서버로 나가는 것 자체가 부담스러웠다.&lt;/p&gt;
&lt;p&gt;그리고 결정적인 이유가 하나 더 있었다. 지금 모든 업무 문서를 Obsidian에서 마크다운으로 관리하고 있는데, 이건 AX(AI Transformation) 관점에서 매우 중요한 선택이었다. &lt;strong&gt;마크다운은 AI 에이전트에게 전달하기 가장 좋은 포맷이다.&lt;/strong&gt; 회의록도 마크다운으로 생성되면 별도 서비스에서 옮기는 번거로움 없이, 다른 업무 문서들과 함께 한 곳에서 관리할 수 있다. 에이전트가 과거 회의 맥락을 참조하고, 액션 아이템을 추적하고, 관련 문서를 연결하는 것까지 자연스럽게 이어진다.&lt;/p&gt;
&lt;p&gt;그래서 만들었다. &lt;strong&gt;완전히 로컬에서 동작하는 회의록 자동화 도구.&lt;/strong&gt; 비용 0원, 오프라인 가능, GPU 가속. 그리고 결과물은 Obsidian Vault 안의 마크다운 파일.&lt;/p&gt;
&lt;h2 id="무엇을-만들었나"&gt;무엇을 만들었나
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://github.com/changsu-jin/meetnote" target="_blank" rel="noopener"
 &gt;Meetnote&lt;/a&gt;는 Obsidian 플러그인 + Python 백엔드로 구성된 로컬 회의록 자동화 도구다.&lt;/p&gt;
&lt;p&gt;핵심 기능:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;실시간 음성 인식&lt;/strong&gt; — Whisper large-v3-turbo, 5초 단위 스트리밍&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;화자 분리&lt;/strong&gt; — pyannote 3.1, GPU 자동 감지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;화자 식별&lt;/strong&gt; — 임베딩 DB 기반, 회의할수록 정확도 향상&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 요약&lt;/strong&gt; — Claude CLI / Ollama, 액션 아이템 자동 생성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이메일 발송&lt;/strong&gt; — 참석자별 회의록 HTML 메일&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;암호화&lt;/strong&gt; — AES 암호화, 자동 삭제, 감사 로그&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apple Silicon 기준 60분 회의를 약 5분 만에 처리한다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://42class.com/img/meetnote-screenshot.png" alt="Meetnote 실제 사용 화면" loading="lazy" class="gallery-image"&gt;
&lt;/p&gt;
&lt;h2 id="기술-스택"&gt;기술 스택
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;영역&lt;/th&gt;
 &lt;th&gt;기술&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Plugin&lt;/td&gt;
 &lt;td&gt;TypeScript, Obsidian API, Web Audio API&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Backend&lt;/td&gt;
 &lt;td&gt;Python, FastAPI, WebSocket&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;STT&lt;/td&gt;
 &lt;td&gt;mlx-whisper (Apple Silicon), faster-whisper (CPU)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;화자 분리&lt;/td&gt;
 &lt;td&gt;pyannote.audio 3.1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;화자 임베딩&lt;/td&gt;
 &lt;td&gt;wespeaker-voxceleb-resnet34-LM&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;AI 요약&lt;/td&gt;
 &lt;td&gt;Claude CLI, Ollama&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;배포&lt;/td&gt;
 &lt;td&gt;Docker, GitHub Actions, GHCR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="아키텍처"&gt;아키텍처
&lt;/h2&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;┌─────────────┐ WebSocket (PCM 16kHz) ┌──────────────┐
│ Obsidian │ ──────────────────────────────▶│ FastAPI │
│ Plugin │◀────────────── JSON ──────────│ Backend │
│ (TS) │ │ (Python) │
└─────────────┘ └──────┬───────┘
 │
 ┌─────────────────┼─────────────────┐
 │ │ │
 ┌─────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
 │ Whisper │ │ pyannote │ │ Speaker │
 │ STT │ │ Diarizer │ │ Embedding │
 └───────────┘ └─────────────┘ └─────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Plugin이 Web Audio API로 마이크 음성을 캡처해서 5초 단위로 WebSocket을 통해 바이너리 전송한다. 백엔드는 Whisper로 실시간 전사하고, 녹음 종료 후 pyannote로 화자 분리를 수행한다. 전사 결과와 화자 분리 결과를 시간 기반으로 병합해서 &amp;ldquo;누가 무슨 말을 했는지&amp;rdquo; 최종 결과를 만든다.&lt;/p&gt;
&lt;h2 id="개발-과정에서-마주친-문제들"&gt;개발 과정에서 마주친 문제들
&lt;/h2&gt;&lt;h3 id="1-mlx-vs-faster-whisper--플랫폼별-분기"&gt;1. MLX vs faster-whisper — 플랫폼별 분기
&lt;/h3&gt;&lt;p&gt;Apple Silicon에서는 MLX가 압도적으로 빠르지만, Linux/CUDA 환경에서는 faster-whisper가 낫다. 처음에는 하나로 통일하려 했지만, 성능 차이가 너무 커서 런타임에 플랫폼을 감지하고 분기하는 방식을 택했다.&lt;/p&gt;
&lt;h3 id="2-화자-분리와-전사-결과-병합"&gt;2. 화자 분리와 전사 결과 병합
&lt;/h3&gt;&lt;p&gt;Whisper와 pyannote는 독립적으로 동작한다. 둘의 타임스탬프가 정확히 일치하지 않기 때문에, 시간 구간의 최대 겹침(temporal overlap)을 기준으로 화자를 배정했다. 같은 화자의 연속 발언은 5초 이내 간격이면 하나로 합친다.&lt;/p&gt;
&lt;h3 id="3-환각hallucination-필터링"&gt;3. 환각(Hallucination) 필터링
&lt;/h3&gt;&lt;p&gt;Whisper는 가끔 무음 구간에서 환각을 일으킨다. &lt;code&gt;no_speech_prob&lt;/code&gt;과 &lt;code&gt;compression_ratio&lt;/code&gt;를 기준으로 품질 필터를 넣어 해결했다. LLM 기반 전사 보정도 추가했는데, 고유명사나 전문 용어의 정확도가 체감될 정도로 올라갔다.&lt;/p&gt;
&lt;h3 id="4-동시-녹음-세션-처리"&gt;4. 동시 녹음 세션 처리
&lt;/h3&gt;&lt;p&gt;Transcriber와 Diarizer는 GPU 메모리를 많이 쓰기 때문에 싱글톤으로 관리한다. 하지만 여러 사용자가 동시에 녹음할 수 있어야 하므로, WebSocket 세션별로 격리하되 공유 리소스는 Lock으로 보호하는 구조로 만들었다.&lt;/p&gt;
&lt;h3 id="5-docker-빌드-최적화"&gt;5. Docker 빌드 최적화
&lt;/h3&gt;&lt;p&gt;처음 Docker 빌드는 46분이 걸렸다. PyTorch + pyannote 의존성이 무거웠기 때문이다. base 이미지와 app 이미지를 분리하고, GitHub Actions에서 base 이미지를 캐싱하는 방식으로 빌드 시간을 3분까지 줄였다. 이후 추가 최적화로 1분대까지 단축했다.&lt;/p&gt;
&lt;h2 id="누적-학습--화자-임베딩-db"&gt;누적 학습 — 화자 임베딩 DB
&lt;/h2&gt;&lt;p&gt;가장 만족스러운 기능이다. 회의에서 새로운 화자가 감지되면 임베딩 벡터를 자동 저장한다. 다음 회의에서 코사인 유사도(임계값 0.70)로 기존 화자와 매칭한다. 회의를 할수록 화자 인식 정확도가 올라가는 구조다.&lt;/p&gt;
&lt;h2 id="타임라인"&gt;타임라인
&lt;/h2&gt;&lt;p&gt;집중적으로 작업한 건 약 일주일. 115개 커밋, 혼자서 기획부터 배포까지.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phase 0&lt;/strong&gt; — MVP: 실시간 전사, 화자 분리, 요약, 암호화, RAG 검색&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 1&lt;/strong&gt; — 안정화: 버그 수정, 환각 필터, 품질 최적화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 2&lt;/strong&gt; — UX: 사이드 패널, 화자 관리 UI, 이메일, 원클릭 설치&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 3&lt;/strong&gt; — 배포: Docker, GHA 캐싱, 온보딩 모달, 보안 강화&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="돌아보며"&gt;돌아보며
&lt;/h2&gt;&lt;p&gt;일주일이라는 짧은 기간이었지만, &amp;ldquo;내가 직접 쓸 도구&amp;quot;를 만드는 과정이라 몰입도가 높았다. 특히 화자 임베딩 DB의 누적 학습은 쓸수록 똑똑해지는 느낌이 있어서 뿌듯하다.&lt;/p&gt;
&lt;p&gt;아쉬운 점은 Windows 네이티브 지원이 아직 Docker(WSL2) 의존이라는 것. 그리고 CPU 환경에서는 60분 회의 처리에 40분이 걸려서 실용성이 떨어진다.&lt;/p&gt;
&lt;p&gt;다음 목표는 웹 기반 대시보드와 팀 단위 화자 DB 공유 기능이다.&lt;/p&gt;
&lt;hr&gt;
&lt;div style="background: linear-gradient(135deg, #24292e 0%, #40464d 100%); border-radius: 12px; padding: 24px 32px; text-align: center; margin-top: 32px;"&gt;
 &lt;p style="color: #f0f0f0; font-size: 1.2em; margin-bottom: 12px; font-weight: bold;"&gt;Meetnote — 로컬 회의록 자동화&lt;/p&gt;
 &lt;p style="color: #8b949e; font-size: 0.9em; margin-bottom: 16px;"&gt;Obsidian plugin for local meeting transcription, speaker diarization, and AI summarization&lt;/p&gt;
 &lt;a href="https://github.com/changsu-jin/meetnote" style="display: inline-block; background: #238636; color: white; padding: 10px 24px; border-radius: 8px; text-decoration: none; font-weight: bold; font-size: 1em;"&gt;GitHub에서 보기 →&lt;/a&gt;
&lt;/div&gt;</description></item><item><title>Spring DATA JPA in MySQL에서 Pessimistic Lock(비관적 락) 처리</title><link>https://42class.com/posts/spring-data-jpa-in-mysql%EC%97%90%EC%84%9C-pessimistic-lock%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD-%EC%B2%98%EB%A6%AC/</link><pubDate>Wed, 03 Feb 2021 00:00:00 +0000</pubDate><guid>https://42class.com/posts/spring-data-jpa-in-mysql%EC%97%90%EC%84%9C-pessimistic-lock%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD-%EC%B2%98%EB%A6%AC/</guid><description>&lt;p&gt;&lt;img src="https://42class.com/img/title_jpa.png" alt="" loading="lazy" class="gallery-image"&gt;
&lt;/p&gt;
&lt;p&gt;WMS운영 중 비정상적인 락 처리 현상이 있어 확인한 결과 공유드립니다.
Spring DATA JPA in MySQL에서 Pessimistic Lock(비관적 락) 처리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;락 어논테이션으로 처리 : @Lock(LockModeType.PESSIMISTIC_WRITE)&lt;/li&gt;
&lt;li&gt;쓰기,읽기 모두 lock를 거는 방식이지만 실제로는 읽기에 대핸 lock이 걸리지 않음&lt;/li&gt;
&lt;li&gt;읽기도 lock을 걸려면, 해당 row에 접근하는 select 쿼리에 모두 for update구문을 걸어줘야 함(@Lock(LockModeType.PESSIMISTIC_WRITE))&lt;/li&gt;
&lt;li&gt;해당 락은 row level lock이지만 mysql innoDB의 락 처리 방식은 인덱싱 되지 않은 조건으로 선별 된 row에 대해선 row level로 락은 잡지 못함&lt;/li&gt;
&lt;li&gt;mysql innoDB는 인덱싱 범위에 대해서 락을 잡는 방식으로 인덱싱 되지 않은 조건으로 선별 된 select결과는 row가 아닌 테이블이 락이 걸리는 현상이 발생됨&lt;/li&gt;
&lt;li&gt;락을 걸어야 하는 데이터 셋의 검색조건은 인덱싱된 컬럼을 기준으로 잡아야 함&lt;/li&gt;
&lt;li&gt;JPA에서 lock scope범위에 따라 릴레이션 된 모델들도 함께 락이 잡히게 됨&lt;/li&gt;
&lt;li&gt;대상 테이블만 락의 범위로 잡으려면 scope를 지정해줘야 함&lt;/li&gt;
&lt;li&gt;락 범위 어논테이션으로 처리 : @QueryHints(value = {@QueryHint(name = &amp;ldquo;javax.persistence.lock.scope&amp;rdquo;, value = &amp;ldquo;NORMAL&amp;rdquo;)})&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>JPA N+1튜닝과정에서 선언한 배치 사이즈와 다르게 쿼리 분할 되어 수행되는 이유</title><link>https://42class.com/posts/jpa-n-1%ED%8A%9C%EB%8B%9D%EA%B3%BC%EC%A0%95%EC%97%90%EC%84%9C-%EC%84%A0%EC%96%B8%ED%95%9C-%EB%B0%B0%EC%B9%98-%EC%82%AC%EC%9D%B4%EC%A6%88%EC%99%80-%EB%8B%A4%EB%A5%B4%EA%B2%8C-%EC%BF%BC%EB%A6%AC-%EB%B6%84%ED%95%A0-%EB%90%98%EC%96%B4-%EC%88%98%ED%96%89%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0/</link><pubDate>Mon, 01 Feb 2021 00:00:00 +0000</pubDate><guid>https://42class.com/posts/jpa-n-1%ED%8A%9C%EB%8B%9D%EA%B3%BC%EC%A0%95%EC%97%90%EC%84%9C-%EC%84%A0%EC%96%B8%ED%95%9C-%EB%B0%B0%EC%B9%98-%EC%82%AC%EC%9D%B4%EC%A6%88%EC%99%80-%EB%8B%A4%EB%A5%B4%EA%B2%8C-%EC%BF%BC%EB%A6%AC-%EB%B6%84%ED%95%A0-%EB%90%98%EC%96%B4-%EC%88%98%ED%96%89%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0/</guid><description>&lt;p&gt;&lt;img src="https://42class.com/img/title_jpa.png" alt="" loading="lazy" class="gallery-image"&gt;
&lt;/p&gt;
&lt;h2 id="jdbc-preparedstatement의-캐싱-방식"&gt;JDBC preparedstatement의 캐싱 방식
&lt;/h2&gt;&lt;p&gt;먼저 이 현상을 설명하려면 JDBC의 preparedstatement의 캐싱 방식을 알아야 합니다.&lt;br&gt;
preparedstatement는 in절이 들어가는 select 쿼리에 대해 각 경우를 모두 캐싱합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff79c6"&gt;#&lt;/span&gt; 데이터가 &lt;span style="color:#bd93f9"&gt;1&lt;/span&gt;개 들어올 때 : &lt;span style="color:#ff79c6"&gt;where&lt;/span&gt; xxx &lt;span style="color:#ff79c6"&gt;in&lt;/span&gt; (&lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff79c6"&gt;#&lt;/span&gt; 데이터가 &lt;span style="color:#bd93f9"&gt;2&lt;/span&gt;개 들어올 때 : &lt;span style="color:#ff79c6"&gt;where&lt;/span&gt; xxx &lt;span style="color:#ff79c6"&gt;in&lt;/span&gt; (&lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;,&lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff79c6"&gt;#&lt;/span&gt; 데이터가 n개 들어올 때 : &lt;span style="color:#ff79c6"&gt;where&lt;/span&gt; xxx &lt;span style="color:#ff79c6"&gt;in&lt;/span&gt; (&lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;,&lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, ...)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="hibernate의-batch_size에-의한-캐싱-방식"&gt;hibernate의 batch_size에 의한 캐싱 방식
&lt;/h2&gt;&lt;p&gt;위의 방식으로 캐시되는 경우 데이터가 많아질수록 많은 케이스를 캐싱해야 해서 성능에 문제가 발생합니다.&lt;br&gt;
그래서 하이버네이트는 최적화를 위해 캐싱케이스를 줄이는 방식으로 수행됩니다.&lt;br&gt;
줄이는 방식은 프로젝트에 선언 된 기본배치사이즈 &lt;em&gt;(hibernate.default_batch_fetch_size: 100)&lt;/em&gt; 를 기준으로 절반씩 나눠가면서 캐싱합니다.&lt;br&gt;
그리고 자주사용할 것으로 예상되는 1~10 사이는 모두 캐싱 하게됩니다.&lt;br&gt;
in절 항목값을 100으로 잡을 때, 기존 preparedstatement 방식에 의하면 총 100개의 케이스를 캐싱해두게 됩니다.&lt;br&gt;
하이버네이트의 방식으로 하게 되면 14개로 줄어듭니다.&lt;br&gt;
(1,2,3,4,5,6,7,8,9,10,12, 25,50,100)&lt;/p&gt;
&lt;p&gt;N+1이슈가 발생한 쿼리가 있고 총 데이터가 83건이면 83번의 동일한 쿼리가 나가는 상황일겁니다.&lt;br&gt;
현상 튜닝을 위해 &lt;em&gt;hibernate.default_batch_fetch_size: 100&lt;/em&gt; 으로 설정 후 조회합니다.&lt;br&gt;
예상으로는 쿼리는 한번 나가고 in절 항목이 83개가 들어간 쿼리를 기대합니다.&lt;br&gt;
하지만 실제로는 캐싱된 케이스에 의해 아래와 같이 총 3번의 동일한 쿼리가 나가게 됩니다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;in절 항목이 50으로 캐싱된 쿼리 한 번
in절 항목이 25로 캐싱된 쿼리 한 번
in절 항목이 8으로 캐싱된 쿼리 한 번
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="83개-항목이-분리-요청-된-실제-예"&gt;83개 항목이 분리 요청 된 실제 예
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;select&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.id &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; id1_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.created_by &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; created_2_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.created_date &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; created_3_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.last_modified_by &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; last_mod4_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.last_modified_date &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; last_mod5_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.code &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; code6_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.name &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; name7_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.product_group_code &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; product_8_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.product_type &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; product_9_38_0_
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;from&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product product0_
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;where&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.id &lt;span style="color:#ff79c6"&gt;in&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#bd93f9"&gt;2020&lt;/span&gt;&lt;span style="color:#ff79c6"&gt;-&lt;/span&gt;&lt;span style="color:#bd93f9"&gt;08&lt;/span&gt;&lt;span style="color:#ff79c6"&gt;-&lt;/span&gt;&lt;span style="color:#bd93f9"&gt;07&lt;/span&gt; &lt;span style="color:#bd93f9"&gt;17&lt;/span&gt;:&lt;span style="color:#bd93f9"&gt;33&lt;/span&gt;:&lt;span style="color:#bd93f9"&gt;30&lt;/span&gt;,&lt;span style="color:#bd93f9"&gt;948&lt;/span&gt; DEBUG [XNIO&lt;span style="color:#ff79c6"&gt;-&lt;/span&gt;&lt;span style="color:#bd93f9"&gt;1&lt;/span&gt; task&lt;span style="color:#ff79c6"&gt;-&lt;/span&gt;&lt;span style="color:#bd93f9"&gt;1&lt;/span&gt;] &lt;span style="color:#ff79c6"&gt;SQL&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;select&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.id &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; id1_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.created_by &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; created_2_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.created_date &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; created_3_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.last_modified_by &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; last_mod4_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.last_modified_date &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; last_mod5_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.code &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; code6_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.name &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; name7_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.product_group_code &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; product_8_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.product_type &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; product_9_38_0_
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;from&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product product0_
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;where&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.id &lt;span style="color:#ff79c6"&gt;in&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#bd93f9"&gt;2020&lt;/span&gt;&lt;span style="color:#ff79c6"&gt;-&lt;/span&gt;&lt;span style="color:#bd93f9"&gt;08&lt;/span&gt;&lt;span style="color:#ff79c6"&gt;-&lt;/span&gt;&lt;span style="color:#bd93f9"&gt;07&lt;/span&gt; &lt;span style="color:#bd93f9"&gt;17&lt;/span&gt;:&lt;span style="color:#bd93f9"&gt;33&lt;/span&gt;:&lt;span style="color:#bd93f9"&gt;30&lt;/span&gt;,&lt;span style="color:#bd93f9"&gt;969&lt;/span&gt; DEBUG [XNIO&lt;span style="color:#ff79c6"&gt;-&lt;/span&gt;&lt;span style="color:#bd93f9"&gt;1&lt;/span&gt; task&lt;span style="color:#ff79c6"&gt;-&lt;/span&gt;&lt;span style="color:#bd93f9"&gt;1&lt;/span&gt;] &lt;span style="color:#ff79c6"&gt;SQL&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;select&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.id &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; id1_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.created_by &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; created_2_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.created_date &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; created_3_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.last_modified_by &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; last_mod4_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.last_modified_date &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; last_mod5_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.code &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; code6_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.name &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; name7_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.product_group_code &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; product_8_38_0_,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.product_type &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; product_9_38_0_
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;from&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product product0_
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;where&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; product0_.id &lt;span style="color:#ff79c6"&gt;in&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;, &lt;span style="color:#ff79c6"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="최적화-전략"&gt;최적화 전략
&lt;/h2&gt;&lt;p&gt;위와 같이 하이버네이트는 최적화 전략으로 수행됩니다.&lt;br&gt;
3번의 캐싱된 쿼리가 수행되는 것은 정상적인 부분이며 하이버네이트에서 권장하는 기본전략입니다.&lt;br&gt;
참고로, 선언한 &lt;em&gt;(hibernate.default_batch_fetch_size: 100 )&lt;/em&gt; 사이즈 만큼 in절 항목을 발생시키고 싶다면 설정파일에 &lt;em&gt;hibernate.batch_fetch_style: dynamic&lt;/em&gt;을 추가하시면 됩니다.&lt;br&gt;
하지만 이 방식은 캐싱되지 않은 케이스로 쿼리가 수행되므로 권장하지 않는 방식이라고 합니다.&lt;/p&gt;</description></item></channel></rss>