<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>yunminaje03.log</title>
        <link>https://velog.io/</link>
        <description>오늘하루도수고하세용~</description>
        <lastBuildDate>Tue, 26 May 2026 10:52:02 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>yunminaje03.log</title>
            <url>https://velog.velcdn.com/images/minjae-yun/profile/ef04efb9-7054-44f7-8053-1ef797f53369/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. yunminaje03.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/minjae-yun" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[융합 동아리]]></title>
            <link>https://velog.io/@minjae-yun/%EC%9C%B5%ED%95%A9-%EB%8F%99%EC%95%84%EB%A6%AC</link>
            <guid>https://velog.io/@minjae-yun/%EC%9C%B5%ED%95%A9-%EB%8F%99%EC%95%84%EB%A6%AC</guid>
            <pubDate>Tue, 26 May 2026 10:52:02 GMT</pubDate>
            <description><![CDATA[<h2 id="동아리-목적">동아리 목적</h2>
<p>temp0.7은 생성형 AI를 단순히 결과물 생성 도구로 소비하는 것이 아니라,<br>AI 모델의 작동 원리, 프롬프트 설계, 데이터 처리 방식, 모델의 한계와 편향을 이해하고<br>이를 실제 문제 해결에 적절히 활용하는 방법을 탐구하는 융합 학술 동아리이다.</p>
<p>본 동아리는 금융 데이터 분석, 경량 LLM 실험, 머신러닝 모델 학습, 웹 서비스 연동 프로젝트를 통해<br>생성형 AI를 똑똑하게 사용하는 역량을 기르는 것을 목표로 한다.</p>
<h2 id="융합-동아리-목표">융합 동아리 목표</h2>
<p>로컬 경량 LLM 모델을 직접 학습/파인튜닝하고, 머신러닝 실험 환경을 확보한 뒤, 학습된 모델을 웹 서비스와 API 형태로 연동하는 것</p>
<h2 id="플랫폼-비교">플랫폼 비교</h2>
<table>
<thead>
<tr>
<th>후보</th>
<th>잘 맞는 목적</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>Hugging Face</td>
<td>경량 LLM 탐색, 파인튜닝, 모델 배포, API 연동</td>
<td>오픈소스 LLM 모델이 가장 많고 Qwen, Llama, Gemma, Phi 등을 바로 비교할 수 있다. 학습한 모델을 업로드하고 API 엔드포인트로 배포하기 쉽다.</td>
<td>GPU 학습 환경 자체는 GCP, AWS, Azure보다 제한적일 수 있어 큰 학습에는 외부 GPU 환경이 필요할 수 있다.</td>
</tr>
<tr>
<td>GCP</td>
<td>Vertex AI 기반 학습, GPU 실험, Cloud Run 배포</td>
<td>Colab/Jupyter 환경에 익숙하면 접근하기 쉽다. Vertex AI, Cloud Storage, Cloud Run 조합으로 ML 학습부터 배포까지 연결하기 좋다.</td>
<td>결제 계정과 카드 등록 과정이 번거로울 수 있고, 처음에는 서비스 종류가 많아 헷갈릴 수 있다.</td>
</tr>
<tr>
<td>Azure</td>
<td>Azure Machine Learning, Azure OpenAI, 모델 배포</td>
<td>VS Code, GitHub와의 연동이 좋고 Azure Machine Learning으로 학습 및 배포 환경을 구성할 수 있다.</td>
<td>오픈소스 LLM을 직접 탐색하고 실험하는 과정은 Hugging Face보다 덜 직관적일 수 있다.</td>
</tr>
<tr>
<td>AWS</td>
<td>SageMaker, S3, Bedrock, 모델 배포</td>
<td>ML 학습과 배포 실무 파이프라인에 강하다. SageMaker, S3, Bedrock 등을 활용해 학습, 저장, API 연동을 구성할 수 있다.</td>
<td>처음 배우기에는 가장 복잡하고 SageMaker 비용 구조도 이해하기 어려울 수 있다.</td>
</tr>
<tr>
<td>RunPod</td>
<td>저렴한 GPU 학습</td>
<td>GPU를 비교적 저렴하게 빌려 LLM 학습이나 실험을 진행하기 좋다.</td>
<td>모델 저장, 배포, 서비스 관리까지 직접 구성해야 하므로 초보자에게는 운영 부담이 있다.</td>
</tr>
</tbody></table>
<h2 id="hugging-face-고려할-만한-모델">Hugging Face 고려할 만한 모델</h2>
<table>
<thead>
<tr>
<th>모델</th>
<th align="right">크기</th>
<th>장점</th>
<th>단점</th>
<th>추천 용도</th>
</tr>
</thead>
<tbody><tr>
<td>Qwen3-0.6B</td>
<td align="right">0.6B</td>
<td>매우 가벼워 로컬 테스트가 쉽고 빠르게 실험할 수 있다.</td>
<td>모델 크기가 작아 복잡한 응답이나 추론 성능은 제한적이다.</td>
<td>첫 실습, 모델 구조 이해</td>
</tr>
<tr>
<td>Qwen3-1.7B</td>
<td align="right">1.7B</td>
<td>가볍지만 기본 성능이 괜찮아 동아리 실습용으로 적당하다.</td>
<td>고난도 추론이나 긴 문맥 처리에는 한계가 있을 수 있다.</td>
<td>로컬 파인튜닝, 챗봇 실습</td>
</tr>
<tr>
<td>Qwen3-4B</td>
<td align="right">4B</td>
<td>성능과 실습 난이도의 균형이 좋고 실제 서비스 프로토타입에 활용하기 좋다.</td>
<td>1B급 모델보다 GPU 메모리 요구량이 높다.</td>
<td>웹 서비스 연동, API 배포</td>
</tr>
<tr>
<td>Llama-3.2-1B</td>
<td align="right">1B</td>
<td>Meta 계열 대표 경량 모델로 자료가 많고 학습용으로 적합하다.</td>
<td>특정 태스크나 한국어 성능은 추가 튜닝이 필요할 수 있다.</td>
<td>LLM 구조 학습</td>
</tr>
<tr>
<td>Llama-3.2-3B</td>
<td align="right">3B</td>
<td>챗봇, 질의응답 등 실제 서비스 실험에 적당한 크기다.</td>
<td>더 작은 모델보다 실행 비용과 학습 부담이 커진다.</td>
<td>챗봇, 질의응답 서비스</td>
</tr>
<tr>
<td>Gemma 3 1B</td>
<td align="right">1B</td>
<td>Google 계열 경량 모델로 비교 실험에 활용하기 좋다.</td>
<td>사용 전 라이선스와 사용 조건을 확인해야 한다.</td>
<td>모델 비교 실험</td>
</tr>
<tr>
<td>Gemma 3 4B</td>
<td align="right">4B</td>
<td>경량 모델 중 성능 기대치가 높고 다양한 실험에 활용할 수 있다.</td>
<td>로컬 GPU 환경이 없으면 학습과 추론이 부담될 수 있다.</td>
<td>성능 비교, 서비스 실험</td>
</tr>
<tr>
<td>Phi-4-mini</td>
<td align="right">3.8B</td>
<td>추론, 교육형 태스크, 문제풀이형 서비스에 강점이 있다.</td>
<td>범용 챗봇 용도로는 다른 모델과 성능 비교가 필요하다.</td>
<td>학습 도우미, 문제풀이</td>
</tr>
</tbody></table>
<h2 id="azure에서-쓸-수-있는-모델서비스-구분">Azure에서 쓸 수 있는 모델/서비스 구분</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
<th>목적과의 관련성</th>
</tr>
</thead>
<tbody><tr>
<td>Azure Machine Learning</td>
<td>머신러닝 학습, 실험 관리, 모델 등록, 엔드포인트 배포를 하는 Azure의 ML 플랫폼</td>
<td>머신러닝 환경 확보와 학습 모델 배포에 적합</td>
</tr>
<tr>
<td>Azure AI Foundry Model Catalog</td>
<td>Microsoft, Meta, Mistral, Hugging Face 등 여러 모델을 탐색하고 배포할 수 있는 모델 카탈로그</td>
<td>오픈소스/상용 모델을 API 형태로 배포할 때 유용</td>
</tr>
<tr>
<td>Azure OpenAI Service</td>
<td>GPT 계열 모델을 Azure 환경에서 API로 호출하는 서비스</td>
<td>직접 학습보다는 상용 LLM API 연동 실험에 적합</td>
</tr>
<tr>
<td>Azure Container Apps</td>
<td>FastAPI 같은 모델 서버를 컨테이너로 배포하는 서비스</td>
<td>직접 학습한 모델을 웹 서비스와 연결할 때 적합</td>
</tr>
<tr>
<td>Azure Blob Storage</td>
<td>데이터셋, 학습 결과, 모델 파일 저장소</td>
<td>학습 데이터와 모델 파일 저장에 필요</td>
</tr>
</tbody></table>
<h2 id="azure에서-고려할-만한-모델">Azure에서 고려할 만한 모델</h2>
<table>
<thead>
<tr>
<th>모델</th>
<th align="right">크기</th>
<th>장점</th>
<th>단점</th>
<th>추천 용도</th>
</tr>
</thead>
<tbody><tr>
<td>Phi-4-mini</td>
<td align="right">3.8B</td>
<td>Microsoft의 경량 모델로 Azure AI Foundry와 잘 맞고, 추론/교육형 태스크에 강점이 있다.</td>
<td>범용 챗봇 성능은 Qwen, Llama 계열과 비교가 필요하다.</td>
<td>학습 도우미, 문제풀이, 추론형 서비스</td>
</tr>
<tr>
<td>Phi-4</td>
<td align="right">14B</td>
<td>Phi 계열 중 성능이 높고 복잡한 추론 태스크에 적합하다.</td>
<td>로컬 경량 학습 목적에는 다소 무겁다.</td>
<td>고성능 추론 실험, 비교 모델</td>
</tr>
<tr>
<td>Llama 3.2 1B</td>
<td align="right">1B</td>
<td>가볍고 로컬 실험에 적합하며 Azure AI Foundry/Hugging Face 흐름 모두에서 다루기 좋다.</td>
<td>성능은 제한적이다.</td>
<td>첫 로컬 LLM 실습</td>
</tr>
<tr>
<td>Llama 3.2 3B</td>
<td align="right">3B</td>
<td>챗봇과 질의응답 프로토타입에 적당한 크기다.</td>
<td>1B 모델보다 학습 및 추론 비용이 증가한다.</td>
<td>웹 서비스 프로토타입</td>
</tr>
<tr>
<td>Mistral / Ministral 계열</td>
<td align="right">3B급 이상</td>
<td>추론 성능과 실사용성이 좋은 편이며 Azure AI Foundry에서 선택 가능하다.</td>
<td>모델별 라이선스와 배포 조건 확인이 필요하다.</td>
<td>API 배포, 성능 비교</td>
</tr>
<tr>
<td>Hugging Face 모델</td>
<td align="right">다양함</td>
<td>Azure AI Foundry와 Azure ML에서 Hugging Face 모델을 배포할 수 있다.</td>
<td>모든 모델이 같은 방식으로 쉽게 배포되는 것은 아니므로 지원 여부 확인이 필요하다.</td>
<td>오픈소스 모델 배포 실험</td>
</tr>
</tbody></table>
<h2 id="플랫폼별-비용크레딧-비교">플랫폼별 비용/크레딧 비교</h2>
<table>
<thead>
<tr>
<th>플랫폼</th>
<th>무료 크레딧/지원</th>
<th>결제 방식</th>
<th>비용 관점 장점</th>
<th>비용 관점 단점</th>
</tr>
</thead>
<tbody><tr>
<td>Azure</td>
<td>Azure for Students 기준 $100 크레딧 제공, 12개월 사용 가능, 신용카드 불필요</td>
<td>사용량 기반 과금</td>
<td>학생 인증만으로 시작 가능하고 카드 등록 부담이 적다. Azure Machine Learning, Blob Storage, Container Apps 등을 크레딧으로 실험할 수 있다.</td>
<td>$100은 GPU 학습에는 금방 소진될 수 있다. GPU VM이나 Azure ML Compute를 켜둔 채로 두면 비용이 빠르게 증가한다.</td>
</tr>
<tr>
<td>GCP</td>
<td>신규 고객 기준 $300 크레딧 제공, 91일 사용 가능</td>
<td>사용량 기반 과금</td>
<td>크레딧 금액이 가장 크다. Vertex AI, Cloud Storage, Cloud Run, Compute Engine GPU 실험에 활용 가능하다.</td>
<td>신용카드 등록이 필요하다. 91일 제한이 있어 장기 프로젝트에는 불리할 수 있다.</td>
</tr>
<tr>
<td>Hugging Face</td>
<td>Inference Providers 무료 사용자는 월 $0.10 크레딧, Pro는 월 $2.00 크레딧</td>
<td>일부 무료 + 사용량 기반 과금</td>
<td>모델 탐색, 다운로드, 로컬 학습, 모델 업로드는 무료로 시작 가능하다. 경량 LLM 실험에 가장 직접적이다.</td>
<td>무료 API 크레딧은 매우 작다. Inference Endpoints는 결제수단 등록과 사용량 과금이 필요하며, Pro/Team 같은 월 구독 요소가 섞일 수 있어 학교 지원 기준과 충돌할 수 있다.</td>
</tr>
<tr>
<td>AWS</td>
<td>AWS Educate/학생 프로그램은 학교나 프로그램에 따라 크레딧 조건이 달라질 수 있음</td>
<td>사용량 기반 과금</td>
<td>SageMaker, S3, Bedrock 등으로 실무형 ML 파이프라인을 경험할 수 있다.</td>
<td>초보자에게 복잡하고 GPU/SageMaker 비용 구조가 어렵다. 고정된 학생 크레딧 조건을 먼저 확인해야 한다.</td>
</tr>
<tr>
<td>RunPod</td>
<td>일반적으로 별도 학생 크레딧보다는 사용량 기반 GPU 대여 중심</td>
<td>초/시간 단위 GPU 과금</td>
<td>GPU 단가가 대형 클라우드보다 저렴한 편이라 경량 LLM 학습 실험에 유리하다.</td>
<td>학교 지출 증빙이나 안정적인 장기 운영 측면에서는 Azure/GCP보다 불리할 수 있다.</td>
</tr>
<tr>
<td>Replicate</td>
<td>일부 무료 실행 후 결제 등록 요구 가능</td>
<td>실행 시간/하드웨어 기반 과금</td>
<td>이미 배포된 모델을 API로 빠르게 실험하기 좋다.</td>
<td>직접 학습 환경 확보보다는 API 테스트에 가까워 주 목적과는 거리가 있다.</td>
</tr>
</tbody></table>
<h2 id="ㅁㄴㅇㅁ">ㅁㄴㅇㅁ</h2>
<table>
<thead>
<tr>
<th>품목명</th>
<th>용도</th>
</tr>
</thead>
<tbody><tr>
<td>AI 모델 학습용 GPU 그래픽카드</td>
<td>경량 LLM 실행 및 파인튜닝, 금융 데이터 기반 머신러닝 모델 학습 실습</td>
</tr>
<tr>
<td>대용량 SSD 저장장치</td>
<td>금융 API 수집 데이터, 학습 데이터셋, 모델 파일 및 실험 결과 저장</td>
</tr>
<tr>
<td>머신러닝/LLM 학습 교재</td>
<td>생성형 AI의 원리, 프롬프트 설계, 모델 학습 및 평가 방법 학습</td>
</tr>
<tr>
<td>AI 엣지 디바이스 개발 키트</td>
<td>학습된 경량 모델을 실제 장비에서 실행하고 웹 서비스와 연동하는 배포 실습</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Theysaid]]></title>
            <link>https://velog.io/@minjae-yun/Theysaid</link>
            <guid>https://velog.io/@minjae-yun/Theysaid</guid>
            <pubDate>Sat, 28 Mar 2026 11:54:53 GMT</pubDate>
            <description><![CDATA[<p><a href="https://yozm.wishket.com/magazine/detail/3070/">https://yozm.wishket.com/magazine/detail/3070/</a></p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/59b4b442-ddd6-4374-b300-f2ab54a147d0/image.png" alt=""></p>
<p>설문조사 만들어보면 구글폼이나 타입폼 같은 거 써봤을 텐데, 막상 응답 보면 좀 아쉽다. 왜 이렇게 생각했는지. 오늘 소개할 <strong>Theysaid</strong>는 그 문제를 AI로 풀어낸 서비스이다.</p>
<hr>
<h2 id="theysaid가-뭔데">Theysaid가 뭔데?</h2>
<p>한 줄 요약하면, <strong>AI가 설문을 대화형 인터뷰로 바꿔주는 툴</strong>이에요.</p>
<p>기존 설문은 응답자가 질문 보고 답 입력하고 끝인데, Theysaid는 응답자가 답을 입력하면 AI가 꼬리 질문을 던져요. 예를 들어 &quot;구독 가능성 4점&quot;이라고 하면, AI가 바로 &quot;오 높게 주셨네요, 어떤 부분에서 그렇게 생각하셨어요?&quot; 이런 식으로요. 인터뷰랑 설문을 합쳐놓은 느낌?</p>
<hr>
<h2 id="핵심-기능들">핵심 기능들</h2>
<h3 id="1-ai-프로젝트-생성-ai-project-creator">1. AI 프로젝트 생성 (AI Project Creator)</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/e7475195-5fa0-4653-be19-877143493995/image.png" alt=""></p>
<p>설문을 만들 때 질문을 하나하나 직접 짤 필요 없어요. AI 버디 Evo한테 목적만 말하면 된다.</p>
<p>예를 들어 &quot;고객 피드백을 모으고 싶어&quot; 라고 입력하면 Evo가 몇 가지 추가 질문을 통해 맥락을 파악하고, 거기에 맞는 설문/인터뷰 프로젝트를 통째로 만들어줘요. 질문 유형(5점 척도, 서술형, 객관식, 랭킹 등)도 알아서 잡아준다.
단순히 질문 생성에서 끝나는 게 아니라, 생성된 질문을 개선 / 수정 / 직접 입력 세 가지 방식으로 다듬을 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/422e4848-1307-4d65-8fab-460be78a2556/image.png" alt=""></p>
<h3 id="2-ai-가르치기-teach-ai">2. AI 가르치기 (Teach AI)</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/73b8af13-97ed-4ad7-8540-650913281091/image.png" alt=""></p>
<p>이게 진짜 특이한 기능인데, 우리 회사 문서나 웹사이트 URL을 AI한테 업로드해주면 AI가 우리 조직의 맥락, 톤, 목표를 학습한다. 그러면 이후 질문을 생성할 때 단순 일반 질문이 아니라 우리 서비스에 맞춤화된 질문을 뽑아줘요. 학습 자체는 약 10~15분이면 완료된다.</p>
<blockquote>
<p>예: 진행중인 서비스라면 우리 제품 도움말 문서를 업로드 → AI가 제품 구조를 이해하고 거기에 맞는 질문 생성</p>
</blockquote>
<h3 id="3-대화형-응답-수집-ai-moderation">3. 대화형 응답 수집 (AI Moderation)</h3>
<p>가장 핵심 기능이다. 응답자가 답변을 입력하면 AI가 그걸 보고 추가 질문(Follow-up)을 던진다. 채팅처럼 자연스럽게 흘러가서 응답자 입장에서도 훨씬 편하고, 우리 입장에서도 더 깊은 인사이트를 얻을 수 있다.</p>
<p>3.0 업데이트부터는 <strong>2-Way Voice</strong> 기능도 추가됐다. 텍스트 대신 음성으로도 대화할 수 있어서 더 자연스러운 인터뷰가 가능해졌다.</p>
<h3 id="4-ai-분석--액션-아이템">4. AI 분석 + 액션 아이템</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/235628e8-983c-4ea8-b6cd-9061766a8397/image.png" alt=""></p>
<p>응답이 다 모이면 그냥 숫자만 보여주는 게 아니다.</p>
<ul>
<li><strong>테마 분석</strong>: 응답 전체에서 반복되는 패턴과 주요 주제를 자동으로 정리</li>
<li><strong>인사이트 요약</strong>: &quot;사용자들은 가격 대비 가치를 긍정적으로 인식하고 있다&quot; 같은 종합 평가</li>
<li><strong>액션 아이템</strong>: &quot;멤버십 소개가 부실하다는 응답이 많음 → 상세 설명 페이지 보완 필요&quot; 식으로 체크리스트 형태로 제공</li>
</ul>
<p>작은 프로젝트라면 여기서 바로 진행 여부 관리도 가능함.</p>
<h3 id="5-conditional-logic-조건부-분기">5. Conditional Logic (조건부 분기)</h3>
<p>응답 내용에 따라 다음 질문을 다르게 보내주는 기능이다. 예를 들어 NPS 점수가 낮으면 &quot;어떤 부분이 불만족스러우셨나요?&quot;, 높으면 &quot;가장 마음에 드셨던 부분이 뭔가요?&quot; 이런 식으로 개인화된 흐름을 만들 수 있다.</p>
<h3 id="6-ai-이탈-방지-re-engagement">6. AI 이탈 방지 (Re-engagement)</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/079edd71-ddeb-4d99-a715-8d5ff7dd7299/image.png" alt=""></p>
<p>응답자가 대화 중간에 멈추면 AI가 먼저 &quot;Still there?&quot; 하고 말을 건다. 일반 설문이면 그냥 탭 닫고 끝인데, 이렇게 대화를 이어가려는 시도 자체가 응답 완료율을 높이는 데 도움이 됨. 작은 기능 같지만 UX 관점에서 꽤 신경 쓴 디테일이다.</p>
<hr>
<h2 id="기존-설문-툴이랑-다른점">기존 설문 툴이랑 다른점</h2>
<table>
<thead>
<tr>
<th></th>
<th>구글폼 / 타입폼</th>
<th>Theysaid</th>
</tr>
</thead>
<tbody><tr>
<td>질문 생성</td>
<td>직접 작성</td>
<td>AI가 목표만 보고 생성</td>
</tr>
<tr>
<td>응답 방식</td>
<td>일방적 답변 입력</td>
<td>AI와 대화형 인터뷰</td>
</tr>
<tr>
<td>분석</td>
<td>수치 집계</td>
<td>인사이트 + 액션 아이템까지</td>
</tr>
<tr>
<td>커스터마이징</td>
<td>제한적</td>
<td>회사 문서로 AI 학습 가능</td>
</tr>
</tbody></table>
<hr>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/1bb615fa-6d14-4bff-8e30-f62aed35ca06/image.png" alt=""></p>
<h2 id="아쉬운-점">아쉬운 점</h2>
<ul>
<li>서비스 언어가 영어라 한글로 바꾸는 작업을 계속 요청해야 함</li>
<li>AI가 가끔 질문 흐름을 놓쳐서 이미 답한 내용을 또 묻기도 함</li>
<li>AI를 가르치다&quot; 기능이 언제 어디에 적용되는지 UI에서 명확하게 안 알려줌</li>
<li>서버가 불안정함</li>
</ul>
<hr>
<h2 id="써볼-만한-이유">써볼 만한 이유</h2>
<p>무료 플랜으로 설문 1개에 응답 50개까지 가능함. 유저 리서치나 팀 내 피드백 수집할 일 있으면 한번 써보셈.
<img src="https://velog.velcdn.com/images/minjae-yun/post/508dea29-e786-4380-b958-7a09f0bdc4b4/image.png" alt=""></p>
<p>👉 <strong><a href="https://ai.theysaid.io/">ai.theysaid.io</a></strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CEN AI]]></title>
            <link>https://velog.io/@minjae-yun/CEN-AI</link>
            <guid>https://velog.io/@minjae-yun/CEN-AI</guid>
            <pubDate>Wed, 18 Mar 2026 13:27:25 GMT</pubDate>
            <description><![CDATA[<p><a href="https://yozm.wishket.com/magazine/detail/3203/">https://yozm.wishket.com/magazine/detail/3203/</a></p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/17aee636-9951-4bc3-ae75-1dc91ccf2883/image.png" alt=""></p>
<p>CEN AI는 아이티센클로잇에서 개발한 기업 맞춤형 클라우드 기반 AI 챗봇 서비스로 이미 사용 중인 구글 클라우드 같은 플랫폼 기반으로 우리 회사만의 생성형 AI 챗봇을 구축할 수 있다면 어떨까? 업무 생산성은 또 얼마나 달라질까요?</p>
<blockquote>
<p>아이티센클로잇은 멀티 클라우드 환경에서 Cloud Native 애플리케이션 개발과 DevOps 플랫폼 구축 등 전문기술 역량을 갖춘 기업이다. 특히 클라우드 기반 AI 기술과 데이터 분석 역량을 바탕으로 다양한 산업분야의 디지털 혁신을 기술적으로 지원하고 있다. 클라우드 전환, 구축과 운영, 컨설팅, 솔루션 제공까지 다양한 영역에서 활동중이다.</p>
</blockquote>
<hr>
<h3 id="회사에-맞는-생성형-ai-챗봇-왜-필요한가">회사에 맞는 생성형 AI 챗봇 왜 필요한가?</h3>
<ul>
<li><p>챗GPT나 gemini와 같은 범용 AI 모델은 개인을 위한 서비스이다.</p>
</li>
<li><p>범용 LLM(대규모 언어 모델)은 일반적인 데이터를 학습한 것이지, 회사의 구체적인 내부 데이터는 학습하지 못한다.</p>
</li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/99751b1a-b923-4a68-bdd7-887370549c8b/image.png" alt=""></p>
<p>실제로 지식 노동자들은 하루 8시간 중 생산적인 시간은 2시간 정도고, 나머지 6시간은 정보 검색 등 준비 작업에 소비한다. </p>
<p>특히 RAG(검색증강생성)기술을 쓰면 기업 내부에서 생산된 최신 데이터를 정화히 활용, AI가 흔히 일으킬 수 있는 할루시네이션 현상을 크게 줄이고, 답변의 출처까지 명확히 제공해 신뢰도를 높인다.</p>
<hr>
<p>생성형 AI 챗봇의 성능은 결국 데이터의 품질과 양에 따라 결정된다. 하지만 많은 기업들이 이미 갖추고 있는 데이터들이 잘 정리되어 있지 않거나, 상당히 파현화되어 있는 경우가 많다.
-&gt; 그래서 도입 초기에 모든 데이터를 완벽하게 준비하려하지 않고, 명확한 평가가 가능한 특정 시나리오와 관련된 데이터를 우선적으로 준비했다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/fd00ed84-dfbe-4f21-9701-f7bbecdec27a/image.png" alt=""></p>
<p>실제로 범용 AI 모델을 쓰면 정보의 정확도가 80%정도지만, 기업 전용 챗봇을 활용하면 정확도가 95% 이상으로 올라간다.</p>
<h3 id="구글-클라우드-벤더-기반-장점">구글 클라우드 벤더 기반 장점</h3>
<ol>
<li><p>빠르게 서비스를 출시할 수 있다.</p>
</li>
<li><p>비용 관리 측면에서 효율적이다.</p>
</li>
<li><p>데이터 관리와 보안이 쉽고 안정적이다.</p>
</li>
</ol>
<hr>
<p>클라우드 환경의 큰 장점은 <strong>관리형 서비스가 많다</strong>는 점이다. 이는 기업이 최신 기술의 인프라나 서비스 구축과 설정, 관리조차 직접하지 않아도 된다.</p>
<p>CEN AI도 최신 기술 기반의 클라우드 서비스를 이용해 개발한 솔루션으로, 고객사 환경에 적용할 때도 클라우드 환경을 활용하기 때문에, 온프레미스 기반의 솔루션보다 훨씬 빠르고 간편하게 구축할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/4ad6af1e-adde-4458-9924-c0183b0ada18/image.png" alt=""></p>
<hr>
<h3 id="기반-클라우드-서비스를-구글-클라우드로-선택한-이유">기반 클라우드 서비스를 구글 클라우드로 선택한 이유</h3>
<ul>
<li><p>다양한 형태의 데이터와 이를 잘 처리하는 제미나이 모델의 장점</p>
</li>
<li><p>제미나이 모델이 다양한 형태의 데이터를 잘 다룬다.</p>
</li>
</ul>
<hr>
<h3 id="cen-ai-유지보수">CEN AI 유지보수</h3>
<p>데이터 라이프 사이클 관리와 챗봇의 응답 품질 관리를 위해 기업 환경에서 정확한 정보를 제공하는 것이 핵심이기 때문에 사용자 피드백을 적극적으로 수집하고, 로그 분석 및 품질 프레임워크를 도입함. 또 보안 리스크를 위해 데이터 암호화 및 마스킹, 입력이나 출력 단계의 가드레일, 역할 기반 접근 제어 등 보안장치들을 추가해 나가고 있다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/a62d949b-6b76-4cb6-9ca9-24d94741df00/image.png" alt=""></p>
<p>RAG 기술은 기본적으로 공개된 정보만 학습된 일반적인 언어모델을 기업 내부의 데이터까지 확장할 수 있도록 해주는 기술이다. 사용자의 질문을 정확히 분석한 뒤, 관련된 기업 내부 문서를 검색하고, 이를 종합적으로 분석,요약해서 최적의 결과를 제공하는 기술이다.</p>
<hr>
<h3 id="내부-시스템-및-데이터-연동과-기존-챗봇과의-비교">내부 시스템 및 데이터 연동과 기존 챗봇과의 비교</h3>
<p>CEN AI는 기업이 내부에 가지고 있는 데이터를 찾아서 쓸 수 있도록 RAG 기술을 기반으로 만들었고 ERP나 CRM 같은 기존 시스템들이랑도 쉽게 연결할 수 있다.</p>
<p>특히 자체적으로 만든 <strong>도큐먼트 파서</strong> 같은 기술을 이용해서 단순히 키워드만 뽑는 게 아니라 문서의 맥락이나 의도까지 이해하도록 했다. 또 데이터를 업무나 특성에 맞춰 각각의 AI 에이전트로 만들고, 이 에이전트들을 하나의 슈퍼 에이전트가 다시 통합해서 관리하기 때문에, 여러 시스템과 데이터를 자연스럽게 연결 할 수 있다.</p>
<p><strong>비즈니스 관점에서 CEN AI는 일반 생성형 AI 챗봇과 차별화된 노선을 지향한다.</strong>
-&gt; 단순히 정보만 주는 게 아니라, 실제 기업 고객의 업무 상황과 맥락을 이해하고, 업무를 직접 도와주는 역할까지 한다. 회사 내부 시스템과 쉽게 연동해서 업무를 자동화하거나 최적하고 불특정 다수의 사용자를 대상으로 한 일반 생성형 AI 챗봇과 달리 CEN AI는 기업마다 서로 다른 요구에 맞춰 유연하게 쓸수 있어 기업 사용자의 실제 업무 효율을 높인다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/e738aff8-afb8-4dfa-bdd2-0b44166a1976/image.png" alt=""></p>
<hr>
<h3 id="cen-ai-핵심-기능">CEN AI 핵심 기능</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/6d655960-bd0b-4302-96c3-bd29e2fdbba9/image.png" alt=""></p>
<hr>
<h3 id="cen-ai-기술">CEN AI 기술</h3>
<ul>
<li><p>MCP(Model Context Protocol)</p>
</li>
<li><p>A2A(Application-to-Apllication)</p>
</li>
<li><p>ADK(Application Development Kit)</p>
</li>
</ul>
<hr>
<h3 id="cen-ai-플로우">CEN AI 플로우</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/ac64edfc-61fd-4d61-a171-2b384ef62da0/image.png" alt=""></p>
<hr>
<h3 id="느낀점">느낀점</h3>
<p>이번 CEN AI를 조사하면서 단순한 AI 챗봇이 아니라, 기업 환경에서 실제 업무를 처리하기 위한 구조적인 AI 시스템이 필요하다는 점을 알게 되었다. 기존에 내가 생각하던 AI는 질문에 답변을 제공하는 수준이었지만, CEN AI는 여러 AI Agent가 역할을 나누고, 내부 데이터와 외부 데이터를 함께 활용하며, 실제 업무까지 수행하는 구조라는 점에서 큰 차이가 있었다.</p>
<p>특히 MCP, A2A 기술을 통해 AI가 협업하고 데이터를 활용하는 구조가 인상 깊었으며, 이는 내가 진행 중인 PromptGuard 프로젝트에도 적용할 수 있는 방향이라고 느꼈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인증과 인가]]></title>
            <link>https://velog.io/@minjae-yun/%EC%9D%B8%EC%A6%9D%EA%B3%BC-%EC%9D%B8%EA%B0%80</link>
            <guid>https://velog.io/@minjae-yun/%EC%9D%B8%EC%A6%9D%EA%B3%BC-%EC%9D%B8%EA%B0%80</guid>
            <pubDate>Wed, 18 Feb 2026 11:08:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/3b8f957d-be18-45c1-9bf5-6b319949ce72/image.png" alt=""></p>
