<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>pang_e.log</title>
        <link>https://velog.io/</link>
        <description>개발자 팡이</description>
        <lastBuildDate>Fri, 10 Oct 2025 09:12:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>pang_e.log</title>
            <url>https://velog.velcdn.com/images/pang_e/profile/6bbbacae-bf21-44a3-ba93-ad2beeccaca1/image.gif</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. pang_e.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/pang_e" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[ollama로 로컬에서 ai 모델을 띄워보자]]></title>
            <link>https://velog.io/@pang_e/ollama%EB%A1%9C-%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-ai-%EB%AA%A8%EB%8D%B8%EC%9D%84-%EB%9D%84%EC%9B%8C%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@pang_e/ollama%EB%A1%9C-%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-ai-%EB%AA%A8%EB%8D%B8%EC%9D%84-%EB%9D%84%EC%9B%8C%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Fri, 10 Oct 2025 09:12:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>나도 ai를 로컬에 띄워서 간단한건 공짜로 써보고싶다!!!</p>
</blockquote>
<h2 id="도커-띄우기">도커 띄우기</h2>
<p>자자 시놀로지 서버에 띄워봅시다.
올라마 이미지 다운로드 딸깍
<img src="https://velog.velcdn.com/images/pang_e/post/0489e054-9d56-4d5d-9eb3-fbb3cf67b7c1/image.png" alt=""></p>
<p>docker 으로 띄워보자</p>
<ul>
<li>포트는 통상적으로 11434 쓰는것같아서 따라했다.</li>
<li><code>/root/.ollama</code> 에 데이터가 저장되는것 같아 볼륨도 따로 잡아줬다.</li>
</ul>
<pre><code>docker run -d -p 11434:11434 -v /volume1/docker/ollama:/root/.ollama --name ollama-pack ollama/ollama:latest</code></pre><p>도커 띄우기 -완-</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/74939469-426c-4e6a-aa44-f58f1c1e4b05/image.png" alt=""></p>
<hr>
<h2 id="모델-다운받기">모델 다운받기</h2>
<p>처음에 들어가서 보면 사용할수 있는 모델이 없다는걸 확인할 수 있다.</p>
<pre><code>ollama list</code></pre><p><img src="https://velog.velcdn.com/images/pang_e/post/ccd6dbde-0364-4246-81fc-e5f1b9b985fe/image.png" alt=""></p>
<p>다음 사이트를 들어가보자
<a href="https://ollama.com/search">https://ollama.com/search</a></p>
<p>아주 많은 모델들이 있는데, 이름이 예쁜걸로 다운받아보자</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/8b18bba8-f8ea-41f9-8fd0-933134c67677/image.png" alt=""></p>
<p>나는 llava 를 선택했다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/543ddc42-6773-419e-9df8-98b29d760ef8/image.png" alt=""></p>
<p>해당 모델을 설치하는데 한참 걸리고있다.</p>
<pre><code>ollama pull [이미지명]</code></pre><p><img src="https://velog.velcdn.com/images/pang_e/post/5faa3962-a5a6-4da8-8186-5a6175be12e5/image.png" alt=""></p>
<p>다운로드 -완-</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/11799925-d2aa-49b4-92d0-f3ed0d278dd6/image.png" alt=""></p>
<p>이제 확인해보면 내가 설치한 모델이 뜬다. </p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/cc540e4f-a322-49be-b838-08a575c2a6ed/image.png" alt=""></p>
<hr>
<h2 id="ai-질문해보기">ai 질문해보기</h2>
<p>먼저, 홈페이지에서 사용법이 나와있다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/1f4ad31b-5e90-4067-9c11-6f82243ee7a3/image.png" alt=""></p>
<h3 id="모델-사용하기-모델에-접속">모델 사용하기 (모델에 접속)</h3>
<p>해당 모델에 접속 해보자</p>
<pre><code>ollama run [모델명]</code></pre><p><img src="https://velog.velcdn.com/images/pang_e/post/591aa039-1d0f-4f4c-bba6-58ebe390977b/image.png" alt=""></p>
<p>이제 이녀석에게 질문을 해보자.</p>
<ul>
<li>2 + 3이 뭔지아니?</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/9af583a0-abb8-413d-80da-5e683f443560/image.gif" alt=""></p>
<p>(감격의 눈물...)</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/7458f8bd-3227-4187-acb1-07a0dab23903/image.png" alt=""></p>
<p>이미지 내부에서 명령어가 어떤게 있는지 알려면 다음으로 볼 수 있는데, 이것저것 나오긴 한다.</p>
<pre><code>/?</code></pre><p><img src="https://velog.velcdn.com/images/pang_e/post/c96bf525-30e9-4f6c-9e25-6b1a9cbb8efe/image.png" alt=""></p>
<h3 id="모델-사용하기api-요청">모델 사용하기(api 요청)</h3>
<p>모델에서 정상 동작하는걸 봤으니, 이제 내가 사용하는 api 형태로 사용하도록 요청해보자.</p>
<p>홈페이지의 예시처럼 curl 명령어로 요청할 예정</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/5609842e-c59a-4dd5-874d-b34cb5ebdfef/image.png" alt=""></p>
<p>이벤트 스트림 형태로 요청이 오는걸 확인할 수 있다!</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/98ab32c8-cc40-4759-8ce5-f9615607b9a8/image.gif" alt=""></p>
<p>이제 개인 프로젝트에서 간단한 ai 는 공짜로 사용할수 있겠다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Claude Code 명령어 이것저것.zip]]></title>
            <link>https://velog.io/@pang_e/Claude-Code-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%9D%B4%EA%B2%83%EC%A0%80%EA%B2%83.zip</link>
            <guid>https://velog.io/@pang_e/Claude-Code-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%9D%B4%EA%B2%83%EC%A0%80%EA%B2%83.zip</guid>
            <pubDate>Sun, 05 Oct 2025 04:33:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>터미널에서 클로드코드를 실행하려면 <code>claude</code>. 한글로는 <code>치명ㄷ</code> 입니다.</p>
</blockquote>
<ul>
<li>이왕 돈내고 쓰는거 기능을 좀더 잘 써보자!</li>
</ul>
<h3 id="설치">설치</h3>
<pre><code>npm install -g @anthropic-ai/claude-code</code></pre><p>다음 URL 에서 확인할 수 있다.
<a href="https://www.claude.com/product/claude-code">https://www.claude.com/product/claude-code</a></p>
<hr>
<h3 id="최초-실행">최초 실행</h3>
<pre><code>claude</code></pre><p>바로 claude code 가 실행된다. 클로드코드 버전, 사용하고있는 모델, 현재 위치를 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/b23a8231-8be1-4aa9-bc33-1c9e0106beb9/image.png" alt=""></p>
<hr>
<h3 id="이전-세션-가져오기">이전 세션 가져오기</h3>
<pre><code>claude -r
또는
claude --resume</code></pre><p>클로드 코드로 개발하다보면 하루에 토큰수도 정해져있고... 오늘은 여기까지만 하고싶기도하고... 
뭐 그런 이유들로 마무리하지않고 다음번에 또다시 해야하는 경우가 있다.
그때 터미널에서 위 옵션을 통해 <strong>과거의 세션에 연결</strong>할 수 있다.</p>
<ul>
<li>폴더마다 세션 히스토리가 각자 저장된다. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/78f52ce4-916b-424f-a707-d97113bd1285/image.png" alt=""></p>
<hr>
<h3 id="가장-최근-세션-불러오기">가장 최근 세션 불러오기</h3>
<pre><code>claude -c
또는
claude --continue</code></pre><p><code>--resume</code> 은 히스토리를 불러와서 고른다고 한다면, 해당 명령어는 가장 최근에 실행했던 세션을 다시 열어준다.</p>
<hr>
<h3 id="모델-선택하기">모델 선택하기</h3>
<pre><code>/model</code></pre><p><code>claude code cli</code> 에 입장하고나서, 모델을 고를 수 있다</p>
<ul>
<li><del>Opus 모델은 비싼 요금제에서만 되는것 같다....</del></li>
<li>분명히 글쓸땐 안됬었는데, 지금은 됩니다.</li>
</ul>
<p>(2025.10.5 기준)
<img src="https://velog.velcdn.com/images/pang_e/post/9c612bc1-9e99-4c66-9f57-3ed6f4c2c176/image.png" alt=""></p>
<p>(2025.12.19 기준)
<img src="https://velog.velcdn.com/images/pang_e/post/19fef9d3-26de-4b6e-9ed6-7927d2f59c6d/image.png" alt=""></p>
<hr>
<h3 id="사용량-확인">사용량 확인</h3>
<pre><code>/usage</code></pre><ul>
<li>현재 기준으로 얼만큼 사용했는지 확인할 수 있다.</li>
<li><code>Current Session</code> 은 약 4-5시간마다, <code>Current week</code> 는 일주일마다 초기화 된다. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/05b76cae-60c7-4c4d-9d98-1022938b3756/image.png" alt=""></p>
<hr>
<h3 id="줄바꿈-활성화">줄바꿈 활성화</h3>
<pre><code>/terminal-setup</code></pre><ul>
<li>해당 명령어로 줄바꿈을 활성화하고, windows 는 <code>shift+enter</code>, mac은 <code>option+enter</code> 으로 줄바꿈을 할 수 있다. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/5e44e50f-da52-4e81-9375-444d93321db9/image.png" alt=""></p>
<hr>
<h3 id="내-레포-읽게-하기">내 레포 읽게 하기</h3>
<pre><code>/init</code></pre><p>바로 &quot;해줘&quot; 를 할수 있겠지만, 먼저 내 레포를 읽고 정리할 시간을 주자.</p>
<p>혼자서 여기저기 들쑤시며 볼수있게 권한을 열어달라고 요청한다.</p>
<p>혼자 만족하고 나면, <code>CLAUDE.md</code> 마크다운 파일을 생성한다. 
<img src="https://velog.velcdn.com/images/pang_e/post/5ab5978a-a9f6-47cd-b58c-4271cef03cff/image.png" alt=""></p>
<p>init 의 결과물을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pang_e/post/737dd8a6-4124-44f5-82ec-fbbfcb83698b/image.png" alt=""></p>
<h4 id="잠깐잠깐-claudemd-가-뭐냐면">잠깐잠깐 CLAUDE.md 가 뭐냐면</h4>
<p><code>claude code</code> 에게 지침을 적어두는 메모라고 보면 된다. -&gt; 메모리에 저장시키는 느낌으로!
대화를 시작할 때 마다 이 마크다운 파일을 먼저 읽게되고, <code>/init</code> 명령어로 만들어진 <code>CLAUDE.md</code> 파일을 보면 그냥 구어체로 적혀있는걸 볼 수 있다 -&gt; <strong>형식에 제한이 없음</strong></p>
<p>다음과 같은 기본 값들을 미리 넣어줄 수 있다. </p>
<ul>
<li>코드 포맷팅 스타일</li>
<li>자주 쓰는 명령어</li>
<li>중요한 파일</li>
<li>선호하는 코딩 패턴 </li>
</ul>
<p>그리고 <code>#</code> 을 누르면 메모에 빠른 추가를 할 수 있다!</p>
<p>해당 프로젝트에만 적용할지, 전체로 적용할지 지정할 수 있는 모습이다.
<img src="https://velog.velcdn.com/images/pang_e/post/96fc6464-4c48-413e-a6ef-066d5014cfd6/image.png" alt=""></p>
<hr>
<h3 id="명령어-권한-쥐어주기">명령어 권한 쥐어주기</h3>
<pre><code>/permissions</code></pre><p>사용하다보면 뭔가 자꾸 권한을 달라고 하는데, 생성/수정/삭제 요청뿐만아니라 읽기 권한도 달라고 한다.
좀 귀찮은 일인데, 권한을 처음부터 지정해서 줄 수 있다.</p>
<p>어떤룰을 추가할지
<img src="https://velog.velcdn.com/images/pang_e/post/2c5e2929-2c0d-43e9-8065-431a93e52f6f/image.png" alt=""></p>
<p>어떤 명령어에 대해
<img src="https://velog.velcdn.com/images/pang_e/post/12f51ac7-b84f-49b5-b7cf-dd16f279ab4c/image.png" alt=""></p>
<p>범위는??</p>
<ul>
<li><code>Project settings (local)</code> : git 에 올리지 않고 로컬에서만 사용하도록 저장</li>
<li><code>Project settings</code> : git 에 까지 세팅을 올리도록 저장</li>
<li><code>User settings</code> : 내 컴퓨터에 글로벌 세팅으로 저장</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/f8ada150-3c02-43d4-8b4e-09664d9afbd5/image.png" alt=""></p>
<p>글로벌 세팅으로 했었고, 확인해보면 <code>ls</code> 명령어가 <code>allow permissions</code> 에 들어간걸 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pang_e/post/e8493f87-f1d6-4368-aac6-632c53e15d3f/image.png" alt=""></p>
<hr>
<h3 id="특정-파일만-읽게-하기">특정 파일만 읽게 하기</h3>
<pre><code>@</code></pre><p>프로젝트 전체를 읽지 않아도 될때, 토큰을 적게 사용하기위한 컨트롤 이라 볼수있다.
혹시 다른 파일을 더 읽어야 할것 같다면 알아서 읽어도되냐고 접근권한을 물어본다.
<img src="https://velog.velcdn.com/images/pang_e/post/d7d1c135-2c3e-4c7e-a56e-d2982b5b938a/image.png" alt=""></p>
<hr>
<h3 id="글자-말고-더-추가하고싶다">글자 말고 더 추가하고싶다!</h3>
<p>URL</p>
<ul>
<li>URL 을 그대로 붙여넣으면, 해당 웹페이지의 내용을 읽는다!
<img src="https://velog.velcdn.com/images/pang_e/post/3142ba8b-6b50-4a80-abf4-88f97dc4082f/image.png" alt=""></li>
</ul>
<p>이미지 추가하기</p>
<ul>
<li>터미널에 이미지를 드래그 앤 드롭을 하면 파일이 인풋으로 들어간다!
<img src="https://velog.velcdn.com/images/pang_e/post/25a20d88-5ca3-4076-adbc-2a67b31610d0/image.png" alt=""></li>
</ul>
<hr>
<h3 id="나만의-커스텀-명령어-만들기">나만의 커스텀 명령어 만들기</h3>
<pre><code>.claude/commands/ 디렉토리에 마크다운 파일 추가</code></pre><p>디렉토리에 마크다운 파일 추가를 해보자
<img src="https://velog.velcdn.com/images/pang_e/post/5858b66a-c495-49b5-84a9-4455261a4b66/image.png" alt=""></p>
<p><code>/</code> 를 하면 나만의 커스텀 명령어가 나온다!!
<img src="https://velog.velcdn.com/images/pang_e/post/54010852-20f3-4015-adc1-d495a59b1e3c/image.png" alt=""></p>
<p>그리고 마크다운 파일에 <code>$ARGUMENTS</code> 를 통해 인자값도 넘겨줄 수 있다.</p>
<pre><code>cat babi.md
&gt; refactoring code in $ARGUMENTS.</code></pre><hr>
<p>이 외에 많은것들이 존재하는데, 생각날때마다 추가하도록 하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[jmeter 무작정 따라하기]]></title>
            <link>https://velog.io/@pang_e/jmeter</link>
            <guid>https://velog.io/@pang_e/jmeter</guid>
            <pubDate>Tue, 12 Aug 2025 14:34:34 GMT</pubDate>
            <description><![CDATA[<h3 id="설치와-실행">설치와 실행</h3>
<p>mac 기준 설치 
<code>brew install jmeter</code></p>
<p>또는 여기서 설치
<a href="https://jmeter.apache.org/download_jmeter.cgi">https://jmeter.apache.org/download_jmeter.cgi</a></p>
<p>mac 기준으로 설치 완료이후 터미널에 그냥 치면 창이 뜬다</p>
<p><code>jmeter</code> </p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/6a9dd5a8-ab8b-415a-bc27-9bf7ae1206a3/image.png" alt=""></p>
<p>jmeter 실행 완료~</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/9a0720e9-841a-4bcb-bb3c-798bef213a2c/image.png" alt=""></p>
<hr>
<h3 id="설정값-추가-a-z">설정값 추가 a-z</h3>
<p>부하테스트를 하기 전에 스레드를 추가해보자</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/bc54830d-ddf5-4dc0-8c5f-2808386bce1d/image.png" alt=""></p>
<ul>
<li>Number of Threads(users) : 몇명이?</li>
<li>Ramp-up period (seconds) : 몇초동안?</li>
<li>Loop Count : 몇번?</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/13c8d153-dfee-46d0-833b-0a31afd03db3/image.png" alt=""></p>
<p>추가로 헤더값이 필요하다면 헤더 매니저도 추가해보자</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/503c4da6-a925-4869-b0d9-d3405f3c0484/image.png" alt=""></p>
<p>하단에 <code>Add</code> 버튼으로 추가 가능하다. <code>key-value</code> 형태로 넣자
<img src="https://velog.velcdn.com/images/pang_e/post/e05d6eca-7cb9-43ea-b7ce-6192ff0f737f/image.png" alt=""></p>
<p>이제 테스트할 서버 정보 및 엔드포인트 추가하자</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/98658fb0-bd33-4bed-8a3e-8203fa2a3eff/image.png" alt=""></p>
<p>주요 데이터로는 다음처럼이 있을수 있겠다</p>
<ul>
<li>서버 DNS 또는 IP</li>
<li>port</li>
<li>http method</li>
<li>엔드포인트</li>
<li>데이터</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/f8d52063-7d36-4670-8aaf-6f68d057e911/image.png" alt=""></p>
<p>그럼 결과를 받아보는것도 추가해보자.
좀 여러가지가 있는데 직접 추가해서 비교해보면 좋을것 같다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/f4e720bd-a801-4801-9d2b-67aad0012b57/image.png" alt=""></p>
<p>결과 화면 까지 추가한 뒤 상단에 시작 버튼을 누르면 여태 준비한대로 테스트가 실행된다.</p>
<p>경고 팝업창이 뜨긴하는데, 저장하라는 것 일뿐 안 해도 상관없다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/96226c39-248b-4505-b220-19316182128e/image.png" alt=""></p>
<ul>
<li>만들어둔대로 실행되고, 성공/실패 값이 결과 화면값에 노출된다. (결과 리스너를 여러개 붙여놨다면, 모든 리스너에 기록된다)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/cf179c75-a23a-4288-823e-d347bc6266e4/image.png" alt=""></p>
<ul>
<li>조금씩 올려가면서 <code>vuser</code> 및 loop 를 늘려가며 부하테스트를 해보자</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/907ccb34-d152-4efa-aadb-e1d1cb6ce191/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Internet Explorer 에서 콘솔 로그 보기]]></title>
            <link>https://velog.io/@pang_e/Internet-Explorer-%EC%97%90%EC%84%9C-%EC%BD%98%EC%86%94-%EB%A1%9C%EA%B7%B8-%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@pang_e/Internet-Explorer-%EC%97%90%EC%84%9C-%EC%BD%98%EC%86%94-%EB%A1%9C%EA%B7%B8-%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 12 Aug 2025 13:55:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>예전에 관짝닫고 들어간녀석 아니었나요?</p>
