<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>💻itzel_dev.log</title>
        <link>https://velog.io/</link>
        <description>🐜👣steadiness🐜👣</description>
        <lastBuildDate>Sun, 20 Apr 2025 14:07:51 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>💻itzel_dev.log</title>
            <url>https://velog.velcdn.com/images/itzel_02/profile/362fd19c-c887-4450-b5fe-f99404910a14/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 💻itzel_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/itzel_02" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[⚠️[SpringBoot] - SpelEvaluationException: EL1007E]]></title>
            <link>https://velog.io/@itzel_02/SpringBoot-SpelEvaluationException-EL1007E</link>
            <guid>https://velog.io/@itzel_02/SpringBoot-SpelEvaluationException-EL1007E</guid>
            <pubDate>Sun, 20 Apr 2025 14:07:51 GMT</pubDate>
            <description><![CDATA[<pre><code>**2025-04-16T21:11:43.487+09:00 ERROR 12360 --- 
[Jinus] [nio-5100-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: 
org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field &#39;dietDate&#39; cannot be found on null] with root cause**</code></pre><p>카카오톡 테스트 챗봇으로 ‘식단 조회’ 시 발생한 오류이다. </p>
<hr>
<h3 id="chat-gpt-오류-분석">[Chat-GPT 오류 분석]</h3>
<p><strong>SpEL</strong></p>
<blockquote>
<p><strong>Spring Expression Language</strong></p>
</blockquote>
<blockquote>
<p><code>${….}</code> or <code>#{….}</code> 식을 해석하려고 했는데, 해당 표현식에서 참조한 <code>dietDate</code> 속성이 <code>null</code> 객체에 대해 호출되었기 때문에 발생한 예외</p>
</blockquote>
<ul>
<li><code>dietDate</code> 속성을 찾음</li>
<li>해당 속성이 붙은 객체가 <code>null</code> 상태임<ul>
<li><code>RequestDto</code>로 파라미터들을 객체로 묶어놓음</li>
<li><code>RequestDto</code> 객체가 <code>null</code>이라는 말</li>
</ul>
</li>
<li>즉, <code>null.dietDate</code>를 참조한 상황</li>
</ul>
<p>⇒ 로그를 확인해 관련 객체가 <code>null</code>이 아닌지 체크</p>
<p><strong>[식단 조회 과정 디버깅 실행]</strong></p>
<p>모든 파라미터들은 값이 잘 들어간 상태</p>
<p>→ 즉, <code>RequestDto</code> 객체는 <code>null</code> 이 아님</p>
<p>오류가 발생하는 지점</p>
<p><code>DietQueryServiceV2</code>의 <code>getDiets</code> 메소드 내 코드에서 발생</p>
<pre><code class="language-java">List&lt;DietDto&gt; dietDtos = cacheServiceV2.getDietList(parameters, cafeteriaId);</code></pre>
<p>디버깅을 따라가보면 이 코드 이후에 <code>InvocableHandlerMethod</code> 클래스 내의 <code>doInvoke</code> 메소드의 <code>try-catch</code> 문에서 <code>InvocationTargetException</code> 예외처리에서 걸림</p>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/94462cb6-d799-4679-9778-ee93c37bffb6/image.png" alt=""></p>
<pre><code class="language-java">catch (InvocationTargetException var9) {
    InvocationTargetException ex = var9;
    Throwable targetException = ex.getCause();
    if (targetException instanceof RuntimeException runtimeException) {
            throw runtimeException;</code></pre>
<p><code>if</code>문까지 실행된 후 <code>runtimeException</code>이 던져짐</p>
<p>그 후, <code>DispatcherServlet</code> 클래스의 <code>doDispatch</code> 메소드의 <code>try-catch</code> 문에서 <code>Exception</code> 예외에서 걸림</p>
<pre><code class="language-java">catch (Exception var20) {
        Exception ex = var20;
    dispatchException = ex;</code></pre>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/95517c1b-6270-4cd4-b6cc-a6e2ce6ed0a8/image.png" alt=""></p>
<p>그 후, <code>catch</code> 문을 벗어난 후 아래 문장 실행</p>
<pre><code class="language-java">this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);</code></pre>
<pre><code class="language-java">catch (Exception var22) {
    Exception ex = var22;
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);</code></pre>
<pre><code class="language-java">finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
        }
    } else if (multipartRequestParsed) {
              this.cleanupMultipart(processedRequest);
    }</code></pre>
<ul>
<li>첫 번째 <code>if</code>문을 실행 → <code>false</code></li>
<li><code>else-if</code> 문 실행 → <code>false</code></li>
</ul>
<pre><code class="language-java">protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception</code></pre>
<p>이 메소드에서 <code>source code does not match bytecode</code>라는 오류가 종종 뜸</p>
<p>그 후, <code>doService</code> 메소드로 이동</p>
<p>등등 그 후에도 여러 예외들로 계속 전파되어 콘솔에 오류 발생</p>
<p>결론적으로는 SpEL 오류라는 것.</p>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/311bba0f-9d76-4cbe-876c-0423ddf89cf5/image.png" alt=""></p>
<p>이 코드를 자세히 보면 <code>java.lang.reflect.InvocationTargetException</code> 이 발생함</p>
<p>이 예외로 빠진다는 건, <code>cacheServiceV2.getDietList</code> 호출 내부에서 <strong>리플렉션</strong>으로 메서드를 호출하다가 예외가 터졌다는 의미</p>
<p><strong>⚠️[InvocationTargetException]</strong></p>
<blockquote>
<p>리플렉션을 통해 메서드를 호출할 때, 그 메서드 내부에서 예외가 발생하면 그 예외를 감싸서 던지는 예외</p>
</blockquote>
<pre><code class="language-java">Method method = someClass.getMethod(&quot;someMethod&quot;);
try {
    method.invoke(someObject);  // ← 내부에서 예외 발생하면
} catch (InvocationTargetException e) {
    Throwable realCause = e.getCause();  // 실제 예외 원인
}</code></pre>
<p><strong>[확인해야 할 것]</strong></p>
<ul>
<li><code>getDietList</code>내부에서 리플렉션을 사용하고 있는지 ? ⇒ NO</li>
<li>리플렉션 호출 대상 메서드에서 SpEL을 사용하는 로직이 있는지 ? ⇒ NO</li>
<li>캐시 관련 로직에서 SpEL을 key로 사용하고 있는지 ? ⇒ YES,,</li>
</ul>
<p><code>@Cacheable(key = &quot;#parameters.dietDate&quot;)</code> 캐시 키값 설정을 위해 해당 코드를 사용중이었음</p>
<p><code>#parameters</code>가 <code>null</code>이어서 <code>.dietDate</code> 접근할 수 없다는 뜻.</p>
<p><strong>[예외 흐름 요약]</strong></p>
<ul>
<li><code>cacheServiceV2.getDietList(parameters, cafeteriaId)</code> 호출</li>
<li>내부에서 <code>@Cacheable</code> 애너테이션 처리 중 → SpEL 평가 수행</li>
<li>SpEL 평가 중 <code>parameters</code>가 null → <code>SpelEvaluationException</code> 발생</li>
<li>이 예외는 리플렉션을 통해 메서드가 호출됐기 때문에 <code>InvocationTargetException</code>으로 감싸짐</li>
</ul>
<h3 id="결론">[결론]</h3>
<p>SpEL 문법 오류</p>
<h3 id="해결">[해결]</h3>
<p>SpEL에서 null-safe 연산자 <code>?.</code> 사용하기</p>
<pre><code class="language-java">@Cacheable(
    value = &quot;dietList&quot;,
    key = &quot;#parameters?.dietDate + &#39;::&#39; + #parameters?.period + &#39;::&#39; + #cafeteriaId&quot;,
    ...
)</code></pre>
<p>이것만 수정하고 나니 실제로 오류는 해결됨</p>
<p>(하지만, SpEL 오류의 근본적인 이유와 dietDate 속성값은 null이 아닌데 null로 인식되는 이유를 모르겠음)</p>
<p><strong>[추가로 알아볼 것]</strong></p>
<ul>
<li>리플렉션</li>
<li>SpEL</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[♻️배열과 리스트]]></title>
            <link>https://velog.io/@itzel_02/%EB%B0%B0%EC%97%B4%EA%B3%BC-%EB%A6%AC%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@itzel_02/%EB%B0%B0%EC%97%B4%EA%B3%BC-%EB%A6%AC%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 15 Apr 2025 16:09:54 GMT</pubDate>
            <description><![CDATA[<p>파이썬에서의 리스트 → 배열의 특성도 내포하여 크게 구분 X</p>
<p>자료 구조에서의 리스트 → 특징과 동작 원리 이해 필요</p>
<h2 id="배열">[배열]</h2>
<p>(in 자료 구조)</p>
<blockquote>
<p>메모리의 연속 공간에 값이 채워져 있는 형태의 자료구조</p>
</blockquote>
<ul>
<li><p>값을 인덱스를 통해 참조 가능
<img src="https://velog.velcdn.com/images/itzel_02/post/1a89e304-11f4-4ef4-80ab-558f8400256f/image.png" alt=""></p>
<h3 id="특징">[특징]</h3>
</li>
<li><p>인덱스로 값에 바로 접근 가능 → 빠름</p>
</li>
<li><p>값의 삽입/삭제가 어려움</p>
<ul>
<li>연속적이므로 해당 인덱스 주변에 있는 값을 이동시키는 과정이 필요 → 느림</li>
</ul>
</li>
<li><p>배열 크기는 선언할 때 지정</p>
<ul>
<li>한 번 선언 시, 크기 늘리거나 줄일 수 없음</li>
</ul>
</li>
</ul>
<h2 id="리스트">[리스트]</h2>
<p>(in 자료구조)</p>
<blockquote>
<p>값과 포인터를 묶은 노드라는 것을 포인터로 연결한 자료구조</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/4571bcc9-f563-42b0-abb0-587ef496d1e1/image.png" alt=""></p>
<h3 id="특징-1">[특징]</h3>
<ul>
<li>인덱스가 없어 값에 접근하려면 Head 포인터부터 순서대로 접근해야 함 → 느림</li>
<li>포인터로 연결되어 삽입/삭제 빠름</li>
<li>선언 시, 크기 별도 지정 X<ul>
<li>크기 변경이 자유로움</li>
</ul>
</li>
</ul>
<p><strong>파이썬에서는 배열과 리스트를 구분하지 않는다 !</strong></p>
<p>→ 파이썬의 리스트 = 리스트 특징 + 배열 특징</p>
<ul>
<li>인덱스로 바로 접근</li>
<li>가변적인 크기</li>
</ul>
<hr>
<h3 id="문제-풀이">[문제 풀이]</h3>
<p>문제 1 - 숫자의 합 구하기
<strong>시간 제한 : 1초  |  난이도: 브론즈 IV  |  백준 11720번</strong></p>
<p>N개의 숫자가 공백 없이 써 있다. 이 숫자를 모두 합해 출력하는 프로그램을 작성하시오.</p>
<h3 id="입력">[입력]</h3>
<p>1번째 줄에 숫자의 개수 N(1 ≤ N ≤ 100), 2번째 줄에 숫자 N개가 공백 없이 주어진다.</p>
<h3 id="출력">[출력]</h3>
<p>입력으로 주어진 숫자 N개의 합을 출력한다.</p>
<aside>

<p><strong>[예제 입력 1]</strong></p>
<p>1  # 숫자의 개수</p>
<p>1  # 공백 없이 주어진 N개의 숫자</p>
<p><strong>[예제 출력 1]</strong></p>
<p>1</p>
<hr>
<p><strong>[예제 입력 2]</strong></p>
<p>5</p>
<p>54321</p>
<p><strong>[예제 출력 2]</strong></p>
<p>15</p>
<hr>
<p><strong>[예제 입력 3]</strong></p>
<p>6</p>
<p>700000</p>
<p><strong>[예제 출력 3]</strong></p>
<p>7</p>
</aside>

<h3 id="1단계---문제-분석">[1단계 - 문제 분석]</h3>
<p>파이썬의 리스트 자료 구조를 통해 쉽게 해결 가능</p>
<ul>
<li>주어진 숫자를 리스트 형태로 저장 → <code>list(input())</code></li>
<li><code>index</code>를 통해 탐색하며 각 자릿수의 값 더하기<ul>
<li>더할 때는 정수형으로 변환 필요</li>
<li>리스트 내의 값은 <code>str</code> 형태</li>
</ul>
</li>
</ul>
<h3 id="2단계---슈도-코드-작성">[2단계 - 슈도 코드 작성]</h3>
<pre><code class="language-python">n값 받기
numbers 변수에 list 함수를 이용하여 숫자를 한 자리씩 나누어 받기
sum 변수 선언

for numbers 탐색:
        sum 변수에 numbers에 있는 각 자릿수를 가져와 더하기

sum 출력</code></pre>
<h3 id="내가-푼-답안">[내가 푼 답안]</h3>
<pre><code class="language-python">num = input()
numbers = list(input())
sum = 0

for n in numbers:
    sum += int(n)

print(sum)</code></pre>
<h3 id="파이썬-형-변환">[파이썬 형 변환]</h3>
<ul>
<li>int형 → int(data)</li>
<li>float형 → float(data)</li>
<li>str형 → str(data)</li>
<li>chr형 → chr(data)</li>
<li>bool형 → bool(data)</li>
</ul>
<p>bool 형 반환의 경우 <code>int</code>, <code>float</code>에서 변환할 때는 데이터가 0인지 아닌지, <code>chr</code>와 <code>str</code>에서 변환할 때는 값의 유무에 따라 <code>True</code>, <code>False</code>를 반환한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[♻️디버깅]]></title>
            <link>https://velog.io/@itzel_02/%EB%94%94%EB%B2%84%EA%B9%85</link>
            <guid>https://velog.io/@itzel_02/%EB%94%94%EB%B2%84%EA%B9%85</guid>
            <pubDate>Tue, 15 Apr 2025 15:24:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>프로그램에서 발생하는 문법 오류나 논리 오류를 찾아 바로잡는 과정</p>
</blockquote>
<h3 id="코드의-오류는-어떻게-잡을까-">코드의 오류는 어떻게 잡을까 ?</h3>
<ul>
<li>문법 오류 ⇒ 컴파일러</li>
<li>논리 오류 ⇒ 로직 step-by-step 검증</li>
</ul>
<h3 id="디버깅-중요성">[디버깅 중요성]</h3>
<p>문법 오류는 컴파일러가 찾아주지만, 논리 오류는 사용자의 의도와 코드의 흐름이 다르게 동작하는 것이므로 형태가 다양하다.</p>
<p>즉, 디버깅을 통해 어떤 흐름으로 동작하는지, 데이터의 상태 변화가 어떤지 등을 직관적으로 확인할 수 있다. 코딩테스트 응시 환경에서는 디버깅이 불가능하기 때문에, 디버깅을 익힌 경험으로 동작의 흐름을 예상할 수 있다.</p>
<h3 id="디버깅-하는-법">[디버깅 하는 법]</h3>
<ul>
<li>중단점 = 브레이크 포인트 설정<ul>
<li>중단점 여러 개 설정 가능</li>
</ul>
</li>
<li>디버깅 실행<ul>
<li>추적할 변숫값 지정 가능 → 변숫값이 의도한 대로 바뀌는지 확인</li>
</ul>
</li>
</ul>
<h3 id="example">[Example]</h3>
<pre><code class="language-python"># 배열의 주어진 범위의 합을 2로 나눈 몫을 구하시오.
import random

testcase = int(input())
answer = 0
A = [0] * (100001)

for i in range(0, 10001):
    A[i] = random.randrange(1, 101)

for t in range(1, testcase+1):
        start, end = map(int, input().split())

        for i in range(start, end + 1):
                answer = answer + A[i]

        print(str(testcase) + &quot; &quot; + str(answer/2))</code></pre>
<p><strong>[오류 1. 변수 초기화]</strong></p>
<p><code>answer</code> 에는 주어진 범위에 해당하는 값들의 합만 더해져야 한다.</p>
<pre><code class="language-python">for i in range(start, end + 1):
        answer = answer + A[i]</code></pre>
<p>디버깅 시, 해당 <code>for</code> 문에서 <code>answer</code> 값이 이전의 수식 결과에 누적되어 더해지는 것을 발견했다.</p>
<p>⇒ <code>answer</code> 값 초기화 로직 추가해야 함</p>
<p><strong>[오류 2. 반복문에서 인덱스 범위 지정]</strong></p>
<pre><code class="language-python">A = [0] * (100001)

for i in range(0, 10001):
    A[i] = random.randrange(1, 101)</code></pre>
<p>배열의 크기는 <code>100001</code> 만큼 할당했는데, 반복문의 범위를 <code>10001</code> 로 지정함.</p>
<p>⇒ 반복문 범위 수정 필요 0 하나 빠트림</p>
<p><strong>[오류 3. 잘못된 변수 사용]</strong></p>
<pre><code class="language-python">print(str(testcase) + &quot; &quot; + str(answer/2))</code></pre>
<p>출력문에서 몇 번째 테스트인지 횟수를 출력하고 싶었으나, 반복문의 변수인 <code>t</code> 가 아닌 <code>testcase</code> 변수로 잘못 사용함.</p>
<p><strong>[오류 4. 파이썬 자동 형 변환]</strong></p>
<p><code>str(answer/2))</code> </p>
<p><code>/</code> 나누기 연산자 사용 시, <code>float</code> 형으로 출력</p>
<p><code>//</code> 몫 구하는 연산자 사용 시, <code>int</code> 형으로 출력</p>
<p>(+ <code>%</code> 나머지 값 구하는 연산자)</p>
<p>→ 실제 문제에서 몫은 자연수 값으로 출력을 원하기 때문에 실패로 결정됨</p>
<p>디버깅을 습관화 해놓아야 코딩 테스트 환경에서 디버깅 없이도 데이터의 상태 변화나 흐름을 캐치할 수 있을 것 같다 ..!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🥽[JWT] - 토큰 기반 인증]]></title>
            <link>https://velog.io/@itzel_02/JWT-%ED%86%A0%ED%81%B0-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D</link>
            <guid>https://velog.io/@itzel_02/JWT-%ED%86%A0%ED%81%B0-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D</guid>
            <pubDate>Thu, 10 Apr 2025 04:33:16 GMT</pubDate>
            <description><![CDATA[<h3 id="정의">[정의]</h3>
<blockquote>
<p>JSON Web Token</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/46fdccd4-0b4d-427a-939e-c6960921a4eb/image.png" alt=""></p>
<blockquote>
<p>인증에 필요한 정보들을 암호화시킨 JSON 토큰</p>
</blockquote>
<p>JWT 토큰을 HTTP 헤더에 실어 서버가 클라이언트를 식별하는 방식</p>
<p>(JWT 토큰 = Access Token)</p>
<h3 id="구조">[구조]</h3>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/4ca01d09-49e0-425f-a0a3-3fb079d27d40/image.png" alt=""></p>
<p><code>.</code> 을 구분자로 <code>Header</code>, <code>Payload</code>, <code>Signature</code> 로 나뉜다.</p>
<ul>
<li><p>Header : JWT에서 사용할 타입과 해시 알고리즘의 종류</p>
</li>
<li><p>Payload : 서버에서 첨부한 사용자 권한 정보와 데이터 → 실제로 사용될 정보들</p>
<ul>
<li><p><code>key-value</code> 로 이루어진 한 쌍을 <code>Claim</code> 이라고 한다.</p>
</li>
<li><p><strong>등록된 클레임</strong></p>
<ul>
<li>iss(issuer; 발행자)</li>
<li>exp(expireation time; 만료 시간)</li>
<li>sub(subject; 제목)</li>
<li>iat(issued At; 발행 시간)</li>
<li>jti(JWI ID)</aside>
</li>
</ul>
</li>
<li><p><strong>공개 클레임</strong></p>
<ul>
<li>공개용 정보 전달을 위함</li>
<li>클레임 이름을 주로 URI로 지음</li>
</ul>
</li>
<li><p><strong>비공개 클레임</strong></p>
<ul>
<li>공개되면 안 되는 클레임</li>
<li>클라이언트-서버 간 통신에 사용</li>
</ul>
</li>
</ul>
</li>
<li><p>Signature : Header &amp; Payload 를 인코딩한 후 Header에 명시된 해시함수를 적용하고, 비밀키로 해시값 생성</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/68d2903e-7acf-4625-b49c-5b8eeaf59e3f/image.png" alt="">
-&gt; 토큰의 위변조 여부 확인에 사용</p>
<h3 id="인증-과정">[인증 과정]</h3>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/5e274b8a-def2-40ce-a557-8751546b6d38/image.png" alt=""></p>
<ol>
<li>사용자가 서버에 로그인 인증 요청</li>
<li>서버에서 <code>Header</code>, <code>Payload</code>, <code>Signature</code> 정의 후 JWT(액세스 + 리프레시)를 생성하여 쿠키에 담아 클라이언트로 전송<ul>
<li>서버는 리프레시 토큰을 DB에 별도 저장</li>
</ul>
</li>
</ol>
<p>3.클라이언트는 JWT를 로컬 스토리지에 저장</p>
<ul>
<li>서버에 API 요청 시 <code>Authorization header</code> 에 <code>Access Token</code>을 담아 전송</li>
</ul>
<p>4.서버는 토큰의 액세스 토큰의 유효성 검사</p>
<ul>
<li>유효 X → 토큰 만료 에러 반환<ul>
<li>클라이언트는 리프레시 토큰으로 새로운 액세스 토큰 발급 요청</li>
<li>서버는 DB의 리프레시 토큰과 비교 후 유효하다면 새로운 액세스 토큰 발급</li>
<li>유효 O → 요청 처리</li>
</ul>
</li>
</ul>
<h3 id="궁금증">[궁금증]</h3>
<p><strong>리프레시 토큰이 탈취당한다면 무용지물 아닐까 ?</strong></p>
<p>YES !</p>
<p>토큰 기반 인증의 특징 중 하나는 “무상태성” 이다. </p>
<p>사용자의 인증 정보가 담긴 토큰이 서버가 아닌 클라이언트에게 있기 때문에, 서버에서 따로 저장할 필요가 없다. 즉, <code>stateless</code> 인 것이다. </p>
<p>따라서 액세스 토큰을 탈취당하면 서버는 확인할 방법이 없다. 이를 위해 리프레시 토큰이라는 개념이 생겼다.</p>
<p>액세스 토큰의 유효기간(보통 30분)을 짧게 한다면 탈취당하더라도 오래 사용하지 못할 것이다. 하지만 사용장 입장에서는 유효기간이 끝날 때마다 토큰을 재발급해야 하는 번거로움이 있다. 따라서 리프레시 토큰의 유효기간을 길게 하여(보통 2주) 액세스 토큰이 만료되면 새로운 액세스 토큰을 발급할 수 있도록 하는 것이다. 결론적으로는 클라이언트 입장에서는 로그인 상태가 유지되는 것처럼 느껴진다.</p>
<p>하지만 리프레시 토큰은 “무효화” 할 수 있다는 장점이 있다. 리프레시 토큰은 서버에서도 저장을 하고 있다. 따라서 리프레시 토큰이 탈취당했을 때, 탈취당했다는 어떠한 요청이 오면 토큰을 무효화 시킬 수 있다는 것이다. </p>
<p>⇒ Refresh Token이 탈취되더라도 서버의 검증으로 AccessToken 재발급을 방지할 수 있다.</p>
<p><strong>토큰은 어디에 저장될까 ?</strong></p>
<p>Access Token은 요청 header의 authorization에 담긴다. → 공식 규격</p>
<p>Refresh Token은 <code>http only cookie</code>로 넘겨주면 js를 통한 접근이 불가능해지므로 스크립트 삽입 공격(XSS)은 막을 수 있다. 또한 <code>http secure</code>  → https로만 통신하는 방법을 통해 쿠키에 보관하는 것이 좋다. 이렇게 하면 탈취 가능성은 거의 없다고 한다.</p>
<p><strong>토큰의 유효성은 어떻게 검증할까 ?</strong></p>
<p>토큰 기반 인증에서 리프레시 토큰 없이 무상태로 운영한다면, 서버는 클라이언트가 보낸 토큰을 어떤 데이터를 바탕으로 검증을 하는건지 궁금했다.</p>
<ol>
<li>서명 검증<ol>
<li>서버는 토큰의 Signature를 서버에만 있는 비밀키를 사용해 검증 → 토큰의 위변조 확인 가능</li>
</ol>
</li>
<li>Payload의 만료시간 확인<ol>
<li>토큰의 <code>exp</code> 를 확인하여 유효 기간이 지났는지 판단</li>
</ol>
</li>
</ol>
<p>⇒ 즉, 토큰 그 자체가 데이터로 이루어져 있기 때문에, 토큰 자체로 인증을 처리함</p>
<p><strong>[추가 보안 강화 방법]</strong></p>
<ol>
<li>JWT 토큰에 JTI (JWT ID) 포함 : 토큰 고유 식별자를 추가해 재사용 방지</li>
<li>토큰 만료 시간 관리 : Access Token은 짧게, Refresh Token은 길게</li>
<li>2FA(Two-Factor Authentication) : 추가 인증 단계 도입</li>
</ol>
<hr>
<h3 id="출처">[출처]</h3>
<ul>
<li><p><a href="https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-JWTjson-web-token-%EB%9E%80-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC#jwt_json_web_token_%EC%9D%B4%EB%9E%80">https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-JWTjson-web-token-%EB%9E%80-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC#jwt_json_web_token_%EC%9D%B4%EB%9E%80</a></p>
</li>
<li><p><a href="https://pizza7311.me/post/dev/refresh-and-access-token">https://pizza7311.me/post/dev/refresh-and-access-token</a></p>
</li>
<li><p><a href="https://stella-ul.tistory.com/entry/Access-Token%EC%9D%98-%EC%B7%A8%EC%95%BD%EC%A0%90%EA%B3%BC-Refresh-Token%EC%9D%84-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8D%A8%EC%95%BC%ED%95%98%EB%8A%94%EC%A7%80">https://stella-ul.tistory.com/entry/Access-Token%EC%9D%98-%EC%B7%A8%EC%95%BD%EC%A0%90%EA%B3%BC-Refresh-Token%EC%9D%84-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8D%A8%EC%95%BC%ED%95%98%EB%8A%94%EC%A7%80</a></p>
</li>
<li><p><a href="https://velog.io/@thekim12/Refresh-Token-%EB%B3%B4%EC%95%88%EC%9D%98-%ED%95%B5%EC%8B%AC-%ED%83%88%EC%B7%A8%EB%A5%BC-%EB%A7%89%EB%8A%94-%EA%B2%83">https://velog.io/@thekim12/Refresh-Token-%EB%B3%B4%EC%95%88%EC%9D%98-%ED%95%B5%EC%8B%AC-%ED%83%88%EC%B7%A8%EB%A5%BC-%EB%A7%89%EB%8A%94-%EA%B2%83</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🥽[GCP] - SSH]]></title>
            <link>https://velog.io/@itzel_02/GCP-SSH</link>
            <guid>https://velog.io/@itzel_02/GCP-SSH</guid>
            <pubDate>Wed, 09 Apr 2025 07:55:20 GMT</pubDate>
            <description><![CDATA[<h2 id="sshsecure-shell">SSH(Secure Shell)</h2>
<blockquote>
<p>원격 호스트에 접속하기 위해 사용되는 보안 프로토콜</p>
</blockquote>
<p>→ TCP 22번 포트 사용하여 통신</p>
<hr>
<h3 id="shell">Shell</h3>
<blockquote>
<p>명령어와 프로그램을 사용할 때 쓰는 인터페이스 / 커널과 사용자 간의 중간 역할</p>
</blockquote>
<p>⇒ 사용자로부터 명령을 받아 해석하고 실행하는 역할</p>
<hr>
<h3 id="배경">[배경]</h3>
<ul>
<li>기존 원격 접속 시 <strong>텔넷</strong>이라는 방식을 사용<ul>
<li>암호화 제공 X / 보안 상 취약 O</li>
<li>암호화하는 SSH 기술 등장</li>
</ul>
</li>
<li>GCP 같은 클라우드 서비스에서 제공하는 서버는 기본적으로 원격 접속을 사용함</li>
<li>즉, 서버 생성 시 필수적으로 SSH 보안 과정 거치는 것 !</li>
</ul>
<hr>
<h3 id="작동-원리">[작동 원리]</h3>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/8867a921-c9b8-4d07-99cc-3c48952df748/image.png" alt="">핵심 키워드 ⇒ <code>KEY</code> </p>
<ul>
<li>사용자와 서버는 각각의 키를 보유</li>
<li>키를 사용해 연결 상대를 인증하고 안전하게 데이터 교환</li>
<li>클라이언트가 서버에 원격 접속하기 위해 서버의 TCP 22번 포트로 SSH 접속 요청</li>
</ul>
<p><strong>[키 생성 방식]</strong></p>
<ul>
<li><p>대칭키 방식</p>
<blockquote>
<p>클라이언트-서버 간 전체 연결을 암호화하는데 사용</p>
</blockquote>
<ul>
<li><p>한 개의 키 사용 → 대칭 키</p>
<p>ex)</p>
</li>
</ul>
<ol>
<li>사용자 or 서버는 하나의 대칭키를 만들어 서로 공유</li>
<li>공유된 대칭 키를 이용해 정보를 암호화</li>
<li>받은 쪽에서 동일한 대칭 키로 복호화하여 정보 습득</li>
<li>정보 교환 완료 시, 대칭 키 폐기</li>
<li>접속할 때마다 대칭 키 생성하여 사용</li>
</ol>
</li>
</ul>
<ul>
<li><p>비대칭키 방식</p>
<blockquote>
<p>키 교환, 클라이언트 인증, 서버 인증에 사용</p>
</blockquote>
<ul>
<li><p>사용자와 서버가 서로의 정체를 증명해야 함</p>
</li>
<li><p>서버 or 사용자가  <code>Key Pair</code> 를 생성</p>
<ul>
<li><code>Key Pair</code> : 공개 키 / 개인 키 2가지로 이루어진 한 쌍</li>
<li>공개 키 → 누구나 가질 수 있음</li>
<li>개인 키 → 개인만 소유할 수 있음</li>
<li><code>.pub</code> : 공개 키 / <code>.pem</code> : 개인 키
  → 키 별로 파일 형식이 다름 </li>
</ul>
<p>ex)</p>
</li>
</ul>
<ol>
<li>사용자가 키 페어 생성 후 공개 키를 서버에 전송</li>
<li>서버는 공개 키를 받아, 랜덤한 값 생성</li>
<li>사용자가 개인 키를 이용해 값 복호화 후 서버 전송</li>
<li>서버는 값을 받고 자신이 생성한 값과 비교 </li>
<li>값이 일치한다면 인증된 사용자로 판단 후 접속 허용</li>
</ol>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🥽[Sentry] - Apdex]]></title>
            <link>https://velog.io/@itzel_02/Sentry-Apdex</link>
            <guid>https://velog.io/@itzel_02/Sentry-Apdex</guid>
            <pubDate>Wed, 09 Apr 2025 07:52:43 GMT</pubDate>
            <description><![CDATA[<h2 id="apdexapplication-performance-indx">Apdex(Application Performance Indx)</h2>
<blockquote>
<p>애플리케이션 성능 지표
⇒ 고객 만족도를 측정하는 지표</p>
</blockquote>
<h3 id="동작-방식">동작 방식</h3>
<ul>
<li>만족<ul>
<li>사용자가 모든 작업을 원활하게 진행</li>
<li>응답 시간 ≤ T</li>
</ul>
</li>
<li>허용<ul>
<li>사용자가 느리다는 생각을 하지만 작업을 마무리함</li>
<li>T &lt; 응답 시간 ≤ 4T</li>
</ul>
</li>
<li>불만<ul>
<li>사용자가 응답 속도의 느림으로 인해 작업을 마무리하지 않고 떠남</li>
<li>4T &lt; 응답 시간 or 오류</li>
<li>오류율이 높으면 평균 응답 시간은 만족스러우나 Apdex 점수가 낮음</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/360c9680-17df-45f5-a1de-db90b8502125/image.png" alt=""></p>
<p>0 → 최악</p>
<p>1 → 만족</p>
<h3 id="점수-높이는-방법">점수 높이는 방법</h3>
<ol>
<li>코드 및 DB 쿼리 최적화<ol>
<li>비효율적인 코드 사용 시, 많은 CPU 및 메모리 리소스를 요구하여 로드 시간이 느려짐</li>
<li>가장 좋은 방법</li>
</ol>
</li>
<li>과중한 작업에는 비동기 처리 사용<ol>
<li>상호 통신 환경의 시스템 간에 애플리케이션에 필요한 처리 분산 가능</li>
<li>비동기 처리 시, 작업을 별도의 프로세스로 오프로드하여 메인 스레드가 사용자 요청을 처리할 수 있도록 리소스 확보</li>
</ol>
</li>
<li>트래픽 수요 증가에 맞춰 서버 확장<ol>
<li>서버 용량을 늘리거나 부하 분산 기능을 사용하지 않는 상태에서 트래픽이 크게 증가하면 응답 시간이 느려짐</li>
</ol>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[♻️시간 복잡도]]></title>
            <link>https://velog.io/@itzel_02/%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84</link>
            <guid>https://velog.io/@itzel_02/%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84</guid>
            <pubDate>Wed, 09 Apr 2025 07:49:17 GMT</pubDate>
            <description><![CDATA[<h3 id="어떤-알고리즘으로-풀어야-할까-">어떤 알고리즘으로 풀어야 할까 ?</h3>
<p>알고리즘 선택의 기준 ⇒ <strong><em>시간복잡도</em></strong></p>
<h3 id="정의">[정의]</h3>
<blockquote>
<p>주어진 문제를 해결하기 위한 연산 횟수</p>
</blockquote>
<p><code>python</code> → 2000만 번 ~ 1억 번의 연산을 1초의 수행 시간으로 예측</p>
<p>연산 횟수는 1초에 2000만 번을 기준으로 생각,,</p>
<h3 id="유형">[유형]</h3>
<ul>
<li>빅-오메가 : 최선일 때의 연산 횟수</li>
<li>빅-세타 : 보통일 때의 연산 횟수</li>
<li>빅-오 :  최악일 때의 연산 횟수</li>
</ul>
<pre><code class="language-python">import random
findNumber = random.randrange(1, 101) # 1~100 사이 랜덤값 생성

for i in range(1, 101):
    if i == findNumber:
        print(i)
        break</code></pre>
<ul>
<li>빅-오메가 : 1번<ul>
<li>최선일 때 → 첫 번째 시도에 찾는 경우</li>
</ul>
</li>
<li>빅-세타 : N/2번 (N은 데이터 개수)<ul>
<li>보통일 때 → 절반쯤 찾는 경우</li>
</ul>
</li>
<li>빅-오 : N번<ul>
<li>최악일 때 → 100번째(마지막) 찾는 경우</li>
</ul>
</li>
</ul>
<p><strong>코딩테스트에서는 빅-오 표기법 사용 → <code>O(n)</code></strong></p>
<p>⇒ 항상 최악의 경우 생각하기</p>
<p>코딩 문제에서 <strong>데이터 범위</strong> 확인(제일 큰 수 확인) !!</p>
<h3 id="연산-횟수-계산-방법">[연산 횟수 계산 방법]</h3>
<ul>
<li>연산 횟수 = 알고리즘 시간 복잡도 n값에 데이터의 최대 크기를 대입하여 도출</li>
</ul>
<p>ex)</p>
<p>데이터 범위 :  0 ~ 100,0000</p>
<p>시간 제한 : 2초</p>
<p>⇒ 조건을 만족하려면 4000만 번 이하의 연산 횟수로 해결 필요</p>
<p>[사용가능한 2개의 알고리즘]</p>
<ul>
<li>버블 정렬<ul>
<li>O(n^2)  → (100만)^2 &gt; 4000만 번 👉🏼 부적합</li>
</ul>
</li>
<li>병합 정렬<ul>
<li>O(nlogn) → (100만)log(100만) &lt; 4000만 번 👉🏼 적합</li>
</ul>
</li>
</ul>
<h3 id="시간-복잡도-도출-기준">[시간 복잡도 도출 기준]</h3>
<ol>
<li>상수는 시간 복잡도 계산에서 제회</li>
<li>가장 많이 중첩된 반복문의 수행 횟수</li>
</ol>
<pre><code class="language-python">N = 100000
cnt = 1

for i in range(N):
    print(&quot;연산 횟수&quot; + str(cnt))
    cnt += 1</code></pre>
<p>연산 횟수 = N</p>
<pre><code class="language-python">N = 100000
cnt = 1

for i in range(N):
    print(&quot;연산 횟수&quot; + str(cnt))
    cnt += 1

for i in range(N):
    print(&quot;연산 횟수&quot; + str(cnt))
    cnt += 1

for i in range(N):
    print(&quot;연산 횟수&quot; + str(cnt))
    cnt += 1</code></pre>
<p>연산 횟수 = 3N</p>
<p>⇒ 3배 차이</p>
<p>코딩 테스트에서는 상수를 무시하므로 시간복잡도는 O(N)으로 동일함</p>
<pre><code class="language-python">N = 100000
cnt = 1

for i in range(N):
    for j in range(N):
        print(&quot;연산 횟수&quot; + str(cnt))
        cnt += 1</code></pre>
<p>연산 횟수 = N^2</p>
<p>중첩이 있는 부분 O(N^2) → 시간 가장 많이 걸리기 때문</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🍃static & templates 내의 파일 경로명]]></title>
            <link>https://velog.io/@itzel_02/static-templates-%EB%82%B4%EC%9D%98-%ED%8C%8C%EC%9D%BC-%EA%B2%BD%EB%A1%9C%EB%AA%85</link>
            <guid>https://velog.io/@itzel_02/static-templates-%EB%82%B4%EC%9D%98-%ED%8C%8C%EC%9D%BC-%EA%B2%BD%EB%A1%9C%EB%AA%85</guid>
            <pubDate>Tue, 08 Apr 2025 15:13:40 GMT</pubDate>
            <description><![CDATA[<h2 id="✔️상황">✔️상황</h2>
<p>블로그 웹 페이지를 구현하는 실습을 진행하고 있다.
블로그 화면을 구성하기 위해 <code>html</code>과 <code>js</code> 파일을 작성하였다.
<code>html</code>상에서 삭제 버튼을 클릭했을 때, <code>js</code>파일이 동작하여 파일을 삭제시키는 메소드 요청을 보내야 하는데, 버튼을 눌려도 아무런 반응이 없었다.</p>
<h2 id="✔️원인">✔️원인</h2>
<p>두 가지 원인을 생각했다.</p>
<ol>
<li>삭제 버튼을 인식하기 위해 작성한 <code>html</code>에서의 id값과 <code>js</code>에서의 id값의 불일치 문제</li>
<li><code>js</code> 파일의 경로명 문제</li>
</ol>
<p>결론은 경로 문제였다.</p>
<p><code>resources</code> &gt;&gt; <code>static</code> &gt;&gt; <code>article.js</code> 와 같은 경로에 <code>js</code> 파일이 존재했기 때문에, <code>html</code> 파일에서 <code>js</code> 파일의 경로를 작성할 때 <code>/static/article.js</code>로 작성했다.</p>
<p>하지만 SpringBoot는 <code>static</code>, <code>public</code>, <code>resources</code>, <code>META-INF/resources</code> 폴더를 자동으로 정적 리소스 경로로 매핑한다.</p>
<p>예를 들어 <code>src/main/resources/static/article.js</code> 경로명이 있다면
웹에서 접근할 때는 👉🏼 <code>http://localhost:8080/article.js</code> 로 접근하는 것이다.</p>
<pre><code class="language-vbnet">classpath:/static/
classpath:/public/
classpath:/resources/
classpath:/META-INF/resources/</code></pre>
<p>SpringBoot의 정적 자원 매핑 경로는 위와 같다.</p>
<p>따라서 브라우저에서 접근할 때 <code>/static/article.js</code>가 아니라 <code>/article.js</code>라고 요청하면 Spring이 알아서 <code>static</code> 폴더 내부를 뒤져 찾아주는 것이다.</p>
<h2 id="✔️결론">✔️결론</h2>
<p>즉, 폴더명인 <code>static</code>은 경로에서 생략하는 것이 SpringBoot의 규칙임 ❗❗</p>
<h2 id="✔️궁금증">✔️궁금증</h2>
<p>그러면 <code>html</code> 파일이 들어있는 <code>templates</code> 폴더도 마찬가지일까? 
=&gt; 아니다.</p>
<p>웹페이지에 <code>http://localhost:8080/templates/article.html</code> 을 요청했을 때 <code>404</code> 에러가 떴다.</p>
<p><code>templates</code> 폴더는 <code>static</code> 폴더와 다르게 동작한다.</p>
<p><code>templates</code> 폴더는 정적 리소스가 아니라 템플릿 엔진이 처리하는 동적 리소스이다.
보통 <code>Thymeleaf</code>, <code>Mustache</code>, <code>Freemarker</code> 같은 템플릿 엔진에서 사용된다.</p>
<p>따라서 <code>templates</code> 안에 있는 파일들은 직접 URL로 접근할 수 없고, <code>Controller</code>에서 해당 파일 이름을 리턴해야 뷰로 렌더링된다.</p>
<pre><code class="language-java">@Controller
public class BlogController {
    @GetMapping(&quot;/blog&quot;)
    public String getBlogPage(Model model) {
        model.addAttribute(&quot;title&quot;, &quot;블로그 제목&quot;);
        return &quot;blog&quot;;  // → templates/blog.html 렌더링
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚠️[SpringBoot] Error creating bean with name 'dataSourceScriptDatabaseInitializer']]></title>
            <link>https://velog.io/@itzel_02/SpringBootError-creating-bean-with-name-dataSourceScriptDatabaseInitializer</link>
            <guid>https://velog.io/@itzel_02/SpringBootError-creating-bean-with-name-dataSourceScriptDatabaseInitializer</guid>
            <pubDate>Mon, 07 Apr 2025 08:23:45 GMT</pubDate>
            <description><![CDATA[<h2 id="✔️문제-상황">✔️문제 상황</h2>
<p><code>resources</code> 폴더 하위에 <code>data.sql</code> 파일을 생성하고, 데이터를 삽입하는 <code>INSERT</code>문을 작성하였다. 애플리케이션을 실행시켰더니 위와 같은 오류가 발생했다.</p>
<h2 id="✔️원인">✔️원인</h2>
<p>Spring Boot에서 <code>data.sql</code> 파일을 사용하여 데이터베이스 스키마를 초기화하려고 할 때 발생하는 오류 중 하나이다. </p>
<blockquote>
</blockquote>
<p>SQL Script DataSource Initialization의 동작 방식 변경으로 인해 Hibernate 초기화 시점 변경</p>
<p>스프링 부트 2.5.x 이전 버전에서는 Hibernate가 초기화된 후에 &#39;data.sql&#39; 스크립트가 실행되었지만, 해당 버전부터는 data.sql 스크립트가 Hibernate 초기화되기 전에 실행되도록 기본 설정된다. 따라서 Hibernate 초기화 후에  &#39;data.sql&#39;스크립트를 실행되게 해야 한다.</p>
<h2 id="✔️해결">✔️해결</h2>
<pre><code class="language-java">// application.yml 설정 파일
spring:
  jpa:
    defer-datasource-initialization: true​</code></pre>
<p> 해당 설정은 Spring Boot에서 데이터소스 초기화를 지연시키는 설정이다. data.sql 스크립트를 지연시켜 Hibernate 초기화가 먼저 실행되도록 한다.</p>
<hr>
<p> [출처]</p>
<ul>
<li><a href="https://cocococo.tistory.com/entry/Spring-Boot-%EC%98%A4%EB%A5%98-datasql-Error-creating-bean-with-name-dataSourceScriptDatabaseInitializer-defined-in-class-path-resource">https://cocococo.tistory.com/entry/Spring-Boot-%EC%98%A4%EB%A5%98-datasql-Error-creating-bean-with-name-dataSourceScriptDatabaseInitializer-defined-in-class-path-resource</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🍃Bean이 등록되는 과정]]></title>
            <link>https://velog.io/@itzel_02/Bean%EC%9D%B4-%EB%93%B1%EB%A1%9D%EB%90%98%EB%8A%94-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@itzel_02/Bean%EC%9D%B4-%EB%93%B1%EB%A1%9D%EB%90%98%EB%8A%94-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Fri, 04 Apr 2025 07:08:25 GMT</pubDate>
            <description><![CDATA[<p>스프링을 학습할 때 핵심 개념 중 하나는 <code>Bean</code>이다.
<code>Bean</code> 개념을 모르면 스프링 프로젝트가 어떻게 동작하는지 이해할 수 없을 것이다.</p>
<p>내가 공부하고 이해한 방식대로 정리를 해보려 한다.</p>
<hr>
<h2 id="bean">Bean</h2>
<p>우선 <code>Bean</code>이란 객체라고 생각하면 된다.</p>
<p>Java의 경우 객체를 생성할 때,</p>
<pre><code class="language-java">public class A {
    b = new B();
}</code></pre>
<p>위와 같은 방식으로 <code>new</code> 키워드를 사용해 생성한다.</p>
<p>하지만 스프링에서는 </p>
<pre><code class="language-java">public class A {
    private B b;
}</code></pre>
<p>해당 객체를 선언해주는 방식이다.</p>
<p>위와 같은 방식으로 객체를 생성할 수 있는 이유는 스프링 컨테이너가 객체를 별도로 관리하고 생성해주기 때문이다.</p>
<p>즉, 객체를 직접 만드는 것이 아니라 <code>A</code>객체를 사용하고 싶다면 스프링 컨테이너가 객체 <code>A</code>를 생성하여 넘겨주는 방식이다.</p>
<p>그럼 스프링 컨테이너는 객체를 어떻게 인식할까 ?
-&gt; 사용자가 작성한 어노테이션을 통해 빈을 등록한다.</p>
<hr>
<p>코드를 통해 빈 등록 과정을 알아보았다.
<img src="https://velog.velcdn.com/images/itzel_02/post/ed161250-95bf-46a5-8150-8e56bd00e042/image.png" alt=""> <code>@SpringBootApplication</code> 이라는 어노테이션은 해당 클래스를 메인 클래스로 인식하고, 스프링 부트 사용에 필요한 기본 설정을 해준다.</p>
<h3 id="springbootapplication-구성">@SpringBootApplication 구성</h3>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/13390216-d8e9-44ed-900f-d5d524ef3e66/image.png" alt=""></p>
<ul>
<li><p><code>@SpringBootConfiguration</code> : 스프링 부트 관련 설정을 나타낸다. <code>@Configuration</code>을 상속해서 만든 어노테이션이다.</p>
</li>
<li><p><code>@ComponentScan</code> : 사용자가 등록한 빈을 읽고 등록한다. 또한 <code>@Component</code>라는 어노테이션을 가진 클래스를 찾아 빈으로 등록하는 역할을 한다.</p>
</li>
<li><p><code>@EnableAutoConfiguration</code> : 자동 구성을 활성화한다. 스프링 부트 서버가 실행될 때, 메타 파일을 읽고 정의된 설정들을 자동으로 구성하는 역할을 한다.</p>
</li>
</ul>
<p>하지만 모든 빈에 <code>@Component</code> 어노테이션을 사용하지 않는다. 개발을 하면 용도에 따라 역할을 구분하기 위해 다른 어노테이션을 사용하기 때문이다.</p>
<ul>
<li><code>@Configuration</code> -&gt; 설정 파일 등록</li>
<li><code>@Repository</code> -&gt; ORM 매핑</li>
<li><code>@Controller</code>, <code>@RestController</code> -&gt; 라우터</li>
<li><code>@Service</code> -&gt; 비즈니스 로직</li>
</ul>
<p>자주 사용하는 어노테이션들이다.</p>
<hr>
<h3 id="restcontroller-구성">@RestController 구성</h3>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/7c87b70b-76df-4d0d-9301-3d6b59822683/image.png" alt="">앞서, <code>@ComponentScan</code> 이 <code>@Component</code> 어노테이션을 가진 클래스들을 찾아 빈으로 등록한다고 하였다.</p>
<p>하지만 다른 어노테이션을 사용했을 때는 어떻게 해당 클래스를 찾을 수 있는 것일까?</p>
<p>어노테이션의 구성을 살펴보았다.</p>
<p><code>@RestController</code>는 <code>@Controller</code>와 <code>@ResponseBody</code> 이 2개의 어노테이션을 합친 것이라고 볼 수 있다.</p>
<p>하지만 여기서 <code>@Component</code>는 찾아볼 수 없다.</p>
<h3 id="controller-구성">@Controller 구성</h3>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/0243c19e-1774-4c9d-bc9b-0fd7aa5037ba/image.png" alt=""><code>@Controller</code> 어노테이션의 구성을 보니 비로소 <code>@Component</code> 어노테이션을 찾을 수 있었다.</p>
<p>즉, 다른 어노테이션들이 <code>@Component</code> 어노테이션을 감싸고 있는 구조이다. 따라서 위에서 소개한 4개의 어노테이션들도 <code>@Component</code> 어노테이션을 포함하고 있기 때문에 빈으로 등록될 수 있는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🌎Localhost란 ?]]></title>
            <link>https://velog.io/@itzel_02/Localhost%EB%9E%80</link>
            <guid>https://velog.io/@itzel_02/Localhost%EB%9E%80</guid>
            <pubDate>Fri, 04 Apr 2025 06:15:09 GMT</pubDate>
            <description><![CDATA[<p>스프링 프레임워크를 학습하며 서버를 가동하고 테스트할 때, <code>localhost</code>를 주소창에 검색한다. 문득, <code>localhost</code>가 궁금해졌다.</p>
<h2 id="localhost">Localhost</h2>
<blockquote>
<p>host는 주최자라는 의미로 IT에서 host는 네트워크를 이용하기 위해 네트워크에 연결된 장치를 의미한다.
루프팩 호스트명이라고도 한다.</p>
</blockquote>
<p>즉, 그 장치는 노트북이 될 수도 스마트폰이 될 수도 아이패드가 될 수도 있다.</p>
<p>그럼 <code>localhost</code>는 무엇일까 ?</p>
<blockquote>
<p>localhost는 네트워크 상에서 자신의 컴퓨터 주소를 의미한다.</p>
</blockquote>
<p>즉, 내 컴퓨터의 주소 == <code>localhost</code>인 것이다.
따라서 다른 컴퓨터에서는 이 주소에 접근할 수는 없다. </p>
<p>localhost의 ip주소는 <code>127.0.0.1</code>이다.
우리가 흔히 알고 있는 네이버의 도메인명은 <code>www.naver.com</code>이다.
하지만 이론적으로 ip주소는 32비트 값의 숫자로 이루어져 있다.</p>
<p>네이버에 접속하는 과정을 설명해보자면 다음과 같다.</p>
<ol>
<li><code>www.naver.com</code>에 접속</li>
<li>DNS(도메인 네임 서버)에서 <code>124.x.x.x</code>와 같이 ip주소로 변환</li>
<li>해당 ip주소로 데이터 요청</li>
<li>화면 출력</li>
</ol>
<p><code>www.naver.com</code>이라는 주소는 숫자로 구성된 ip주소 대신에 가독성을 위해,, 비유하자면 별명을 붙여 부르는 것이라고 생각하면 될 것 같다.</p>
<hr>
<h2 id="그럼-127001은-무엇일까-">그럼 127.0.0.1은 무엇일까 ?</h2>
<blockquote>
<p>컴퓨터 네트워크에서 localhost를 가리키는 특별한 ip주소이다. </p>
</blockquote>
<p>인터넷에 연결되어 있지 않더라도 항상 동작한다는 특징이 있다.
또한, 고정적으로 localhost는 <code>127.0.0.1</code>이라는 ip주소를 갖는다.</p>
<p><code>cmd</code>에서 <code>ipconfig</code>를 입력하고 볼 수 있는 ip주소는 사용중인 공유기의 주소이거나 외부 네트워크에 연결되어 있는 주소이다.
따라서 localhost는 다른 네트워크에 연결되지 않은 자신의 컴퓨터를 의미하는 것이다.</p>
<h2 id="언제-사용하는지-">언제 사용하는지 ?</h2>
<blockquote>
<p>주로 네트워크 테스트 및 개발 목적으로 사용한다.</p>
</blockquote>
<p>만약 로컬에서 서버를 실행시키고 테스트하려고 한다면, 브라우저에 localhost를 입력하여 자신의 컴퓨터 서버에 접속할 수 있다. 인터넷 연결없이도 작동하기 때문에 개발 및 테스트에 매우 편리하다는 장점이 있다.</p>
<hr>
<p>[출처]</p>
<ul>
<li><a href="https://unit-15.tistory.com/66">https://unit-15.tistory.com/66</a></li>
<li><a href="https://allmeaning.tistory.com/entry/127001-%EC%9D%98%EB%AF%B8-Localhost-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C">https://allmeaning.tistory.com/entry/127001-%EC%9D%98%EB%AF%B8-Localhost-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚠️[SpringBoot] Failed to calculate the value of task ':compileJava' property 'javaCompiler'.]]></title>
            <link>https://velog.io/@itzel_02/Failed-to-calculate-the-value-of-task-compileJava-property-javaCompiler</link>
            <guid>https://velog.io/@itzel_02/Failed-to-calculate-the-value-of-task-compileJava-property-javaCompiler</guid>
            <pubDate>Thu, 03 Apr 2025 05:22:46 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-상황">[문제 상황]</h3>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/87541aef-d8ea-4ced-8c4e-06da37519046/image.png" alt="">프로젝트 생성 후 빌드에서 오류가 발생하였다.</p>
<p>IntelliJ IDEA에서 SDK(Software Development Kit)는 특정 소프트웨어 프레임워크에 대한 응용 프로그램 개발 도구 모음이다. 예를 들어, Java로 애플리케이션을 개발하려면 Java SDK=JDK가 필요하다.</p>
<h3 id="원인">[원인]</h3>
<p>자바와 프로젝트 버전이 맞지 않아 발생한다고 한다.</p>
<h3 id="해결">[해결]</h3>
<p>프로젝트 버전을 맞추어 주자 !
현재 JDK 버전 21
Java 버전 17</p>
<p>build.gradle 파일에서
<img src="https://velog.velcdn.com/images/itzel_02/post/5873978a-390f-4066-979f-c51073c25050/image.png" alt="">괄호 안의 버전을 21로 변경해주었다.</p>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/cac3d16b-fed0-4426-ac5c-c34bee13ca9b/image.png" alt="">성공!</p>
<hr>
<p>[출처]
<a href="https://ririthebest.tistory.com/entry/IntelliJ-Failed-to-calculate-the-value-of-task-compileJava-property-javaCompiler">https://ririthebest.tistory.com/entry/IntelliJ-Failed-to-calculate-the-value-of-task-compileJava-property-javaCompiler</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🍵자바의 데이터 타입 ]]></title>
            <link>https://velog.io/@itzel_02/%EC%9E%90%EB%B0%94%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@itzel_02/%EC%9E%90%EB%B0%94%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85</guid>
            <pubDate>Sun, 23 Mar 2025 15:37:31 GMT</pubDate>
            <description><![CDATA[<h2 id="📌데이터-타입">📌데이터 타입</h2>
<blockquote>
<p>자바에서 다룰 수 있는 데이터의 종류</p>
</blockquote>
<h3 id="✔️기본-타입8개">✔️기본 타입(8개)</h3>
<ul>
<li>boolean</li>
<li>char</li>
<li>byte</li>
<li>short</li>
<li>int</li>
<li>long</li>
<li>float</li>
<li>double</li>
</ul>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/1e227a9b-8c81-49f9-b08e-fbff6f8222f6/image.png" alt="">정수 저장하는 타입 👉🏼 <code>byte</code>, <code>short</code>, <code>int</code>, <code>long</code>
실수 저장하는 타입 👉🏼 <code>float</code>, <code>double</code>
» <strong>타입마다 크기가 다르기 때문에, 다루는 값의 범위에 따라 적절한 타입을 선택해야 한다.</strong></p>
<p>자바에서 영어, 한글 구분없이 문자 하나는 <strong>2바이트의 유니코드</strong>로 저장된다. 따라서 문자 타입을 나타내는 <code>char</code>는 2바이트이다. </p>
<h3 id="✔️레퍼런스-타입1개">✔️레퍼런스 타입(1개)</h3>
<p>타입은 1개 이지만 용도는 3가지이다.</p>
<ul>
<li>배열에 대한 레퍼런스 👉🏼 배열에 대한 주소 값</li>
<li>클래스에 대한 레퍼런스 👉🏼 객체에 대한 주소 값 </li>
<li>인터페이스에 대한 레퍼런스 </li>
</ul>
<blockquote>
<p>레퍼런스란?
C언어의 포인터와 비슷한 개념이다.
한 가지 다른 점은 실제 주소값을 가지지 않는다는 것이다.</p>
</blockquote>
<h3 id="✔️문자열">✔️문자열</h3>
<p>반면, 문자열은 자바의 기본 타입에 속하지 않기 때문에 자바 라이브러리에서 제공하는 <code>String</code> 클래스를 이용한다. </p>
<pre><code class="language-java">String strName = &quot;apple&quot;;</code></pre>
<p>자바에서는 문자열과 기본 타입의 <code>+</code> 연산이 실행되면, 기본 타입의 값이 문자열로 바뀌고 두 문자열이 연결된 새로운 문자열이 생성된다.</p>
<pre><code class="language-java">strName + 1.8 -&gt; &quot;apple1.8&quot;</code></pre>
<h3 id="✔️변수와-선언">✔️변수와 선언</h3>
<blockquote>
<p>변수 : 데이터를 저장하는 공간</p>
</blockquote>
<p>변수를 선언하면 타입 크기의 메모리가 할당되며, 프로그램 실행 중 값을 쓰고 읽는 공간으로 사용된다. </p>
<pre><code class="language-java">int price;</code></pre>
<p>데이터 타입과 변수 이름으로 선언한다.
실수는 저장되지 않는다❗</p>
<ul>
<li>정수를 저장할 4바이트 공간이 할당</li>
<li>공간의 이름은 price로 붙임</li>
</ul>
<pre><code class="language-java">char c1, c2, c3;</code></pre>
<p>같은 타입의 변수는 <code>,</code>로 여러 개 선언 가능하다.</p>
<h4 id="🔹변수-선언과-동시에-초기화">🔹변수 선언과 동시에 초기화</h4>
<pre><code class="language-java">int radius = 10;
char c1 = &#39;a&#39;, c2 = &#39;b&#39;, c3 = &#39;c&#39;;
double weight = 25.3;</code></pre>
<h4 id="🔹변수-읽기와-저장">🔹변수 읽기와 저장</h4>
<pre><code class="language-java">radius = 10 * 5;
c1 = &#39;r&#39;;
weight = weight + 5.0; // 변수 값을 읽고 5.0을 더해 weight에 다시 저장</code></pre>
<p>선언한 후에도 값을 다시 저장하고 읽을 수 있다.</p>
<h3 id="✔️리터럴">✔️리터럴</h3>
<blockquote>
<p>프로그램에 직접 표현한 값</p>
</blockquote>
<ul>
<li><code>정수</code> : 34</li>
<li><code>실수</code> : 42.1</li>
<li><code>문자</code> : &quot;%&quot;</li>
<li><code>논리</code> : true</li>
<li><code>문자열</code> : &quot;hi&quot;</li>
</ul>
<h4 id="🔹정수-리터럴">🔹정수 리터럴</h4>
<pre><code class="language-java">int n = 15; // 10진수
int m = 015; // 8진수
int k = 0x15; // 16진수
int b = 0b0101; // 2진수</code></pre>
<p>정수 리터럴은 기본적으로 <code>int</code> 타입으로 자동 컴파일된다.
만약 <code>long</code> 타입으로 지정하려면 숫자 뒤에 <code>L</code> 또는 <code>l</code>을 붙여야 한다. 보통 소문자 <code>l</code>은 숫자 <code>1</code>과 혼동되므로 대문자 <code>L</code> 을 사용하는 것이 좋다. </p>
<p>리터럴 뒤에 붙이는 알파벳을 <code>접미사</code>라고 한다. 앞에 붙이면 <code>접두사</code>라고 한다. </p>
<pre><code class="language-java">long g = 24L;</code></pre>
<p>이미 <code>long</code>이라는 데이터 타입을 명시했는데, 왜 굳이 <code>접미사</code>를 붙여야 하는지 궁금했다.</p>
<p>여기서 먼저 알아야 하는 것은
<strong>변수에 값을 저장할 때는 &quot;변수의 타입&quot;과 &quot;리터럴 타입&quot;이 일치해야 한다는 것이다❗</strong>
(물론, 예외는 존재한다,,) </p>
<p>그래서 변수 선언 시 실행 과정을 알아보았다.</p>
<p>자바는 리터럴을 먼저 인식한 후 변수 타입을 고려하여 타입 변환을 수행한다는 것이다.</p>
<pre><code class="language-java">long s = 10;</code></pre>
<p>위의 코드를의 코드를 컴파일러가 해석하는 과정을 설명해보자면</p>
<ol>
<li><p>리터럴 <code>10</code>을 인식
👉🏼 <code>10</code>은 정수 리터럴로 기본적으로 <code>int</code> 타입으로 해석</p>
</li>
<li><p><code>리터럴 타입</code>과 <code>변수 타입</code>을 비교
👉🏼 <code>s</code>는 <code>long</code> 타입이지만, <code>10</code>은 <code>int</code> 타입</p>
</li>
<li><p>형 변환
👉🏼 <code>int</code> 타입의 <code>10</code>을 <code>long</code> 타입으로 자동 변환</p>
</li>
</ol>
<p>다른 경우를 보자.</p>
<pre><code class="language-java">long s = 2147483648;  // 컴파일 오류 ❌</code></pre>
<p><code>2147483648</code>은 기본적으로 <code>int</code> 타입으로 해석된다.</p>
<p>하지만 <code>int</code>의 최대값은 <code>2147483647</code>이므로 <code>2147483648</code>은 int 범위를 초과한다.</p>
<p>따라서 리터럴을 <code>int</code>로 먼저 인식해버려, 변수 타입을 고려하는 과정에서 오류가 발생하는 것이다.</p>
<p><code>접미사</code>를 사용하는 경우</p>
<pre><code class="language-java">long s = 2147483648L;  // 정상 동작 ✅</code></pre>
<p><code>L</code>을 붙이면 처음부터 <code>long</code> 리터럴로 인식되므로 오류가 발생하지 않는다.</p>
<p>예외적인 상황을 보자.</p>
<pre><code class="language-java">byte s = 127;</code></pre>
<p>byte의 범위는 <code>-128 ~127</code>이다 .
리터럴은 <code>int</code> 타입, 변수 타입은 <code>byte</code>이다.
위의 내용을 따르면 두 개의 타입이 다르므로 오류가 나야 정상이다.
하지만 리터럴이 <code>int</code> 타입일지라도 <code>byte</code>타입의 범위를 벗어나지 않으므로 문제가 없다.</p>
<pre><code class="language-java">double s = 3.14f;</code></pre>
<p>이 경우도 마찬가지이다. 
접미사 <code>f</code>를 명시하여 리터럴은 <code>float</code> 타입이지만 <code>double</code>의 데이터 범위가 더 크기 때문에 문제가 없다.</p>
<h4 id="🔹실수-리터럴">🔹실수 리터럴</h4>
<pre><code class="language-java">12.  12.0  .1234  0.1234  1234E-4</code></pre>
<p>소수점 형태나 지수 형태로 표현한다.
지수 <code>e</code>는 10의 n승을 나타낸다. <code>e3</code>은 10의 3승으로 1000을 나타낸다.</p>
<p>여기서 중요한것 <code>e</code>는 실수형을 나타내므로 그냥 <code>1000</code>이 아닌 <code>1000.0</code>이다. </p>
<p>실수 리터럴은 <code>double</code> 타입으로 자동 처리된다.</p>
<pre><code class="language-java">float f = 0.1234f;
double w = .1234D;</code></pre>
<p><code>f = 0.1234</code>로 하면 컴파일 오류가 난다. 기본 <code>double</code>타입으로 인식하고 변수 타입과 일치하지 않기 때문❗</p>
<h4 id="🔹문자-리터럴">🔹문자 리터럴</h4>
<p>단일 인용부호 <code>&#39; &#39;</code>로 표현하거나 <code>\u</code> 다음에 문자의 유니코드 값을 사용하여 표현한다.</p>
<pre><code class="language-java">char a = &#39;A&#39;;
char b = &#39;글&#39;;
char c = \u0041;</code></pre>
<h4 id="🔹특수문자-리터럴">🔹특수문자 리터럴</h4>
<p>백슬래시 <code>\</code> 다음에 특수 기호를 붙여 표현한다. 
이스케이프 시퀀스라고도 한다.
<img src="https://velog.velcdn.com/images/itzel_02/post/4add1dfa-5cd9-474a-8ab8-4ad3ea5880da/image.png" alt=""></p>
<h4 id="🔹논리-리터럴과-boolean-타입">🔹논리 리터럴과 boolean 타입</h4>
<p><code>true</code>, <code>false</code> </p>
<pre><code class="language-java">boolean a = true;
boolean b = 10 &gt; 0; // 참이므로 true
boolean c = 1; // 타입 불일치 오류. 숫자를 참, 거짓으로 사용 불가</code></pre>
<h3 id="👻tip-기본-타입-이외의-리터럴">👻Tip. 기본 타입 이외의 리터럴</h3>
<h4 id="🔹null-리터럴">🔹null 리터럴</h4>
<p>null은 기본 타입에 사용될 수 없고, 객체 레퍼런스에 대입된다.</p>
<pre><code class="language-java">int n = null; // 오류. 기본 타입은 불가
String str = null; // 정상</code></pre>
<h4 id="🔹문자열-리터럴">🔹문자열 리터럴</h4>
<p>이중 인용부호 <code>&quot; &quot;</code> 사용하며, 모든 문자열은 <code>String</code>클래스의 객체이다. </p>
<pre><code class="language-java">String str = &quot;Good&quot;;</code></pre>
<h4 id="🔹var-키워드">🔹var 키워드</h4>
<p>Java10 부터 지역 변수 선언 시, 변수 타입 대신 <code>var</code> 키워드를 사용할 수 있다. </p>
<pre><code class="language-java">var price = 200;
var name = &quot;what&quot;;
var pi = 3.14;
var point = new Point();</code></pre>
<p>리터럴 타입을 통해 자동으로 변수 타입을 지정한다.</p>
<p>하지만, 변수 선언문에 초깃값이 주어지지 않으면 오류가 발생한다.</p>
<pre><code class="language-java">var name;</code></pre>
<p>타입을 추론할 수 없기 때문❗
<strong>또한, <code>var</code> 사용은 지역 변수에만 한정된다는 점</strong></p>
<h3 id="✔️상수">✔️상수</h3>
<p>변수 선언 시 <code>final</code> 키워드를 사용해 선언한다.
변수와 달리 실행 중에 값을 변경할 수 없다.</p>
<pre><code class="language-java">final double PI = 3.141592;</code></pre>
<p>상수 선언은 변수 이름은 주로 대문자로 작성한다.</p>
<pre><code class="language-java">PI = 3.14; // 컴파일 오류</code></pre>
<pre><code class="language-java">public class CircleArea {
    public static void main(String[] args) {
        final double PI = 3.14; // 상수 선언

        double radius = 10.0; // 리터럴 기본 타입은 double
        double circleArea = radius*radius*PI;

        System.out.println(&quot;면적:&quot; + circleArea);
    }
}</code></pre>
<h3 id="✔️타입-변환">✔️타입 변환</h3>
<blockquote>
<p>변수나 상수 또는 리터럴 타입을 다른 타입으로 바꾸는 것</p>
</blockquote>
<h4 id="🔹자동-타입-변환">🔹자동 타입 변환</h4>
<p>치환문(=) 이나 수식 내에서 타입이 일치하지 않을 때, 컴파일러는 오류 대신 작은 타입을 큰 타입으로 자동 변환한다.</p>
<pre><code class="language-java">long m = 25; // 리터럴은 int. long으로 자동 변환
double d = 3.14 * 10; // 실수 연산을 위해 10이 10.0으로 자동 변환</code></pre>
<h4 id="🔹강제-타입-변환">🔹강제 타입 변환</h4>
<p>개발자가 강제로 타입 변환을 지시하는 경우.</p>
<pre><code class="language-java">double d = 1.9;
int n = (int)d; // 강제 타입 변환</code></pre>
<p><code>1.9</code>에서 <code>1</code>만 저장된다. 이에 데이터 손실이 발생하게 된다. 캐스팅이라고도 부른다.</p>
<hr>
<p>[출처]</p>
<ul>
<li><a href="https://mgyo.tistory.com/214">https://mgyo.tistory.com/214</a></li>
<li><a href="https://isoomni.tistory.com/entry/Java-%EB%AC%B8%EC%9E%90%EC%97%B4-String">https://isoomni.tistory.com/entry/Java-%EB%AC%B8%EC%9E%90%EC%97%B4-String</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🍵식별자]]></title>
            <link>https://velog.io/@itzel_02/%EC%8B%9D%EB%B3%84%EC%9E%90</link>
            <guid>https://velog.io/@itzel_02/%EC%8B%9D%EB%B3%84%EC%9E%90</guid>
            <pubDate>Thu, 20 Mar 2025 14:42:45 GMT</pubDate>
            <description><![CDATA[<h2 id="📌식별자identifier">📌식별자(Identifier)</h2>
<blockquote>
<p>식별자란 클래스, 변수, 상수, 메소드 등에 붙이는 이름을 말한다.</p>
</blockquote>
<h4 id="✔️식별자-이름-규칙">✔️식별자 이름 규칙</h4>
<p>명명 규칙이라고도 한다. 
모든 프로그래밍 언어별로 규칙이 존재하며 꼭 준수해야 한다.</p>
<ul>
<li>특수문자, 공백은 불가 ❌<ul>
<li><code>_</code>, <code>$</code> 는 예외로 사용 가능 ⭕</li>
</ul>
</li>
<li>한글 사용 가능 ⭕</li>
<li>예약어 즉, 자바의 키워드는 사용 불가 ❌</li>
<li>첫 번째 문자로 숫자는 사용 불가 ❌ </li>
<li>대소문자 구별 ⭕</li>
<li>길이 제한 ❌</li>
</ul>
<pre><code class="language-java">int name; // 가능
char student_ID; // 언더바 사용 O
void $func() {} // $ 사용 O
class Monster3 {} // 숫자 O
int whatsYourNameMyNameIsKitae; // 길이 제한 X
int barChart; int barchart; // 대소문자 구별
int 가격; // 한글 O</code></pre>
<pre><code class="language-java">int 3Chapter // 첫 번째 문자로 숫자 X
class if {} // 키워드 사용 X
char false; // 키워드 사용 X
void null() {} // 키워드 사용 X
class %calc {} // 특수기호 X</code></pre>
<h4 id="✔️자바-키워드">✔️자바 키워드</h4>
<blockquote>
<p>키워드란 자바에서 이미 용도가 정해진 것으로 <code>예약어</code>라고도 한다.</p>
</blockquote>
<p>for, if, while, true, false, null, this, try, void, class, char, case, int, long 등등 많다.</p>
<h4 id="✔️좋은-이름-붙이는-관습">✔️좋은 이름 붙이는 관습</h4>
<ol>
<li>변수 이름만 보고도 어떤 의미인지 이해할 수 있도록 만든다.</li>
<li>이름의 길이에 연연하지 말고 의미를 알 수 있게 충분히 긴 이름으로 만든다.</li>
<li>언어의 관습을 따르는 것이 좋다</li>
</ol>
<h4 id="✔️자바-식별자-관습">✔️자바 식별자 관습</h4>
<ul>
<li><p>클래스 이름</p>
<ul>
<li>첫 번째 문자는 <code>대문자</code>로 시작</li>
<li>여러 단어가 붙여지면 첫 번째 문자만 대문자로 표시<pre><code class="language-java">public class HelloWorld {}
class AutoVendingMachine {}</code></pre>
</li>
</ul>
</li>
<li><p>변수, 메소드 이름</p>
<ul>
<li>첫 단어는 소문자로 표기</li>
<li>이후의 각 단어 첫 번째 문자는 대문자로 표기<pre><code class="language-java">int myAge;
boolean isSingle;
public int getAge() {return 20;}</code></pre>
</li>
</ul>
</li>
<li><p>상수 이름</p>
<ul>
<li>이름 전체를 대문자로 표기<pre><code class="language-java">final double PI = 3.141592;</code></pre>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🍵자바 프로그램의 구조]]></title>
            <link>https://velog.io/@itzel_02/%EC%9E%90%EB%B0%94-%EA%B8%B0%EB%B3%B8-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@itzel_02/%EC%9E%90%EB%B0%94-%EA%B8%B0%EB%B3%B8-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Thu, 20 Mar 2025 14:25:18 GMT</pubDate>
            <description><![CDATA[<h2 id="📌자바-프로그램의-구조">📌자바 프로그램의 구조</h2>
<pre><code class="language-java">public class Hello {

    public static int sum(int n, int m) {
        return n + m;
    }

    // main() 메소드에서 실행 시작
    public static void main(String[] args) {
        int i = 20;
        int s;
        char a;

        s = sum(i, 10); // 메소드 호출
        a = &#39;?&#39;;
        System.out.println(a);
        System.out.println(&quot;Hello&quot;);
        System.out.println(s);
    }
}</code></pre>
<h4 id="✔️클래스-만들기">✔️클래스 만들기</h4>
<p>가장 기본적인 것은 <code>클래스</code>를 만들고 그 안에 필드나 메소드와 같은 모든 프로그램 요소를 작성한다는 점이다. <strong>클래스 바깥에는 어떤 것도 작성해서는 안된다.</strong></p>
<p><code>class</code> 키워드로 클래스명 <code>Hello</code>를 선언하고, <code>{}</code> 대괄호 사이에 코드를 작성한다. <code>public</code>은 접근지정자로 다른 모든 클래스에서 <code>Hello</code> 클래스를 자유롭게 사용할 수 있다는 의미로 지정해주는 것이다.</p>
<h4 id="✔️주석문">✔️주석문</h4>
<blockquote>
<p>주석문이란, 프로그램의 실행에 영향을 미치지 않으며, 코드에 대한 설명이나 특이사항 등을 자유롭게 기록하기 위해 사용한다. </p>
</blockquote>
<p>그냥 메모하는 것이라고 이해했다.</p>
<ul>
<li><code>//</code> 👉🏼 한 줄 주석 처리</li>
<li><code>/* */</code> 👉🏼 여러 줄 주석처리</li>
</ul>
<h4 id="✔️main-메소드">✔️main() 메소드</h4>
<p>자바 프로그램은 <code>main()</code> 메소드에서부터 실행을 시작한다.
<code>main()</code>은 반드시 <code>public</code>, <code>static</code>, <code>void</code> 타입으로 선언되어야 하며, 한 클래스당 단 하나의 <code>main()</code> 클래스를 가진다.</p>
<p>만약 여러 클래스를 작성한 경우, <code>main()</code> 클래스를 둔 파일만 실행되는 것이다. </p>
<h4 id="✔️메소드">✔️메소드</h4>
<blockquote>
<p>메소드는 멤버 함수이다.</p>
</blockquote>
<p>메소드 개수는 제한이 없다.</p>
<p>위의 <code>sum</code> 메소드는 매개변수 <code>n</code>과 <code>m</code>을 가지고 합을 계산하는 함수이다.</p>
<p><code>s = sum(i, 10)</code> 이 코드는 <code>sum</code> 메소드를 호출하는데, <code>i</code>와 <code>10</code>은 각각 매개변수 <code>n</code>과 <code>m</code>으로 전달된다. 그 후 합을 계산하여 그 결과를 <code>s</code> 변수로 반환한다.</p>
<h4 id="✔️변수-선언">✔️변수 선언</h4>
<blockquote>
<p>변수란 프로그램 실행 동안 데이터를 저장하는 공간이다.</p>
</blockquote>
<p><code>지역변수</code> : 메소드 내에 선언되어 사용되는 변수</p>
<ul>
<li>메소드 내에서만 사용 가능</li>
<li>메소드 실행이 끝나면 소멸됨</li>
</ul>
<p><code>int i = 20;</code> 👉🏼 변수를 선언하는 동시에 값 초기화</p>
<h4 id="✔️문장">✔️문장</h4>
<p>모든 문장은 <code>세미콜론(;)</code>으로 끝나야 한다.</p>
<p>자바 컴파일러는 세미콜론을 문장의 끝으로 인식한다.</p>
<h4 id="✔️출력">✔️출력</h4>
<p>데이터를 화면에 출력하려면 <code>System.out.println()</code> 또는 <code>System.out.print()</code>를 사용하면 된다.</p>
<p><code>println</code>은 출력 후 다음 행으로 넘어가고, <code>print</code>는 다음 줄로 넘어가지 않는다.</p>
<pre><code class="language-java">System.out.print(&quot;Hello&quot;);
System.out.print(&quot;Java&quot;);

&gt;&gt; HelloJava</code></pre>
<p>```java
System.out.println(&quot;Hello&quot;);
System.out.println(&quot;Java&quot;);</p>
<blockquote>
<blockquote>
<p>Hello
Java</p>
</blockquote>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚠️[SpringBoot] @Cacheable 동작 오류 - Self Invocation]]></title>
            <link>https://velog.io/@itzel_02/Redis-Cacheable-%EB%8F%99%EC%9E%91-%EC%98%A4%EB%A5%98-Self-Invocation</link>
            <guid>https://velog.io/@itzel_02/Redis-Cacheable-%EB%8F%99%EC%9E%91-%EC%98%A4%EB%A5%98-Self-Invocation</guid>
            <pubDate>Fri, 14 Mar 2025 06:36:06 GMT</pubDate>
            <description><![CDATA[<p><code>@Cacheable</code>을 이용하여 해당 데이터를 캐싱하려고 하는데, redis DB에 데이터가 저장되지를 않는다. 즉, 동작을 하지 않는 것이다.</p>
<p>하지만 다른 코드의 어노테이션은 동작을 잘 수행하는데, 특정 코드의 <code>@Cacheable</code>만 동작하지 않는 것이다.</p>
<p>구글링을 해본 결과 내부 메서드에 <code>@Cacheable</code>을 설정하면 동작하지 않는다는 것이다.</p>
<h2 id="✔️spring-cache-동작-원리">✔️Spring Cache 동작 원리</h2>
<p>Spring Cache는 <code>AOP</code>를 이용한다. Aspect Oriented Programming !
관점 지향 프로그래밍이라고 불린다.</p>
<p><code>@Cacheable</code>이 설정된 메서드를 호출할 때, Proxy 객체가 생성되어 해당 호출을 intercept 한다. 그리고 proxy 객체가 다시 <code>@Cacheable</code>이 설정된 메서드를 호출하는 방법이다.</p>
<pre><code class="language-java">@Cacheable(
            value = &quot;dietList&quot;,
            key = &quot;#parameters.day + &#39;::&#39; + #parameters.period + &#39;::&#39; + #cafeteriaId&quot;,
            unless = &quot;#result == null || #result.isEmpty()&quot;,
            cacheManager = &quot;contentCacheManager&quot;
    )
    // 식단 데이터 가져오기
    public List&lt;DietDto&gt; getDietList(HandleRequestDto parameters, int cafeteriaId) {
        System.out.println(&quot;getDietList 호출&quot;);
        // 오늘, 내일 문자열로 날짜 설정하기
        Date dietDate = getCurrentDate(parameters.getDay());
        // 식단 데이터 반환
        return dietRepositoryV2.findDietList(dietDate, parameters.getPeriod(), cafeteriaId);
    }</code></pre>
<pre><code class="language-java">// 카테고리별 메뉴 리스트 생성하기
    public MultiValueMap&lt;String, String&gt; getDiets(HandleRequestDto parameters, int cafeteriaId) {
        List&lt;DietDto&gt; dietDtos = getDietList(parameters, cafeteriaId);
        MultiValueMap&lt;String, String&gt; dietList = new LinkedMultiValueMap&lt;&gt;(); // 중복 키 허용(값을 리스트로 반환)

        for (DietDto o : dietDtos) {
            String key = (o.getDishCategory() != null) ? o.getDishCategory()
                    : (o.getDishType() != null) ? o.getDishType()
                    : &quot;메뉴&quot;;

            dietList.add(key, o.getDishName());
        }
        return dietList;
    }</code></pre>
<p><code>getDietList</code>함수에 <code>@Cacheable</code>을 적용했고, 같은 클래스 내부의 <code>getDiets</code> 함수 내에서 <code>getDietList</code>를 호출하는 코드이다.</p>
<p>@Cacheable self-invocation (in effect, a method within the target object calling another method of the target object). The cache annotation will be ignored at runtime </p>
<p>Spring AOP 기반의 <code>@Cacheable</code>은 자기 자신의 메서드를 호출할 때 동작하지 않는다. -&gt; Self Invocation 문제 발생</p>
<p>위의 코드처럼 자기 자신의 메서드를 호출하면 프록시를 거치지 않아서 <code>@Cacheable</code>이 무시된다.</p>
<h3 id="📑현재-코드-흐름">📑현재 코드 흐름</h3>
<ol>
<li>getDiets 실행</li>
<li>getDietList를 자기 자신이 직접 호출</li>
<li>프록시를 거치지 않으므로 <code>@Cacheable</code>이 무시됨</li>
<li>매번 db에서 데이터를 가져옴</li>
</ol>
<h2 id="해결-방법">해결 방법</h2>
<h4 id="cacheable-메서드를-외부에서-호출하도록-변경"><code>@Cacheable</code> 메서드를 외부에서 호출하도록 변경</h4>
<p>현재 클래스가 아닌 다른 빈에서 <code>getDietList</code>를 호출하는 것 !
<img src="https://velog.velcdn.com/images/itzel_02/post/f3f928a1-322b-46f9-ab4e-df146f33825e/image.png" alt="">
<img src="https://velog.velcdn.com/images/itzel_02/post/8f6d34f8-65cc-4fab-85bb-35c23f080f51/image.png" alt=""> 이렇게 클래스를 분리하여 import하여 사용하는 방식으로 변경하였다.</p>
<p>또 다른 방법으로는 <code>ApplicationContext</code>를 이용해 프록시를 직접 호출하는 것이다.
클래스를 분리하기 어려운 경우에 사용할 수 있다.</p>
<pre><code class="language-java">@Autowired
private ApplicationContext applicationContext;

public MultiValueMap&lt;String, String&gt; getDiets(HandleRequestDto parameters, int cafeteriaId) {
    DietService dietServiceProxy = applicationContext.getBean(DietService.class); // 프록시를 직접 가져옴
    List&lt;DietDto&gt; dietDtos = dietServiceProxy.getDietList(parameters, cafeteriaId);

    MultiValueMap&lt;String, String&gt; dietList = new LinkedMultiValueMap&lt;&gt;();
    for (DietDto o : dietDtos) {
        String key = (o.getDishCategory() != null) ? o.getDishCategory()
                : (o.getDishType() != null) ? o.getDishType()
                : &quot;메뉴&quot;;

        dietList.add(key, o.getDishName());
    }
    return dietList;
}</code></pre>
<p>spring이 직접 프록시를 가져와 실행하므로 <code>@Cacheable</code>이 적용될 것이다.</p>
<p>하지만 서비스 클래스를 따로 분리해서 하는 방식이 명확하고 유지보수에 좋을 것 같아 저 방법을 사용하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🍃redis 캐싱]]></title>
            <link>https://velog.io/@itzel_02/redis-%EC%BA%90%EC%8B%B1</link>
            <guid>https://velog.io/@itzel_02/redis-%EC%BA%90%EC%8B%B1</guid>
            <pubDate>Sun, 09 Mar 2025 13:26:31 GMT</pubDate>
            <description><![CDATA[<p>캐싱하기 위해 redis를 활용한다.</p>
<p>redis를 따로 설치하지 않고 redis cloud를 사용하였다.</p>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/1c1ee94c-0a98-405f-82ac-0ca76538620b/image.png" alt="">캐시 설정 파일을 작성하고, 캐시 수명을 임의로 30분으로 설정하였다.</p>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/4d211360-8bd8-481a-b0aa-18420aef4dea/image.png" alt="">redis와 연결하기 위한 설정도 마쳤다. <code>application.properites</code> 파일에서 redis cloud의 정보를 확인하고 <code>host</code>, <code>port</code>, <code>password</code>를 작성하였다.</p>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/dc0e80d1-3dc1-498d-a576-30d4ed125032/image.png" alt="">사용자 등록 여부를 확인하는  과정이 기능마다 하나씩 존재하기에 이 부분을 캐싱하였다. 
<code>value</code> : 캐시 네임스페이스 지정
<code>key</code> : 메소드의 파라미터를 지정해줌
<code>unless</code> : 특정 조건일 때 캐싱에서 제외함
<code>cacheManager</code> : 사용할 캐시 매니저 클래스 지정</p>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/732a0676-22f2-4c3e-afa0-7b97a05b938d/image.png" alt=""> 테스트 코드를 작성하여 테스트를 진행하였다.
첫 번째 조회할 때에는 캐시DB에 데이터가 없기 때문에 DB를 조회하여 데이터를 가져온 후 캐시 DB에 저장한다. 두 번째 조회에서는 DB를 조회하는 것이 아니라 캐시 DB에서 가져와야 한다. </p>
<p>동일한 값이 반환되어야 하며, 캐싱이 적용된 메소드는 한 번만 실행되어야 한다. 즉 DB를 조회하는 쿼리문이 한번만 실행되어야 한다는 것이다. 그 후로는 캐시 DB를 사용하므로 ,,</p>
<p>실제 테스트를 진행해보았다.
<img src="https://velog.velcdn.com/images/itzel_02/post/70e20a93-4caa-4e74-9381-62b292fce47e/image.png" alt="">처음 실행했을 때는 쿼리문도 같이 실행된 것을 볼 수 있다.
<img src="https://velog.velcdn.com/images/itzel_02/post/cca6ce8d-2185-4ff0-b6cd-e14c4ee8f4f5/image.png" alt=""> 또한 redis insight에서도 데이터가 생성된 것을 볼 수 있다.
<img src="https://velog.velcdn.com/images/itzel_02/post/51e4efa5-f777-4f31-9dbf-63f660257edb/image.png" alt="">두 번째 실행에서는 쿼리문이 실행되지 않았고, 총 3개의 쿼리문이 실행되던 것이 하나가 줄어 두개의 쿼리문이 실행되는 것을 확인할 수 있다.</p>
<p>캐싱이 어떤 과정으로 발생하는지 알았으니,
캐싱 최적화 전략과 ttl 설정 등에 대해 더 조사해보아야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🍃캐싱 조건]]></title>
            <link>https://velog.io/@itzel_02/%EC%BA%90%EC%8B%B1-%EC%A1%B0%EA%B1%B4</link>
            <guid>https://velog.io/@itzel_02/%EC%BA%90%EC%8B%B1-%EC%A1%B0%EA%B1%B4</guid>
            <pubDate>Sat, 08 Mar 2025 15:46:14 GMT</pubDate>
            <description><![CDATA[<p>개발한 코드를 리팩토링 하던 중 동일한 데이터를 여러 번 요청하고 있다는 것을 알게 되었다. 사용자의 요청에 대한 응답 속도를 높이는 것에 몰두하고 있는 상황이라, 중복되는 상황은 최소화하고 싶은 생각이 먼저 든다. </p>
<p>이때, 네트워크 시간에 배웠던 캐시 개념이 떠올랐다.</p>
<p>실제 데이터베이스에 접근하여 데이터를 가져오는 것이 일반적이라면, 캐시 DB를 도입하여 자주 쓰는 데이터들은 실제 DB까지 접근하지 않고 중간 다리 역할을 하는 캐시  DB를 통해 데이터를 가져와 시간을 절약시켜주는 효과를 얻을 수 있는 것이다.</p>
<p>먼저 중복되는 로직을 한 번 정리해보자.</p>
<ol>
<li>식당 또는 캠퍼스 찾기 기능
<img src="https://velog.velcdn.com/images/itzel_02/post/24c05d20-7967-453f-8ff8-4e3bcd9996a1/image.png" alt="">사용자 테이블에는 <code>사용자id</code>, <code>학과id</code>, <code>캠퍼스id</code>가 존재한다.</li>
</ol>
<ul>
<li><strong>사용자가 캠퍼스 or 식당 정보 요청</strong></li>
<li>사용자 테이블에서 <code>사용자id</code>로 사용자 존재 여부 확인<ul>
<li>사용자가 존재한다면 👉🏼 <code>캠퍼스id</code> 찾아 사용자의 캠퍼스에 존재하는 식당 반환</li>
<li>사용자가 존재하지 않는다면 👉🏼 캠퍼스 반환</li>
</ul>
</li>
</ul>
<ol start="2">
<li>학과 공지사항 조회 기능
<img src="https://velog.velcdn.com/images/itzel_02/post/56f3b541-50bd-4109-99da-37a1ecda1c96/image.png" alt=""></li>
</ol>
<ul>
<li><strong>사용자가 공지 사항 조회를 요청</strong></li>
<li>사용자 테이블에서 사용자의 <code>학과id</code> 확인</li>
<li>사용자 학과 공지 사항 반환</li>
</ul>
<ol start="3">
<li>캠퍼스별 식당 식단 조회 기능
<img src="https://velog.velcdn.com/images/itzel_02/post/55b30a74-6918-4aef-8905-f17e2f7e72b0/image.png" alt=""></li>
</ol>
<ul>
<li>사용자가 식단 요청</li>
<li>사용자 테이블에서 <code>사용자id</code> 확인</li>
<li><code>사용자id</code>로 캠퍼스 이름 찾기</li>
</ul>
<p>위의 3가지 기능에서 동일하게 사용하는 로직이 사용자 테이블에 접근하여 사용자의 정보를 찾는 것이다.</p>
<p>사용자의 캠퍼스나 학과 정보는 변경이 거의 없는 데이터이고, 사용자가 요청할 때마다 사용자 테이블에 접근해야 하는 상황이라면 캐시화 하기에 매우 적절한 것 같다.</p>
<hr>
<p>[출처] </p>
<ul>
<li><a href="https://seungjjun.tistory.com/246">https://seungjjun.tistory.com/246</a></li>
<li><a href="https://yozm.wishket.com/magazine/detail/2296/">https://yozm.wishket.com/magazine/detail/2296/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚒️StringBuffer 사용해 문자열 연산하기]]></title>
            <link>https://velog.io/@itzel_02/StringBuffer-%EC%82%AC%EC%9A%A9%ED%95%B4-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%97%B0%EC%82%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@itzel_02/StringBuffer-%EC%82%AC%EC%9A%A9%ED%95%B4-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%97%B0%EC%82%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 06 Mar 2025 07:38:51 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">String title = &quot;\uD83C\uDF71 &quot; + parameters.getCafeteriaName() + &quot;(&quot; + parameters.getCampusName().substring(0,2) + &quot;) 메뉴&quot;;
String description = dietDate + &quot;\n\n&quot; + dietDescription;</code></pre>
<p>사용자가 요청한 식당의 메뉴들을 반환해주기 위해서는
식당의 카테고리와 메뉴들을 구분하고, 또 날짜 데이터와 덧붙여주어야 한다. </p>
<p>그래서 위의 코드와 같이 문자열 데이터를 계속해서 더해주는 코드가 여러 개 있다.</p>
<p>이전의 글에서 설명했듯이 
<code>String</code>은 문자열 연산을 했을 때 메모리 내의 데이터가 불변이라 문자열이 더해진 새로운 객체를 또 생성하여 비효율적이라는 것을 알게 되었다.</p>
<p>반면에 <code>StringBuffer</code>는 가변적이라서 <code>append</code> 메소드를 사용해 문자열을 이어붙여주면 그 크기만큼 기존의 메모리 크기를 늘려준다. 즉, 새로운 객체를 생성하는 것이 아니라 기존 객체에 문자열이 더해지는 것이다.</p>
<p>따라서 오늘은 비효율적인 연산을 줄이고자 한다.</p>
<pre><code class="language-java">// title 데이터 연결
StringBuilder title = new StringBuilder(&quot;\uD83C\uDF71 &quot;)
                .append(parameters.getCafeteriaName())
                .append(&quot;(&quot;)
                .append(parameters.getCampusName(), 0, 2)
                .append(&quot;) 메뉴&quot;);

// 메뉴 연결
StringBuilder description = new StringBuilder(dietDate)
                .append(&quot;\n\n&quot;)
                .append(dietDescription);</code></pre>
<p>위와 같이 <code>StringBuilder</code>를 사용해 수정하였다.
멀티 스레드 환경이 아니기 때문에 굳이 <code>StringBuffer</code>를 사용하지 않았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚒️하나의 메소드에는 몇 개의 파라미터가 적절할까 ? (2)]]></title>
            <link>https://velog.io/@itzel_02/%ED%95%98%EB%82%98%EC%9D%98-%EB%A9%94%EC%86%8C%EB%93%9C%EC%97%90%EB%8A%94-%EB%AA%87-%EA%B0%9C%EC%9D%98-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EA%B0%80-%EC%A0%81%EC%A0%88%ED%95%A0%EA%B9%8C-2</link>
            <guid>https://velog.io/@itzel_02/%ED%95%98%EB%82%98%EC%9D%98-%EB%A9%94%EC%86%8C%EB%93%9C%EC%97%90%EB%8A%94-%EB%AA%87-%EA%B0%9C%EC%9D%98-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EA%B0%80-%EC%A0%81%EC%A0%88%ED%95%A0%EA%B9%8C-2</guid>
            <pubDate>Thu, 06 Mar 2025 05:20:42 GMT</pubDate>
            <description><![CDATA[<h4 id="데이터-전달-시-인스턴스변수를-활용하라">데이터 전달 시 인스턴스변수를 활용하라!</h4>
<blockquote>
<p>함수의 인수를 통해 데이터를 전달하는 것은 코드를 읽는이에게 복잡함을 줄 수 있다. 다음 함수로 데이터를 전달하는 경우에는 Parameter로 넘기지 말고 Instance 변수를 활용하면 코드가 깔끔해진다.
<a href="https://mangkyu.tistory.com/45">출처</a></p>
</blockquote>
<p>여러 개의 메소드에서 공통적으로 사용하는 파라미터들이 있어 이 변수들을 <code>private</code> 인스턴스 변수로 선언하여 여러 파라미터들을 전달하고 받아쓰는 것을 최소화시켰다.</p>
<p><code>StringBuffer</code>와 <code>String</code> 중에 어떤 변수로 선언해야 할지 고민했었는데, 내가 현재 수정하고 있는 코드는 변수에 새로운 값을 계속 할당(덮어쓰기)하는 것이지, 문자열을 추가하거나 연산하는 것은 아니기 때문에 <code>String</code>으로 선언하였다.</p>
<pre><code class="language-java">private String kakaoId;
private String campusName;
private String day;
private String period;
private String cafeteriaName;</code></pre>
<p>요청 파라미터를 처리하기 위해 필요한 변수들이 이렇게 5개가 있는데, 이 변수들을 <code>DTO</code> 객체로 묶었다.</p>
<p>이유는</p>
<ol>
<li>여러 메소드에서 위의 변수들을 필요로 함</li>
<li>변경이 없어야 함 -&gt; 불변성</li>
</ol>
<p>인스턴스 변수를 사용하는 것이 바로 변수를 불러 사용할 수 있기 때문에, 코드 상 편리했지만 변수가 변경될 상황이 없고, 새로운 파라미터가 추가될 상황을 고려해서 하나의 객체로 만들기로 했다. </p>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/3d8c16f1-99a8-4a8f-9aff-62c6283332bb/image.png" alt="">이렇게 <code>HandleRequestDto</code> 클래스를 만들어 총 5개의 변수들을 <code>final</code>로 선언하였다. </p>
<p><img src="https://velog.velcdn.com/images/itzel_02/post/3ead2dbe-9a09-417f-9b87-fc5e559d1aa3/image.png" alt=""> 이렇게 객체를 생성해주고
<img src="https://velog.velcdn.com/images/itzel_02/post/f256cc65-e502-4b71-b7d0-e7163ebdb54b/image.png" alt=""> 메소드에 객체를 넘겨주면 <code>getter</code> 를 통해 값을 가져오는 방식으로 변경했다.</p>
<p><span style="color:#808080">내가 이런 코드를 작성한 것에 대해 정확한 근거와 확신이 있어야 한다. 하지만,, 인스턴스 변수가 몇 개 존재할 때, 많은 건지 적은건지 또, 언제 DTO 클래스를 생성해서 사용하는 것이 더 효율적인건지 이런 기준들이 좀 모호한 것 같다. 
개발을 더 많이 해보고 공부해봐야 저 기준들이 선명해질 것이다.</span></p>
]]></description>
        </item>
    </channel>
</rss>