<p>서비스에서 사용자의 신원을 검증하고 식별된 사용자에 맞춰 서비스를 제공하기 위해서는 별도의 검증 프로세스가 필요하다.</p>
<h3 id="인증">인증</h3>
<blockquote>
<p>&quot;너 누구야?&quot;를 확인하는 과정</p>
</blockquote>
<p>사용자가 누구인지 신원을 확인하는 절차이다.</p>
<ul>
<li>아이디 + 비밀번호 로그인</li>
<li>지문 인식</li>
<li>OTP 인증</li>
<li>구글 로그인</li>
</ul>
<p><strong>과정 흐름</strong></p>
<ol>
<li>사용자가 로그인 요청</li>
<li>서버가 비밀번호/토큰 확인</li>
<li>맞으면 -&gt; 인증 성공</li>
<li>틀리면 -&gt; 인증 실패</li>
</ol>
<pre><code>@PostMapping(&quot;/login&quot;)
public ResponseEntity&lt;?&gt; login(@RequestBody LoginDto dto) {
    authenticationManager.authenticate(
        new UsernamePasswordAuthenticationToken(dto.getUsername(), dto.getPassword())
    );
}
// 인증은 신원 확인이다.</code></pre><h3 id="인가">인가</h3>
<blockquote>
<p>&quot;그래서 이 사람이 이걸 해도 돼?&quot;를 확인하는 과정</p>
</blockquote>
<p>이미 인증된 사용자가 특정 자원에 접근할 권한이 있는지를 확인하는 과정이다.</p>
<ul>
<li>일반 유저는 관리자 페이지 접근 불가</li>
<li>글 작성자는 자기 글만 수정이 가능</li>
<li>관리자만 회원 삭제 가능</li>
</ul>
<pre><code>@PreAuthorize(&quot;hasRole(&#39;ADMIN&#39;)&quot;)
@GetMapping(&quot;/admin&quot;)
public String adminPage() {
    return &quot;관리자 페이지&quot;;
}
// 인가는 권한 확인이다.</code></pre><p>현대적인 온라인 서비스들은 대부분 인증과 인가를 기반으로 운영된다. 또한 대부분의 웹/앱 서비스들은 HTTP 프로토콜을 사용하여 통신한다. HTTP 프로토콜은 비연결성(Connectionless), 무상태(Stateless)의 특징을 지니기 때문에, 최초 로그인을 한 이후 서버에서 클라이언트에 이전 상태를 기억하고 있지 않다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Lazy Loading]]></title>
            <link>https://velog.io/@minjae-yun/Lazy-Loading</link>
            <guid>https://velog.io/@minjae-yun/Lazy-Loading</guid>
            <pubDate>Fri, 06 Feb 2026 02:30:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/9cfd36ee-1e28-452e-be64-b5c1bbae3b76/image.png" alt=""></p>
<h3 id="지연로딩이란">지연로딩이란?</h3>
<p>JPA에서 실제 데이터가 필요한 시점까지 데이터베이스 조회를 지연하는 기법이다.</p>
<p>엔티티를 처음 조회할 때 연관된 데이터를 즉시 로드하지 않고, 그 연관된 데이터가 실제로 사용될 때 데이터베이스에서 조회하는 방식이다.
-&gt; 불필요한 데이터 조회를 줄여서 성능을 최적하하는데 유리하다.</p>
<hr>
<h3 id="지연로딩의-기본동작">지연로딩의 기본동작</h3>
<p>지연 로딩을 설정하면 연관된 엔티티나 컬렉션은 처음에 프록시 객체로 로드된다.</p>
<p>프록시는 실제 엔티티를 대신하는 객체로, 데이터베이스 조회가 필요할 때 프록시가 실제 데이터를 조회하여 값을 제공한다.</p>
<p>따라서 처음부터 연관된 데이터를 모두 로드하는 것이 아니라<span style = "color : red"> 실제 접근 시점에 데이터베이스에서 로드 되도록 지연된다.</span></p>
<blockquote>
</blockquote>
<h3 id="onetomany-지연로딩-설정">@oneToMany 지연로딩 설정</h3>
<pre><code>@Entity
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = &quot;team&quot;, fetch = FetchType.LAZY) // 지연 로딩 설정
    private List&lt;Member&gt; members = new ArrayList&lt;&gt;();

    // getter, setter
}

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = &quot;team_id&quot;)
    private Team team;

    // getter, setter
}</code></pre><p>Team 엔티티의 members 필드에 fetch = FetchType.LAZY 옵션을 설정했다.</p>
<p>Team 엔티티를 조회할 때 members 리스트는 데이터베이스에서 즉시 로드되지 않으며, members 컬렉션에 실제 접근하는 순간 데이터베이스에서 조회가 이루어진다.</p>
<hr>
<h3 id="지연로딩의-장점">지연로딩의 장점</h3>
<ol>
<li><strong>성능 최적화</strong></li>
</ol>
<ul>
<li>SELECT 수 감소</li>
<li>JOIN 감소</li>
<li>네트워크 트래픽 감소</li>
<li>DB CPU 사용량 감소</li>
</ul>
<ol start="2">
<li><strong>쿼리 예측이 쉬워짐</strong></li>
</ol>
<ul>
<li>EAGER는 언제 쿼리가 나가는지 코드만 보고 알기 어렵다.</li>
</ul>
<ol start="3">
<li><strong>큰 연관 관계 관리</strong></li>
</ol>
<ul>
<li>연관된 데이터가 많은 경우, 초기 로딩 시 성능 저하를 방지하고 필요한 시점에 필요한 데이터를 불러온다.</li>
</ul>
<ol start="4">
<li><strong>복잡한 연관관계에서 안전하다.</strong></li>
<li><strong>화면/기능별로 유연한 조회 전략이 가능하다.</strong></li>
</ol>
<ul>
<li>지연로딩을 쓰면 조회 전략을 선택할 수 있음.</li>
</ul>
<ol start="6">
<li><strong>실무 패턴과 잘 맞는다.</strong></li>
</ol>
<hr>
<h3 id="지연로딩의-주의사항">지연로딩의 주의사항</h3>
<p><strong>1. 프록시 초기화</strong></p>
<ul>
<li>지연 로딩된 프록시는 트랜잭션 범위를 벗어난 후에는 초기화할 수 없기 때문에, 연관 데이터를 접근하려면 트랜잭션 내에서 접근해야 한다.</li>
</ul>
<p>*<em>2. N + 1 *</em></p>
<ul>
<li>지연 로딩을 잘못 사용하면 N + 1 문제가 발생한다. 이를 해결하기 위해 fetch join을 쓰거나 DTO를 직접 조회해야한다.</li>
</ul>
<ol start="3">
<li><p>직렬화 문제
→ 지연 로딩된 엔티티를 컨트롤러에서 그대로 반환하면, JSON 직렬화 과정에서 프록시가 초기화되며 예외나 불필요한 쿼리가 발생할 수 있다.</p>
</li>
<li><p>로그·디버깅 문제
→ 엔티티를 로그로 출력하거나 디버깅할 때 toString()이나 getter 호출로 지연 로딩이 실행되어 의도치 않은 DB 조회가 발생할 수 있다.</p>
</li>
<li><p>equals / hashCode 문제
→ equals나 hashCode에서 연관 엔티티에 접근하면 프록시가 초기화되어 성능 저하나 예외가 발생할 수 있으므로 식별자 기준으로만 구현해야 한다.</p>
</li>
<li><p>컬렉션 + 페이징 문제
→ 지연 로딩된 컬렉션을 fetch join과 함께 페이징하면 메모리 페이징이 발생해 심각한 성능 문제가 생길 수 있다.</p>
</li>
<li><p>만능 오해
→ 지연 로딩은 성능 최적화 수단일 뿐 만능이 아니며, 상황에 따라 fetch join이나 DTO 조회와 함께 사용해야 한다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[변경감지(Dirty Checking)]]></title>
            <link>https://velog.io/@minjae-yun/%EB%B3%80%EA%B2%BD%EA%B0%90%EC%A7%80Dirty-Checking</link>
            <guid>https://velog.io/@minjae-yun/%EB%B3%80%EA%B2%BD%EA%B0%90%EC%A7%80Dirty-Checking</guid>
            <pubDate>Fri, 06 Feb 2026 01:51:51 GMT</pubDate>
            <description><![CDATA[<p>dirty checking이란 영속 상태(entityManager가 관리 중인 상태)의 엔티티 값이 바뀌었는지 JPA가 자동으로 감지해서 트랜잭션이 끝날 때(=flush 시점)에 UPDATE SQL을 알아서 날려주는 기능이다.</p>
<p>JPA를 사용하여 데이터를 수정하려면 Entity를 조회하여 조회된 Entity 데이터를 변경만 하면 데이터 베이스에 자동으로 반영이 되도록하는 기능을 바로 Ditry Checking이라고 한다.</p>
<h3 id="dirty-checking의-동작-방법">Dirty Checking의 동작 방법</h3>
<p>JPA는 영속성 컨텍스트를 생성하여 DB와 유사하게 이용하여 Entity의 Life Cycle을 관리한다.</p>
<p>그럼 JPA는 영속성 컨텍스트에 Entity를 보관할 때 최초의 상태를 저장하고 있다. 이것을 스냅샷이라고 하며 영속성 컨텍스트가 Flush되는 시점에 스냅샷과 Entity의 현재 상태를 비교하여 달라진 Entity를 찾는다.</p>
<p>이후 변경된 필드들을 이용하여 쓰기지연 SQL 저장소에 Update 쿼리를 생성하여 쌓아둔다.</p>
<p>모든 작업이 끝나고 트랜잭션을 커밋을 하면 이때 쓰기지연 SQL 저장소에 있는 쿼리들을 DB에 전달항 Update를 진행한다.</p>
<pre><code>@Transactional
public void updateUsername(Long id) {
    Member member = memberRepository.findById(id).get(); // 영속 상태
    member.setUsername(&quot;newName&quot;); // 값 변경
    // save() 없음!
}
</code></pre><blockquote>
<p>이 코드에 반영되는 것은?</p>
</blockquote>
<pre><code>UPDATE member SET username = &#39;newName&#39; WHERE id = ?
</code></pre><p>-&gt; 이게 dirty checking이다.</p>
<hr>
<h3 id="dirty-checking의-동작조건">Dirty Checking의 동작조건</h3>
<ol>
<li>영속 상태여야 한다.</li>
<li>트랜잭션 안에 있어야 한다.</li>
<li>값이 실제로 변경되야 한다.</li>
</ol>
<blockquote>
<p>readOnly = true 라면?</p>
</blockquote>
<ul>
<li>dirty checking 비활성화</li>
<li>조회 전용 메서드에 붙이는게 좋음</li>
</ul>
<p>Dirty Checking은 영속성 컨텍스트가 관리하는 엔티티의 변경 사항을 자동으로 감지하여
트랜잭션 커밋 시점에 UPDATE SQL을 생성하는 JPA의 핵심 기능이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 문제(Java) No.14 <서울에서 김서방 찾기>]]></title>
            <link>https://velog.io/@minjae-yun/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9CJava-No.14-%EC%84%9C%EC%9A%B8%EC%97%90%EC%84%9C-%EA%B9%80%EC%84%9C%EB%B0%A9-%EC%B0%BE%EA%B8%B0</link>
            <guid>https://velog.io/@minjae-yun/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9CJava-No.14-%EC%84%9C%EC%9A%B8%EC%97%90%EC%84%9C-%EA%B9%80%EC%84%9C%EB%B0%A9-%EC%B0%BE%EA%B8%B0</guid>
            <pubDate>Tue, 03 Feb 2026 07:27:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/a2c9bf53-99bc-4c8d-b85b-f6ba7c83acfd/image.png" alt=""></p>