</blockquote>
<p>2022년부터 지원을 안한다곤 하긴하지만, 관공서등 아직도 쓰는곳이 있다고 한다</p>
<p>사내에서도 지원을 안한다고 나와있긴하지만 간혹 VOC 로 <code>internet explorer</code> 로 하면 안된다는 이슈가 인입되고있어, 최소한으로 지원을 하고있긴하다</p>
<hr>
<h3 id="왜안켜져">왜안켜져</h3>
<p>어휴 이놈 켜지지도 않는다</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/b1bebc16-6678-4ad5-80d5-36414a93c929/image.png" alt=""></p>
<ul>
<li>병합됬다고 이제는 엣지가 켜진다. <del>관공서에서는 도대체 어케 사용하나요</del></li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/7307f692-852d-4037-85a2-3714923682ab/image.png" alt=""></p>
<p>지원하지 않는다고 발표한 2022년부터 많은 <code>edge 대신 internet explorer 켜기</code> 블로그 글이 존재하는데, 나는 성공한게 없었다...</p>
<p>여태 해봤는데 실패 매드무비 리스트</p>
<ul>
<li><code>BHO</code> 폴더 삭제</li>
<li>기본 프로그램 설정</li>
<li>Edge 자동 실행 레지스트리 변경</li>
<li>PC 업데이트 다운그레이드</li>
</ul>
<p>대신 edge에서 <strong>internet explorer 모드에서 켜기</strong>로 켜줬다.</p>
<hr>
<h3 id="internet-explorer-모드로-켜기">internet explorer 모드로 켜기</h3>
<p>edge 로 사이트 이동한 뒤, 우측 상단의 <code>...</code> 버튼을 눌러 <code>Internet Explorer 모드에서 다시 로드</code> 버튼을 누른다</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/b1413255-e229-4801-91f3-1cbb86f4f3d1/image.png" alt=""></p>
<p>대부분의 페이지는 Edge에서 잘 작동한다는 기가막힌 팁이 있으니 다들 꼭 기억하자.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/29829158-9dba-4864-bc48-dde3f31673bf/image.png" alt=""></p>
<p>콘솔창을 보면서 디버깅해야하는데 콘솔창에 아무것도 안나오네;;</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/03016913-dae6-4ad3-b202-c884d94bcf1f/image.png" alt=""></p>
<blockquote>
<p>Edge 의 IE 모드는 내부적으로 Internet Explorer 엔진을 사용하기 때문에, 일반적인 Edge 개발자 도구에서는 <strong>콘솔 로그가 표시되지 않습니다.</strong></p>
</blockquote>
<hr>
<h3 id="다-방법이-있습니다">다 방법이 있습니다</h3>
<p><code>ctrl + r</code> 을 눌러 실행창을 켜서 다음을 입력하자</p>
<ul>
<li><code>%systemroot%\system32\f12\IEChooser.exe</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/569391a1-4e0b-4e1f-926c-35e8600da36d/image.png" alt=""></p>
<p>그럼 IE 중에 선택할수있는 대상이 나오는데, 콘솔창을 띄우고싶은걸 선택하자</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/ba338167-2183-4f52-bbfd-652f88914927/image.png" alt=""></p>
<p>캬;; 이제 해피 디버깅타임</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/37432809-e051-4f81-812c-a8a6c5b5c950/image.png" alt=""></p>
<hr>
<ul>
<li>다들 부탁입니다. 이제 인터넷 익스플로어는 보내주자구요</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/7d228c69-bba8-404f-94a8-2dd46850143f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MAC 용량별 파일 만들기]]></title>
            <link>https://velog.io/@pang_e/MAC-%EC%9A%A9%EB%9F%89%EB%B3%84-%ED%8C%8C%EC%9D%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@pang_e/MAC-%EC%9A%A9%EB%9F%89%EB%B3%84-%ED%8C%8C%EC%9D%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Mon, 16 Jun 2025 04:43:25 GMT</pubDate>
            <description><![CDATA[<p>특정 용량을 가진 더미 데이터를 만들어야겠다.
CLI 에서 간단하게 명령어로 사용이 가능하다.</p>
<h3 id="mkfile">mkfile</h3>
<p><code>man mkfile</code> 을 보면 세부적인 내용을 확인할수있는데 옵션이 많은 명령어는 아니다.
<del>mkdir 이랑 비슷하군</del></p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/607de33f-930c-4e4a-9fcb-fa521d89dffa/image.png" alt=""></p>
<hr>
<h3 id="사용">사용</h3>
<p><code>mkfile [옵션] [파일크기] [파일명]</code></p>
<ul>
<li>파일 크기는 바이트 기준이다.</li>
<li>suffix 에 <code>b</code>, <code>k</code>, <code>m</code>, <code>g</code> 등이 올수있다.</li>
</ul>
<p>ex) <code>mkfile 100m 100m.txt</code> : <code>100m.txt</code> 이름을 가진 100mb 크기의 파일을 생성</p>
<hr>
<h3 id="옵션">옵션</h3>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>-n</td>
<td>파일을 생성하긴하지만, 디스크에 기록하지 않는다.<br/> 논리적인 용량은 있지만 실제 용량을 차지하진 않는다</td>
</tr>
<tr>
<td>-v</td>
<td>결과값 노출</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Transaction numbers are only allowed on a replica set member or mongos]]></title>
            <link>https://velog.io/@pang_e/Transaction-numbers-are-only-allowed-on-a-replica-set-member-or-mongos</link>
            <guid>https://velog.io/@pang_e/Transaction-numbers-are-only-allowed-on-a-replica-set-member-or-mongos</guid>
            <pubDate>Sat, 31 May 2025 09:19:06 GMT</pubDate>
            <description><![CDATA[<h3 id="아잇">아잇</h3>
<blockquote>
<p>com.mongodb.MongoCommandException: Command failed with error 20 (IllegalOperation): &#39;Transaction numbers are only allowed on a replica set member or mongos&#39; on server 127.0.0.1:27017. The full response is {&quot;ok&quot;: 0.0, &quot;errmsg&quot;: &quot;Transaction numbers are only allowed on a replica set member or mongos&quot;, &quot;code&quot;: 20, &quot;codeName&quot;: &quot;IllegalOperation&quot;}</p>
</blockquote>
<p>spring boot 와 mongodb를 사용하고 있는데 <code>@Transactional</code> 어노테이션을 쓰니 위와같은 에러가 발생한다.</p>
<p>에러에서 확인할 수 있듯, <code>replica set</code> 에서만 동작이 가능하다는 의미다.</p>
<ul>
<li>해당 내용은 <a href="https://monday9pm.com/mongodb%EC%9D%98-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%B2%98%EB%A6%AC%EC%99%80-%EC%9D%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B5%AC%EC%84%B1-d2118dbd3429">여기 블로그</a>와 <a href="https://velog.io/@rosewwross/Database-architecture">저기 블로그</a>에서 정리를 기가막히게 해놓았으니 참고하면 좋을것같다.</li>
</ul>
<hr>
<h3 id="로그-확인">로그 확인...</h3>
<p>디버그 모드로 로그를 찍고, 서버 연결시 수초마다 한번씩 다음과 같이 로그가 찍히는데 <code>type=STANDALONE</code> 이라는 로그를 확인할 수 있다.</p>
<pre><code>2025-05-29T17:18:31.092+09:00 DEBUG 46252 --- [127.0.0.1:27017] org.mongodb.driver.cluster               : Updating cluster description to  {type=STANDALONE, servers=[{address=127.0.0.1:27017, type=STANDALONE, roundTripTime=13.6 ms, state=CONNECTED}]
2025-05-29T17:18:31.093+09:00 DEBUG 46252 --- [127.0.0.1:27017] org.mongodb.driver.cluster               : Checking status of 127.0.0.1:27017</code></pre><p><img src="https://velog.velcdn.com/images/pang_e/post/900363f2-6a97-4891-b89f-f25d6dd8312c/image.png" alt=""></p>
<p>해당 타입을 <code>replica set</code> 으로 동작하도록 변경해보자!</p>
<hr>
<h3 id="로컬에서도-복제본으로">로컬에서도 복제본으로..!</h3>
<p>일단 나는 MongoDB를 <code>homebrew</code> 으로 설치했었었다.</p>
<p>그리고 설정파일은 <code>/opt/homebrew/etc/mongod.conf</code> 경로에 있었다.</p>
<p>설정파일 하위에 다음과 같이 레플리카 설정을 추가하자.</p>
<pre><code class="language-yaml">replication:
  replSetName: rs0</code></pre>
<p><img src="https://velog.velcdn.com/images/pang_e/post/36936f21-68f1-4bb4-8f00-8483c4fafd5d/image.png" alt=""></p>
<p>그리고 mongodb를 재시작 해주자.</p>
<p>리스트를 확인하고 mongodb 에 해당하는 서비스를 재시작</p>
<pre><code class="language-bash">brew services list

brew services restart mongodb-communitiy</code></pre>
<p><img src="https://velog.velcdn.com/images/pang_e/post/d49bd3cb-0f0c-4397-8d49-94a8cb53b84c/image.png" alt=""></p>
<hr>
<p>그리고 다시 로그 확인해봤는데 뭔가 이상하다.
<code>type=REPLICA_SET</code>이 되긴 했는데, 내부 설정이 <code>TYPE=REPLICA_SET_GHOST</code> 라고 되어있고 여전히 동작하지 않는다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/77ef1498-49c4-40e8-a7cd-506d1e4eb400/image.png" alt=""></p>
<p>찾아보니 최초에 한번 초기화작업을 해줘야한다고 한다.</p>
<p>mongosh 접속해서 설정해주자.</p>
<pre><code>// mongosh 접속 (없으면 설치)
mongosh

// 상태값 확인
rs.status()

// 초기화
rs.initiate()</code></pre><p><img src="https://velog.velcdn.com/images/pang_e/post/c3426288-3eac-4d5d-a9dc-7fd04003c844/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/db36d690-df98-4480-a7b4-e8233f292d6d/image.png" alt=""></p>
<p>이제 헐레벌떡 로그확인 하러 가자</p>
<hr>
<p>성공~</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/1eaede9f-6e30-4d3c-9b96-946c37486f15/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[cannot lock file hash cache as it has already been locked by this process]]></title>
            <link>https://velog.io/@pang_e/cannot-lock-file-hash-cache-as-it-has-already-been-locked-by-this-process</link>
            <guid>https://velog.io/@pang_e/cannot-lock-file-hash-cache-as-it-has-already-been-locked-by-this-process</guid>
            <pubDate>Thu, 15 May 2025 15:12:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>왜 나한테만 이런 시련이</p>
</blockquote>
<h3 id="흠">흠....</h3>
<p>사실 왜 발생했는지는 모른다. Intellij 에서 스프링 서버를 실행하려고 했는데, 다음과 같은 에러가 발생한다</p>
<pre><code>Gradle could not start yout build.
&gt; could not create service of type FileHasher using BuildSessionService.createFileHasher()
  &gt; cannot lock file hash cache as it has already been locked by this process</code></pre><p><img src="https://velog.velcdn.com/images/pang_e/post/31386c25-1896-4fac-80a2-1d4c96f52cb5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/aac49a6d-1d30-4aef-9101-1f009ab131f5/image.png" alt=""></p>
<hr>
<h3 id="해결">해결....</h3>
<ul>
<li><strong>껐다켜봤다 -&gt; 안된다</strong></li>
<li><strong><code>.gradle</code> 폴더 삭제했다 -&gt; 안된다</strong></li>
<li><strong><code>intellij</code> 캐시 삭제했다 -&gt; 안된다</strong></li>
<li><strong>재부팅했다 -&gt; 안된다</strong></li>
</ul>
<p>그래서 구글에 찾아보니 <a href="https://stackoverflow.com/questions/45177977/gradle-could-not-create-service-of-type-filehasher">스택오버플로우</a>에서 다음과 같이 하라고 한다.</p>
<pre><code class="language-bash">// gradle 과 관련된 실행 프로세스 찾기
ps aux | grep gradle

// 해당 실행 프로세스 삭제
kill -9 &lt;PID&gt;</code></pre>
<ul>
<li><code>ps aux | grep gradle</code> 명령어를 통해 다음 두가지 프로세스가 백그라운드에서 실행되고 있는걸 확인했다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/0fbc4234-ad05-4dd6-9e44-7a7345450890/image.png" alt=""></p>
<hr>
<ul>
<li>아무리봐도 <code>44540</code> PID 프로세스가 gradle 관련 프로세스처럼 보이는데, <code>62363</code> PID 프로세스를 죽이려하니 <code>failed: no such process</code> 가 발생한다 (왜인지 아는 사람은 알려주면 감사하겠습니다..)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/9a144c74-29dc-4cc0-8c4e-ebe9c34fcc41/image.png" alt=""></p>
<hr>
<ul>
<li>다행히 이제 gradle 으로 실행이 가능해졌다!
<img src="https://velog.velcdn.com/images/pang_e/post/df72d5cb-808f-449e-ad02-c7a6f6371da6/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[jcmd로 스프링 힙덤프 뜨기]]></title>
            <link>https://velog.io/@pang_e/spring-%ED%9E%99%EB%8D%A4%ED%94%84-%EB%9C%A8%EB%8A%94%EB%B2%95</link>
            <guid>https://velog.io/@pang_e/spring-%ED%9E%99%EB%8D%A4%ED%94%84-%EB%9C%A8%EB%8A%94%EB%B2%95</guid>
            <pubDate>Thu, 15 May 2025 03:44:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>실행중인 스프링 어플리케이션에서 힙덤프 파일을 뜨고싶다</p>
</blockquote>
<h3 id="jcmd">JCMD</h3>
<p><a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/jcmd.html">공식문서</a>에서 자세한 정보를 확인할 수 있다.</p>
<ul>
<li>JVM 환경을 진단 명령을 하는 용도로 사용된다.</li>
<li>로컬에서 실행중인 Java 프로세스</li>
<li>JDK 에 포함되어있다.</li>
</ul>
<h3 id="힙덤프뜨기">힙덤프뜨기!!!</h3>
<p>일단 지금 실행중인 프로세스가 어떤게 있는지 확인해보자.</p>
<pre><code class="language-bash">jcmd -l</code></pre>
<p>기본으로 떠있는게 다음과 같다.
<img src="https://velog.velcdn.com/images/pang_e/post/f8d89f90-5394-4643-b312-8ad4d2a99af9/image.png" alt=""></p>
<p>이제 스프링 프로젝트를 실행한뒤, 실행중인 프로세스를 확인해보자
<code>39117</code> 라는 <code>PID</code>를 가진 어플리케이션이 떴다!
<img src="https://velog.velcdn.com/images/pang_e/post/8c170dba-b00a-431d-8fb2-c87a3c4249df/image.png" alt=""></p>
<hr>
<p>어떤 명령어들을 할수 있을지 확인해보자
다음 명령어로, jcmd에 어떤 command를 사용할수있는지 확인할 수 있다.
중간에 보면 우리가 하려는 힙덤프 명령어도 있다.</p>
<pre><code class="language-bash">jcmd &lt;PID&gt; help</code></pre>
<p><img src="https://velog.velcdn.com/images/pang_e/post/45ba5458-6896-49bc-8357-d39a2195133c/image.png" alt=""></p>
<hr>
<p>다음 명령어로 힙덤프를 떠보자</p>
<pre><code class="language-bash">jcmd &lt;PID&gt; GC.heap_dump &lt;저장경로&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/pang_e/post/2c69dd1a-712e-41ec-a9b4-57e96d1847cc/image.png" alt=""></p>
<hr>
<p>힙덤프파일 완성~
이제 힙덤프파일 분석하러가자....</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[공유기에서 포트포워딩 설정하기]]></title>
            <link>https://velog.io/@pang_e/%EA%B3%B5%EC%9C%A0%EA%B8%B0%EC%97%90%EC%84%9C-%ED%8F%AC%ED%8A%B8%ED%8F%AC%EC%9B%8C%EB%94%A9-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pang_e/%EA%B3%B5%EC%9C%A0%EA%B8%B0%EC%97%90%EC%84%9C-%ED%8F%AC%ED%8A%B8%ED%8F%AC%EC%9B%8C%EB%94%A9-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 05 May 2025 14:55:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>포트포워딩 하고싶다!!!!!</p>
</blockquote>
<hr>
<h3 id="내-공유기의-ip를-알아보자">내 공유기의 IP를 알아보자</h3>
<p>공유기에 접속하려면 IP를 알아야한다.
통상적으로 각 통신사에서는 다음과 같은 주소로 접속할 수 있다 (잘 모름) </p>
<table>
<thead>
<tr>
<th>통신사</th>
<th>IP</th>
</tr>
</thead>
<tbody><tr>
<td>KT</td>
<td>192.168.219.1</td>
</tr>
<tr>
<td>SK</td>
<td>192.168.35.1</td>
</tr>
<tr>
<td>LG</td>
<td>192.168.219.1 or 192.168.123.1</td>
</tr>
</tbody></table>
<hr>
<p>직접 확인하고 싶다면 다음과 같은 명령어로 확인하자</p>
<table>
<thead>
<tr>
<th>OS</th>
<th>명령어</th>
</tr>
</thead>
<tbody><tr>
<td>Windows</td>
<td>ipconfig</td>
</tr>
<tr>
<td>MAC</td>
<td>netstat -nr | grep default</td>
</tr>
</tbody></table>
<hr>
<ul>
<li>본인 컴퓨터는 MAC 이라 <code>netstat -nr | grep default</code> 으로 확인했다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/b46e17f0-28e4-4def-be97-88dbac2db363/image.png" alt=""></p>
<hr>
<h3 id="접속해서-바꿔보자">접속해서 바꿔보자</h3>
<p>공유기 개인정보 빼고 가렸지만, 하여간 접속했다. (LG 공유기임)
<img src="https://velog.velcdn.com/images/pang_e/post/3d4fb673-4ce0-4e35-9bd6-e67890c4952d/image.png" alt=""></p>
<hr>
<p>접속하면 로그인해라고 팝업이 뜨는데, 공유기에 찾아보면 관리자 비밀번호가 따로 기재되어있다. 적어주자
<img src="https://velog.velcdn.com/images/pang_e/post/3a633b24-c9ad-4ba6-b7ab-4896ab56a92c/image.png" alt=""></p>
<hr>
<ul>
<li>GNB - 네트워크 설정 - NAT 설정으로 들어가면
<img src="https://velog.velcdn.com/images/pang_e/post/7d5ca15b-4dbb-4155-8522-7349ab8f7fe8/image.png" alt=""></li>
</ul>
<hr>
<ul>
<li>짜잔 포트포워딩을 할수있다.
<img src="https://velog.velcdn.com/images/pang_e/post/9ce2e32a-4f65-41cc-95eb-7cdef74bb47f/image.png" alt=""></li>
</ul>
<hr>
<ul>
<li><del>손글씨 힘드네</del> 하여간 <code>서비스 포트 = 외부에서 들어오는 포트</code> 라고 생각하면 된다</li>
<li>나는 내부 시놀로지로 들어가도록 해야하기 때문에 <code>내부 IP 주소</code>에는 시놀로지 IP를 기재했다
<img src="https://velog.velcdn.com/images/pang_e/post/8596ec53-74a6-47d5-a840-5a110f46b5a8/image.png" alt=""></li>
</ul>
<hr>
<ul>
<li>정상적으로 설정하면 목록에선 다음과 같이 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pang_e/post/7433387e-8cf7-4ced-b51d-3c126c06646e/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[젠킨스 설정 날라감...]]></title>
            <link>https://velog.io/@pang_e/%EC%A0%A0%ED%82%A8%EC%8A%A4-%EC%84%A4%EC%A0%95-%EB%82%A0%EB%9D%BC%EA%B0%90</link>
            <guid>https://velog.io/@pang_e/%EC%A0%A0%ED%82%A8%EC%8A%A4-%EC%84%A4%EC%A0%95-%EB%82%A0%EB%9D%BC%EA%B0%90</guid>
            <pubDate>Thu, 01 May 2025 08:39:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>올해초에 시놀로지 구매해서 이것저것 서버 띄우면서 잘 놀다가, 이사오고나서 놓았었다.