<p>이 문제는 문자열 배열 seoul이 주어졌을 때, 배열 안에 포함된 Kim의 위치를 찾아서 그 값을 이용해 지정된 형식의 문자열을 반환하는 문제이다.</p>
<p>for문을 사용해서 Seoul 배열안에 Kim이라는 문자열과 동일한 이름의 문자열이 있으면 그 인덱스를 찾아 반환하면 된다.</p>
<pre><code>class Solution {
  public String solution(String[] seoul) {
    for (int i = 0; i &lt;= seoul.length; i++){
      if(seoul[i].equals(&quot;Kim&quot;)){
        return &quot;김서방은 &quot; + i + &quot;에 있다&quot;;
      }
    }
    return &quot;&quot;;
  }
}</code></pre><p>String 배열의 seoul에 문자를 넣어주고 solution() 메서드를 호출하고 결과를 result에 저장해 호출한다.</p>
<pre><code>public class Main {
  public static void main(String[] args) {
    Solution sol = new Solution();
    String[] seoul = {&quot;Jane&quot;, &quot;Kim&quot;};
    String result = sol.solution(seoul);
    System.out.println(result);
  }
}</code></pre><p><img src="https://velog.velcdn.com/images/minjae-yun/post/ead55fe7-dd8a-4fa1-99f2-c95446cc9a96/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/e3b9f3fd-928f-4668-a383-14f994798c18/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Hook]]></title>
            <link>https://velog.io/@minjae-yun/React-Hook</link>
            <guid>https://velog.io/@minjae-yun/React-Hook</guid>
            <pubDate>Tue, 03 Feb 2026 07:10:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/7a5da2b9-eab0-41f6-a80c-e00d48734aad/image.png" alt=""></p>
<h3 id="hook">Hook</h3>
<p>Hook을 직역하면 &quot;갈고리&quot;라는 뜻이다. react에서 hook은 hook into에서 따왔다고 한다. hook into는 &quot;~에 연결하다&quot; 혹은 &quot;연동&quot;의 의미를 가지고 있다. 이 Hook을 사용하면 Functional compenentd에 변수를 연결하여 lifecycle을 관리할 수 있다.</p>
<blockquote>
<p>Funtional component?
-&gt; 함수로 작성된 component</p>
</blockquote>
<p>Hook은 Web application을 설계하는데 매우 직접적인 개념이다. 사실 Web에서 javascript 기능 없이 index.html만 간단하게 디자인 적으로 제공한다면 Hook은 필요 없을지도 모릅니다. </p>
<h3 id="react-hook의-규칙">React Hook의 규칙</h3>
<ol>
<li><p>리액트 Hook은 반복문, 조건문 혹은 중첩된 함순 내에서 호출되며 받느시 최상위에서만 Hook을 호출 해야한다. 또한 Hook은 렌더링 시 항상 동일한 순서로 호출 되어야한다.</p>
</li>
<li><p>Hook은 React함수 내에서 호출 해야한다. 즉 리액트 Hook은 함수형 컴포넌트에서 호출해야하고 추가적으로 custom hooks에서 호출이 가능하다.</p>
</li>
</ol>
<hr>
<p>마지막으로, 커스텀 Hook은 여러 Hook을 조합하여 반복되는 로직을 하나의 함수로 분리한 것이다.
커스텀 Hook을 사용하면 코드 중복을 줄이고, 기능 단위로 로직을 재사용할 수 있어 유지보수성이 향상된다.</p>
<p>다음은 UseState와 use Effect에 대해 알아보겠다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vite 프로젝트 생성하기]]></title>
            <link>https://velog.io/@minjae-yun/Vite-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@minjae-yun/Vite-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 28 Jan 2026 05:21:47 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/db99844b-7c0a-4963-8f01-6319972aaa2b/image.png" alt=""></p>
<p>vite(프랑스어로 &#39;빠르다&#39;)는 Vue.js의 창시자 Evan You가 개발한 차세대 프론트엔드 빌드 도구로 네이티브 ESM을 기반으로 한 즉가적인 서버 구동과 빠른 HMR(페이지를 새로고침하지 않고 수정된 모듈만 즉시 교체하여 반영하는 기능)을 제공한다.</p>
<p>Vite 프로젝트를 실행하기 위해서는 node.js가 깔려있어야합니다.</p>
<p><a href="https://velog.io/@minjae-yun/%EC%9C%88%EB%8F%84%EC%9A%B0-NVM-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0">node.js 설치</a></p>
<p>그럼 이제 본격적으로 vite 프로젝트를 생성해보겠다. 일단 먼저 cmd창을 관리자 권한으로 실행해주세요. 저는 D드라이브에 폴더 생성하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/cc3a15ed-5115-4a0b-bfb3-2c47269ed743/image.png" alt=""></p>
<blockquote>
<p>npm create vite@latest</p>
</blockquote>
<p>위 명령어를 입력해서 폴더를 생성해주세요. 그리고 Project name을 입력해주세요.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/4349a60f-5ccb-4b05-a15f-b1969e0a855a/image.png" alt=""></p>
<p>저는 framework를 React를 사용할 것이기 때문에 React 골라주도록 하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/3921aa20-f952-44fb-9797-91d6ec8f254b/image.png" alt=""></p>
<p>언어 방식 선택에서 Javascript를 골랐고 rolldonw-vite가 뜨는데 No 선택해주시면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/772cf5e3-de80-4b19-b20c-7fb9593976ff/image.png" alt=""></p>
<p>npm 설치해주시면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/004b8156-ca52-4974-8cfd-ddad159e2413/image.png" alt=""></p>
<p>터미널 창에 주소를 복사해서 url 주소에 넣고 다음과 같은 화면이 나오면 성공입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[윈도우 NVM 설치하기]]></title>
            <link>https://velog.io/@minjae-yun/%EC%9C%88%EB%8F%84%EC%9A%B0-NVM-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@minjae-yun/%EC%9C%88%EB%8F%84%EC%9A%B0-NVM-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 28 Jan 2026 02:50:43 GMT</pubDate>
            <description><![CDATA[<p>오늘은 NVM을 설치하고 Node 24.x 버전을 설치해보겠다.</p>
<p>먼저 NVM을 설치해보자. 인터넷에 NVM 설치를 검색하면 NVM 설치 공식 사이트가 있습니다. 거기서 <strong>nvm-setup.exe</strong> 클릭해서 다운받아주세요.</p>
<p><a href="https://github.com/coreybutler/nvm-windows/releases">NVM 설치 공식사이트</a></p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/1d118a19-d6da-43b1-9b0f-ef8383617287/image.png" alt=""></p>
<p>이 화면이 나오면 설치해주시면 됩니다.</p>
<p>설치가 완료되면 cmd창을 열어서 nvm이라고 검색해주세요. 
⚠️ cmd는 관리자 권한으로 실행 눌러주세요</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/e7aea414-147f-47a0-b1bf-98c0c90d52c8/image.png" alt=""></p>
<p>nvm 이라고 검색을 하면 nvm 설치 확인 여부와 버전을 알 수 있습니다.</p>
<hr>
<h3 id="node-24-설치">Node 24 설치</h3>
<blockquote>
<p>nvm install 24</p>
</blockquote>
<p>cmd 창에 위 명령어를 입력해주세요.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/93efd403-7b8d-4c02-9f26-bd438656c607/image.png" alt=""></p>
<p>이렇게 뜨면 24 버전이 설치가 완료되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 문제(Java) No.13 <콜라츠 추측>]]></title>
            <link>https://velog.io/@minjae-yun/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9CJava-No.13-%EC%BD%9C%EB%9D%BC%EC%B8%A0-%EC%B6%94%EC%B8%A1</link>
            <guid>https://velog.io/@minjae-yun/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9CJava-No.13-%EC%BD%9C%EB%9D%BC%EC%B8%A0-%EC%B6%94%EC%B8%A1</guid>
            <pubDate>Tue, 27 Jan 2026 05:19:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/7c277db2-5973-48f1-80b4-0423a3a5edde/image.png" alt=""></p>