오랜만에 다시 들어가서 하려니 도커로 띄워놓은 젠킨스 설정 바꾸고 젠킨스 들어가니 처음부터 세팅하라는 화면이 나온다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pang_e/post/aca0563c-0e42-434b-9b0b-6d0b1bb46864/image.png" alt=""></p>
<ul>
<li>난 예전에 다 했던건데;;</li>
</ul>
<hr>
<h3 id="아-왜날라가요">아 왜날라가요</h3>
<p>젠킨스의 설정파일을 내가 따로 저장을 안했으니까!!!
당연하게도 젠킨스의 설정파일은 도커에서 젠킨스를 실행하여 어딘가에 저장될텐데 내가 따로 외부에 저장한게 없었다. 그냥 컨테이너 내리고 재시작하니 다시 초기세팅이 되어버린것.</p>
<p><a href="https://www.jenkins.io/doc/book/installing/docker/">공식문서</a>에서도 한글로 안해줬을뿐 말해줬다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/866c987f-5859-4413-a10c-7978f9e95663/image.png" alt=""></p>
<hr>
<h3 id="다시-붙여보자">다시 붙여보자</h3>
<ul>
<li><a href="https://www.jenkins.io/doc/book/installing/docker/">공식문서</a>에서도 도커 볼륨 잡아라고 했으니 직접 볼륨 붙여본다.</li>
<li>시놀로지는 <code>Contianer Manager</code> 를 통해 CLI 가 아니라 마우스 <strong>딸깍</strong>으로 설정 가능하다.
<img src="https://velog.velcdn.com/images/pang_e/post/2f297ba8-5224-445f-ac0a-5b23ca9844a9/image.png" alt=""></li>
<li>볼륨도 붙였겠다 다시 시작한다.
<img src="https://velog.velcdn.com/images/pang_e/post/47337a8a-8463-483b-9530-7c47c020a375/image.png" alt=""></li>
<li>권한 이슈면 해당 폴더는 누구든 접근할수 있도록 하자!
<img src="https://velog.velcdn.com/images/pang_e/post/f37654fc-0c11-473c-9181-20bcbd1f02df/image.png" alt=""></li>
<li>권한을 nobody로 변경하여 해치웠다.
<img src="https://velog.velcdn.com/images/pang_e/post/da78620d-3258-48a0-b908-9eaf35068b7d/image.png" alt=""></li>
<li>그래도 똑같이 <code>Permission Denied</code> 권한 에러가 발생해서 좀 찾아봤다.
nobody 가 아니라 Jenkins 컨테이너의 UID 로 변경해야 한다고 한다.</li>
</ul>
<hr>
<p>UID 확인 : <code>docker exec -it %젠킨스_컨테이너_ID% id</code></p>
<img src = "https://velog.velcdn.com/images/pang_e/post/ca4ed2e0-52c4-4a0c-902b-350028980dcd/image.png" width ="500" />

<ul>
<li>사용자 ID도 1000이고 그룹 ID도 1000이다.</li>
</ul>
<hr>
<p>권한 변경 : <code>chown -R %사용자ID%:%그룹ID% %경로%</code></p>
<img src = "https://velog.velcdn.com/images/pang_e/post/2d228edf-4ee3-4d9d-ac18-6d93f9d70a68/image.png" width ="400" />

<hr>
<p>이제 진짜 해치웠다.
<img src="https://velog.velcdn.com/images/pang_e/post/19e88c6c-8364-4ff2-8b4c-479c295cda61/image.png" alt=""></p>
<hr>
<p>이제 세팅 다시하러가자.....</p>
<img src = "https://velog.velcdn.com/images/pang_e/post/7fc2be19-51fb-4538-8eba-bc01788ce993/image.gif" width = "300px"/>
]]></description>
        </item>
        <item>
            <title><![CDATA[10분만에 자동배포 프로세스 만들기 ]]></title>
            <link>https://velog.io/@pang_e/10%EB%B6%84%EB%A7%8C%EC%97%90-%EC%9E%90%EB%8F%99%EB%B0%B0%ED%8F%AC-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@pang_e/10%EB%B6%84%EB%A7%8C%EC%97%90-%EC%9E%90%EB%8F%99%EB%B0%B0%ED%8F%AC-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 31 Dec 2024 06:37:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>얼마전에 AI로 어플을 직접 만든 이후, 이것저것 만들어보는 취미가 생겼다.