<p>이 문제는 주어진 수가 짝수이면 2로 나누고, 홀수이면 3을 곱한 뒤 1을 더하는 과정을 반복하면서 해당 연산이 1이 될 때까지 몇 번 수행되는지를 계산하는 문제이다.</p>
<p>주어진 수가 1인 경우에 0, 수행된 횟수가 500번을 넘길 때까지 1이 되지 않으면 -1을 반환하여야합니다.</p>
<hr>
<pre><code>class Solution {
  public int solution(long num) {
    int count = 0;
    if (num == 1) return 0;

    while(true){
      if (num % 2 ==0){
        num /= 2;
        count += 1;
      }
      else {
        num = num *3 + 1;
        count += 1;
      }
      if (num == 1){
        break;
      }
    }
    if(count &gt; 500){
      return -1;
    }
    else{
      return count;
    }
  }
}</code></pre><p>while(true)를 사용해서 무한 루프를 굴려주고 정수가 1이 되면 break로 반복문을 빠져나오게 만들면 되는 간단한 문제입니다.</p>
<pre><code>public class Main{
  public static void main(String[] args) {
    Solution sol = new Solution();
    int result = sol.solution(626331);
    System.out.println(result);
  }
}</code></pre><p><img src="https://velog.velcdn.com/images/minjae-yun/post/cefad32b-48dc-435e-b369-131c76fcc7bb/image.png" alt=""></p>
<p>626331은 카운트 횟숫가 500이 넘어가기 때문에 -1을 반환하는 것을 볼 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/7deceee0-fa86-47c3-bb37-9ee101bcefce/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Entity의 정의와 JPA의 작동방식]]></title>
            <link>https://velog.io/@minjae-yun/Entity%EC%9D%98-%EC%A0%95%EC%9D%98%EC%99%80-JPA%EC%9D%98-%EC%9E%91%EB%8F%99%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@minjae-yun/Entity%EC%9D%98-%EC%A0%95%EC%9D%98%EC%99%80-JPA%EC%9D%98-%EC%9E%91%EB%8F%99%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Sun, 25 Jan 2026 09:49:34 GMT</pubDate>
            <description><![CDATA[<p>엔티티는 이전에도 용어를 정리한 적이 있지만 간단하게 정의만 정리해보자.</p>
<p>자바 애플리케이션에서 데이터베이스와 상호작용할 때 Entity는 중요한 역할을 합니다.
JPA에서 Entity는 데이터베이스의 테이블과 자바 클래스 간의 매핑을 정의하며, 데이터베이스 작업을 객체지향적으로 관리할 수 있게 도와줍니다.</p>
<hr>
<h3 id="entitymangerfactory">EntityMangerFactory</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/ea559df3-aa44-4b07-8c6c-b2f572f07713/image.png" alt=""></p>
<p>클래스 자체나 생성한 인스턴스도 엔티티라고 부릅니다. 엔티티 매니저 팩토리는 엔티티 매니저 인스턴스를 관리하는 주체이다 애플리케이션 실행 시 한 개만 만들어지며 사용자로부터 요청이 오면 엔티티 매니저 팩토리부터 엔티티 매니저를 생성합니다.</p>
<hr>
<h3 id="entitymanager">EntityManager</h3>
<p>엔티티 매니저란 영속성 컨텍스트에 접근하여 엔티티에 대한 테이터 베이스 작업을 제공합니다. </p>
<ol>
<li><p>find() : 영속성 컨텍스트에서 엔티티를 검색하고 영속성 컨텍스트에 없을 경우 데이터베이스에서 데이터를 찾아 영속성 컨텍스트에 저장한다.</p>
</li>
<li><p>persit() : 영속성 컨텍스트를 저장한다.</p>
</li>
<li><p>remove() : 엔티티 클래스를 영속성 컨텍스트에서 삭제한다.</p>
</li>
<li><p>flush() : 영속성 컨텍스트에 저장된 내용을 데이터베이스에 반영한다.</p>
</li>
</ol>
<blockquote>
<p>영속성 컨텍스트와 사용시 이점에 대해서는 전 블로그를 참고해주세요
<a href="https://velog.io/@minjae-yun/Entity">엔티티 설명</a></p>
</blockquote>
<hr>
<p><a href="https://velog.io/@minjae-yun/ORM%EA%B3%BC-JPA">ORM과 JPA에 대한 설명</a></p>
<h3 id="jpa의-작동방식">JPA의 작동방식</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/aad08c4e-8593-4930-b610-5f552e8a5664/image.png" alt=""></p>
<p>JPA는 자바 객체(Entity)와 데이터베이스 테이블을 매핑하여, SQL을 직접 작성하지 않고도 DB를 다룰 수 있게 해주는 ORM 표준이다.</p>
<p>Controller -&gt; Service -&gt; Repository(JPA) -&gt; EntityManager-&gt; 영속성 컨텍스트 -&gt; DB</p>
<hr>
<p><strong>Entity</strong></p>
<p>DB 테이블과 매핑되는 자바 객체</p>
<blockquote>
<pre><code>@Entity
public class Member {
  @Id
  private Long id;
}</code></pre></blockquote>
<pre><code>
**EntityManager**

JPA의 실질적인 관리자
CRUD 담당

&gt; ```
entityManager.persist(entity);
entityManager.find(Member.class, id);</code></pre><p><strong>영속성 컨텍스트</strong></p>
<blockquote>
<p>Entity를 관리하는 1차 캐시 공간</p>
</blockquote>
<hr>
<h3 id="jap-동작-단계별-설명">JAP 동작 단계별 설명</h3>
<p><strong>1. 엔티티 생성 (비영속 상태)</strong></p>
<pre><code> Member member = new Member(&quot;kim&quot;);</code></pre><ul>
<li>자바 객체</li>
<li>DB랑 관계 X</li>
</ul>
<p><strong>2. 영속 상태 전환</strong></p>
<pre><code>entityManager.persist(member);</code></pre><ul>
<li>객체가 영속성 컨텍스트의 1차 캐시에 저장됨</li>
<li>엔티티 상태가 비영속 -&gt; 영속(managed)로 변경</li>
</ul>
<p><strong>3. 쓰기 지연(SQL 저장 단계)</strong></p>
<ul>
<li>persist() 호출과 동시에 JPA는 해당 엔티티에 대한 INSER SQL을 생성하지만 즉시 DB에 실행하지 않고 쓰기지연 SQL 저장소에 보관된다.</li>
</ul>
<p>-&gt; DB에 반영 X</p>
<p><strong>4. 트랜잭션 커밋 또는 flush</strong></p>
<pre><code>@Transactional</code></pre><p>또는</p>
<pre><code>entityManager.flush();</code></pre><ul>
<li>트랜잭션이 끝나는 순간 영속성 컨텍스트에 쌓여있던 SQL 실행.</li>
</ul>
<blockquote>
<span style = "color : green">
flush는 쓰기 지연 SQL 저장소에 있는 SQL들을 DB로 전송하고 DB와 영속성 컨텍스 상태를 동기화함 </span>
-> rollback이 가능해짐
</blockquote>
<p><strong>5. 조회 동작(1차 캐시)</strong></p>
<pre><code>entityManager.find(Member.class, id);
</code></pre><ul>
<li>먼저 영속성 컨텍스트(1차 캐시) 확인</li>
<li>있으면 DB 조회 X</li>
<li>없으면 DB 조회 O</li>
</ul>
<p><strong>5. 변경 감지(Dirty Checking)</strong></p>
<pre><code>member.setName(&quot;park&quot;);</code></pre><ul>
<li>트랜잭션 커밋 시점에 JPA가 변경 여부 자동 감지</li>
<li>UPDATE 쿼리 자동 생성</li>
</ul>
<p><strong>6. 삭제</strong></p>
<pre><code>entityManager.remove(member);</code></pre><ul>
<li>트랜잭션 종료 시 DELETE 실행</li>
</ul>
<hr>
<h3 id="repository">Repository</h3>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt;
</code></pre><p>내부적으로 EntityManager를 사용하고 데이터 접근 로직을 담당한다.</p>
<p>JpaRepository는 Spring Data JPA에서 제공하는 인터페이스로, EntityManager를 직접 사용하지 않고 CRUD 기능을 수해할 수 있게 해준다. 그 중에서 JpaRepository에 기본 메서드들이 내장되어 있어 Spring Data JPA가 런타임에 구현체를 자동 생성하여 데이터베이스 접근을 처리한다. </p>
<p><strong>findAll()</strong></p>
<pre><code>List&lt;Member&gt; members = memberRepository.findAll();</code></pre><ul>
<li>해당 Entity 테이블의 모든 행 조회
== SELECT * FROM member;</li>
</ul>
<p><strong>FindById()</strong></p>
<pre><code>Optional&lt;Member&gt; member = memberRepository.findById(1L);</code></pre><ul>
<li>PK 기준 조회</li>
<li>1차 캐시를 먼저확인 후 없으면 DB 조회</li>
</ul>
<p>그 외에도 save(), count(), delete()등의 메서드가 내장되어있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 문제(Java) No.12 <두 정수의 합>]]></title>
            <link>https://velog.io/@minjae-yun/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9CJava-No.12-%EB%91%90-%EC%A0%95%EC%88%98%EC%9D%98-%ED%95%A9</link>
            <guid>https://velog.io/@minjae-yun/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9CJava-No.12-%EB%91%90-%EC%A0%95%EC%88%98%EC%9D%98-%ED%95%A9</guid>
            <pubDate>Sun, 25 Jan 2026 09:01:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/e7472496-5928-40d1-904f-dc4077086fdb/image.png" alt=""></p>
<p>이 문제는 두 정수 a, b가 주어지면 a와 b 사이에 있는 모든 정수의 합을 구하는 문제이다.</p>
<p>간단한 문제이지만 a가 b보다 무조건 크다고 생각하고 풀었다가 틀렸음;;</p>
<p>따라서 a와 b의 대소관계가 정해져 있지 않기 때문에 if를 사용하고 사이에 있는 정수의 합을 구해야하기 때문에 for문을 사용해야한다.</p>
<p>a &lt; b이면 a부터 b까지 1씩 증가하며 더해야 하고 a &gt;=b 이면 a부터 b까지 1씩 감소하며 더해야한다.</p>
<pre><code>class Solution {
  public long solution(int a, int b) {
    long sum = 0;

    if(a &lt; b){
      for (int i = a; i &lt;= b; i++){
        sum += i;
      }
    }
    else{
      for (int i = a; i &gt;= b; i--){
        sum += i;
      }
    }

    return sum;
  }
}</code></pre><p>a == b가 같은 경우는 else에 들어간다. solution 메서드에서 두개의 정수 a와 b를 매개벼누로 받도록 정의되어 있으므로, main 메서드에서 해당메서드에 두 개인자를 전달해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/dbb79b11-ad69-4d37-ac83-e25753334863/image.png" alt=""></p>
<p>3 ~ 5까지 3 + 4 + 5 = 12가 나오는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/cf8727dd-9794-4db1-8e45-d86446065f3c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 문제(Java) No.11<하샤드 수>]]></title>
            <link>https://velog.io/@minjae-yun/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9CJava-No.11%ED%95%98%EC%83%A4%EB%93%9C-%EC%88%98</link>
            <guid>https://velog.io/@minjae-yun/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9CJava-No.11%ED%95%98%EC%83%A4%EB%93%9C-%EC%88%98</guid>
            <pubDate>Sun, 25 Jan 2026 08:49:14 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/3192cc98-82cd-46db-9027-c71db837bd52/image.png" alt=""></p>
<p>이 문제는 어떤 정수 x가 하샤드 수가 되려면 x가 자기 자릿수의 합으로 나누어 떨어져야 합니다.</p>
<p>정수 x를 받아서 하샤드 수면 true 아니면 false를 반환하게 하는 문제입니다. </p>
<p>자릿수의 합을 구하기 위해 x % 10 -&gt; 마지막 자릿수를 추출하고  x / 10 한 값을 더하여 answer 변수에 넣고 x는 원래 숫자를 보존하기 위해 sum 변수에 넣어두고 마지막에 sum % answer == 0이 되면 true 아니면 false를 반환하게 한다.</p>
<hr>
<pre><code>class Solution {
  public boolean solution(int x) {
    int answer = 0;
    int sum = x;
    while (x &gt; 0){
      answer += x%10;
      x /= 10;
    }

    if (sum % answer == 0){
      return true;
    }
    return false;
  }
}</code></pre><p>main 함수도 Solution 클래스 객체를 생성하고 solution 메서드가 boolean 반환타입으로 명시되어 있으므로 main 함수에서 참/거짓을 판단하는 boolean을 써야합니다. 
<span style = "color : red">-&gt; int로 쓰면 오류</span></p>
<pre><code>public class Main{
  public static void main(String[] args) {
    Solution sol = new Solution();
    boolean result = sol.solution(11);
    System.out.println(result);
  }
}</code></pre><p><img src="https://velog.velcdn.com/images/minjae-yun/post/98db5fb3-bc52-4ba2-8c67-367bb0305da8/image.png" alt=""></p>
<p>11 -&gt; 1 + 1 =2이다. 11 / 2을 하면 0이 아니므로 하샤드 수가 아님 -&gt; false 반환</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/e0fa3728-683a-4aa8-bbda-7fef617f0f57/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 문제(Java) No.10 <정수 내림차순으로 배치하기>]]></title>
            <link>https://velog.io/@minjae-yun/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9CJava-No.10-%EC%A0%95%EC%88%98-%EB%82%B4%EB%A6%BC%EC%B0%A8%EC%88%9C%EC%9C%BC%EB%A1%9C-%EB%B0%B0%EC%B9%98%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@minjae-yun/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9CJava-No.10-%EC%A0%95%EC%88%98-%EB%82%B4%EB%A6%BC%EC%B0%A8%EC%88%9C%EC%9C%BC%EB%A1%9C-%EB%B0%B0%EC%B9%98%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 23 Jan 2026 08:46:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/98180049-c3a1-4cc9-af8d-7a7c407d260b/image.png" alt=""></p>
<p>이 문제는 정수 n을 매개 변수로 입력받으면 각 자릿수를 큰 수부터 작은 순으로 정렬하여 값을 반환하는 문제이다.</p>
<hr>
<p>이코드는 정수 -&gt; 문자 -&gt; 배열 -&gt; 정렬 -&gt; 다시 정수로 풀어야한다.</p>
<p><strong>1. 정수를 문자열로 반환</strong></p>
<ul>
<li>정수 상태에서 자릿수로 바로 접근하기 위해 사용</li>
<li>문자열로 바꾸면 한글자씩 처리가 가능</li>
</ul>
<p><strong>2. 자릿수를 배열로 저장</strong></p>
<ul>
<li>Collections.reverseOdrder()가 내림차순을 쓸 때 사용하는 것인데 Integer 타입에서만 사용이 가능함</li>
</ul>
<p><strong>3. 문자열를 숫자로 반환</strong>
s.charAt(i) -&gt; 문자 &#39;1&#39;, &#39;8&#39;에서 -&#39;0&#39;을 하면 ASCII 코드로 숫자로 반환됨</p>
<p><strong>4.내림차순 정렬</strong>
**
5. 숫자를 문자열로 다시 붙이기**</p>
<ul>
<li>기존 값에 10을 곱해 자리를 왼쪽으로 밀고 현재 자릿수를 더해 새로운 정수를 만든다.</li>
</ul>
<hr>
<pre><code>import java.util.Arrays;
import java.util.*;
import java.util.Collections;

class Solution {
  public long solution(long n) {
    String s = String.valueOf(n);
    Integer[] arr = new Integer[s.length()];

    for (int i = 0; i &lt; s.length(); i++) {
      arr[i] = s.charAt(i) - &#39;0&#39;;
    }

    Arrays.sort(arr, Collections.reverseOrder());

    long answer = 0;
    for (int j = 0; j &lt; arr.length; j++) {
      answer = answer * 10 + arr[j];
    }

    return answer;
  }
}</code></pre><p>정수를 문자열로 변환해서 배열에 저장한 뒤 내림차순으로 정렬하고 곱셈 연산을 이용해서 새로운 정수를 직접 재구성하면 된다.</p>
<pre><code>public class Main {
  public static void main(String[] args) {
    Solution sol = new Solution();
    long n = 118372;
    long result = sol.solution(n);
    System.out.println(result);
  }
}</code></pre><p><img src="https://velog.velcdn.com/images/minjae-yun/post/ae5c4c39-a35c-4611-b2a6-ebd9ebf7c90d/image.png" alt=""></p>
<h3 id="다른-사람-풀이">다른 사람 풀이</h3>
<pre><code>import java.util.*;

class Solution {
  public long solution(long n) {
    String s = String.valueOf(n);
    Integer[] arr = new Integer[s.length()];

    for (int i = 0; i &lt; s.length(); i++) {
      arr[i] = s.charAt(i) - &#39;0&#39;;
    }
    Arrays.sort(arr, Collections.reverseOrder());


    StringBuilder sb = new StringBuilder();
    for (int j = 0; j &lt; arr.length; j++) {
      sb.append(arr[j]);
    }

    return Long.parseLong(sb.toString());
  }
}</code></pre><p>StringBuilder는 문자열을 계속 이어 붙이기 위한 객체로 쓰이고 내부적으로 가변 문자열 구조를 가진다.</p>
<p>예) sb.append(8), sb.append(7) <del>~</del></p>
<p>-&gt; &quot;873211&quot;</p>
<p>그리고 Long.parseLong(sb.toString());으로 문자열 형태를 정수형 Long으로 변환시켜 반환한다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/3cde8f56-88ad-4275-9031-e3a43bd919f8/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[메모앱의 간단 검색 기능 추가 실습]]></title>
            <link>https://velog.io/@minjae-yun/%EB%A9%94%EB%AA%A8%EC%95%B1%EC%9D%98-%EA%B0%84%EB%8B%A8-%EA%B2%80%EC%83%89-%EA%B8%B0%EB%8A%A5-%EC%B6%94%EA%B0%80-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@minjae-yun/%EB%A9%94%EB%AA%A8%EC%95%B1%EC%9D%98-%EA%B0%84%EB%8B%A8-%EA%B2%80%EC%83%89-%EA%B8%B0%EB%8A%A5-%EC%B6%94%EA%B0%80-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Thu, 22 Jan 2026 07:05:47 GMT</pubDate>
            <description><![CDATA[<h3 id="초기-구현-상태">초기 구현 상태</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/973bb355-865c-44ba-bd9d-87e5cd8e72d6/image.png" alt=""></p>
<p>초기 구현 단계에서는 GET/api/memos 요청을 통해 전체 메모 목록을 조회하도록 구현되어 있었다.</p>
<p>초기 구현 단계에서 Memo 엔터티는 메모 테이터 구조를 정의하고 @Entity 어노테이션을 통해 DB테이블과 매핑되어 있었고 timestamped의 상속을 받아 생성/수정시간 등이 기록되어 있었다. </p>
<p>본 프로젝트는 메모 앱에서 간단 검색 기능을 추가하는 것이고 정렬이나 로직은 엔터티의 역할이 아니므로 Service Repository에서 처리해야한다.</p>
<p>MemoRequstDto는 클라이언트로부터 전달받는 데이터만을 담는 객체로 사용되었고 사용자의 입력값(usernmae,contents)를 엔티티로 전달하기 전 중간 매게체 역할을 하고 있었고 MemoResponseDto는 서버가 클라이언트에게 반환하는 데이터를 담는 객체로 메모 조회시 클라이언트에게 전달할 데이터를 정희하고 엔티티의 필요한 정보만 선택적으로 노출하는 역할을 하고 있었다.</p>
<p>이를 통해 엔티티를 외부에 직접 노출하지 않고, 요청과 응답에 필요한 데이터만을 전달하는 구조를 구성하여 유지보수성과 확장성을 확보하는 기반이 되었다.</p>
<hr>
<p><strong>메모 조회 동작 방식</strong></p>
<ul>
<li>Controller에서 getMemos()메서드를 통해 서비스 호출하였습니다.</li>
<li>Service에서 Repository의 메서드인 findAll()을 사용하였습니다.</li>
<li>데이터베이스에 저장된 메모를 정렬 조건 없이 그대로 조회</li>
</ul>
<hr>
<p>그럼 지금부터 검색기능을 구현해보자</p>
<h3 id="memocontroller">MemoController</h3>
<pre><code>package com.meta.memo.controller;
import com.meta.memo.dto.MemoRequestDto;
import com.meta.memo.dto.MemoResponseDto;
import com.meta.memo.service.MemoService;
import org.springframework.web.bind.annotation.*;
import java.util.List;


@RestController
@RequestMapping(&quot;api/memos&quot;)

public class MemoController {
    // 멤버 변수 선언
    private final MemoService memoService;

    //생성자 주입(DI)
    public MemoController(MemoService memoService){
        this.memoService =  memoService;
    }

    @PostMapping()
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto memoRequestDto) {
        return memoService.createMemo(memoRequestDto);
    }

    @GetMapping()
    public List&lt;MemoResponseDto&gt; getMemos(){
        return memoService.getMemos();
    }

    @PutMapping(&quot;{id}&quot;)
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto memoRequestDto) {
        return memoService.updateMemo(id, memoRequestDto);
    }

    @DeleteMapping(&quot;{id}&quot;)
    public Long deleteMemo(@PathVariable Long id){
        return memoService.deleteMemo(id);
    }
}
</code></pre><p>MemoController는 현재 메모 생성, 조회, 수정, 삭제에 대한 Rest API를 제공하고 있고 클라이언트의 요청을 받아 DTO 형태로 전달받아 서비스 계층에 처리결과를 JSON 형태로 반환한다. 여기서 특정 키워드가 포함된 메모를 조회하는 API를 구현해야한다.</p>
<p>API 구현 조건</p>
<blockquote>
<p><a href="http://localhost:8080/api/memos/contents?keyword=">http://localhost:8080/api/memos/contents?keyword=</a></p>
</blockquote>
<ul>
<li>메서드명은 getMemosByKeyword</li>
</ul>
<pre><code>@GetMapping()
    public List&lt;MemoResponseDto&gt; getMemosByKeyword(@RequestParam String keyword){
        return memoService.getMemosByKeyword(keyword);
    }</code></pre><p>해당 메서드는 메모 내용(/contents)에 특정 키워드가 포함된 메모 목록을 조회하는 기능을 수행한다. 클라이언트는 Query String 방식으로 검색할 키워드를 전달하고 JSON형태의 메모 목록으로 반환된다.</p>
<p><strong>@RequestParam</strong>
-URL에 포함된 keyword 값을 파라미터로 바인딩하고 별도의 RequestBody 없이 조회 목적의 요청을 한다.</p>
<p>return memoService로 검색 조건 keyword만 서비스 계층으로 전달된다 참고로 memo.java에 @Getter가 있기 때문에 get~가 사용이 가능함</p>
<hr>
<h3 id="memoservice">MemoService</h3>
<p>컨트롤러 계층에서는 위와 같은 키워드 기반 조회 API를 추가하였지만 해당 API를 호출했을 때 서비스 계층에 실제 실행될 메서드가 존재하지 않고 구현이 되어있지 않아 정상적으로 응답을 반환할 수 없다.</p>
<p>그래서 서비스 계층에 getMemosByKeyword를 구현하였다.</p>
<pre><code>  public List&lt;MemoResponseDto&gt; getMemosByKeyword(String keyword){
    List&lt;MemoResponseDto&gt; memoResponseDtoList = memoRepository.findAll().stream()
        .map(MemoResponseDto::new)
        .toList();
    return memoResponseDtoList;
  }</code></pre><p>**
해당 메서드의 역할**</p>
<ul>
<li>MemoRepository.findAll()을 통해 전체 메모 데이터를 조회
<span style = "color : red">-&gt; 참고로 findAll()은 JpaRepository를 MemoRepository에서 상속받고 있기 때문에 사용이 가능하다.</span></li>
<li>각 엔터티를 MemoResponseDto로 매핑</li>
<li>조회된 Memo엔터티를 스트림으로 변환</li>
<li>변환된 결과를 리스트로 반환</li>
</ul>
<hr>
<h3 id="memorepository">MemoRepository</h3>
<p>서비스 계층에서는 위와 같은 방식으로 findAll()로 임시 구현을 하였는데 이 방식은 문제가 생긴다. 일단 키워드 검색 조건이 데이터베이스에 반영되지 않고 메서드 이름은 검색이지만 실제로 작동은 되지 않는다. </p>
<p>그래서 검색과 정렬을 데이터베이스에서 처리하도록 MemoRepository에 쿼리 메서드를 추가하였다.</p>
<pre><code>public interface MemoRepository extends JpaRepository&lt;Memo, Long&gt; {
    List&lt;Memo&gt; findAllByContentsContainingOrderByModifiedAtDesc(String keyword);
}
</code></pre><p><strong>메서드 구성과 의미</strong></p>
<ul>
<li><p>findAllBy : 조건에 맞는 모든 데이터를 조회한다.</p>
</li>
<li><p>contents : Memo 엔터티의 contents 내용</p>
</li>
<li><p>Containing : Keyword가 포함된 데이터</p>
</li>
<li><p>OrderByModifedAtDesc : 수정 시간 기준 내림차순 정렬</p>
</li>
</ul>
<p>새로운 쿼리 메서드를 정의 했기 때문에 서비스 계층에 있는 List도 다시 작성해야한다.</p>
<pre><code>public List&lt;MemoResponseDto&gt; getMemosByKeyword(String keyword){
    List&lt;MemoResponseDto&gt; memoResponseDtoList = memoRepository.&lt;span style = &quot;color : red&quot;&gt;findAllByContentsContainingOrderByModifiedAtDesc(keyword)&lt;/span&gt;
        .stream()
        .map(MemoResponseDto::new)
        .toList();
    return memoResponseDtoList;
  }</code></pre><p>findAll()을 바꿔주어야한다.  <span style = "color : red">findAllByContentsContainingOrderByModifiedAtDesc(keyword)</span></p>
<p>하지만 실제로 코드를 반영해보면 웹에서는 내림차순 정렬이 되지 않는다. 검색 기능(getMemosByKeyword)은 수정 시간 기준 내림차순 정렬이 포함된 Query Method를 적용했으나, 실제 웹 화면에서는 전체 메모 목록을 조회하는 /api/memos API만 호출하고 있었기 때문에 검색 API의 정렬 결과가 화면에 반영되지 않았다. 이후 전체 조회(getMemos)에도 수정 시간 기준 내림차순 Query Method를 별도로 적용하면서 웹 화면에 최신순 정렬이 정상적으로 반영되었다.</p>
<pre><code>public interface MemoRepository extends JpaRepository&lt;Memo, Long&gt; {
    List&lt;Memo&gt; findAllByContentsContainingOrderByModifiedAtDesc(String keyword);
    List&lt;Memo&gt; findAllByOrderByModifiedAtDesc();
}</code></pre><p>MemoRepository에 새로운 메서드를 추가했으니 서비스 계층에서 getMemos 메서드에도 쿼리 메서드를 적용해야한다.</p>
<pre><code>  public List&lt;MemoResponseDto&gt; getMemos() {
    List&lt;MemoResponseDto&gt; memoResponseDtoList = memoRepository.findAllByOrderByModifiedAtDesc().stream()
        .map(MemoResponseDto::new).toList();
    return memoResponseDtoList;
  }</code></pre><p><img src="https://velog.velcdn.com/images/minjae-yun/post/6daa9edc-07ce-4f73-aa51-78795209efbd/image.png" alt=""></p>
<p>코드를 실행하면 코드가 내림차순으로 정렬되는 것을 볼 수 있다. Postman에서도 실행해보자.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/7cb24f18-7895-4512-aad9-a3d0beba696e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/bd8d02ff-8fbb-48ab-8d42-33822130e5b6/image.png" alt=""></p>
<p>Postman과 구글 크롭 url에 조건에 있는 해당 url을 넣었을 때 Json 텍스트를 반환하고 200 OK가 뜨는 것을 볼 수 있다.</p>
<hr>
<h3 id="html-반영">HTML 반영</h3>
<p>html은 시간 부족으로  기존에서 크게 건들지는 못했다.</p>
<p><strong>1. Header 영역 구성</strong></p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/d44dd3a9-cbb2-4009-ad31-47f53c3c9f0d/image.png" alt=""></p>
<p>Memo 페이지의 메인 타이틀 header부분에 변화를 주었다. 먼저 좌우에 이미지를 배치하여 무슨 내용을 메모에 기록하면 되는지 주제를 주었고 이미지는 position : abolute를 기반으로 간단히 배치하였다. </p>
<p><strong>2. 검색 영역 구성</strong></p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/85376cd2-ab10-47e7-9cb3-a25c911f89c3/image.png" alt=""></p>
<p>사용자가 특정 키워드를 입력해 메모를 검색할 수 있도록 검색 영역을 추가하였다. 검색 입력창과 버튼을 분리하여 기능별 역할이 수행하도록 구생하였다. 검색 버튼은 입력한 키워드를 기반으로 서버에 검색 요청을 보내고, 전체보기는 기존 전체 메모 목록을 다시 불러오는 역할을 한다.</p>
<pre><code>// 메모 검색
      function searchMemos() {
          const keyword = $(&#39;#keyword&#39;).val().trim();

          if (keyword === &#39;&#39;) {
              alert(&#39;검색어를 입력해주세요&#39;);
              return;
          }

          // 1) 기존 목록 비우기 (검색 결과로 덮어쓰기)
          $(&#39;#cards-box&#39;).empty();

          // 2) 검색 API 호출
          $.ajax({
              type: &#39;GET&#39;,
              url: `/api/memos/contents?keyword=${encodeURIComponent(keyword)}`,
              success: function (response) {
                  // 3) 검색 결과 렌더링 (기존 addHTML 재사용)
                  for (let i = 0; i &lt; response.length; i++) {
                      const message = response[i];
                      addHTML(message[&#39;id&#39;], message[&#39;username&#39;], message[&#39;contents&#39;], message[&#39;modifiedAt&#39;]);
                  }

                  // 검색 결과 없을 때(선택)
                  if (response.length === 0) {
                      $(&#39;#cards-box&#39;).append(`&lt;div style=&quot;padding:15px; color:#666;&quot;&gt;검색 결과가 없습니다.&lt;/div&gt;`);
                  }
              },
              error: function () {
                  alert(&#39;검색 요청 실패: 서버 실행/URL을 확인하세요&#39;);
              }
          });
      }</code></pre><p>검색 버튼을 클릭할 경우 seachMemos()함수가 실행되고 해당 함수는 Ajax를 통해 GET /api/memos/contents?keyword=? 형태로 API에 요청을 보내고 서버는 contents에 키워드가 포함된 메모를 조회하여 JSON 형태로 응답한다. 응답으로 받은 결과는 기존 메모 목록을 비우고 검색결과를 화면에 랜더링한다.</p>
<p>새로고침은 getMessages() 함수를 재사용하여 검색결과에서 전체 메모 목록으로 돌아갈 수 있도록하게 한다.</p>
<p><strong>3. 올라가는 버튼 구현</strong>
<img src="https://velog.velcdn.com/images/minjae-yun/post/c6f91f88-f71c-4e65-bde3-70e5eef43764/image.png" alt=""></p>
<p>마지막으로 간단하게 전에 썼던 누르면 위로 올라가는 버튼 구현</p>
<hr>
<h3 id="회고록">회고록</h3>
<ul>
<li><p>MemoController에서 처음에 getMemosByKeyword 함수 기능을 넣으라고 했을 때 getMemos 전체 조회를 없애고 기능을 넣었다가 웹사이트가 안열려서 코드를 복구하고 새로 특정 키워드가 포함된 APi 메모함수를 만들었음</p>
</li>
<li><p>이후 서비스 계층과 MemoRepository까지 잘 작성하였으나 쿼리 메서드 이름을 찾아보고 입력했지만 실행에 오류가 생겨서 gpt에게 쿼리 메서드 이름을 어떻게 작성하면 되는지 물어보고 작성함.</p>
</li>
<li><p>이후 postman에 실행할 때 json 텍스트를 불러오지 못함 -&gt; String keyword를 작성안했었음.</p>
</li>
<li><p>쿼리 메서드를 잘 작성했지만 실제 웹사이트에서는 반영이 되지 않음 -&gt; Gpt에게 물어보니 getMemosByKeyword가 수정 시간 기준 내림차순 정렬은 반영이 되었으나 실제 웹사이트에서는 전체 메모 목록을 조회하는 api/memos를 호출하고 있었기 때문에 getMemos로 반영이 되어 내림차순 정렬이 되지 않고 있었다. 그래서 새로운 쿼리 메서드를 추가하였다. </p>
</li>
<li><p>html을 구성하는데는 시간이 좀 부족해서 디자인적으로 많이 아쉬웠다. 기존 html에 사진밖에 넣지 못했다.</p>
</li>
<li><p>검색기능을 구현하면서 기존목록을 비우는 과정을 생각하지는 못했고 키워드를 불러와야겠다는 생각만했었다. 그리고 js는 아직 잘 사용을 못한다고 생각했다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ORM과 JPA]]></title>
            <link>https://velog.io/@minjae-yun/ORM%EA%B3%BC-JPA</link>
            <guid>https://velog.io/@minjae-yun/ORM%EA%B3%BC-JPA</guid>
            <pubDate>Wed, 21 Jan 2026 14:05:45 GMT</pubDate>
            <description><![CDATA[<h3 id="ormobject-relational-mapping">ORM(object-relational mapping)</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/685d7fde-2de4-46ca-a303-6d19937956b6/image.png" alt=""></p>
<p>객체지향 언어의 객체와 관계형 데이터베이스의 테이블을 자동으로 매핑해주는 기술을 말합니다.</p>
<p>예를 들어, 데이터베이스에 &#39;age&#39;와 &#39;name&#39;이라는 컬럼이 있고, 그 값으로 각각 20, 홍길동이 저장되어 있다고 가정하면 이런 데이터를 자바에서 어떻게 해야할까?</p>
<p>보통 우리는 SQL이라는 언어를 사용해서 데이터베이스에서 데이터를 추출한다. 원래라면 SELECT * From user와 같은 쿼리를 직접 써서 데이터를 가져와야 하지만 데이터 양이 방대할 수 있고 코드랑 sql을 따로 관리해야 해서 헷갈릴 수가 있다.</p>
<span style = "color : red">
하지만 ORM이 있으면 데이터베이스의 값을 마치 객체처럼 사용할 수 있다.</span>

<p>쉽게 말해 SQL에 대한 지식이 깊지 않아도 기본적인 CRUD는 자바 코드로만 데이터베이스에 접근해서 원하는 데이터만 가져올 수 있습니다.
<span style = "color : red">
-&gt; 자바 코드 속의 객체와 데이터 베이스의 테이블을 연결해주는 다리 역할을 해주는 것이 ORM이다.</span></p>
<hr>
<h3 id="orm의-장점과-단점">ORM의 장점과 단점</h3>
<p><strong>장점</strong></p>
<ul>
<li>SQL을 직접 작성하지 않고, 사용하는 언어로 데이터베이스에 접근할 수 있다.</li>
<li>객체지향적으로 코드를 작성할 수 있기 때문에 비즈니스 로직에만 집중할 수 있다.</li>
<li>데이터베이스 시스템이 추상화되어 있어, 예를들어 MySQL에서 postgreSQL로 전환할 때 추가 자작업이 거의 필요없다. 즉 데이터베이스 시스템에 대한 종속성이 줄어든다.</li>
<li>매핑하는 정보가 명확해지기 때문에 ERD에 대한 의존도를 낮출 수 있고, 유지보수 시 유리하다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>프로젝트가 복잡해질수록 ORM의 사용 난이도 높아질 수 있다.</li>
<li>복잡하고 성능이 중요한 쿼리는 ORM으로 해결하기 어려운 경유가 있다.</li>
</ul>
<h3 id="jpa란">JPA란</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/13fa7ca2-f9ae-4f70-a4de-3ea7c2946203/image.png" alt=""></p>
<p>자바 객체를 데이터베이스 테이블과 매핑해서 DB작업을 쉽게 해주는 자바에서 사용하는 ORM기술 표준이다.</p>
<p>쉽게 말하면 SQL을 직접 많이 안 써도 자바 객체만으로  DB에 저장,조회,수정,삭제(CRUD)를 할 수 있게 해주는 기술</p>
<hr>
<h3 id="jpa의-장점과-단점">JPA의 장점과 단점</h3>
<p><strong>장점</strong></p>
<ul>
<li><p>기본 CRUD를 SQL없이 처리가 가능해 반복적인 SQL작성이 줄어들어 개발 속도가 빨라지고 생산성이 매우 높아진다.</p>
</li>
<li><p>테이블이 아니라 자바 객체 중심으로 개발 되고객체 간 관계를 코드로 표현해 비즈니스 로직이 명확해지고 코드 가독성이 좋아져 객체적인 개발이 가능해진다.</p>
</li>
<li><p>DB가 바뀌어도 코드 수정이 거의 없고 특정 DB에 종속되지 않는 구조를 가져 데이터베이스 독립성을 가진다.</p>
</li>
<li><p>테이블 구조 변경시 SQL 전부를 수정할 필요가 없고 엔티티만 수정하면 자동 반영되어 유지보수에 용이하다.</p>
</li>
<li><p>1차 캐시, 변경 감지, 쓰기 지연 개발자가 신경 쓰지 않아도 성능을 어느 정도 보장해 자동 최적화가 가능하다.</p>
</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li><p>영속성 컨텍스트, 트랜잭션, 지연로딩/즉시 로딩등의 개념을 이해하지 못하면 오류 원인 파악이 어렵다.</p>
</li>
<li><p>복잡한 통계, 다중 조인 쿼리 등 복잡한 쿼리에 한계가 있다.</p>
</li>
<li><p>SQL이 자동 생성되기 때문에 어떤 쿼리가 나가는지 모를 수 있고 N + 1 문제 등 성능 튜닝이 어렵다.</p>
</li>
<li><p>대량 배치 처리와 SQL 최적화가 매우 중요한 경우 등 모든 상황에 적합하지 않을 수 있다.</p>
</li>
</ul>
<hr>
<h3 id="jpa의-핵심-개념">JPA의 핵심 개념</h3>
<h4 id="1-entity엔터티">1. Entity(엔터티)</h4>
<pre><code>@Entity
public class Memo {
    @Id
    @GeneratedValue
    private Long id;
}
</code></pre><p>자바 클래스가 DB테이블과 매핑된다. @Entity는 DB랑 연결이 된다.</p>
<h4 id="2entitymanager">2.EntityManager</h4>
<pre><code>entityManager.persist(memo);
</code></pre><p>JPA의 핵심 관리자이고 엔터티를 저장,조회,삭제하는 역할이 있다.</p>
<h4 id="3-persistence-context영속성-컨텍스트">3. Persistence Context(영속성 컨텍스트)</h4>
<blockquote>
<p>엔티티를 관리하는 1차 캐시 공간</p>
</blockquote>
<p><strong>특징 :</strong></p>
<ul>
<li>같은 ID조회 시 DB재조회를 하지 않는다.</li>
<li>변경 감지가 자동으로 된다.</li>
<li>트랜잭션이 끝나면 DB에 반영된다.</li>
</ul>
<h4 id="4-repositoryspring-data-jpa">4. Repository(Spring Data JPA)</h4>
<pre><code>public interface MemoRepository extends JpaRepository&lt;Memo, Long&gt; {
}
</code></pre><p>기본 CRUD를 자동 제공하고 메서드 이름만으로 쿼리 생성이 가능하다.</p>
<h4 id="5-트랜잭션transactional">5. 트랜잭션(@Transactional)</h4>
<pre><code>@Transactional
public void updateMemo(...) { }
</code></pre><p>작업 단위를 보장하고 중간에 에러가 나면 자동으로 롤백하는 기능이 있다.</p>
<hr>
<p>JPA는 개발 생산성과 유지보수성을 크게 향상시키는  ORM 기술이지만, 내부 동작에 대한 이해 없이 사용할 경우 성능 문제를 유발할 수 있다. 따라서 JPA의 장점을 살리기 위해서는 트랜잭션과 영속성 컨텍스트에 대한 충분한 이해가 필요하다. </p>
<p>본 개발자도 JPA 내부 동작을 구현하다가 코드가 꼬여서 성능문제를 유발했음..</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/1c2b2804-d5fc-422e-99ce-f9be02c2fe31/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[싱글톤 패턴]]></title>
            <link>https://velog.io/@minjae-yun/%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@minjae-yun/%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Wed, 21 Jan 2026 09:12:14 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/58c5bb9d-3324-478a-977d-fe172e805787/image.png" alt=""></p>
<p>싱글톤 패턴은 특정 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 디자인 패턴이다. 이 패턴은 주로 전역 상태를 관리하거나 자원의 중복을 방지하고 싶을 때 사용됩니다.</p>
<p>예를 들어 네트워크 매니저, 데이터 베이스 연결 객체 등은 애플리케이션 전체에서 하나의 인스턴스로만 존재해야합니다.</p>
<span style = "color : red">
여러 인스턴스가 생성된다면 리소스를 낭비하거나, 데이터를 일관성 있게 유지하는 것이 어려울 수 있습니다.</span>

<hr>
<h3 id="싱글톤-패턴의-특징">싱글톤 패턴의 특징</h3>
<ul>
<li><p>인스턴스의 유일성 보장</p>
</li>
<li><p>클래스 인스턴스가 하나만 존재합니다.</p>
</li>
<li><p>전역 접근</p>
</li>
<li><p>프로그램 어디서나 동일한 인스턴스를 접근할 수 있습니다.</p>
</li>
<li><p>지연 초기화</p>
</li>
<li><p>필요할 때까지 인스턴스를 생성하지 않아 메모리 사용을 최적화하 수 있습니다.</p>
</li>
</ul>
<hr>
<h3 id="싱글톤-패턴을-언제-사용할까">싱글톤 패턴을 언제 사용할까?</h3>
<ul>
<li><p>전역 상태를 관리할 때</p>
</li>
<li><p>사용자의 로그인 상태나 애플리케이션 설정 등 앱 전체에서 일관된 상태를 관리해야 할 때</p>
</li>
<li><p>공유 자원을 관리할 때</p>
</li>
<li><p>네트워크 연결, 파일 입출력, 데이터베이스 접근 등 여러 클래스에서 동시에 접근하는 자원을 중앙에서 관리하고 싶을 때</p>
</li>
<li><p>비용이 큰 객체의 중복 생성을 방지할 때</p>
</li>
<li><p>초기화 비용이 크거나 메모리 사용량이 큰 객체를 여러번 생성하는 것을 방지하고 싶을 때</p>
</li>
</ul>
<hr>
<h3 id="싱글톤-패턴의-사용방법">싱글톤 패턴의 사용방법</h3>
<h4 id="1-생성자-private">1. 생성자 private</h4>
<ul>
<li>외부에서 new로 객체 생성을 못하게 막는다.</li>
</ul>
<h4 id="2-static-변수로-인스턴스-하나만-생성">2. static 변수로 인스턴스 하나만 생성</h4>
<ul>
<li>클래스 내부에 자기 자신 타입의 객체를 하나만 둠</li>
</ul>
<h4 id="3-static-메서드로-그-인스턴스-반환">3. static 메서드로 그 인스턴스 반환</h4>
<ul>
<li>어디서든 접근이 가능하다.</li>
</ul>
<pre><code>public class Singleton {

    // 1. 유일한 인스턴스
    private static Singleton instance = new Singleton();

    // 2. 생성자 private
    private Singleton() {}

    // 3. 인스턴스 반환 메서드
    public static Singleton getInstance() {
        return instance;
    }
}
</code></pre><pre><code>Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();

System.out.println(s1 == s2); // true (같은 객체)
</code></pre><h3 id="싱글톤-패턴의-주의사항">싱글톤 패턴의 주의사항</h3>
<p>싱글톤 패턴은 편리하지만 잘못 사용하면 유지보수와 테스트면에서 어려움을 초래할 수 있습니다.</p>
<ul>
<li><p>의존성 주입의 어려움</p>
</li>
<li><p>싱글톤 객체는 전역적으로 사용되기 때문에 테스트 시에 모킹을 사용하기 어렵습니다.</p>
</li>
<li><p>과도한 전역 상태 사용</p>
</li>
<li><p>전역적으로 접근 가능한 객체가 많아지면 코드의 의존성이 높아지고 버그 발생 가능성이 증가합니다.</p>
</li>
<li><p>멀티스레드 환경에서의 안정성</p>
</li>
<li><p>스레드 안전한 싱글톤 구현이 필요할 수 있습니다. static let은 기본적으로 스레드 안전성을 제공하므로 대부분의 경우 안전하지만 추가적인 고려가 필요할 수 있습니다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/b4d0c751-c521-4750-9bf0-f27d0f872eb9/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DI와 IoC]]></title>
            <link>https://velog.io/@minjae-yun/DI%EC%99%80-IoC</link>
            <guid>https://velog.io/@minjae-yun/DI%EC%99%80-IoC</guid>
            <pubDate>Wed, 21 Jan 2026 08:43:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-yun/post/44daf7cb-3b54-4428-84c2-7802c39a3c5b/image.png" alt=""></p>