<a href="https://velog.io/@pang_e/ChatGPT-%EB%A1%9C-%EC%96%B4%ED%94%8C%EB%A7%8C%EB%93%A4%EA%B8%B0">따봉 GPT야 고마워~</a>
그러다보니 로컬에서 관리하기엔 한계도 있고... 무엇보다도 빌드-배포를 직접하는게 귀찮다!!</p>
</blockquote>
<h3 id="github">Github</h3>
<p>솔직히 나는 깃헙에 손이 잘 안간다.</p>
<p>매일매일 공부를 하며 잔디관리를 하는분도 계시긴하지만, 뭔가 의무감처럼 느껴지는 느낌이랄까 🧐
<img src="https://velog.velcdn.com/images/pang_e/post/11eca902-6c90-4404-bd6b-293ec585221f/image.webp" alt=""></p>
<p>이번에 혼자 어플을 만들며 놀다보니, 사내와 같이 <code>특정 브랜치에 올리기만하면 자동배포가 되는 그런 환경을 구축하고싶다!</code> 라는 생각이 들었다.</p>
<p>그리고 내가 배포할때 사용했었던 firebase에서는 아주 쉽게 해당 기능을 쓸수있다!</p>
<hr>
<h3 id="as-is">as-is</h3>
<p>그러니깐 여태까지는 <a href="https://firebase.google.com/docs/hosting?hl=ko">Firebase 공식문서</a>를 확인하면서 손수 배포를 진행했는데, 그 과정은 간략하게 다음과 같다</p>
<pre><code class="language-bash"># Flutter 패키지 설치
flutter pub get
# Flutter 웹으로 빌드
flutter build web
# Firebase 배포
firebase deploy</code></pre>
<p>쓰고나니깐 간단하긴한데 오탈자 하나 수정하고나서 저 과정을 맨날 해야하는게 쉽지않다 ...</p>
<h3 id="to-be">to-be</h3>
<h4 id="배포할-어플-뚝딱">배포할 어플 뚝딱</h4>
<ul>
<li>일단 간단하게 페이지 하나 뚝딱 만들어보자.<img src = "https://velog.velcdn.com/images/pang_e/post/753f1a64-767d-4316-b51e-b85e07c515c0/image.png" width=700px/></li>
<li>뚝딱</li>
</ul>
<h4 id="firebase-홈페이지">Firebase 홈페이지</h4>
<ul>
<li><p>배포할 프로젝트는 만들었고, Firebase 홈페이지에서 프로젝트를 만들자!
<a href="https://console.firebase.google.com/?hl=ko">Firebase 홈페이지</a>
<img src="https://velog.velcdn.com/images/pang_e/post/de153b7b-779a-4c54-8228-31ddfdae428f/image.png" alt=""></p>
</li>
<li><p>이름도 내가 원하는걸 지정해보고
<img src="https://velog.velcdn.com/images/pang_e/post/fba4cf37-7a98-45a9-8b60-80e2a62180c7/image.png" alt=""></p>
</li>
<li><p>분석 예... 해주십시오
<img src="https://velog.velcdn.com/images/pang_e/post/069b2d44-cff2-4fc2-a183-2bd0cac66579/image.png" alt=""></p>
</li>
<li><p>기본계정으로 뚝딱
<img src="https://velog.velcdn.com/images/pang_e/post/52b5ff1f-0399-4a5b-84c1-7fe11f855244/image.png" alt=""></p>
</li>
<li><p>하면 만들어준다!
<img src="https://velog.velcdn.com/images/pang_e/post/5574faa4-702b-4ded-9934-b13e4636e07f/image.png" alt=""></p>
</li>
</ul>
<hr>
<h4 id="firebase와-내-어플을-연결하자">Firebase와 내 어플을 연결하자!</h4>
<ul>
<li><p>배포할 내 어플의 루트폴더로 다시가서 다음과 같은 명령어를 치자!
<code>firebase init</code></p>
</li>
<li><p>그럼 다음과 같은 화면이 나올텐데 천천히 해석하면 알아들을수 있다. <del>물론 나는 해석안하고 넘겨서 github에서 자동배포는 나중에 알았다</del></p>
</li>
<li><p>우리는 배포용으로 사용하니 <code>Hosting</code> 선택
<img src="https://velog.velcdn.com/images/pang_e/post/0f70b9f5-9fd5-405d-88c9-2d6b5dd03ecc/image.png" alt=""></p>
</li>
<li><p>우리는 Firebase 홈페이지에서 프로젝트를 만들고 왔기 때문에  <code>Use an existing project</code> 선택
<img src="https://velog.velcdn.com/images/pang_e/post/a992c13c-20fa-4b8d-95bc-09140f808c30/image.png" alt=""></p>
</li>
<li><p>이후 입맛에 맞게 선택 몇개하다보면 다음과 같은 문항이 나온다.</p>
</li>
<li><p>Github 자동배포.. 해주십시오...
<img src="https://velog.velcdn.com/images/pang_e/post/2202d41d-d500-462e-ac1d-cbafd62de19b/image.png" alt=""></p>
</li>
</ul>
<h4 id="github-배포될-프로젝트-만들기">github 배포될 프로젝트 만들기</h4>
<ul>
<li><p>github에서 사용자 인증을 하고 다음과 같이 github repository를 달라고 한다.
<img src="https://velog.velcdn.com/images/pang_e/post/bf3ac767-903b-46d1-b251-9c2984b011ec/image.png" alt=""></p>
</li>
<li><p>그렇담 <a href="https://github.com">github</a>가서 하나 뚝딱 만들자
<img src="https://velog.velcdn.com/images/pang_e/post/9235efef-3a3d-4c71-8463-e97be8b11033/image.png" alt=""></p>
</li>
<li><p>방금 만든 github 주소를 넣어주면 배포되기전 빌드를 하겠냐고 물어봐준다.</p>
</li>
<li><p>빌드가 필요하다면 넣어주자!
<img src="https://velog.velcdn.com/images/pang_e/post/47de4f84-7174-4836-bc5f-b70d46632356/image.png" alt=""></p>
</li>
<li><p>예... 이것도 부탁드립니다.
<img src="https://velog.velcdn.com/images/pang_e/post/f4bbda51-7308-468f-bc7b-68defbba0a89/image.png" alt=""></p>
</li>
<li><p>어느 브랜치에 될때인지 지정해주기
<img src="https://velog.velcdn.com/images/pang_e/post/a17017c4-6a14-4418-a279-65c637d44990/image.png" alt=""></p>
</li>
<li><p>여기까지 하면 뭔가 다된것처럼 보인다!</p>
</li>
</ul>
<hr>
<h3 id="자동으로-생성된-파일들-커스텀하기">자동으로 생성된 파일들 커스텀하기</h3>
<ul>
<li><p>루트폴더에 생성되어있는 <code>.github/workflow</code> 를 살펴보자</p>
</li>
<li><p>두가지 파일이 생성되어있는데, 이것들을 내 프로젝트에 맞게 커스텀해줘야한다.</p>
</li>
<li><p><code>Flutter</code> 를 사용하며 내게 필요한 빌드 과정들을 <code>jobs.build_and_deploy.steps</code> 에서 순차적으로 변경해준다.</p>
<pre><code class="language-yml"># firebase-hosting-merge.yml
name: Deploy to Firebase Hosting on merge
on:
push:
  branches:
    - main
jobs:
build_and_deploy:
  runs-on: ubuntu-latest
  steps:
    # Flutter 설치
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: &#39;3.27.1&#39;

    # Flutter 패키지 설치
    - name: Install dependencies
      run: flutter pub get

    # Flutter 웹 빌드
    - name: Build Flutter Web App
      run: flutter build web

    # Firebase Hosting 배포
    - uses: FirebaseExtended/action-hosting-deploy@v0
      with:
        repoToken: ${{ secrets.GITHUB_TOKEN }}
        firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_PANG_DEPLOY_TEST }}
        channelId: live
        projectId: pang-deploy-test
</code></pre>
</li>
</ul>
<pre><code>- 그리고 하나더 루트에 생성된 `firebase.json` 도 수정이 필요할수 있다!
- 나같은경우는 `flutter` 를 웹으로 빌드하기때문에 `build/web` 하위를 배포해야한다