<h3 id="didependency-injection">DI(Dependency injection)</h3>
<p>DI를 이해하기 위해 먼저 Dependency부터 알아보자.</p>
<p>Dependency, 한국어로 종속성, 의존성은 하나의 객체가 다른 객체의 기능에 의존하는 관레를 말합니다. DI에는 직접 객체를 생성해서 자신이 사용하는 일체형 관계와 외부에서 생성한 객체를 사용하는 결합형 관계가 있다.</p>
<hr>
<h4 id="1-constructor-injection--직접-객체-생성">1. Constructor Injection : 직접 객체 생성</h4>
<pre><code>class B {
    // B 클래스의 내용
}

class A {
    private B b;

    public A() {
        b = new B(); // A의 생성자에서 B 객체를 직접 생성
    }
}</code></pre><p>일체형 관계에서는 class A와 Class B가 강하게 결합되어 있음을 의미합니다. 이 경우 class B의 구현 변경이 필요할 때 class A도 함께 수정해야 하는 단점을 갖게 됩니다.</p>
<hr>
<h4 id="2setter-injection--외부에서-객체-주입">2.Setter Injection : 외부에서 객체 주입</h4>
<pre><code>class B {
    // B 클래스의 내용
}

class A {
    private B b;

    public A() {
        // A 생성자에서는 B 객체를 생성하지 않습니다.
    }