```yml
# firebas.json
{
  &quot;hosting&quot;: {
    &quot;public&quot;: &quot;build/web&quot;, # 수정
    &quot;ignore&quot;: [
      &quot;firebase.json&quot;,
      &quot;**/.*&quot;,
      &quot;**/node_modules/**&quot;
    ],
    &quot;rewrites&quot;: [
      {
        &quot;source&quot;: &quot;**&quot;,
        &quot;destination&quot;: &quot;/index.html&quot;
      }
    ]
  }
}</code></pre><hr>
<h3 id="준비는-끝났다">준비는 끝났다</h3>
<ul>
<li><p>이제 git에 올려보자!!!
<img src="https://velog.velcdn.com/images/pang_e/post/b06f3232-bef6-4229-b974-dba36ca206cf/image.png" alt=""></p>
</li>
<li><p>내가 설정했던 <code>main</code> 브랜치에 올리니 아주 잘되어보인다. 만족스럽다</p>
</li>
<li><p>firebase로 배포된 URL을 보자
<img src="https://velog.velcdn.com/images/pang_e/post/44c43e0d-370b-4606-9b06-8aed0121fde8/image.png" alt=""></p>
</li>
<li><p>캬</p>
<img src="https://velog.velcdn.com/images/pang_e/post/3e8a708e-645c-4b38-bba6-c9c9d648430d/image.png" width=600 />

</li>
</ul>
<p><del>깃헙잔디들 딱대</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ChatGPT 로 어플만들기]]></title>
            <link>https://velog.io/@pang_e/ChatGPT-%EB%A1%9C-%EC%96%B4%ED%94%8C%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@pang_e/ChatGPT-%EB%A1%9C-%EC%96%B4%ED%94%8C%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 10 Nov 2024 06:54:26 GMT</pubDate>
            <description><![CDATA[<h3 id="결과물부터-보시죠">결과물부터 보시죠</h3>
<img width="250" src="https://velog.velcdn.com/images/pang_e/post/a5a8c977-e124-4633-9c4f-da293bc767cf/image.gif" />

<ul>
<li>ChatGPT 를 활용해서 만들었다는 그냥저냥의 회고록이랍니다</li>
</ul>
<hr>
<blockquote>
<p>이래 봬도 돈 관리를 열심히 하기 위해서 가계부를 꾸준히 작성 중이다.
며칠 전 집에 살림이 늘어나다 보니 공용으로 쓸 수 있는 가계부 앱을 다운로드했다.
아니 근데 이놈의 앱은 광고가 왜 이리 많은 것인가</p>
</blockquote>
<p>JSON싸개 호칭이 붙어있는 나로썬 어떻게 시작해야할지 막막했다. </p>
<p>심지어 뷰 영역은 그냥 HTML 개발자라고 할수있다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/12f2f6bf-9334-4ed1-8655-cbd798470546/image.png" alt=""></p>
<hr>
<h3 id="나도쓴다-chatgpt">나도쓴다 ChatGPT</h3>
<p>구글링을 하면서 하나하나 해볼수도 있겠지만 궁금해졌다.</p>
<p>요즘 그렇게 대세라고하는 AI를 어디까지 활용해볼수있을까?</p>
<p>chatgpt 한테 질문들이 여기 올리기 부끄럽긴한데 대부분 요렇게 사용하고있었다</p>
<ul>
<li><p>변수이름 추천좀요
<img src="https://velog.velcdn.com/images/pang_e/post/f24f16d5-a5e7-4f0c-a6bd-f8b8fcfc8fe0/image.png" alt=""></p>
</li>
<li><p>이거 설명좀요
<img src="https://velog.velcdn.com/images/pang_e/post/9ea2c374-a1bb-455c-ad66-1bbef51d6b96/image.png" alt=""></p>
</li>
</ul>
<hr>
<h3 id="일단-교육부터-시키자">일단 교육부터 시키자</h3>
<p>언제부터 적용되었는지 모르겠지만 나름 최근에 chatgpt에 <code>메모리 업데이트됨</code> 이라는게 생겼다. 
내가 한 말을 기억한다? 뭐 이런식으로 이해하고있다.</p>
<ul>
<li>왕초보방입니다. (전적봄)
<img src="https://velog.velcdn.com/images/pang_e/post/64187b32-40b6-4b83-87a7-838e68a3caa1/image.png" alt=""></li>
<li>한국인 패치좀요
<img src="https://velog.velcdn.com/images/pang_e/post/deface79-2e39-4f2b-9671-173331278df1/image.png" alt=""></li>
<li>질문 세개째 만에 왕초보인걸 인지했나보다. 다음 스텝도 알려주려고 한다.
<img src="https://velog.velcdn.com/images/pang_e/post/bfad8357-577f-455d-ac51-05dda7bd8b80/image.png" alt=""></li>
</ul>
<hr>
<h3 id="이젠-나도-기획자">이젠 나도 기획자</h3>
<p>내가 구상한 기획을 chatgpt 에게 쏟아내보자.</p>
<ul>
<li>가계부 어플 달력으로 만들어줘
<img src="https://velog.velcdn.com/images/pang_e/post/12581a85-cce5-432e-bddb-7b9ab4c5bbb2/image.png" alt=""></li>
<li>흠... 입력하는방식 바꿔줘
<img src="https://velog.velcdn.com/images/pang_e/post/0c353288-dacc-4211-b941-41cf77cf8df2/image.png" alt=""></li>
<li>조회 결과 나오게 해줘
<img src="https://velog.velcdn.com/images/pang_e/post/19458a0f-6b29-4a8b-8595-3f0fa88fd86a/image.png" alt=""></li>
<li>에러 왜남!!!!!!
<img src="https://velog.velcdn.com/images/pang_e/post/5b9008a1-b951-435e-907c-bebe4fc59c0d/image.png" alt=""></li>
<li>DB 연동해줘
<img src="https://velog.velcdn.com/images/pang_e/post/daac25db-ef69-4e5e-9558-fba5124ba4e5/image.png" alt=""></li>
<li>진짜 초보만 제발
<img src="https://velog.velcdn.com/images/pang_e/post/77d578ff-72e2-441a-9d23-7b4402c896fc/image.png" alt=""></li>
<li>추가기능 넣어서 2차개편 해줘
<img src="https://velog.velcdn.com/images/pang_e/post/4d7a829e-3ee6-4a0c-81b1-b76a694ddf22/image.png" alt=""></li>
<li>한 파일의 볼륨이 너무 커지는데 파일 분리해줘
<img src="https://velog.velcdn.com/images/pang_e/post/7780f676-46e8-4624-8927-c201bfcfa360/image.png" alt=""></li>
<li>너가만든 코드 직접보고 기능추가해줘
<img src="https://velog.velcdn.com/images/pang_e/post/7026be9c-f4da-4303-bb1c-48d15366790d/image.png" alt=""></li>
</ul>
<hr>
<h3 id="따봉gpt야-고마워">따봉GPT야 고마워</h3>
<p>이번에 AI를 통해 어플리케이션을 만들어봤는데 JSON 싸개로써 아주 재미있는 작업이었다. <del>기계부수기 운동 드가자</del></p>
<p>추후에 또 이런 재미난 작업이 생각난다면 다시한번 해봐야겠다.
<img src="https://velog.velcdn.com/images/pang_e/post/c4dc49e5-6252-499c-905e-f93835c4c286/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[부동소수점과 고정소수점]]></title>
            <link>https://velog.io/@pang_e/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90%EA%B3%BC-%EA%B3%A0%EC%A0%95%EC%86%8C%EC%88%98%EC%A0%90</link>
            <guid>https://velog.io/@pang_e/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90%EA%B3%BC-%EA%B3%A0%EC%A0%95%EC%86%8C%EC%88%98%EC%A0%90</guid>
            <pubDate>Tue, 17 Sep 2024 08:12:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>앞으로 돈에 관련된 프로젝트를 맡게 될 예정이라 조금 찾아보다보니 <code>BigDecimal</code> 자료형을 사용해야한다고 한다.
<code>DataSize</code> 를 사용하는것처럼 직관적이게 사용하기위해 그런걸까 싶어서 관련 내용을 조금 찾아봤다.</p>
</blockquote>
<p>java에서 실수(mistake 아님)를 표현하는 방식으로 <code>double</code>, <code>float</code> 등을 사용할수있다. 그런데 이녀석들은 정확하지 않다.</p>
<h3 id="double">double</h3>
<p>0.1 과 0.2를 더하면 0.3이 된다.</p>
<p>아주 간단한 테스트다.</p>
<pre><code class="language-java">@Test
void wrong_double_test(){
    double a = 0.1;
    double b = 0.2;
    assertThat(a + b).isEqualTo(0.3);
}</code></pre>
<p>아니, 틀렸다!</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/c5b36f5f-2e15-4b39-9c31-03b48fa0dd2b/image.png" alt=""></p>
<hr>
<h3 id="float">float</h3>
<p>그렇담 이건 어떨까</p>
<p>0.1을 100번 더한다!</p>
<p>그렇담 당연히 10이 아닐까?</p>
<pre><code class="language-java">@Test
void wrong_float_test(){
    float result = 0f;
    for(int i = 0; i &lt; 100; i++){
        result += 0.1f;
    }
    assertThat(result).isEqualTo(10f);
}</code></pre>
<p>그렇게 생각했다면 역시 틀렸다!</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/713b5eb4-584c-4296-8331-7ef797cfaf97/image.png" alt=""></p>
<hr>
<h2 id="10진수를-2진수로">10진수를 2진수로~</h2>
<h3 id="정수부">정수부</h3>
<p>위와같은 실수 타입의 계산은 2진법을 사용하는 컴퓨터의 이슈로 생각하면 된다.</p>
<p>그러니깐 간단히 예를들면, 정수부는 다음 그림처럼 2진수로 깔끔하게 떨어질수있게 표현할수있다. (10진수 <code>19</code>를 2진수로 바꾸는 작업! )
<img src = "https://velog.velcdn.com/images/pang_e/post/8e7451b4-6c45-4f8b-97c5-916ae49b7dfc/image.png" width = "300px" /></p>
<p><a href="https://m.blog.naver.com/piyoro/221770535071">그림 출처</a></p>
<hr>
<h3 id="소수부">소수부</h3>
<p>그렇다면 소수부를 10진수에서 2진수로 변환해보자.</p>
<p>10진수를 2진수로 변환할때 정수부에서는 2를 나누어준것처럼, 소수부에서는 2를 곱하여 나오는 정수를 차례로 쓰는걸로 변환을 할수있다.</p>
<p>10진수 <code>0.625</code> 를 2진수로 변환하는 과정은 다음과 같다.</p>
<pre><code>1) 0.625 * 2 = 1.250  &gt;  1
2) 0.250 * 2 = 0.50   &gt;  0
3) 0.50  * 2 = 1.0    &gt;  1
</code></pre><p>깔끔하게 10진수 <code>0.625</code>는 2진수 <code>0.101</code>로 변환되는것을 확인했다.</p>
<hr>
<p>하지만 아주 <strong>운이 좋았다</strong>고 할수있다.</p>
<p>동일한 방법으로 10진수 <code>0.1</code>을 2진수로 변환해보자.</p>
<pre><code>1) 0.1 * 2 = 0.2   &gt;  0
2) 0.2 * 2 = 0.4   &gt;  0
3) 0.4 * 2 = 0.8   &gt;  0
4) 0.8 * 2 = 1.6   &gt;  1
5) 0.6 * 2 = 1.2   &gt;  1
6) 0.2 * 2 = 0.4   &gt;  0 
...</code></pre><p>동일하게 구하려고 했는데 <strong>2단계</strong>에서 나온 <code>0.2</code>가 <strong>6단계</strong>에서도 동일하게 나온걸 본다면 깔끔하게 떨어지지 않고 다음과 같이 구질구질하게 나올 예정이다.
<code>0.1₍₁₀₎ = 0.000110011001100...₍₂₎</code></p>
<p>여기에서 위 테스트가 실패한 이유를 알 수 있다. 컴퓨터는 제한된 메모리에 데이터를 저장하기 때문에, 2진수로 변환했을때 무한소수로 나오는 값들은 <strong>근사값</strong>을 저장하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/2defe8f0-eb14-4092-8171-6982c7a1a43d/image.webp" alt=""></p>
<hr>
<h2 id="고정소수점과-부동소수점-용어정리">고정소수점과 부동소수점 용어정리</h2>
<p>일단 용어부터 정리를 하겠다. <strong>고정</strong> 과 <strong>부동</strong> 은 같은말처럼 생겨서 헷갈린다...</p>
<ul>
<li><strong>고정(固定)</strong><img src = "https://velog.velcdn.com/images/pang_e/post/1549b76f-f4a6-4406-8ad4-cbcdf91b37fe/image.png" width = "500px"/>


</li>
</ul>
<ul>
<li><strong>부동(浮動)</strong><img src = "https://velog.velcdn.com/images/pang_e/post/73437100-f0db-42f4-9588-8e52db7eeeaf/image.png" width = "300px"/>

</li>
</ul>
<hr>
<h2 id="고정소수점">고정소수점</h2>
<ul>
<li>간단하게 설명하고 넘어가겠다. 저장할 공간에 정수부를 저장하는 비트와 실수부를 저장하는 공간이 <strong>고정</strong>되어있다는것.</li>
<li>예를들면 다음 그림은 32bit를 저장하는 공간에서 정수부에는 16bit만큼, 소수부에는 15bit만큼 넣겠다고 고정한 자료형이 되겠다.
<img src="https://velog.velcdn.com/images/pang_e/post/c140f27d-68a6-4667-ac4f-eda080ac9444/image.png" alt=""></li>
<li>하지만 숫자를 사용하다보면 아주 큰수를 사용할때가 있고, 소수점으로 아주 세밀하게 사용할 때도 있기때문에 해당 메모리를 효율적으로 사용할 수 없을때가 있다.</li>
</ul>
<hr>
<h2 id="부동소수점">부동소수점</h2>
<ul>
<li>그래서 컴퓨터에서는 메모리를 효율적으로 사용하기 위해 부동소수점 방식을 택하게 된다.</li>
<li>일단 부동소수점을 사용한다라는건 <code>정규화</code>에 대해 알아야하는데, <strong><code>2진법으로 변환하여 정수부를 1로 만든다!</code></strong> 라고 생각해주면 좋을것 같다. </li>
</ul>
<hr>
<p>10진수 <code>19.125</code> 를 2진수로 변환하여 정규화를 해보자!</p>
<pre><code>19.125₍₁₀₎
= 10011.001₍₂₎
= 1.0011001₍₂₎ * 2⁴    &lt;&lt; 이게 정규화된거다!</code></pre><p>정규화된 <code>1.0011001₍₂₎ * 2⁴</code> 를 <code>IEEE 754 표준</code>에 맞게 넣어보자.</p>
<p>일단 <code>IEEE 754</code>의 메모리 할당은 다음 그림과 같다. </p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/769e7aaf-a1a5-43e5-a44b-7ec08f20d2b5/image.png" alt=""></p>
<p><a href="https://steemit.com/kr/@modolee/floating-point">그림 출처</a></p>
<p>부호 비트, 지수 비트, 가수 비트의 계산은 다음과 같다.</p>
<pre><code>1) 양수/음수에 따라 부호 비트에 넣는다
  - 양수 : 0
  - 음수 : 1
2) 자릿수를 나타내는 2⁴의 지수인 4와 127을 더하여 지수 비트에 넣는다.
  - 127₍₁₀₎ = 01111111₍₂₎
3) 소수 부분은 가수 비트에 차례대로 넣는다.
  - 0.0011001₍₂₎

계산 결과값은 다음과 같다
(부호) (지수)           (소수)
  0  10000011  00110010000000000000000</code></pre><hr>
<h2 id="다른-언어에서는-어떤걸-쓰나">다른 언어에서는 어떤걸 쓰나</h2>
<p>2진수로 표현했을때 무한소수를 표현할 수 없어서 위와같은 이슈가 있는걸 알아봤다.</p>
<p>그리고 다른 언어에서도 세밀한 실수를 표현하기위해서는 사용하는 대체제가 따로 존재하는것 같다.</p>
<table>
<thead>
<tr>
<th>언어</th>
<th>대체</th>
</tr>
</thead>
<tbody><tr>
<td>Java</td>
<td>BigDecimal</td>
</tr>
<tr>
<td>javascript</td>
<td>big.js</td>
</tr>
<tr>
<td>python</td>
<td>decimal</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[TransactionalEventListener 데이터 변경이 안돼요]]></title>
            <link>https://velog.io/@pang_e/TransactionalEventListener-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B3%80%EA%B2%BD%EC%9D%B4-%EC%95%88%EB%8F%BC%EC%9A%94</link>
            <guid>https://velog.io/@pang_e/TransactionalEventListener-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B3%80%EA%B2%BD%EC%9D%B4-%EC%95%88%EB%8F%BC%EC%9A%94</guid>
            <pubDate>Sun, 08 Sep 2024 12:25:58 GMT</pubDate>
            <description><![CDATA[<h2 id="transactionaleventlistener-가-동작을안한다"><code>TransactionalEventListener</code> 가 동작을안한다!!!</h2>
<p>그러니깐 내가 원하는대로 동작을 하지않는다. 이벤트 리스너에서 데이터 변경이 반영되지 않는다...</p>
<p>간단한 코드를 보겠다.</p>
<hr>
<ul>
<li><code>GroupService.kt</code> 에서 <code>Group</code> 을 삭제하고 삭제 이벤트를 발행한다. <pre><code class="language-kotlin">  /**
  * GroupService.kt
  */
  @Transactional
  fun deleteGroupPublish(id: Long){
      groupRepository.deleteById(id)
      applicationEventPublisher.publishEvent(
              DeleteGroupEvent(id)
      )
  }</code></pre>
</li>
</ul>
<hr>
<ul>
<li>발행한 이벤트를 받아서 <code>Group</code> 에 속하는 <code>GroupMember</code> 를 삭제한다.</li>
</ul>
<pre><code class="language-kotlin">    /**
    * GroupMemberService.kt
    */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    fun deleteGroupMemberEventHandler(event: DeleteGroupEvent){
        groupMemberRepository.deleteByGroupId(event.id)
    }</code></pre>
<hr>
<ul>
<li>그에따른 간단한 테스트를 다음처럼 구성해봤다.</li>
</ul>
<pre><code class="language-kotlin">@SpringBootTest
class GroupServiceTest{

    @Autowired
    lateinit var groupService: GroupService
    @Autowired
    lateinit var groupRepository: GroupRepository
    @Autowired
    lateinit var groupMemberRepository: GroupMemberRepository

    @Test
    fun delete_group_and_publish_event(){
        // given
        val group = groupRepository.save(Group())
        val groupMember = groupMemberRepository.save(GroupMember(group.no))
        // when
        groupService.deleteGroup(group.no)
        // then
        val groupList = groupRepository.findAll()
        val groupMemberList = groupMemberRepository.findAll()
        assertThat(groupList).isEmpty()
        assertThat(groupMemberList).isEmpty()
    }
}
</code></pre>
<hr>
<ul>
<li>왜 실패하는거야
<img src="https://velog.velcdn.com/images/pang_e/post/668ef45f-487f-43ce-93a9-c6dbb2633204/image.png" alt=""></li>
</ul>
<hr>
<h2 id="이벤트-문제가-아니고-transaction를-보자">이벤트 문제가 아니고 Transaction를 보자</h2>
<blockquote>
<p><del>그러니깐 ORM 에 등록해서 데이터베이스가 실제로 변경되게 하려면 <code>commit</code>, <code>rollback</code> 등의 기능이 되어야하고, 그걸 편하게 하기위해서 <code>TransactionManager</code> 인터페이스가 존재하고, 이걸 대부분 구현해놓은 <code>AbstractPlatformTransactionManager</code> 추상클래스가 존재하고, 이걸 또 각자 사용하는 ORM에서 <code>TransactionManager</code> 를 구현해놓았는데...</del> 
나중에 블로그를 따로 정리하겠다.</p>
</blockquote>
<p>구글링으로 좀 이것저것 찾아봤었는데 <a href="https://hojun-dev.tistory.com/entry/JAVA-eventListener-transactionalEventListener-%EC%98%88%EC%99%B8-%EB%B0%8F-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A0%84%ED%8C%8C-%EC%B4%9D%EC%A0%95%EB%A6%AC">여기 블로그</a>를 좀 참고했다.</p>
<h3 id="어디서-커밋을-하나요">어디서 커밋을 하나요</h3>
<p>일단 내부구조를 슥 한번 보겠다.</p>
<p>변경사항이 반영되는<code>doCommit()</code> 을 실행하는 부분은 여기 한군데 밖에 없다</p>
<p><code>AbstractPlatformTransactionManager</code> 클래스의 <code>processCommit()</code> 메서드이다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/57433a67-2c56-4d91-b00e-f3a7e3cdbb4b/image.png" alt=""></p>
<p>코드를 보아 <code>status.isNewTransaction()</code> 이 true 여야 <code>doCommit()</code> 이 실행되어 데이터베이스 변경이 반영할수있다는걸 알수 있다.</p>
<hr>
<h3 id="언제-커밋을-하나요">언제 커밋을 하나요</h3>
<p>그렇다면 <code>status.isNewTransactiion()</code> 의 상태값이 세팅 부분을 찾아보겠다.</p>
<p>동일한 클래스의 <code>getTransaction()</code> 에서 현재 실행중인 <code>Transaction</code> 이 새로운 트랜잭션인지 분기한다는걸 확인했다.</p>
<p>현재 <code>Transactional</code> 의 propagation이 기본값 <code>(Propagation.REQUIRED)</code>으로 되어있으니 하단의 조건문에 걸려 트랜잭션을 시작하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/5c9183b6-fd5a-4605-babd-189bf71f8a06/image.png" alt=""></p>
<hr>
<p>그리고 해당 메서드는 트랜잭션을 생성하며 <code>newTransaction</code>이 true로 들어가는걸 확인할수 있다. </p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/8e5fb85b-6c11-440c-8af2-7bd20394ee13/image.png" alt=""></p>
<hr>
<p>AbstractPlatformTransactionManager 클래스의 <code>processCommit()</code> 메서드의 <code>status.isNewTransaction()</code>이 true 로 들어갈수가 있겠다.
(중복된 사진) 
<img src="https://velog.velcdn.com/images/pang_e/post/7a75d146-a2c7-4c87-94e4-5435b24b178c/image.png" alt=""></p>
<hr>
<h3 id="그래서-리스너에서는-외않되">그래서 리스너에서는 외않되</h3>
<p>동일하게 <code>getTransaction()</code>으로 현재 트랜잭션의 상태값을 받아오는건 동일하지만, 상위 클래스에서 전파된 트랜잭션이 존재하기 때문에 <code>handleExistingTransaction()</code> 메서드를 타게 된다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/2cd76733-7c87-431b-82ae-99758e85fba4/image.png" alt=""></p>
<p>그리고 <code>handleExistingTransaction</code> 내부를 확인하면 <code>definition.getPropagationBehavior()</code> 에 따라서 트랜잭션의 <code>newTransaction</code> 의 상태값이 다르게 들어가는걸 확인할수 있다.</p>
<p>그리고 해당 값의 기본값은 Transactional의 기본값인 REQUIRED</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/3e5a4610-4f93-49e6-9e29-8d4dd56c2390/image.png" alt=""></p>
<p><code>PROPAGATION_REQUIRED</code> 상태값을 따라가면 마지막에 <code>newTransaction</code> 이 false 로 생성되게 되고 아까 확인했었던 commit이 일어나지 않게 된다.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/1ea3c032-515c-4372-a387-47f3586d0eeb/image.png" alt=""></p>
<hr>
<h3 id="그럼-어케해">그럼 어케해!!!</h3>
<p>상위클래스에서 전파된 트랜잭션은뭐 그거대로 두고 새로운 트랜잭션을 열면 된다.</p>
<p>아까 확인했었던 <code>handleExistingTransaction</code> 에서 트랜잭션 <code>PROPAGATION</code>이 <code>REQUIRES_NEW</code> 라면 최초에 트랜잭션을 열때와 동일하게 여는 방식을 사용하는걸 볼수 있다. </p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/da535e8a-5cd0-46f6-81ce-3f22e935c805/image.png" alt=""></p>
<p>그렇지만 <a href="https://seongonion.tistory.com/166">여기 블로그</a> 도 한번 확인해보도록 하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[더티 체킹이 안돼요]]></title>
            <link>https://velog.io/@pang_e/transactional</link>
            <guid>https://velog.io/@pang_e/transactional</guid>
            <pubDate>Sun, 01 Sep 2024 06:11:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>jpa dirty checking is not working</code>
말그대로 JPA 에서 더티 체킹이 되지 않는다. 물론 더티체킹 사용하는걸 좋아하진 않지만 왜 안되는건지 알고싶다.</p>
</blockquote>
<hr>
<h2 id="상황-설명">상황 설명</h2>
<p>코드로 보는게 편할듯하여 코드부터 살짝 본다면 문제의 엔티티는 다음과 같다.</p>
<pre><code class="language-kotlin">@Entity
@Table(name = &quot;company&quot;, catalog = &quot;slave_babi_01&quot;)
data class SlaveCompany(
        ...

        @Column(name = &quot;name&quot;)
        private var name: String = &quot;&quot;
){
        fun changeName(name: String){
                this.name = name
        }
}</code></pre>
<p>그리고 그 문제의 서비스 코드는 다음과 같다.</p>
<pre><code class="language-kotlin">
@Service
@Transactional
class CompanyService(
        private val masterCompanyRepository: MasterCompanyRepository,
        private val slaveCompanyRepository: SlaveCompanyRepository
) {

    ...

    fun changeSlaveCompanyName(id: Long, name: String){
        val company = slaveCompanyRepository.findById(id)
                .orElseThrow()
        company.changeName(name)
    }

}</code></pre>
<p>그럼 이 문제의 코드를 실행해보자</p>
<pre><code class="language-kotlin">@Test
fun change_slave_name_test(){
    // given
    val id = 1L
    val changeName = &quot;change slave name&quot;
    // when
    companyService.changeSlaveCompanyName(id,changeName)
    // then
    val slaveCompany = slaveCompanyRepository.findById(id)
            .orElseThrow()
    assertThat(slaveCompany.name).isEqualTo(changeName)
}</code></pre>
<p>이녀석 진짜 문제네</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/2e5259bb-0da3-4884-abae-2599bf77fa63/image.png" alt=""></p>
<hr>
<h2 id="아니-왜죠">아니 왜죠</h2>
<p>그러게 말이다. 한번 이것저것 시도를 해봤었다.</p>
<h3 id="save-를-직접해주면-되는가">save() 를 직접해주면 되는가?</h3>
<pre><code class="language-kotlin">fun changeSlaveCompanyName(id: Long, name: String){
    val company = slaveCompanyRepository.findById(id)
            .orElseThrow()
    company.changeName(name)
    // 직접 save 추가
    slaveCompanyRepository.save(company)
}</code></pre>
<p>예 됩니다. update 쿼리 잘날라가네요 </p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/3f1c50dd-49e6-4d18-9fad-fd515062b226/image.png" alt=""></p>
<h3 id="혹시-entitymanager에-등록되어있을까요">혹시 EntityManager에 등록되어있을까요</h3>
<pre><code class="language-kotlin">
@Service
@Transactional
class CompanyService(
        private val masterCompanyRepository: MasterCompanyRepository,
        private val slaveCompanyRepository: SlaveCompanyRepository,
        // EntityManager 추가
        private val entityManager: EntityManager
) {

    ...

    fun changeSlaveCompanyName(id: Long, name: String){
        val company = slaveCompanyRepository.findById(id)
                .orElseThrow()
        // 추가
        entityManager.contains(company)
        company.changeName(name)
    }

}</code></pre>
<p>안되어 있었네요.</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/ec8c9084-85cc-4af9-a8ff-7a25114bb7a7/image.png" alt=""></p>
<hr>
<h2 id="정답은-다중-datasource-였습니다">정답은 다중 datasource 였습니다~</h2>
<p>혼날까봐 미리 이야기안했었는데, 사내 프로젝트에서 다중 datasource 를 만들어서 사용하고 있었고, <code>slaveDatasource</code>, <code>masterDatasource</code> 를 각각 선언해서 사용중이다.</p>
<p>그리고 config 파일을 찾아보니 다음과 같이 설정되어 있었다.</p>
<p>그리고 당연하게 <code>@Primary</code> 붙은 <code>masterDatasource</code> 를 기본으로 사용하게되어 내가 원하는 <code>Slave</code> 관련 entity 는 관리 대상이 아니어서 더티체킹이 나가지 않았던것...</p>
<p><del>아오 @Primary</del>
<img src="https://velog.velcdn.com/images/pang_e/post/5fd91ccc-bf87-48dc-9b84-abe1ac207a87/image.png" alt=""></p>
<p>그래서 트랜잭션에 직접 내가 사용할 <code>Transaction Manager</code>를 기재해주면 더티체킹을 사용할수있다~</p>
<pre><code class="language-kotlin">// 내가 사용할 transactionManager 명시 
@Transactional(transactionManager = &quot;slaveTransactionManager&quot;)
fun changeSlaveCompanyName(id: Long, name: String){
    val company = slaveCompanyRepository.findById(id)
            .orElseThrow()
    company.changeName(name)
}</code></pre>
<p>update도 기똥차게 나가는걸 확인할수있다
<img src="https://velog.velcdn.com/images/pang_e/post/ce65283f-200e-41e3-bdd8-ab195ab1303e/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[@Enumerated와 @Convert]]></title>
            <link>https://velog.io/@pang_e/nested-exception-is-org.springframework.dao.InvalidDataAccessApiUsageException-No-enum-constant</link>
            <guid>https://velog.io/@pang_e/nested-exception-is-org.springframework.dao.InvalidDataAccessApiUsageException-No-enum-constant</guid>
            <pubDate>Sun, 18 Aug 2024 11:38:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>@Enumerated</code> 어노테이션과 <code>@Convert</code> 어노테이션은 공존할 수 없는걸까</p>
</blockquote>
<h2 id="아니-분명히-달아줬다니깐요">아니 분명히 달아줬다니깐요?</h2>
<p>기존 존재하는 php 코드를 spring 으로 전환하는 업무가 최근에 꽤나 들어오는 편이다. </p>
<p>그러니깐 마이그레이션과 같은 데이터베이스 모델링부터 시작이 아닌, 기존 사용하던 데이터베이스를 사용하며 그대로 호환성을 맞추는 그런 업무들!</p>
<p>사내에서 기존에 사용하는 데이터베이스에서는 <code>enum</code> 타입으로 사용되는것들이 많았다.</p>
<h3 id="간단히-enumerated를-달아볼까">간단히 @Enumerated를 달아볼까?</h3>
<p><img src="https://velog.velcdn.com/images/pang_e/post/ac1678cd-7e86-4a28-a03c-826d21a0630c/image.webp" alt=""></p>
<p>데이터베이스에서 <strong>Notnull</strong> 필드인걸 확인하고, 다음과 같이 <code>@Enumerated(value = EnumType.STRING)</code> 을 추가해줬다.</p>
<pre><code class="language-java">@Column(name = &quot;type&quot;)
@Enumerated(value = EnumType.STRING)
private YorN type;
</code></pre>
<p>그리고 간단한 CRUD 테스트를 진행하니 enum 타입으로 변환을 할수 없다는 다음과 같은 에러가 발생한다...</p>
<pre><code>nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: No enum constant</code></pre><p>확인해보니 데이터베이스의 enum 데이터타입이 <code>enum(&#39;Y&#39;, &#39;N&#39;)</code> 으로 선언되어있지만, 실제로는 빈 스트링(<code>&quot;&quot;</code>)으로 들어가있었음!!!</p>
<blockquote>
<p>해당 이슈는 MYSQL 5.7에서 만나볼수있는 이슈로, enum에 맞지않는 값은 빈 스트링으로 들어간다고 한다 😠
<img src="https://velog.velcdn.com/images/pang_e/post/a8c07330-c113-43d4-ae0d-4d0f9456bd81/image.png" alt="">
<a href="https://dev.mysql.com/doc/refman/5.7/en/enum.html">참고 페이지</a></p>
</blockquote>
<hr>
<h3 id="그럼-컨버터를-달아볼까">그럼 컨버터를 달아볼까?</h3>
<p><img src="https://velog.velcdn.com/images/pang_e/post/ac1678cd-7e86-4a28-a03c-826d21a0630c/image.webp" alt=""></p>
<p>이번에는 <code>Converter</code> 를 달아주도록 결심했다. 
내가 사용하는 <code>YorN</code> enum에 대하여<code>AttributeConverter</code> 를 <strong><code>null</code>에 대한 방어코드도 추가하여 구현</strong>해주고 <code>Entity</code>는 다음과 같이 <code>Converter</code> 어노테이션을 추가해줬다</p>
<pre><code class="language-java">@Column(name = &quot;type&quot;)
@Convert(converter = YorNConverter.class)
@Enumerated(value = EnumType.STRING)
private YorN type;</code></pre>
<p>그리고 여전히 만나볼수있는 enum 타입으로 변환 못한다는 그에러...</p>
<pre><code>nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: No enum constant </code></pre><blockquote>
<h3 id="아니-컨버터-달아줬다니깐요-😡">아니 컨버터 달아줬다니깐요? 😡</h3>
</blockquote>
<hr>
<h2 id="내부로직-구경하기">내부로직 구경하기</h2>
<p>그래서 한번 들어가봤습니다.</p>
<h3 id="닭이-먼저냐-달걀이-먼저냐">닭이 먼저냐 달걀이 먼저냐</h3>
<p>hibernate에서 얻어낸 value를 <code>Entity</code> 로 맵핑해주는 클래스가 <code>BasicResultAssembler.java</code> 라는것을 알게되었고, 해당 함수를 확인했을때 생겨난 이슈를 확인할 수 있었다.</p>
<pre><code class="language-java">/**
 * BasicResultAssembler.java
 */
@Override
public J assemble(
        RowProcessingState rowProcessingState,
        JdbcValuesSourceProcessingOptions options) {
    // 이놈
    final Object jdbcValue = extractRawValue( rowProcessingState );

    ResultsLogger.RESULTS_LOGGER.debugf( &quot;Extracted JDBC value [%d] - [%s]&quot;, valuesArrayPosition, jdbcValue );
    ...
    // converter는 여기 있어요
}</code></pre>
<p><code>extractRawValue()</code> 함수를 통해 엔티티에 삽입되어야 할 데이터를 변환하여 가지고 오게 되는데, <code>@Enumerated</code> 어노테이션이 선언된 필드들은 다음 <code>BasicValueBinder.java</code> 에서 최초에 hibernate 에서 <code>JdbcType</code>이 enum타입으로 등록되게 된다</p>
<pre><code class="language-java">/**
 * BasicValueBinder.java
 */ 
private void prepareBasicAttribute(String declaringClassName, XProperty attributeDescriptor, XClass attributeType) {

    ...

    final Enumerated enumeratedAnn = attributeDescriptor.getAnnotation( Enumerated.class );
    if ( enumeratedAnn != null ) {
        this.enumType = enumeratedAnn.value();
    }

    ...

}</code></pre>
<p><strong>값을 추출할때 <code>JdbcType</code> 별로 다르게 맵핑하여 가지고 오기 때문에, 데이터베이스값이 enum에 맞지 않다면 추출할때 부터 enum타입으로 선언되어있으면 <code>converter</code> 와는 무방하게 에러가 발생한다</strong></p>
<p>타입별 컨버터의 실제 구현은 다음과 같은 차이가 존재한다</p>
<ul>
<li>EnumJavaType 컨버터<pre><code class="language-java">/*
* EnumJavaType.java
*/
@Override
public &lt;X&gt; T wrap(X value, WrapperOptions options) {
  ...
  else if ( value instanceof String ) {
      return fromName( (String) value );
  }
  ...
}
</code></pre>
</li>
</ul>
<p>public T fromName(String relationalForm) {
    if ( relationalForm == null ) {
        return null;
    }
    // 에러 잘나게 생긴녀석
    return Enum.valueOf( getJavaTypeClass(), relationalForm.trim() );
}</p>
<pre><code>- StringJavaType 컨버터
```java
public &lt;X&gt; String wrap(X value, WrapperOptions options) {
    ...
    // String 이라면 String 반환. 아주 군더더기 없는 녀석
    if (value instanceof String) {
        return (String) value;
    }
    ...
}</code></pre><p>이번 섹터 제목이 <code>닭이 먼저냐 달걀이 먼저냐</code> 라고 지었던것도, 컬럼에 <code>@Enumerated</code> 어노테이션을 추가해준것 만으로, <code>converter</code> 를 추가한것은 아무런 도움이 안된다는 뜻에서 정해봤다(<del>설명이 필요한 드립은 실패한 드립</del>)</p>
<hr>
<h3 id="컨버터-여기-뒤에있어요">컨버터 여기 뒤에있어요</h3>
<p><code>BasicResultAssembler.java</code> 에서의 <code>assemble()</code> 메서드를 대량 생략했었는데 전체 구현부는 다음과 같다.</p>
<p>컨버터가 존재한다면 비로소 우리가 구현한 <code>AttributeConverter</code> 컨버터를 사용하게 된다.</p>
<pre><code class="language-java">/**
 * BasicResultAssembler.java
 */
@Override
public J assemble(
        RowProcessingState rowProcessingState,
        JdbcValuesSourceProcessingOptions options) {
    final Object jdbcValue = extractRawValue( rowProcessingState );

    ResultsLogger.RESULTS_LOGGER.debugf( &quot;Extracted JDBC value [%d] - [%s]&quot;, valuesArrayPosition, jdbcValue );

    if ( valueConverter != null ) {
        if ( jdbcValue != null ) {
            // the raw value type should be the converter&#39;s relational-JTD
            if ( ! valueConverter.getRelationalJavaType().getJavaTypeClass().isInstance( jdbcValue ) ) {
                throw new HibernateException(
                        String.format(
                                Locale.ROOT,
                                &quot;Expecting raw JDBC value of type `%s`, but found `%s` : [%s]&quot;,
                                valueConverter.getRelationalJavaType().getTypeName(),
                                jdbcValue.getClass().getName(),
                                jdbcValue
                        )
                );
            }
        }

        // 이놈
        return (J) ( (BasicValueConverter) valueConverter ).toDomainValue( jdbcValue );
    }

    return (J) jdbcValue;
}</code></pre>
<hr>
<h3 id="그러면-어떻게-하죠">그러면 어떻게 하죠</h3>
<p>결론... 이라고하기엔 좀 그렇지만 데이터베이스에 값이 꼬여있는 경우가 아니라면 <code>@Enumerated</code>로도 커버가 가능하며, 굳이굳이 둘다 혼용을 한다면, 빈 문자열또한 어플리케이션 로직에 추가하여 <code>Converter</code>로 정상값으로 변환시켜주자.</p>
<hr>
<p>환경
Spring Boot 3.3.2
hibernate 6.5.2</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 필터로 간단히 로깅찍기]]></title>
            <link>https://velog.io/@pang_e/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%84%ED%84%B0%EB%A1%9C-%EA%B0%84%EB%8B%A8%ED%9E%88-%EB%A1%9C%EA%B9%85%EC%B0%8D%EA%B8%B0</link>
            <guid>https://velog.io/@pang_e/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%84%ED%84%B0%EB%A1%9C-%EA%B0%84%EB%8B%A8%ED%9E%88-%EB%A1%9C%EA%B9%85%EC%B0%8D%EA%B8%B0</guid>
            <pubDate>Sun, 21 Jul 2024 09:03:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>로그를 남기는것은 아주 중요한 일이다. 추후 오류를 찾을때 추적하기도 쉬우며...(주절주절)</p>