    public void setB(B b) {
        this.b = b; // 세터를 통한 의존성 주입
    }
}

// 사용 예
class Main {
    public static void main(String[] args) {
        B b = new B(); // B 객체를 외부에서 생성
        A a = new A(); // A 객체 생성
        a.setB(b);     // Setter를 통한 A 객체에 B 객체 주입
    }
}</code></pre><p>결합형 관계에서는 class B의 인스턴스를 외부에서 생성한 후 class A에 제공합니다. 이렇게 다른 클래스에 대한 의존성을 외부에서 주입 바든 방식을 바로 의존성 주입이라고 하고 이 같은 경우 class B를 다른 구현으로 쉽게 바꿀 수 있으며, class A를 변경할 필요가 없는 장점을 얻으며 테스트도 용이하게 됩니다.</p>
<hr>
<h3 id="스프링이-di를-해주는-방식">스프링이 DI를 해주는 방식</h3>
<span style = "color : purple">
바로 객체를 IoC컨테이너 안에 생성하여 사용자가 필요할 때 꺼내쓸 수 있도록 해주는 방식으로 도와줍니다. </span>

<p>개발자가 Bean으로 ICO 컨테이너 등록한 객체를 쓰는 방식이다.</p>
<hr>
<h3 id="iocinversion-of-control">IoC(Inversion of control)</h3>
<p>IoC는 영어 그대로 제어의 역전이라는 뜻이다. 프로그램의 흐름을 개발자가 제어하지 않고, 프로그램이나 프레임웍이 직접 제어를 한다는 말이다. 컴포넌트의 의존 관계 결정, 객체의 생명 주기와 같은 일을 대신 수행해준다.</p>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/ab6c6ddc-cf57-4139-9527-39eb1e9aa2da/image.png" alt=""></p>
<p>사용자의 요청이 들어오면, 요청에 알맞은 Bean을 생성해서 필요한 일을 하도록 시킨다. 해당 Bean이 할 일을 마치면 Bean을 삭제해준다. 하지만 이런 객체의 생성과 소멸이 어떻게 이루어지는지 알 필요가 없다. 프로그래머가 직접 제어 할 필요가 없이 프레임웤이 직접 제어해주기 때문이다. 이것이 IoC 제어의 역전이다.</p>
<h3 id="컨테이너">컨테이너?</h3>
<p>컨테이너는 IoC컨테이너라고도 불린다. 컨테이너는 코드의 처리 과정을 위임 받은 독립적인 존재이다.</p>
<p>적절한 설정을 해주면, 프로그래머가 작성한 코드를 스스로 참조한 뒤 알아서 객체의 생성과 소멸을 관리한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OOP 3요소 와 5원칙]]></title>
            <link>https://velog.io/@minjae-yun/OOP-3%EC%9A%94%EC%86%8C-%EC%99%80-5%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@minjae-yun/OOP-3%EC%9A%94%EC%86%8C-%EC%99%80-5%EC%9B%90%EC%B9%99</guid>
            <pubDate>Tue, 20 Jan 2026 11:16:30 GMT</pubDate>
            <description><![CDATA[<h3 id="oopobject-oriented-programming">OOP(Object-Oriented Programming)</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/9527a9a0-5d3a-415e-b557-ffdf54f18205/image.png" alt=""></p>
<p>객체 및 이들간의 관계, 상호작용 등을 기반으로 프로그램을 설계하는 방법론 및 프로그래밍 패러다임의 일종으로, 넓게는 명령형 프로그래밍에 속한다.</p>
<p>프로그램을 단순히 데이터와 처리 방법으로 나누는것이 아니라, 프로그램을 수많은 객체라는 기본 단위로 나누고 이들의 상호 작용으로 서술하는 방식이다. 여기서의 객체란 메소드 변수를 가지며 특정 역할을 수행하도록 인간이 정의한 추상적인 개념이다.</p>
<hr>
<h3 id="객체지향의-3요소">객체지향의 3요소</h3>
<h4 id="1-객체object">1. 객체(Object)</h4>
<ul>
<li>데이터(속성) + 기능(메서드)를 하나로 묶은 단위
<span style ="color : blue">예) 학생 객체 -&gt; 이름, 학번 + 수강신청(), 성적조회()</span></li>
</ul>
<h4 id="2-클래스class">2. 클래스(Class)</h4>
<ul>
<li>객체를 만들기 위한 설계도
<span style ="color : blue">class Student{...}</span></li>
</ul>
<h4 id="3-메시지message">3. 메시지(Message)</h4>
<ul>
<li>객체 간 메서드 호출(요청)
<span style ="color : blue">student.getScore()</span></li>
</ul>
<h3 id="객체지향의-4대-특징">객체지향의 4대 특징</h3>
<h4 id="캡슐화">캡슐화</h4>
<p>-프로그램 내에서 같은 기능을 목적으로 작성된 코드를 모아서 다른 곳(클래스)에서 안보이게 숨기는 것 클래스에 정의된 속성(Attribue)는 숨기고 (private), 객체가 수행할 기능은 공개(Public)하는 것을 의미한다.</p>
<h4 id="상속">상속</h4>
<p>-클래스 사이에 부모와 자식 클래스가 존재할 수 있다는 뜻이다. 자식 클래스는 상속받는 부모 클래스의 속성(변수) 및 기능(메소드, 함수)를 물려받는 것을 의미한다.</p>
<h4 id="추상화">추상화</h4>
<h4 id="-객체의-공통적인-특징과-핵심-기능만을-뽑아내고-불필요한-세부-구현을-숨기는-것을-의미한다-이를-통해-구현에-대한-의존도를-낮추고-코드의-유지보수성과-확장성을-향상시킬-수-있다">-객체의 공통적인 특징과 핵심 기능만을 뽑아내고, 불필요한 세부 구현을 숨기는 것을 의미한다. 이를 통해 구현에 대한 의존도를 낮추고 코드의 유지보수성과 확장성을 향상시킬 수 있다.</h4>
<h4 id="다형성">다형성</h4>
<p>Overriding과 Overloading으로 나뉨
<a href="https://velog.io/@minjae-yun/Overload-VS-Override">https://velog.io/@minjae-yun/Overload-VS-Override</a>
자세한 설명은 위 사이트에서..</p>
<hr>
<h3 id="5원칙-solid">5원칙 SOLID</h3>
<p><img src="https://velog.velcdn.com/images/minjae-yun/post/5f6dae7a-a66d-482c-ad6a-bee1f7754b62/image.png" alt=""></p>
<h4 id="s---srp단일-책임의-원칙-single-responsibility-principle">S -&gt; SRP(단일 책임의 원칙: Single Responsibility Principle)</h4>
<ul>
<li><p>하나의 클래스는 하나의 목적을 위해서 생성되며, 클래스가 제공하는 모든 서비스는 하나의 책임을 수행하는 데 집중되어 있어야 한다는 원칙입니다.</p>
</li>
<li><p>이는 어떤 변화에 의해 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 함을 의미합니다.</p>
</li>
<li><p>SRP 원리를 적용하면 책임 영역이 확실해지기 때문에 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용에서 자유로울 수 있습니다.</p>
</li>
<li><p>코드의 가독성 및 유지보수에 있어서 유리합니다.</p>
</li>
<li><p>객체 지향 프로그래밍의 5원칙 중 나머지 4원칙의 기초가 되는 원칙이다.</p>
</li>
</ul>
<h4 id="o---ocp개방폐쇄의-원칙-open-close-principle">O -&gt; OCP(개방폐쇄의 원칙: Open Close Principle)</h4>
<ul>
<li><p>소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수)는 확장에는 열려있고, 변경에는 닫혀있어야 한다는 원리이다.</p>
</li>
<li><p>이것은 변경을 위한 비용은 가능한 줄이고 확장을 위한 비용은 가능한 극대화 해야 한다는 의미로, 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 말아야 하며, 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 한다는 뜻이다.</p>
</li>
<li><p>재사용 코드를 만드는 기반이며, OCP를 가능하게 하는 중요 메커니즘은 추상화와 다형성.</p>
</li>
</ul>
<h4 id="l---lsp리스코브-치환의-워칙-the-liskov-substitution">L -&gt; LSP(리스코브 치환의 워칙: The Liskov Substitution)</h4>
<ul>
<li><p>자식 클래스(서브 타입)는 언제나 자신의 부모 클래스(기반 타입)를 대체할 수 있다. 즉, 부모 클래스가 들어갈 자리에 자식 클래스를 넣어도 계획대로 잘 작동해야 한다는 것.</p>
</li>
<li><p>상속의 본질인 원리</p>
</li>
<li><p>이를 지키지 않을 경우 부모 클래스 본래의 의미가 변해서 is a 관계가 무너짐</p>
</li>
</ul>
<h4 id="i---isp인터페이스-분리의-원칙-interface-segregation-principle">I -&gt; ISP(인터페이스 분리의 원칙: Interface Segregation Principle)</h4>
<ul>
<li><p>한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원리.</p>
</li>
<li><p>어떤 클래스가 다른 클래스에 종속될 때에는 가능한 최소환의 인터페이스만을 사용해야한다.</p>
</li>
</ul>
<h4 id="d---dip의존성역전의-워칙-dependency-inversion-principle">D -&gt; DIP(의존성역전의 워칙: Dependency Inversion Principle)</h4>
<ul>
<li><p>구조적 디자인에서 발생하던 하위 레벨 모듈의 변경이 상위 레벨 모듈의 변경을 요구하는 위치관계를 끊는 의미의역전입니다.</p>
</li>
<li><p>실제 사용 관계는 바뀌지 않으며, 추상을 매개로 메시지를 주고 받음으로써 관계를 최대한 느슨하게 만드는 원칙이다.</p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>