</blockquote>
<p>로그가 중요하다는것은 모두 알거고, 그럼 어떠한 방식으로 로그를 남기는게 좋을까? 라는 다음 단계가 있을수 있겠다. </p>
<p>사내에서는 다양한 프레임워크를 사용하고 있기에, 전사적으로 사용하기 위해 처음 구상한 내용은, 로깅용 데이터베이스를 따로 구축하여(<code>nosql</code>) 카프카 등 이벤트로 처리하도록 하는걸 생각해봤었다.</p>
<p>하지만 트래픽이 적지도않을뿐더러, 솔루션마다 주요하게 저장할 로깅 데이터가 달라 정형화하기 힘들다 라는 이슈...</p>
<p><img src="https://velog.velcdn.com/images/pang_e/post/8c38ce24-b62b-4faf-8170-a8e12a935008/image.webp" alt=""></p>
<hr>
<h3 id="스프링부터-천천히">스프링부터 천천히...</h3>
<p>전사적으로 가장 많이 채택되어 사용되고있는 스프링 모듈로부터 따로 생각해봤다.</p>
<p>그리고 아주 편리하게 사용할수있는 <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/AbstractRequestLoggingFilter.html"><code>AbstractRequestLoggingFilter</code></a> 이 있다고 할수 있겠다.</p>
<p><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/OncePerRequestFilter.html"><code>OncePerRequestFilter</code></a>을 상속받고 있으며, 해당 클래스를 조금만 손보면 요청 전후로 로그를 받을수 있다. </p>
<hr>
<h3 id="당장-구현">당장 구현</h3>
<p>해당 모듈을 구현하며, 사용자가 커스텀하게 할수있도록 하고 싶었다. 예를들면 특정 사용자별 로그를 찍고싶지 않은 엔드포인트를 정하는느낌으로!</p>
<p>그리고 <code>AbstractRequestLoggingFilter</code>에서 커스텀하게 지정해줄수 있는 세팅값들은 다음과 같다. (message prefix, suffix 데코레이터는 제외!)</p>
<pre><code class="language-java">    public void setIncludeQueryString(boolean includeQueryString) {
        this.includeQueryString = includeQueryString;
    }

    public void setIncludeClientInfo(boolean includeClientInfo) {
        this.includeClientInfo = includeClientInfo;
    }

    public void setIncludeHeaders(boolean includeHeaders) {
        this.includeHeaders = includeHeaders;
    }

    public void setIncludePayload(boolean includePayload) {
        this.includePayload = includePayload;
    }

    public void setHeaderPredicate(@Nullable Predicate&lt;String&gt; headerPredicate) {
        this.headerPredicate = headerPredicate;
    }

    public void setMaxPayloadLength(int maxPayloadLength) {
        Assert.isTrue(maxPayloadLength &gt;= 0, &quot;&#39;maxPayloadLength&#39; must be greater than or equal to 0&quot;);
        this.maxPayloadLength = maxPayloadLength;
    }</code></pre>
<p>다들 메서드가 직관적이라 이해하기가 아주 좋다.</p>
<p>그리고 사용자가 설정값을 커스텀하게 할수 있도록 <code>ConfigurationProperties</code>으로 받도록 구성하였다.</p>
<pre><code class="language-kotlin">@Component
@ConfigurationProperties(LoggingProperties.LOGGING_PREFIX)
data class LoggingProperties(
        var excludePath: MutableList&lt;String&gt; = mutableListOf(&quot;**/health/**&quot;),
        var maxPayloadLength: Int = 1000,
        var includeHeaders: Boolean = true,
        var includePayload: Boolean = true,
        var includeQueryString: Boolean = true,
){
    companion object{
        const val LOGGING_PREFIX=&quot;logging.config&quot;
    }
}</code></pre>
<hr>
<p>그리고 <code>AbstractRequestLoggingFilter</code>를 구현해주도록 한다. 구현해야할 주요 메서드들은 다음 세개가 있다.</p>
<p>로그를 찍을지 여부 판단</p>
<ul>
<li><code>protected boolean shouldLog(HttpServletRequest request)</code></li>
</ul>
<p>before 로그</p>
<ul>
<li><code>protected abstract void beforeRequest(HttpServletRequest request, String message)</code></li>
</ul>
<p>after 로그</p>
<ul>
<li><code>protected abstract void afterRequest(HttpServletRequest request, String message)</code></li>
</ul>
<hr>
<p><code>sholudLog</code> 메서드를 재정의하지 않는다면 k8s에서 health 체크등 불필요한 요청들도 다 찍혀서 오히려 보기가 힘들수 있기에, 제외하고싶은 엔드포인트들을 받을수 있도록 하였다.</p>
<pre><code class="language-kotlin">
class RequestLoggingFilter(
        private val excludePath: List&lt;String&gt;
): AbstractRequestLoggingFilter() {

    private val matcher = AntPathMatcher()

    override fun shouldLog(request: HttpServletRequest): Boolean {
        return !excludePath.any {
            matcher.match(it, request.requestURI)
        }
    }

    override fun beforeRequest(request: HttpServletRequest, message: String) {
        this.logger.info(message)
    }

    override fun afterRequest(request: HttpServletRequest, message: String) {
        this.logger.info(message)
    }
}</code></pre>
<hr>
<p>이후 해당 로깅 내용을 Bean 설정까지만 해주면 뚝딱!</p>
<pre><code class="language-kotlin">@Configuration
class RequestLoggingConfig{

    @Bean
    fun requestLoggingFilter(
            properties: LoggingProperties
    ):RequestLoggingFilter{
        val filter = RequestLoggingFilter(properties.excludePath);
        filter.setMaxPayloadLength(properties.maxPayloadLength)
        filter.setIncludeHeaders(properties.includeHeaders)
        filter.setIncludePayload(properties.includePayload)
        filter.setIncludeQueryString(properties.includeQueryString)

        return filter
    }
}</code></pre>
<p>해당 모듈을 사용자가 가져오기만 하더라도 기본적인 로깅이 찍히도록 구성하였다. 물론 모듈을 만들때 사용자가 커스텀하게 설정하는것도 <code>@ConfigurationProperties</code> value로 설정한 값들을 yml 에 설정해줄수 있다고 할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mysql Select-Insert]]></title>
            <link>https://velog.io/@pang_e/Mysql-Select-Insert</link>
            <guid>https://velog.io/@pang_e/Mysql-Select-Insert</guid>
            <pubDate>Sat, 01 Jun 2024 04:54:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>사내에서 데이터베이스를 마이그레이션할때 항상 스크립트를 작성했었는데, mysql의 이런 기능도 있었다</p>
</blockquote>
<p>오래전부터 사용되던 데이터베이스들은 테이블 구조가 구린경우가 많다.</p>
<ul>
<li>더이상 사용하지 않는 컬럼</li>
<li>과한 정규화</li>
<li>엄청난 반정규화</li>
</ul>
<p>하여간 다양한 상황에 직면하게 되는데, 개선건으로 테이블의 정규화가 필요하다고 생각하고, 스크립트를 준비하려했는데, 옆에서 <code>그거 쿼리 한줄이면 끝나는데</code> 라며 직접 쿼리를 작성해줬다.</p>
<hr>
<h3 id="insert---select">Insert - Select</h3>
<ul>
<li>그러니깐 <code>select</code>  된 결과값들을 모두 <code>insert</code> 할 수 있다.</li>
<li>예를들면 다음과 같은 쿼리가 있겠다</li>
</ul>
<pre><code class="language-sql">insert into table_1 (name)
  select table_2.name
  from table_2 where table2.id &gt; 10;</code></pre>
<ul>
<li>다음 <code>select</code> 절의 결과값을 <code>table_1.name</code> 필드에 전부 넣겠다 라는 뜻이다.<pre><code class="language-sql">select table_2.name
from table_2 where table2.id &gt; 10;</code></pre>
</li>
</ul>
<hr>
<ul>
<li>그리고 전체 테이블을 옮기는 방법도 있는데 <pre><code class="language-sql">insert into table_1 table table_2</code></pre>
</li>
<li>위의 sql 문은 다음 쿼리와 동일하다<pre><code class="language-sql">insert into table_1
select * from table_2</code></pre>
</li>
</ul>
<hr>
<ul>
<li>모종의 사유로 <code>insert</code> 처리중 에러가 발생한다면 롤백과 동시에 종료를 하게 되는데, 이때 <code>insert ignore</code>를 사용하면 에러를 발생할 수 있는 레코드를 제외하고 구문을 실행하게 된다</li>
</ul>
<pre><code class="language-sql">insert ignore into table_1
  select * from table_2</code></pre>
<hr>
<ul>
<li>동일한 테이블에 서브쿼리로 <code>insert</code> 하는것은 안된다-로 읽히는데 재현이 안된다... <blockquote>
<p>고수들의 도움이 필요</p>
</blockquote>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/5a8ece64-a2d9-4233-87b9-39aad9d65739/image.png" alt=""></p>
<hr>
<h3 id="당장써보자">당장써보자?</h3>
<ul>
<li><p>아주 좋다 그래서 당장 쓰고싶긴하지만 사내에 쿼리를 날리기에는 조금 찜찜한 부분이 있기에 조금 수정을 해보도록 하자</p>
</li>
<li><p>기존 테이블 구조</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/73362ce1-35b8-44b5-a68f-58312855aceb/image.png" alt=""></p>
<ul>
<li><code>address</code> 필드를 정규화 하여, 다음과 같은 테이블 구조를 갖고싶다!</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/077fc7ae-a660-4aed-bb71-5627a08e10ea/image.png" alt=""></p>
<ul>
<li>사실 다음과 같은 <code>select-insert</code> 쿼리로 한번에 가능하긴하다!<pre><code class="language-sql">insert into `정규화 할 테이블` (new_address, fk_id)
select name, id from `기존 테이블`
</code></pre>
</li>
</ul>
<pre><code>
- 하지만 중간에 에러가 터진다면..!!
**멱등성**이 보장되는 쿼리로 좀더 편안하게 쿼리를 변경하자

```sql
insert into `정규화 할 테이블` (new_address, fk_id)
    select name, id from `기존 테이블`
    where not exists (
                    select * from `정규화 할 테이블` 
                      where `정규화 할 테이블`.fk_id = `기존 테이블`.id
                    )
</code></pre><ul>
<li>존재하지 않는 레코드만 삽입이 가능하다!</li>
</ul>
<hr>
<p><a href="https://dev.mysql.com/doc/refman/8.4/en/insert-select.html">출처 : mysql 문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[미리 알았다면 좋았을 자바 팁]]></title>
            <link>https://velog.io/@pang_e/%EB%AF%B8%EB%A6%AC-%EC%95%8C%EC%95%98%EB%8B%A4%EB%A9%B4-%EC%A2%8B%EC%95%98%EC%9D%84-%EC%9E%90%EB%B0%94-%ED%8C%81</link>
            <guid>https://velog.io/@pang_e/%EB%AF%B8%EB%A6%AC-%EC%95%8C%EC%95%98%EB%8B%A4%EB%A9%B4-%EC%A2%8B%EC%95%98%EC%9D%84-%EC%9E%90%EB%B0%94-%ED%8C%81</guid>
            <pubDate>Sun, 19 May 2024 14:43:55 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://levelup.gitconnected.com/15-11-mistakes-every-java-developer-must-avoid-today-ccd7e681a970">여기</a>의 내용을 번역하여 정리했습니다</p>
</blockquote>
<h3 id="null-반환을-조심하라">Null 반환을 조심하라</h3>
<ul>
<li>우리는 Java를 사용할때, <code>nullpointerexception</code> 의 굴레에서 살아간다.</li>
<li>특정 메서드의 리턴값이 <code>null</code> 을 반환하기 보다는 <code>Optional</code>을 사용하자</li>
</ul>
<h5 id="bad-practice">Bad Practice</h5>
<pre><code class="language-java">public String getString(){
    return null;
}
</code></pre>
<h5 id="good-practice">Good Practice</h5>
<pre><code class="language-java">public Optional&lt;String&gt; getString(){
    return Optional.empty();
}</code></pre>
<hr>
<h3 id="string-자료형으로-변환">String 자료형으로 변환</h3>
<ul>
<li><code>+</code> 연산자를 통하여 특정 자료형을 <code>String</code> 으로 변환할수도 있다</li>
<li>하지만 이러한 방식은 많은 수의 변수를 문자열로 변환할 때 비효율적이니 내장 메서드를 사용하자</li>
</ul>
<h5 id="bad-practice-1">Bad Practice</h5>
<pre><code class="language-java">double d = 3.141592;
String s = &quot;&quot; + d;
</code></pre>
<h5 id="good-practice-1">Good Practice</h5>
<pre><code class="language-java">double d = 3.141592;
String s = String.valueOf(d);</code></pre>
<hr>
<h3 id="배열을-복사할때도-스마트하게">배열을 복사할때도 스마트하게</h3>
<ul>
<li>배열 길이를 할당하고, 모든 인덱스에 반복문을 돌며 하나씩 값을 넣어주는게 아니라 만들어져있는 메서드를 사용하자.</li>
<li>코드적으로도 이해하기 쉽다</li>
</ul>
<h5 id="bad-practice-2">Bad Practice</h5>
<pre><code class="language-java">int[] = sourceArray = {1, 2, 3, 4, 5};
int[] = targetArray = new int[sourceArray.length]; // 배열 길이 할당
for (int i = 0; i &lt; sourceArray.length; i++){
    targetArray[i] = sourceArray[i];
}
</code></pre>
<h5 id="good-practice-2">Good Practice</h5>
<pre><code class="language-java">int[] originalArray = {1, 2, 3, 4, 5};
int[] copiedArray = Arrays.copyOf(originalArray, originalArray.length);</code></pre>
<hr>
<h3 id="자료형의-상태를-확인할-수-있는-메서드를-사용하자">자료형의 상태를 확인할 수 있는 메서드를 사용하자</h3>
<ul>
<li>리스트가 비어있는지, 문자열이 존재하는지 등 자료형의 상태를 어떻게 확인을 하고있나?</li>
<li>리스트가 비어있는지 <code>if(list.size() == 0)</code> 와 같이 확인하기 보다는 해당 자료형에서 상태값을 확인할 수 있는 메서드가 있다</li>
</ul>
<h5 id="bad-practice-3">Bad Practice</h5>
<pre><code class="language-java">String text = &quot;Hello!&quot;;
if(text.length() == 0){
    // do
}

List&lt;Long&gt; numbers = new ArrayList&lt;&gt;();
if(numbers.size() == 0){
    // do
}
</code></pre>
<h5 id="good-practice-3">Good Practice</h5>
<pre><code class="language-java">String text = &quot;Hello!&quot;;
if(text.isEmpty()){
    // do
}

List&lt;Long&gt; numbers = new ArrayList&lt;&gt;();
if(numbers.isEmpty()){
    // do
}</code></pre>
<hr>
<h3 id="리스트에-대한-수정을-조심">리스트에 대한 수정을 조심</h3>
<ul>
<li>다음과 같이 리스트를 순회하며 수정하는 로직은 <code>ConcurrentModificationException</code> 에러를 만날 수 있다</li>
</ul>
<h5 id="bad-practice-4">Bad Practice</h5>
<pre><code class="language-java">List&lt;String&gt; words = new ArrayList&lt;&gt;();
words.add(&quot;A&quot;);
words.add(&quot;B&quot;);
words.add(&quot;C&quot;);

for(String word: words){
    if(word.equals(&quot;A&quot;)){
        words.remove(word);
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/pang_e/post/c6d8e9f7-6cdb-48d1-967b-0e7edfee67d7/image.png" alt=""></p>
<ul>
<li><code>ArrayList</code> 객체 내부를 확인해보면 엘리먼트의 추가 및 삭제마다 <code>modCount</code> 가 1씩 증가하게 되는데, <code>next()</code>를 호출할때마다 다음 <code>checkForComodification</code> 함수를 호출하게 된다.</li>
<li><code>modCount</code> 는 배열의 수정으로 인하여 <code>expectedModCount</code> 와 달라졌기 때문에 <code>ConcurrentModificationException</code> 에러가 발생하는것</li>
<li><a href="https://brandpark.github.io/java/2021/01/24/iterator.html">좀더 자세한 설명은 여기로~</a></li>
</ul>
<p><img src="https://velog.velcdn.com/images/pang_e/post/7e0cb9fd-d268-4632-bea8-daa8b7b5b372/image.png" alt=""></p>
<h5 id="good-practice-4">Good Practice</h5>
<ul>
<li><code>iterator</code> 에서 지원하는 <code>remove</code> 함수의 사용을 권장!<pre><code class="language-java">List&lt;String&gt; words = new ArrayList&lt;&gt;();
words.add(&quot;A&quot;);
words.add(&quot;B&quot;);
words.add(&quot;C&quot;);
</code></pre>
</li>
</ul>
<p>Iterator<String> iterator = words.iterator();</p>
<p>while (iterator.hasNext()){
    String word = iterator.next();
    if(word.equals(&quot;A&quot;)){
        iterator.remove();
    }
}</p>
<pre><code>- 혹은 간단하게 `removeIf` 함수의 사용하는 법도 있겠다. (자바8 이상)
```java
List&lt;String&gt; words = new ArrayList&lt;&gt;();
words.add(&quot;A&quot;);
words.add(&quot;B&quot;);
words.add(&quot;C&quot;);

words.removeIf(word -&gt; word.equals(&quot;A&quot;));</code></pre><hr>
<h3 id="정규-표현식은-미리-정의하기">정규 표현식은 미리 정의하기</h3>
<ul>
<li>반복되는 정규 표현식들을 사용할때마다 사용하지않고 미리 정의하자</li>
</ul>
<h5 id="bad-practice-5">Bad Practice</h5>
<pre><code class="language-java">String text = &quot;Hello!&quot;;
if (text.matches(&quot;Hello.*&quot;)) {
    System.out.println(&quot;Matched!&quot;);
}
String replaced = text.replaceAll(&quot;\\s&quot;, &quot;&quot;);
</code></pre>
<h5 id="good-practice-5">Good Practice</h5>
<pre><code class="language-java">final Pattern PATTERN1 = Pattern.compile(&quot;Hello.*&quot;);
final Pattern PATTERN2 = Pattern.compile(&quot;\\s&quot;);

String text = &quot;Hello!&quot;;
if (PATTERN1.matcher(text).matches()){
    System.out.println(&quot;Matched!&quot;);
}
String replaced = PATTERN2.matcher(text).replaceAll(&quot;&quot;&quot;);</code></pre>
<hr>
<h3 id="검색하기-전-미리-데이터의-존재-여부를-확인하지-마라">검색하기 전 미리 데이터의 존재 여부를 확인하지 마라</h3>
<ul>
<li>바로 코드를 본다면<h5 id="bad-practice-6">Bad Practice</h5>
<pre><code class="language-java">public static String findNameById(Map&lt;Integer, String&gt; idNameMap, int id){
  if (idNameMap.containsKey(id)){
      return idNameMap.get(id);
  }
  else {
      return &quot;Unknown&quot;;
  }
}
</code></pre>
</li>
</ul>
<pre><code>- `Map` 에서 내가 원하는 데이터가 있는지 존재여부 확인 후, 내가 원하는 데이터를 반환하는 로직으로 확인이 가능하다.
- 하지만 이런 코드는 중복된 로직으로써 다음과 같이 수정 가능하다

##### Good Practice

```java
public static String findNameById(Map&lt;Integer, String&gt; idNameMap, int id){
    String name = idNameMap.get(id);
    if (name != null){
        return name;
    }
    else {
        return &quot;Unknown&quot;;
    }
}</code></pre><hr>
<h3 id="컬렉션을-배열로-변경하기">컬렉션을 배열로 변경하기</h3>
<h5 id="bad-practice-7">Bad Practice</h5>
<ul>
<li>내부 배열을 선언할때 컬렉션의 크기를 직접 지정해주고 있다<pre><code class="language-java">List&lt;String&gt; stringList = new ArrayList&lt;&gt;();
stringList.add(&quot;a&quot;);
stringList.add(&quot;b&quot;);
stringList.add(&quot;c&quot;);
</code></pre>
</li>
</ul>
<p>String[] array = stringList.toArray(new String[stringList.size()]);</p>
<pre><code>##### Good Practice

- 하지만 컬렉션의 `toArray` 내부를 확인해보면 다음과 같이 선언되어 있어, 크기 조정을 직접 해줄 필요가 없다는걸 알 수 있다.
![](https://velog.velcdn.com/images/pang_e/post/de8144a3-df89-4004-8f4d-960772ab1c83/image.png)

```java
List&lt;String&gt; stringList = new ArrayList&lt;&gt;();
stringList.add(&quot;a&quot;);
stringList.add(&quot;b&quot;);
stringList.add(&quot;c&quot;);

String[] array = stringList.toArray(new String[0]);</code></pre><hr>
<h3 id="디폴트-메서드도-사용하자">디폴트 메서드도 사용하자</h3>
<ul>
<li>만약 내가 오픈소스를 만들었는데 너무 잘 만들어서 전 세계 사람들이 사용하고 있다고 하자. 그런데 인터페이스에서 필수적으로 구현해줘야할 메서드가 생겨버린다면 ?</li>
<li>인터페이스에 메서드를 추가뒤 배포했다. 내 오픈소스를 사용하는 사람들은 버전업을 함과 동시에 추가된 메서드를 구현해야한다...(죄송)</li>
<li>그럴때 구현할 필요없이 바로 사용할 수 있는 디폴트 메서드 잠수함패치!</li>
</ul>
<h5 id="bad-practice-8">Bad Practice</h5>
<pre><code class="language-java">interface Logger {
    void log(String message);
}</code></pre>
<h5 id="good-practice-6">Good Practice</h5>
<pre><code class="language-java">interface Logger {
    void log(String message);

    default void logError(String errorMessage){
        System.err.println(&quot;Error: &quot; + errorMessage);
    }
}</code></pre>
<hr>
<h3 id="date-클래스는-이제-놓아주자">Date 클래스는 이제 놓아주자</h3>
<ul>
<li><code>LocalDate</code>, <code>LocalDateTime</code> 이 훨씬 직관적이다<h5 id="bad-practice-9">Bad Practice</h5>
</li>
</ul>
<pre><code class="language-java">import java.util.Date;

public class DateUtil {
    public static void main(String[] args){
        Date currentDate = new Date();
        System.out.println(&quot;Current date: &quot; + currentDate);
    }
}</code></pre>
<h5 id="good-practice-7">Good Practice</h5>
<pre><code class="language-java">import java.time.LocalDate;

public class DateUtil {
    public static void main(String[] args){
        LocalDate currentDate = LocalDate.now();
        System.out.println(&quot;Current date: &quot; + currentDate);
    }
}</code></pre>
<hr>
<h3 id="제네릭-타입을-써주자">제네릭 타입을 써주자</h3>
<ul>
<li>컴파일 시점에 에러를 확인할 수 있게 해주는 제네릭타입을 적극 활용하자</li>
</ul>
<h5 id="bac-practice">Bac Practice</h5>
<pre><code class="language-java">ArrayList list = new ArrayList();
list.add(10);
list.add(&quot;Hello&quot;);    // Runtime Error!</code></pre>
<h5 id="good-practice-8">Good Practice</h5>
<pre><code class="language-java">ArrayList&lt;Integer&gt; list = new ArrayList();
list.add(10);
list.add(&quot;Hello&quot;);    // Compile Error!</code></pre>
<hr>
<p>자바를 사용할때 생각할만한 것들을 정리한 블로그를 읽으면서, 사실상 당연한것들이라고 생각을 했던것들이 대부분이긴 하다.
그래도 생각만 하고있는것보다 한번 정리해보는 기회로 그래도 나쁘진 않은것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프록시와 프록시 패턴]]></title>
            <link>https://velog.io/@pang_e/%ED%94%84%EB%A1%9D%EC%8B%9C%EC%99%80-%ED%94%84%EB%A1%9D%EC%8B%9C-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@pang_e/%ED%94%84%EB%A1%9D%EC%8B%9C%EC%99%80-%ED%94%84%EB%A1%9D%EC%8B%9C-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Sat, 18 May 2024 07:34:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>항상 들리는 그이름 프록시</p>
</blockquote>
<hr>
<h3 id="프록시가-뭔가요">프록시가 뭔가요</h3>
<p><img src="https://velog.velcdn.com/images/pang_e/post/8a8b0624-10ec-4225-b9b7-a0ffd6319624/image.png" alt=""></p>
<p>몇몇 블로그를 봤었는데, <strong>비서</strong> 와 같은 개념으로 이해하는게 가장 쉬웠다
사장님에게 직접 이야기 하기 전, 비서에게 먼저 물어보는 개념이다.</p>
<p>이와같이 중간에 비서 역할의 매개체가 존재함에 따라, 생각해볼수 있는 장점과 단점이 몇가지 있다.</p>
<h4 id="장점">장점</h4>
<ol>
<li>보안 강화<ul>
<li>중간에 위치하여 데이터 확인. 마치 차단 기능 수행</li>
</ul>
</li>
<li>트래픽 최적화<ul>
<li>동일한 데이터를 반복하여 요청하는 경우, 캐시 형태로 데이터를 저장하여 서버 부담 감소</li>
</ul>
</li>
<li>사용자 익명성<ul>
<li>서버는 프록시에 의해 호출되기 때문에 클라이언트의 익명성 보장</li>
</ul>
</li>
</ol>
<h4 id="단점">단점</h4>
<ol>
<li>성능 저하의 원인이 될수도<ul>
<li>중간 매개체가 생겨, 속도가 감소할 가능성 존재</li>
</ul>
</li>
</ol>
<hr>
<h3 id="프록시-패턴">프록시 패턴</h3>
<p>아주 좋다. 프록시가 무엇인지는 느낌적인 느낌으로 이해를 했는데, <code>프록시 패턴</code> 이란 무엇일까</p>
<p>우선 위키백과에서는 다음과 같이 나와있다
<img src="https://velog.velcdn.com/images/pang_e/post/622879a8-2b48-4ee9-a149-6ab86484a08c/image.png" alt=""></p>
<img src = "https://media.giphy.com/media/l3q2K5jinAlChoCLS/giphy.gif?cid=790b76118regvoriavg066vqm834h778e1duzff8vfkkglmj&ep=v1_gifs_search&rid=giphy.gif&ct=g" width = 400>


<p>... 간단하게 예제를 만들어보자</p>
<p><code>File</code> 이라는 인터페이스</p>
<pre><code class="language-kotlin">
interface File {
    fun read(): String
}</code></pre>
<p><code>RealFile</code> 클래스는 <code>File</code> 인터페이스의 구현체이다</p>
<pre><code class="language-kotlin">class RealFile(private val fileInfo: String): File {

    init {
        println(&quot;${fileInfo} - RealFile Constructor Call&quot;)
    }
    override fun read(): String {
        return &quot;${this.fileInfo} - Read Call&quot;
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/pang_e/post/af6a8d39-a7f6-4603-bd7e-cb7849d18603/image.png" alt=""></p>
<p>사실 여기까지 일반적으로 우리가 사용하는 방식이지만, 다음 <code>ProxyFile</code>을 추가해보겠다</p>
<p><code>ProxyFile</code>은 <code>File</code> 인터페이스의 실제 구현체 <code>RealFile</code>의 일을 위임받아 대신 처리한다.</p>
<p>또한 <code>File</code> 인스턴스를 갖고있어, 캐시 형태로 데이터를 저장하여 실제 서버(<code>RealFile</code>) 의 부담을 줄임과 동시에 지연로딩으로 동작한다</p>
<pre><code class="language-kotlin">class ProxyFile(private val fileInfo: String):File {

    private var file: File? = null
    init {
        println(&quot;${fileInfo} - ProxyFile Constructor Call&quot;)
    }
    override fun read(): String {
        if(this.file == null){
            this.file = RealFile(fileInfo)
        }
        return this.file?.read() ?: throw RuntimeException()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/pang_e/post/299f0049-c560-44cd-8240-3becd8796707/image.png" alt=""></p>
<p>위와 같은 관계에서 사용자는 프록시 객체 <code>ProxyFile</code>를 호출하였지만, 실제로 동작하는 구현부는 <code>RealFile</code>가 실행된다.</p>
<pre><code class="language-kotlin">@Test
fun `프록시 객체를 생성하여 호출한다`(){
    val file = ProxyFile(&quot;file1&quot;)

    println(&quot;&gt; Constructor End&quot;)

    println(file.read())
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/pang_e/post/2bcc5a3a-9db2-4884-811b-04d1d3f24008/image.png" alt=""></p>
<hr>
<h3 id="데코레이터-패턴은-또-뭐임">데코레이터 패턴은 또 뭐임</h3>
<p>찾아보다가 데코레이터 패턴과도 굉장히 유사하다고 느꼈다.</p>
<p>실제로 둘다 원본 객체를 건들이지 않으며 동작한다는게 헷갈릴수도 있는데, 가장 큰 차이점은 두 패턴의 <code>목적</code> 이다</p>
<h4 id="데코레이터-패턴">데코레이터 패턴</h4>
<ul>
<li><p>객체의 새로운 기능의 추가</p>
</li>
<li><p>기존 객체의 응답에 대한 변경</p>
</li>
</ul>
<h4 id="프록시-패턴-1">프록시 패턴</h4>
<ul>
<li><p>기존 객체에 대한 접근 제어 및 부가기능의 제공</p>
</li>
<li><p>기존 객체에 대한 불변성</p>
</li>
</ul>
<p><a href="https://ppusda.tistory.com/86">데코레이터 패턴과 프록시 패턴의 차이</a> 는 여기 블로그에서 아주 야무지게 정리해놨다. <del>여기보다 잘쓸 자신이 없어서 이러는거 아님</del></p>
<hr>
<p>코드 : <a href="https://github.com/superpangE/oneday_study/tree/design-pattern">https://github.com/superpangE/oneday_study/tree/design-pattern</a></p>
]]></description>
        </item>
    </channel>
</rss>