<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>NaEun's Notes</title>
    <link>https://annovation.tistory.com/</link>
    <description> 급할수록도라에몽✨</description>
    <language>ko</language>
    <pubDate>Fri, 8 May 2026 12:37:46 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>annovation</managingEditor>
    <image>
      <title>NaEun's Notes</title>
      <url>https://tistory1.daumcdn.net/tistory/7468106/attach/98f5e792132c457486542605268da58a</url>
      <link>https://annovation.tistory.com</link>
    </image>
    <item>
      <title>Codex Chat 삭제하는 방법 (MacOS)</title>
      <link>https://annovation.tistory.com/553</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Codex 저장 구조&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Codex는 로컬에 세션 파일을 저장한다.&lt;/li&gt;
&lt;li&gt;해당 파일을 앱 UI 내에서 삭제하는 기능은 아직 제공되지 않고 있기 때문에 로컬에 저장된 파일 경로로 직접 삭제해야한다.&lt;/li&gt;
&lt;li&gt;macOS 기본 경로는 다음과 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1778046047883&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;~/.codex/sessions
~/.codex/archived_sessions&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;해결 방법 : archived chat 직접 삭제하기&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Finder 로 삭제하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Codex 앱에서 삭제할 chat을 Archive Chat 으로 옮긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Finder 를 연다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. ⌘ + Shift + G를 누른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 아래 경로로 이동한다.&lt;/p&gt;
&lt;pre id=&quot;code_1778046361980&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;~/.codex/archived_sessions/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 삭제할 archived session 파일을 휴지통으로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Terminal 로 삭제하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Codex 앱에서 삭제할 chat을 Archive Chat 으로 옮긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. archived session 목록을 확인하려면&lt;/p&gt;
&lt;pre id=&quot;code_1778046517156&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd ~/.codex/archived_sessions
ls&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 특정 파일만 삭제하려면&lt;/p&gt;
&lt;pre id=&quot;code_1778046532062&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rm 파일명&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 예시&lt;/p&gt;
&lt;pre id=&quot;code_1778046557628&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rm session-abc123.jsonl&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 보관된 채팅을 전부 삭제하려면&lt;/p&gt;
&lt;pre id=&quot;code_1778046578478&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rm -rf ~/.codex/archived_sessions/*&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;주의할 점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필요한 채팅은 백업&lt;/li&gt;
&lt;li&gt;archived_sessions 전체 삭제 시 복구 어려움&lt;/li&gt;
&lt;li&gt;sessions 폴더와 archived_sessions 폴더를 혼동하지 않기&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. OpenAI Developers&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.openai.com/codex/app/troubleshooting&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.openai.com/codex/app/troubleshooting&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778046116759&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Troubleshooting &amp;ndash; Codex app | OpenAI Developers&quot; data-og-description=&quot;FAQ and fixes for common Codex app issues&quot; data-og-host=&quot;developers.openai.com&quot; data-og-source-url=&quot;https://developers.openai.com/codex/app/troubleshooting&quot; data-og-url=&quot;https://developers.openai.com/codex/app/troubleshooting&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oWcX4/dJMb9lla4Lc/kQ0qFxzyujMJKX3TzAK0YK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/vngbo/dJMb9kmgD9S/lALVBTJCs2fcUUIruCSRXk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://developers.openai.com/codex/app/troubleshooting&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.openai.com/codex/app/troubleshooting&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oWcX4/dJMb9lla4Lc/kQ0qFxzyujMJKX3TzAK0YK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/vngbo/dJMb9kmgD9S/lALVBTJCs2fcUUIruCSRXk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Troubleshooting &amp;ndash; Codex app | OpenAI Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;FAQ and fixes for common Codex app issues&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.openai.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Tips ✨</category>
      <category>Tips</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/553</guid>
      <comments>https://annovation.tistory.com/553#entry553comment</comments>
      <pubDate>Wed, 6 May 2026 19:50:33 +0900</pubDate>
    </item>
    <item>
      <title>[block-server] 휴지통 자동 삭제 기능 구현 (2)</title>
      <link>https://annovation.tistory.com/551</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Application&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;DemoApplication.java&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-ab779d4521dde4efe0f3ff659cccdca4ef8a83a094859cc2572c010cccf49446&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PR Link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1774707286295&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;feat: 문서 휴지통 자동 삭제 기능 추가 by hellonaeunkim &amp;middot; Pull Request #43 &amp;middot; jho951/Block-server&quot; data-og-description=&quot;  Part (해당되는 것만 체크) BE FE Infra Docs Test #️⃣ 연관된 이슈 closes #41   작업 내용 1. 주요 변경 사항 요약 기존 문서 삭제 API를 soft delete에서 hard delete로 변경 문서를 휴지통으로 보내는 별&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-ab779d4521dde4efe0f3ff659cccdca4ef8a83a094859cc2572c010cccf49446&quot; data-og-url=&quot;https://github.com/jho951/Block-server/pull/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/9Ufai/dJMb9cBH0M6/LZ2TNVDVwZuozzeBESqMuK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/babytW/dJMb9gxljvj/Tn0mKF76XKAn1KARA71eS0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-ab779d4521dde4efe0f3ff659cccdca4ef8a83a094859cc2572c010cccf49446&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-ab779d4521dde4efe0f3ff659cccdca4ef8a83a094859cc2572c010cccf49446&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/9Ufai/dJMb9cBH0M6/LZ2TNVDVwZuozzeBESqMuK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/babytW/dJMb9gxljvj/Tn0mKF76XKAn1KARA71eS0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;feat: 문서 휴지통 자동 삭제 기능 추가 by hellonaeunkim &amp;middot; Pull Request #43 &amp;middot; jho951/Block-server&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  Part (해당되는 것만 체크) BE FE Infra Docs Test #️⃣ 연관된 이슈 closes #41   작업 내용 1. 주요 변경 사항 요약 기존 문서 삭제 API를 soft delete에서 hard delete로 변경 문서를 휴지통으로 보내는 별&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774707307988&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.documents;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;코드 해석&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;@EnableScheduling
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Spring에서&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;@Scheduled&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;가 붙은 메서드를 찾아서 주기적으로 실행하도록 켜는 설정&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Spring이 애플리케이션 시작 시 스케줄링 기능을 활성화하고, 예를 들어&amp;nbsp;아래 나오는&amp;nbsp;&lt;/span&gt;DocumentTrashPurgeScheduler.java&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;같은 Spring Bean 안의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;@Scheduled&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;메서드를 등록해서 실행한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Spring Javadoc : &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/5.3.37/javadoc-api/org/springframework/scheduling/annotation/EnableScheduling.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@EnableScheduling&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Schedular&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;DocumentTrashPurgeScheduler.java&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-1b3111c7b77047d90f77b1e1d160394efbd8ac41134fdd3b8f59d1d3ceda842f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PR Link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1774707431279&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;feat: 문서 휴지통 자동 삭제 기능 추가 by hellonaeunkim &amp;middot; Pull Request #43 &amp;middot; jho951/Block-server&quot; data-og-description=&quot;  Part (해당되는 것만 체크) BE FE Infra Docs Test #️⃣ 연관된 이슈 closes #41   작업 내용 1. 주요 변경 사항 요약 기존 문서 삭제 API를 soft delete에서 hard delete로 변경 문서를 휴지통으로 보내는 별&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-1b3111c7b77047d90f77b1e1d160394efbd8ac41134fdd3b8f59d1d3ceda842f&quot; data-og-url=&quot;https://github.com/jho951/Block-server/pull/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/1nHx9/dJMb9bv2czW/zNgRGAP6eawIeW66F283Dk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/c2TCPZ/dJMb9frFl6B/dOyygU7bArpsTPIRSj5Bb0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-1b3111c7b77047d90f77b1e1d160394efbd8ac41134fdd3b8f59d1d3ceda842f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-1b3111c7b77047d90f77b1e1d160394efbd8ac41134fdd3b8f59d1d3ceda842f&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/1nHx9/dJMb9bv2czW/zNgRGAP6eawIeW66F283Dk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/c2TCPZ/dJMb9frFl6B/dOyygU7bArpsTPIRSj5Bb0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;feat: 문서 휴지통 자동 삭제 기능 추가 by hellonaeunkim &amp;middot; Pull Request #43 &amp;middot; jho951/Block-server&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  Part (해당되는 것만 체크) BE FE Infra Docs Test #️⃣ 연관된 이슈 closes #41   작업 내용 1. 주요 변경 사항 요약 기존 문서 삭제 API를 soft delete에서 hard delete로 변경 문서를 휴지통으로 보내는 별&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774707443103&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.documents;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.documents.service.DocumentService;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class DocumentTrashPurgeScheduler {

	private final DocumentService documentService;

	@Scheduled(fixedDelay = 60000)
	public void purgeExpiredTrash() {
		documentService.purgeExpiredTrash();
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;코드 해석&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;이 클래스는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;휴지통에 들어간 문서 중 만료된 문서를 주기적으로 정리하는 스케줄러 역할을 한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Spring의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;@Scheduled&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;를 이용해서 일정 주기마다 휴지통 만료 문서를 자동 삭제하도록 만든 배치성 컴포넌트이다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;실행될 때 직접 삭제 로직을 구현하지 않고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.0470588);&quot;&gt;documentService.purgeExpiredTrash()&lt;/span&gt;를 호출함&lt;/li&gt;
&lt;li&gt;즉, 스케줄링 책임과 비즈니스 삭제 책임을 분리한 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;코드 설명&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.0470588);&quot;&gt;@Component : &lt;/span&gt;Spring이 관리하는 Bean&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.0470588);&quot;&gt;@Scheduled(fixedDelay = 60000)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이전 실행이 끝난 뒤 60초 후 다시 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;클래스를 따로 둔 이유&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스케줄링은 실행 트리거 역할, 실제 정책 판단과 DB 삭제는 서비스 계층 역할로 구분하여 구현&lt;/li&gt;
&lt;li&gt;이렇게 분리하면 테스트가 쉬워진다. 즉, 서비스는 스케줄러 없이도 단위 테스트가 가능해진다.&lt;/li&gt;
&lt;li&gt;나중에 수동 실행(휴지통 비즈니스 로직 직접 호출), 관리자 API, 다른 배치 시스템으로 바꿔도 서비스 재사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.0470588); color: #333333; text-align: start;&quot;&gt;fixedDelay&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ㅇ&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.0470588); color: #ffffff; text-align: start;&quot;&gt;documentService.purgeExpiredTrash()&lt;/span&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.0470588); color: #ffffff; text-align: start;&quot;&gt;documentService.purgeExpiredTrash()&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Service&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;DocumentServiceImpl.java&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-47569d612045c6da04f60bf5ce8b1e8171cb00be301560c2a1fb6ba5c71cf79b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PR Link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1774713250720&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;feat: 문서 휴지통 자동 삭제 기능 추가 by hellonaeunkim &amp;middot; Pull Request #43 &amp;middot; jho951/Block-server&quot; data-og-description=&quot;  Part (해당되는 것만 체크) BE FE Infra Docs Test #️⃣ 연관된 이슈 closes #41   작업 내용 1. 주요 변경 사항 요약 기존 문서 삭제 API를 soft delete에서 hard delete로 변경 문서를 휴지통으로 보내는 별&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-47569d612045c6da04f60bf5ce8b1e8171cb00be301560c2a1fb6ba5c71cf79b&quot; data-og-url=&quot;https://github.com/jho951/Block-server/pull/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dI7too/dJMb9cBH0Z3/7feKkiyQIl7kS0VOKCJnH1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/dpQST8/dJMb9iaQ2z6/OkImLLQU0lvxRlzXRhPJ21/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-47569d612045c6da04f60bf5ce8b1e8171cb00be301560c2a1fb6ba5c71cf79b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-47569d612045c6da04f60bf5ce8b1e8171cb00be301560c2a1fb6ba5c71cf79b&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dI7too/dJMb9cBH0Z3/7feKkiyQIl7kS0VOKCJnH1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/dpQST8/dJMb9iaQ2z6/OkImLLQU0lvxRlzXRhPJ21/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;feat: 문서 휴지통 자동 삭제 기능 추가 by hellonaeunkim &amp;middot; Pull Request #43 &amp;middot; jho951/Block-server&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  Part (해당되는 것만 체크) BE FE Infra Docs Test #️⃣ 연관된 이슈 closes #41   작업 내용 1. 주요 변경 사항 요약 기존 문서 삭제 API를 soft delete에서 hard delete로 변경 문서를 휴지통으로 보내는 별&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774713295023&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
@Transactional
public void purgeExpiredTrash() {
    LocalDateTime expiredAt = LocalDateTime.now().minusMinutes(TRASH_RETENTION_MINUTES);
    List&amp;lt;Document&amp;gt; expiredTrashRoots = documentRepository.findExpiredTrashRoots(expiredAt);

    for (Document expiredTrashRoot : expiredTrashRoots) {
        documentRepository.delete(expiredTrashRoot);
    }
}

...

private void validateTrashRestoreAvailable(Document document) {
    LocalDateTime restoreDeadline = document.getDeletedAt().plusMinutes(TRASH_RESTORE_AVAILABLE_MINUTES);
    LocalDateTime restoreDeadline = document.getDeletedAt().plusMinutes(TRASH_RETENTION_MINUTES);
    if (!LocalDateTime.now().isBefore(restoreDeadline)) {
        throw new BusinessException(BusinessErrorCode.DOCUMENT_NOT_FOUND);
    }
	}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Repository&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;DocumentRepository.java&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-a9f5f265dada3a449538247148f01ed946943c615ece07925b1c0977ce289881&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PR Link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1774713554220&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;feat: 문서 휴지통 자동 삭제 기능 추가 by hellonaeunkim &amp;middot; Pull Request #43 &amp;middot; jho951/Block-server&quot; data-og-description=&quot;  Part (해당되는 것만 체크) BE FE Infra Docs Test #️⃣ 연관된 이슈 closes #41   작업 내용 1. 주요 변경 사항 요약 기존 문서 삭제 API를 soft delete에서 hard delete로 변경 문서를 휴지통으로 보내는 별&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-a9f5f265dada3a449538247148f01ed946943c615ece07925b1c0977ce289881&quot; data-og-url=&quot;https://github.com/jho951/Block-server/pull/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XVl0b/dJMb9g5a3Xn/pgKqaam7op2XM9FyiE41K1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/G6h1S/dJMb9b3RXJI/a41AVl9wgGB5jjpkfz2eJK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-a9f5f265dada3a449538247148f01ed946943c615ece07925b1c0977ce289881&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jho951/Block-server/pull/43/changes/b1fd9c650c3bf41f07a11d4d84b56c5edb2b6101#diff-a9f5f265dada3a449538247148f01ed946943c615ece07925b1c0977ce289881&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XVl0b/dJMb9g5a3Xn/pgKqaam7op2XM9FyiE41K1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/G6h1S/dJMb9b3RXJI/a41AVl9wgGB5jjpkfz2eJK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;feat: 문서 휴지통 자동 삭제 기능 추가 by hellonaeunkim &amp;middot; Pull Request #43 &amp;middot; jho951/Block-server&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  Part (해당되는 것만 체크) BE FE Infra Docs Test #️⃣ 연관된 이슈 closes #41   작업 내용 1. 주요 변경 사항 요약 기존 문서 삭제 API를 soft delete에서 hard delete로 변경 문서를 휴지통으로 보내는 별&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774713563576&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Query(&quot;&quot;&quot;
    select d
    from Document d
    left join d.parent p
    where d.deletedAt is not null
      and d.deletedAt &amp;lt;= :expiredAt
      and (
        p is null
        or p.deletedAt is null
      )
    order by
      d.deletedAt asc,
      d.createdAt asc,
      d.id asc
    &quot;&quot;&quot;)
List&amp;lt;Document&amp;gt; findExpiredTrashRoots(@Param(&quot;expiredAt&quot;) LocalDateTime expiredAt);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Github repo&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jho951/Block-server/pull/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/jho951/Block-server/pull/43&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774707173958&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;feat: 문서 휴지통 자동 삭제 기능 추가 by hellonaeunkim &amp;middot; Pull Request #43 &amp;middot; jho951/Block-server&quot; data-og-description=&quot;  Part (해당되는 것만 체크) BE FE Infra Docs Test #️⃣ 연관된 이슈 closes #41   작업 내용 1. 주요 변경 사항 요약 기존 문서 삭제 API를 soft delete에서 hard delete로 변경 문서를 휴지통으로 보내는 별&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jho951/Block-server/pull/43&quot; data-og-url=&quot;https://github.com/jho951/Block-server/pull/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xo47Z/dJMb82MCZTm/pRzs4137PkT81Bnl0uxYbK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bxPGsL/dJMb83SiJiR/3MkC8cIUQW1h5ZKwZHgy41/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jho951/Block-server/pull/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jho951/Block-server/pull/43&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xo47Z/dJMb82MCZTm/pRzs4137PkT81Bnl0uxYbK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bxPGsL/dJMb83SiJiR/3MkC8cIUQW1h5ZKwZHgy41/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;feat: 문서 휴지통 자동 삭제 기능 추가 by hellonaeunkim &amp;middot; Pull Request #43 &amp;middot; jho951/Block-server&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  Part (해당되는 것만 체크) BE FE Infra Docs Test #️⃣ 연관된 이슈 closes #41   작업 내용 1. 주요 변경 사항 요약 기존 문서 삭제 API를 soft delete에서 hard delete로 변경 문서를 휴지통으로 보내는 별&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/block-server</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/551</guid>
      <comments>https://annovation.tistory.com/551#entry551comment</comments>
      <pubDate>Sat, 28 Mar 2026 23:30:51 +0900</pubDate>
    </item>
    <item>
      <title>[block-server] 휴지통 자동 삭제 기능 구현 (1) : Spring Scheduling vs Spring Batch</title>
      <link>https://annovation.tistory.com/547</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Spring Scheduling&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;소제목&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Spring Batch&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;소제목&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;두꺼운 밑줄 스타일&lt;/span&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;Scheduler (@Scheduled)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;Spring Batch&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;목적&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;작업 실행 시점 관리&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;대용량 데이터 처리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;역할&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;ldquo;언제 실행할까&amp;rdquo;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;ldquo;어떻게 처리할까&amp;rdquo;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;데이터 처리&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;단순 로직&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;대량 데이터 처리 최적화&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;구조&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;메서드 단위 실행&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Job &amp;rarr; Step 구조&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;트랜잭션&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;직접 처리&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;자동 관리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;실패 처리&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;없음 (직접 구현)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;재시작, retry 지원&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;사용 난이도&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;쉬움&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;비교적 복잡&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Spring Framework Docs : Spring Scheduling&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/scheduling.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/integration/scheduling.html?utm_source=chatgpt.com&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774331611731&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Task Execution and Scheduling :: Spring Framework&quot; data-og-description=&quot;All Spring cron expressions have to conform to the same format, whether you are using them in @Scheduled annotations, task:scheduled-tasks elements, or someplace else. A well-formed cron expression, such as * * * * * *, consists of six space-separated time&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/reference/integration/scheduling.html?utm_source=chatgpt.com&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/reference/integration/scheduling.html?utm_source=chatgpt.com&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/scheduling.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/reference/integration/scheduling.html?utm_source=chatgpt.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Task Execution and Scheduling :: Spring Framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;All Spring cron expressions have to conform to the same format, whether you are using them in @Scheduled annotations, task:scheduled-tasks elements, or someplace else. A well-formed cron expression, such as * * * * * *, consists of six space-separated time&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/block-server</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/547</guid>
      <comments>https://annovation.tistory.com/547#entry547comment</comments>
      <pubDate>Mon, 23 Mar 2026 23:41:56 +0900</pubDate>
    </item>
    <item>
      <title>[block-server] 프로젝트 구조</title>
      <link>https://annovation.tistory.com/545</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Structure&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Notion 스타일의 문서 계층과 블록 트리를 다루는 백엔드 서비스이며, Spring Boot 기반 Gradle 멀티모듈 구조로 책임을 분리한 것이 핵심!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774107231400&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;
  Block-server
├─&amp;nbsp;AGENTS.md
├─&amp;nbsp;README.md
├─&amp;nbsp;docker
│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;Dockerfile
│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;docker-compose.yml
│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;docker.sh
├─&amp;nbsp;docs
│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;REQUIREMENTS.md
│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;decisions
│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;000-adr-template.md
│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;README.md
│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;discussions
│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;000-strategy-review-template.md
│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;README.md
│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;roadmap
│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;README.md
│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;v2
│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;blocks
│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;block-delete.md
│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;block-restore.md
│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;runbook
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;DEBUG.md
├─&amp;nbsp;documents-api
│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;build.gradle
│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;src
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;main
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;com
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;documents
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;api
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;block
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;BlockApiMapper.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;BlockController.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;dto
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;BlockResponse.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;CreateBlockRequest.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;UpdateBlockRequest.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;support
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;BlockJsonCodec.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;validation
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;BlockContentValidator.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;ValidBlockContent.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;code
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;ErrorCode.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;SuccessCode.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;document
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;DocumentApiMapper.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;DocumentController.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;dto
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;CreateDocumentRequest.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;DocumentResponse.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;MoveDocumentRequest.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;UpdateDocumentRequest.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;support
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;DocumentJsonCodec.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;validation
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;DocumentMetaValidator.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;ValidDocumentMeta.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;dto
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;BaseResponse.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;GlobalResponse.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;exception
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;GlobalException.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;GlobalExceptionHandler.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;workspace
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;WorkspaceController.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;dto
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;CreateWorkspaceRequest.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;WorkspaceResponse.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;common
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;SwaggerConfig.java
├─&amp;nbsp;documents-boot
│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;build.gradle
│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;src
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;main
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;com
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;documents
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;DemoApplication.java
├─&amp;nbsp;documents-core
│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;build.gradle
│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;src
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;main
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;com
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;documents
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;common
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;BaseEntity.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;domain
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;Block.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;BlockType.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;Document.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;Workspace.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;exception
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;BusinessErrorCode.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;BusinessException.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;service
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;BlockService.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;DocumentService.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;WorkspaceService.java
├─&amp;nbsp;documents-infrastructur
│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;src
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;main
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;com
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;documents
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;repository
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;BlockRepository.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;DocumentRepository.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;WorkspaceRepository.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;service
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;BlockServiceImpl.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;├─&amp;nbsp;DocumentServiceImpl.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp;&amp;nbsp;└─&amp;nbsp;WorkspaceServiceImpl.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;support
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;OrderedSortKeyGenerator.java
│&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;TextNormalizer.java
└─&amp;nbsp;prompts
&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;0-initial-setup.md
&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;README.md
&amp;nbsp;&amp;nbsp;&amp;nbsp;├─&amp;nbsp;explainer
&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;plan&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Explains&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;documents-boot&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;애플리케이션 실행 진입점, 프로필/환경설정, 패키징 담당&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;`DemoApplication.java`가&amp;nbsp;위치하며,&amp;nbsp;실제&amp;nbsp;서버&amp;nbsp;실행과&amp;nbsp;설정&amp;nbsp;로딩을&amp;nbsp;담당한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;즉,&amp;nbsp;다른&amp;nbsp;모듈이&amp;nbsp;기능을&amp;nbsp;제공하는&amp;nbsp;라이브러리라면&amp;nbsp;`documents-boot`는&amp;nbsp;그것들을&amp;nbsp;조립해서&amp;nbsp;실행&amp;nbsp;가능한&amp;nbsp;애플리케이션으로&amp;nbsp;만드는&amp;nbsp;역할을&amp;nbsp;한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;documents-api&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;외부&amp;nbsp;요청을&amp;nbsp;직접&amp;nbsp;받는&amp;nbsp;계층이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;이 모듈에는 다음과 같은 요소가 들어 있다.&lt;br /&gt;-&amp;nbsp;`Controller`:&amp;nbsp;문서,&amp;nbsp;블록,&amp;nbsp;워크스페이스&amp;nbsp;API&amp;nbsp;엔드포인트&lt;br /&gt;-&amp;nbsp;`dto`:&amp;nbsp;요청/응답&amp;nbsp;객체&lt;br /&gt;-&amp;nbsp;`validation`:&amp;nbsp;요청값&amp;nbsp;검증&amp;nbsp;로직&lt;br /&gt;-&amp;nbsp;`support`:&amp;nbsp;JSON&amp;nbsp;변환&amp;nbsp;보조&amp;nbsp;로직&lt;br /&gt;-&amp;nbsp;`code`,&amp;nbsp;`exception`:&amp;nbsp;공통&amp;nbsp;응답&amp;nbsp;코드와&amp;nbsp;예외&amp;nbsp;처리&lt;br /&gt;-&amp;nbsp;`SwaggerConfig`:&amp;nbsp;API&amp;nbsp;문서화&amp;nbsp;설정&lt;/li&gt;
&lt;li&gt;즉,&amp;nbsp;HTTP&amp;nbsp;요청을&amp;nbsp;받아&amp;nbsp;검증하고,&amp;nbsp;도메인&amp;nbsp;계층에&amp;nbsp;작업을&amp;nbsp;위임한&amp;nbsp;뒤,&amp;nbsp;다시&amp;nbsp;응답&amp;nbsp;형식으로&amp;nbsp;변환하는&amp;nbsp;역할을&amp;nbsp;맡는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;documents-core&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;text-align: start;&quot;&gt;서비스의&amp;nbsp;중심&amp;nbsp;규칙을&amp;nbsp;담는&amp;nbsp;계층이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;여기에는 다음이 위치한다.&lt;br /&gt;-&amp;nbsp;`domain`:&amp;nbsp;`Document`,&amp;nbsp;`Block`,&amp;nbsp;`Workspace`&amp;nbsp;같은&amp;nbsp;핵심&amp;nbsp;엔티티&lt;br /&gt;-&amp;nbsp;`service`:&amp;nbsp;도메인&amp;nbsp;서비스&amp;nbsp;인터페이스&lt;br /&gt;-&amp;nbsp;`exception`:&amp;nbsp;비즈니스&amp;nbsp;예외와&amp;nbsp;에러&amp;nbsp;코드&lt;br /&gt;-&amp;nbsp;`common`:&amp;nbsp;공통&amp;nbsp;엔티티&amp;nbsp;기반&amp;nbsp;클래스&lt;/li&gt;
&lt;li&gt;중요한 점은, 이 계층은 &quot;무엇을 해야 하는가&quot;를 정의한다는 것이다.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;구체적으로&amp;nbsp;어떻게&amp;nbsp;저장하는지는&amp;nbsp;여기서&amp;nbsp;다루지&amp;nbsp;않고,&amp;nbsp;서비스&amp;nbsp;계약과&amp;nbsp;도메인&amp;nbsp;모델&amp;nbsp;중심으로&amp;nbsp;구조를&amp;nbsp;잡고&amp;nbsp;있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;documents-infrastructure&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제&amp;nbsp;구현을&amp;nbsp;담당한다.&lt;/li&gt;
&lt;li&gt;이 모듈에는 다음이 있다.&lt;br /&gt;-&amp;nbsp;`repository`:&amp;nbsp;JPA&amp;nbsp;Repository&lt;br /&gt;-&amp;nbsp;`service`:&amp;nbsp;`documents-core`의&amp;nbsp;서비스&amp;nbsp;인터페이스&amp;nbsp;구현체&lt;br /&gt;- `support`: 정렬 키 생성기, 문자열 정규화 같은 보조 유틸리티&lt;/li&gt;
&lt;li&gt;예를 들어 문서 이동, 블록 생성, soft delete 같은 실제 비즈니스 처리는 이 계층의 `ServiceImpl`에서 수행된다.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;즉,&amp;nbsp;`core`가&amp;nbsp;계약이라면&amp;nbsp;`infrastructure`는&amp;nbsp;그&amp;nbsp;계약의&amp;nbsp;실행체다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;문서화와&amp;nbsp;운영&amp;nbsp;보조&amp;nbsp;구조&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;루트에는 코드 외에도 협업과 기록을 위한 디렉터리가 따로 존재한다.&lt;br /&gt;-&amp;nbsp;`docs/REQUIREMENTS.md`:&amp;nbsp;현재&amp;nbsp;유효한&amp;nbsp;요구사항&amp;nbsp;기준서&lt;br /&gt;-&amp;nbsp;`docs/decisions`:&amp;nbsp;채택된&amp;nbsp;기술&amp;nbsp;결정(ADR)&lt;br /&gt;-&amp;nbsp;`docs/discussions`:&amp;nbsp;전략&amp;nbsp;검토,&amp;nbsp;비교,&amp;nbsp;회의&amp;nbsp;메모&lt;br /&gt;-&amp;nbsp;`docs/roadmap`:&amp;nbsp;후속&amp;nbsp;버전&amp;nbsp;확장&amp;nbsp;계획&lt;br /&gt;-&amp;nbsp;`docs/runbook`:&amp;nbsp;재현&amp;nbsp;가능한&amp;nbsp;디버깅&amp;nbsp;절차&lt;br /&gt;-&amp;nbsp;`prompts`:&amp;nbsp;AI&amp;nbsp;작업&amp;nbsp;로그와&amp;nbsp;설명&amp;nbsp;문서&lt;/li&gt;
&lt;li&gt;이 구조는 단순히 코드를 작성하는 데서 끝나지 않고,&amp;nbsp;&amp;nbsp;&quot;왜&amp;nbsp;이렇게&amp;nbsp;만들었는지&quot;,&amp;nbsp;&quot;무엇을&amp;nbsp;결정했는지&quot;,&amp;nbsp;&quot;다음엔&amp;nbsp;무엇을&amp;nbsp;할지&quot;까지&amp;nbsp;함께&amp;nbsp;관리하려는&amp;nbsp;의도가&amp;nbsp;반영된&amp;nbsp;설계다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;실행&amp;nbsp;환경&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;루트의 `docker/` 디렉터리는 로컬 실행 환경을 담당한다.&lt;br /&gt;-&amp;nbsp;`Dockerfile`&lt;br /&gt;-&amp;nbsp;`docker-compose.yml`&lt;br /&gt;-&amp;nbsp;`docker.sh`&lt;/li&gt;
&lt;li&gt;즉, 애플리케이션과 DB를 포함한 실행 환경을 빠르게 재현할 수 있도록 준비된 구조다.&lt;/li&gt;
&lt;li&gt;`docker-compose.yml`을 활용해 애플리케이션과 MySQL 같은 실행 의존성을 함께 올릴 수 있도록 구성해, 로컬 개발 환경을 빠르게 재현할 수 있게 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Github Repo&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jho951/Block-server&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/jho951/Block-server&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774106475532&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - jho951/Block-server: server drawer&quot; data-og-description=&quot;server drawer. Contribute to jho951/Block-server development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jho951/Block-server&quot; data-og-url=&quot;https://github.com/jho951/Block-server&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4BYEK/dJMb87NVGTK/nPDxQRxCi35CC1t6OnfMeK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/sRGaH/dJMb89ycQMF/ll4dlIA0jmdmi5WBmpxW11/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jho951/Block-server&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jho951/Block-server&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4BYEK/dJMb87NVGTK/nPDxQRxCi35CC1t6OnfMeK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/sRGaH/dJMb89ycQMF/ll4dlIA0jmdmi5WBmpxW11/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - jho951/Block-server: server drawer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;server drawer. Contribute to jho951/Block-server development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/block-server</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/545</guid>
      <comments>https://annovation.tistory.com/545#entry545comment</comments>
      <pubDate>Fri, 20 Mar 2026 23:49:54 +0900</pubDate>
    </item>
    <item>
      <title>[바이브 코딩] MacOS Codex CLI 설치</title>
      <link>https://annovation.tistory.com/542</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;1. Node.js 설치 확인&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Node.js 설치 확인&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773675531898&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;node -v&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;zsh: command not found: node 처럼 아무것도 안나온다면, brew 로 설치&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773675577196&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install node&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;2. Codex CLI 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Codex CLI 설치&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773675717966&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g @openai/codex&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;node 버전이 오래된 경우 Error 가 뜨기도 한다.&lt;/li&gt;
&lt;li&gt;ex. node -v 명령어를 통해 확인한 버전이 v20.11.0 였는데 오류가 나서 brew upgrade node 후 다시 진행했더니 오류가 없었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;설치 확인&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773675760851&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;codex --version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-03-17 at 12.43.23 AM.png&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8khtq/dJMcajaoi3w/yVkPdvpHtjQE5m8nkhocak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8khtq/dJMcajaoi3w/yVkPdvpHtjQE5m8nkhocak/img.png&quot; data-alt=&quot;설치된 codex cli 버전 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8khtq/dJMcajaoi3w/yVkPdvpHtjQE5m8nkhocak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8khtq%2FdJMcajaoi3w%2FyVkPdvpHtjQE5m8nkhocak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;886&quot; height=&quot;83&quot; data-filename=&quot;Screenshot 2026-03-17 at 12.43.23 AM.png&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;설치된 codex cli 버전 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;계정 연동&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773675960775&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;codex&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;터미널에 codex 입력 후 아래와 같이 계정을 연동할 수 있는 옵션이 뜬다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-03-17 at 12.46.58 AM.png&quot; data-origin-width=&quot;2100&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lc1vb/dJMcacJaoLi/2q7e3xQz5NrkrYTKZR8cm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lc1vb/dJMcacJaoLi/2q7e3xQz5NrkrYTKZR8cm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lc1vb/dJMcacJaoLi/2q7e3xQz5NrkrYTKZR8cm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flc1vb%2FdJMcacJaoLi%2F2q7e3xQz5NrkrYTKZR8cm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2100&quot; height=&quot;314&quot; data-filename=&quot;Screenshot 2026-03-17 at 12.46.58 AM.png&quot; data-origin-width=&quot;2100&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mac 의 경우 Codex 앱에 로그인이 되어있으면 추가로 웹페이지로 리다이렉트해서 로그인하는 과정 없이 바로 연동이 되는 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-03-17 at 12.49.52 AM.png&quot; data-origin-width=&quot;2196&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pKZqe/dJMcach494A/AtkmKGgxfWtp7FcPtml4m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pKZqe/dJMcach494A/AtkmKGgxfWtp7FcPtml4m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pKZqe/dJMcach494A/AtkmKGgxfWtp7FcPtml4m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpKZqe%2FdJMcach494A%2FAtkmKGgxfWtp7FcPtml4m0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2196&quot; height=&quot;582&quot; data-filename=&quot;Screenshot 2026-03-17 at 12.49.52 AM.png&quot; data-origin-width=&quot;2196&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;CLI 명령어&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Codex 실행&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773676351698&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;codex&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 바로 프롬프트 명령 가능&lt;/p&gt;
&lt;pre id=&quot;code_1773676509038&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;codex &quot;Explain this project&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Codex 종료&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773676574314&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/quit&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;모델 변경&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773676402848&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/model&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;빠른 응답, 가벼운 작업&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773676477233&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/fast&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1773676883223&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/fast
Explain this file&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;깊은 분석, 복잡한 작업&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773676865643&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/slow&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1773676904075&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/slow
Find architecture problems in this repo&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;현재 변경사항 리뷰&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773676608564&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/review --uncommitted&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;터미널 명령 직접 실행&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773676652996&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;!ls
!pwd
!git status&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Codex 안에서 앞에 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 붙이면 &lt;/span&gt;&lt;b&gt;실제 shell command 실행&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 블로그 : 맥북에 Codex CLI 설치하기 (feat. Ghat GPT CLI 설치 방법)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ratatou2.tistory.com/333&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ratatou2.tistory.com/333&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773676219804&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;맥북에 Codex CLI 설치하기 (feat. Ghat GPT CLI 설치 방법)&quot; data-og-description=&quot;Ghat GPT 유료를 쓰면 Codex가 무료..?난 몰랐다 GPT 유료버전을 쓰면 Codex를 준다는 것을 ㅠ지금이라도 알았으니까 얼른 써야지 뭐...Codex는 Cluade CLI처럼 터미널 창에서 쓸 수 있는 AI 코딩툴? 같은 것&quot; data-og-host=&quot;ratatou2.tistory.com&quot; data-og-source-url=&quot;https://ratatou2.tistory.com/333&quot; data-og-url=&quot;https://ratatou2.tistory.com/333&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3a7mU/dJMb8952pdH/NKvEuGY7gFOx01BhK6f7EK/img.png?width=800&amp;amp;height=774&amp;amp;face=0_0_800_774,https://scrap.kakaocdn.net/dn/bxyUCt/dJMb88F3HiG/PQlhpL3nYL1nt7U2331KG1/img.png?width=800&amp;amp;height=774&amp;amp;face=0_0_800_774,https://scrap.kakaocdn.net/dn/dffO6p/dJMb84p7vxb/8WJKhcK4W1VB0foEa8MyW0/img.png?width=1032&amp;amp;height=1024&amp;amp;face=0_0_1032_1024&quot;&gt;&lt;a href=&quot;https://ratatou2.tistory.com/333&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ratatou2.tistory.com/333&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3a7mU/dJMb8952pdH/NKvEuGY7gFOx01BhK6f7EK/img.png?width=800&amp;amp;height=774&amp;amp;face=0_0_800_774,https://scrap.kakaocdn.net/dn/bxyUCt/dJMb88F3HiG/PQlhpL3nYL1nt7U2331KG1/img.png?width=800&amp;amp;height=774&amp;amp;face=0_0_800_774,https://scrap.kakaocdn.net/dn/dffO6p/dJMb84p7vxb/8WJKhcK4W1VB0foEa8MyW0/img.png?width=1032&amp;amp;height=1024&amp;amp;face=0_0_1032_1024');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;맥북에 Codex CLI 설치하기 (feat. Ghat GPT CLI 설치 방법)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Ghat GPT 유료를 쓰면 Codex가 무료..?난 몰랐다 GPT 유료버전을 쓰면 Codex를 준다는 것을 ㅠ지금이라도 알았으니까 얼른 써야지 뭐...Codex는 Cluade CLI처럼 터미널 창에서 쓸 수 있는 AI 코딩툴? 같은 것&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ratatou2.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/block-server</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/542</guid>
      <comments>https://annovation.tistory.com/542#entry542comment</comments>
      <pubDate>Tue, 17 Mar 2026 22:50:39 +0900</pubDate>
    </item>
    <item>
      <title>[바이브 코딩] AGENTS.md 로 코딩 에이전트 활용하기</title>
      <link>https://annovation.tistory.com/541</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;바이브 코딩&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;AGENTS.md&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OpenAI Codex 팀에서 처음 발표한 AI 에이전트 용 표준 규칙 파일&lt;/li&gt;
&lt;li&gt;프로젝트에서 AI 에이전트에게 필요한 맥락과 지침을 주입시켜주는 간단한 마크다운 파일&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;AI 에이전트가 뭐지? (&lt;a href=&quot;https://openai.com/ko-KR/index/introducing-codex/?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고 자료&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순히 답하는 AI가 아니라 실제 작업을 스스로 수행하고 끝까지 완료하는 실행형 AI 시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;효율적인 AGENTS.md 구조&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;하나의 AGENTS.md는 500줄 이내로 유지&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM 모델의 특성상 컨텍스트 윈도우 사이즈가 한정되어 있기 때문에 에이전트 규칙이 차지하는 정의 때문에 컨텍스트 낭비가 생길 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❓컨텍스트&lt;br /&gt;LLM이 쓸 수 있는 제한된 공간&lt;br /&gt;&lt;br /&gt;❓LLM&lt;br /&gt;자연어를 토큰과 숫자(벡터)로 변환한 뒤, 이전 문맥을 기반으로 다음에 올 &amp;lsquo;단어(토큰)&amp;rsquo;의 확률을 계산해 생성하는 모델&lt;br /&gt;ex. 나는 밥을 먹었다. (자연어) -&amp;gt; 나는 / 밥을 / 먹었다. (토큰) -&amp;gt; 숫자 (벡터)로 변환 -&amp;gt; 나는 밥을 ___. 에서 먹었다를 예측&lt;br /&gt;&lt;br /&gt;❓컨텍스트 원도우 사이즈&lt;br /&gt;LLM이 한 번에 읽고 기억할 수 있는 텍스트 용량의 한계&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;큰 프로젝트에서는 여러 개의 조합 가능한 작은 규칙으로 나누어 정의&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 개의 중첩된 파일로 구조적으로 규칙 파일 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-04-02 at 2.22.19 PM.png&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Itg8o/dJMcacCCzaw/e1wNXSZQkRG7R69bkS1gyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Itg8o/dJMcacCCzaw/e1wNXSZQkRG7R69bkS1gyK/img.png&quot; data-alt=&quot;출처 : https://www.youtube.com/watch?v=LHx_EFoLonQ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Itg8o/dJMcacCCzaw/e1wNXSZQkRG7R69bkS1gyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FItg8o%2FdJMcacCCzaw%2Fe1wNXSZQkRG7R69bkS1gyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1690&quot; height=&quot;796&quot; data-filename=&quot;Screenshot 2026-04-02 at 2.22.19 PM.png&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://www.youtube.com/watch?v=LHx_EFoLonQ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;AGENTS.md 파일도 소스코드와 함께 버전관리 하기&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드가 변경되었을 때, 관련된 AGENTS.md 파일도 함께 수정하여 버전 관리를 코드와 같이 해주는 것을 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❓버전 관리&lt;br /&gt;파일의 변화 과정을 시간순으로 저장하고 관리하는 것&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AI 에이전트가 실제로 코드를 생성하고 작업할 때 AGENTS.md 파일이 큰 영향을 미치기 때문에 코드 리뷰에서 코드만 리뷰하는 것이 아닌, AGENTS.md 파일도 함께 리뷰 하는 것을 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;신규 팀원 온보딩에 AGENTS.md 활용하기&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AGENTS.md 파일에는 아래와 같은 여러 가지 정보가 포함되어 있기 때문에 프로젝트의 코드베이스를 굉장히 빠르게 파악하고 작업을 시작할 수 있는 역할을 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-04-03 at 9.45.46 PM.png&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKQqlX/dJMcagLOFDd/Lv4bqMa4KIkpcbjPr9rdB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKQqlX/dJMcagLOFDd/Lv4bqMa4KIkpcbjPr9rdB1/img.png&quot; data-alt=&quot;출처 : https://www.youtube.com/watch?v=LHx_EFoLonQ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKQqlX/dJMcagLOFDd/Lv4bqMa4KIkpcbjPr9rdB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKQqlX%2FdJMcagLOFDd%2FLv4bqMa4KIkpcbjPr9rdB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;481&quot; height=&quot;169&quot; data-filename=&quot;Screenshot 2026-04-03 at 9.45.46 PM.png&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://www.youtube.com/watch?v=LHx_EFoLonQ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;한 번 작성으로 끝나는 것이 아니라 계속해서 반복적으로 개선하기&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 구조 변경, 새로운 기술 스택 추가, 정해진 규칙 준수 여부를 지속적으로 모니터링하고 업데이트 하는 것이 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;읽어 보기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 컬리 기술 블로그 : &lt;a href=&quot;https://helloworld.kurly.com/blog/vibe-coding-with-claude-code/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://helloworld.kurly.com/blog/vibe-coding-with-claude-code/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773643591277&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Claude Code를 활용한 예측 가능한 바이브 코딩 전략&quot; data-og-description=&quot;에이전트 주도 개발에서 발생하는 문제를 해결하고, 컨텍스트를 정밀하게 관리하는 방법&quot; data-og-host=&quot;helloworld.kurly.com&quot; data-og-source-url=&quot;https://helloworld.kurly.com/blog/vibe-coding-with-claude-code/&quot; data-og-url=&quot;http://thefarmersfront.github.io/blog/vibe-coding-with-claude-code/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b71HqR/dJMb8U8Sm9r/LPFT4eXvkEbrL8Dx274VN1/img.png?width=1452&amp;amp;height=1452&amp;amp;face=0_0_1452_1452&quot;&gt;&lt;a href=&quot;https://helloworld.kurly.com/blog/vibe-coding-with-claude-code/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://helloworld.kurly.com/blog/vibe-coding-with-claude-code/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b71HqR/dJMb8U8Sm9r/LPFT4eXvkEbrL8Dx274VN1/img.png?width=1452&amp;amp;height=1452&amp;amp;face=0_0_1452_1452');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Claude Code를 활용한 예측 가능한 바이브 코딩 전략&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;에이전트 주도 개발에서 발생하는 문제를 해결하고, 컨텍스트를 정밀하게 관리하는 방법&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;helloworld.kurly.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 유튜브 - 개발동생 : &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;코딩 에이전트 성능을 20배 올리는 가장 쉬운 방법 | AGENTS.md로 표준 규칙 파일 정의하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LHx_EFoLonQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=LHx_EFoLonQ&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=LHx_EFoLonQ&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/ujfmp/dJMb9dHmPP0/QwQlzGRSfg3SoJU8QKhYj1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=18_282_224_506,https://scrap.kakaocdn.net/dn/bEkPSo/dJMb9c9wCFP/NFK0l4JAvUaFSx4Eg8Z751/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=18_282_224_506,https://scrap.kakaocdn.net/dn/eiafcX/dJMb9iaPTYE/wNY423KFV2pC1X5qdRpUj1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=18_282_224_506&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;코딩 에이전트 성능을 20배 올리는 가장 쉬운 방법 | AGENTS.md로 표준 규칙 파일 정의하기&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/LHx_EFoLonQ&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;</description>
      <category>Projects/block-server</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/541</guid>
      <comments>https://annovation.tistory.com/541#entry541comment</comments>
      <pubDate>Mon, 16 Mar 2026 16:00:05 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 - (3) Redis Distributed Lock</title>
      <link>https://annovation.tistory.com/540</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;Redis Distributed Lock&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Redis에서 제공하는 대표적인 라이브러리 Lettuce 와 Reddison 을 활용하여 해결할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Lettuce&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-start=&quot;170&quot; data-end=&quot;233&quot;&gt;비동기(Async) 및 동기(Sync) 방식 모두 지원하는 Redis 클라이언트이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️Redis 클라이언트 란?&lt;br /&gt;애플리케이션(Spring, Node 등)이 Redis 서버와 통신하기 위해 사용하는 라이브러리&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-start=&quot;234&quot; data-end=&quot;276&quot;&gt;Netty 기반의 비동기 I/O 처리를 지원한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️Netty 란?&lt;br /&gt;Lettuce가 Redis 서버와 통신할 때 사용하는 비동기 내부 네트워크 프레임워크&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-start=&quot;277&quot; data-end=&quot;316&quot;&gt;Lettuce는 분산락을 제공하는 라이브러리가 아니라 Redis 명령을 실행하는 클라이언트이기 때문에 lock을 Redis 명령어로 직접 구현해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️setnx 명령어를 활용하여 분산락 구현&lt;br /&gt;setnx :&amp;nbsp;Set If Not Exist의 줄임말로, 특정 key에 value 값이 존재하지 않을 경우에 값을 설정(set) 하는 명령어&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-start=&quot;277&quot; data-end=&quot;316&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;spin Lock 방식&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- lock을 획득하려는 스레드가 lock을 사용할 수 있는지 반복적으로 확인하면서 lock을 시도하는 방식&lt;br /&gt;- retry 로직을 작성해야한다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-start=&quot;277&quot; data-end=&quot;316&quot;&gt;Spring Data Redis 라이브러리에서 기본적으로 사용 가능하다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-start=&quot;277&quot; data-end=&quot;316&quot;&gt;Spring Boot 2.x 이상에서는 기본 Redis 클라이언트로 Lettuce가 사용된다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-start=&quot;277&quot; data-end=&quot;316&quot;&gt;Redis 명령을 이용한 간단한 분산 락 구현은 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-end=&quot;316&quot; data-start=&quot;277&quot;&gt;Lettuce는 Redis Cluster 환경에서도 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;❗️Redis 클러스터 환경(cluster environment) 이란? (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/&quot;&gt;공식 문서&lt;/a&gt;)&lt;br /&gt;여러 개의 Redis 서버를 묶어서 하나의 Redis처럼 사용하는 구조&lt;br /&gt;데이터를 여러 서버에 나눠 저장(sharding) 하는 것&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 단점 (&lt;a href=&quot;https://girokeulhaja.tistory.com/107#%F0%9F%93%8CSynchronized%20%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고 자료&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-start=&quot;342&quot; data-end=&quot;408&quot;&gt;단순한 분산 락을 구현하는 데 사용할 수 있지만, 완전한 Redisson 수준의 분산 락을 제공하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️분산 락&lt;br /&gt;여러 프로세스가 공유 자원을 '상호 배타적으로' 접근하도록 하는 것 (&lt;a href=&quot;https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;redis 공식 문서&lt;/a&gt;)&lt;br /&gt;즉, 여러 서버나 요청이 같은 데이터를 사용하려고 할 때, 동시에 처리하지 않고 한 요청이 작업을 끝낼 때까지 다른 요청은 기다리게 만드는 것&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 사용 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;infrastructure/redis/RedisLockRepository.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1773404290826&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@Component
public class RedisLockRepository {
    private RedisTemplate&amp;lt;String, String&amp;gt; redisTemplate;

    public Boolean lock(Long key) {
        return redisTemplate
                .opsForValue()
                .setIfAbsent(generateKey(key), &quot;lock&quot;, Duration.ofMillis(3_000));
    }

    public Boolean unlock(Long key) {
        return redisTemplate.delete(generateKey(key));
    }

    public String generateKey(Long key) {
        return key.toString();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;➡️ lock(Long key) 메서드&lt;/p&gt;
&lt;pre id=&quot;code_1773404698185&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public boolean lock(Long key) {
    return redisTemplate.opsForValue()
        .setIfAbsent(key, &quot;lock&quot;, Duration.ofMillis(3000));
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Duration.ofMillis(3_000) : 3초&amp;nbsp;후&amp;nbsp;자동으로&amp;nbsp;lock&amp;nbsp;삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 Redis 명령&lt;/p&gt;
&lt;pre id=&quot;code_1773404737365&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET key value NX PX 3000&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;key가 없으면 생성&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;rarr; 락 획득&lt;/li&gt;
&lt;li&gt;key가 있으면 실패 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;rarr; 이미 다른 스레드가 락 사용중&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;➡️ unlock(Longe key) 메서드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773404913347&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void unlock(Long key) {
    redisTemplate.delete(key);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 Redis 명령&lt;/p&gt;
&lt;pre id=&quot;code_1773405294610&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DEL key&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;lock 삭제 &lt;/span&gt;&lt;span&gt;&amp;rarr; 다른 프로세스가 lock 획득 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Reddison&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 특징&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 장점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 단점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 사용 방법&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/540</guid>
      <comments>https://annovation.tistory.com/540#entry540comment</comments>
      <pubDate>Wed, 11 Mar 2026 23:52:22 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 - (2) Database Lock</title>
      <link>https://annovation.tistory.com/537</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Database Lock&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스가 제공하는 Lock을 활용하여 데이터 정합성을 맞출 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Pessimistic Lock (비관적 락)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;동시성 제어-비관적 락.drawio.png&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvL9Ow/dJMcaa5lklp/CI33kyUxJexkjAbmCxlaK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvL9Ow/dJMcaa5lklp/CI33kyUxJexkjAbmCxlaK1/img.png&quot; data-alt=&quot;출처 : https://minjooig.tistory.com/147#비관적%20락(Perssimistic%20Locking)이란%3F-1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvL9Ow/dJMcaa5lklp/CI33kyUxJexkjAbmCxlaK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvL9Ow%2FdJMcaa5lklp%2FCI33kyUxJexkjAbmCxlaK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;591&quot; height=&quot;409&quot; data-filename=&quot;동시성 제어-비관적 락.drawio.png&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;409&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://minjooig.tistory.com/147#비관적%20락(Perssimistic%20Locking)이란%3F-1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;실제 데이터에 직접적으로 Lock&lt;/b&gt;을 거는 방식&lt;/li&gt;
&lt;li&gt;다른 트랜잭션은 Lock이 해제되기 전까지 데이터를 읽을 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1) 특징 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%953-Lock#-1-pessimistic-lock%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD&quot;&gt;참고 자료&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 충돌을 피하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;먼저 Lock을 걸고 작업을 진행&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;일반적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;Exclusive Lock(X Lock, 배타적 락)을 사용해 다른 트랜잭션의 Read/Write을 방지한다.&lt;/li&gt;
&lt;li&gt;은행 계좌, 항공권 좌석, 콘서트 티켓 등&amp;nbsp;충돌이 빈번하고 데이터 정합성이 중요한 환경에서 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2) 장점 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue#2-database-lock&quot;&gt;참고 자료&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;충돌이 빈번하게 일어날 경우 Optimistic Lock보다 성능이 좋을 수 있다.&lt;/li&gt;
&lt;li&gt;Lock을 통해 update를 제어하기 때문에 데이터의 정합성이 보장된다.&lt;br /&gt;(데이터를 수정할 때 미리 잠가(lock) 버리기 때문에, 여러 사람이 동시에 건드려도 데이터가 꼬이지 않는다&lt;span&gt;는 의미)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3) 단점 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.youtube.com/watch?v=oJrVl6QKzHw&quot;&gt;참고 자료&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서로 다른 자원을 쥐고 놓지 않는 데드락(Deadlock) 발생 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4) 사용 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Data JPA에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;@Lock&lt;span&gt;&amp;nbsp;&lt;/span&gt;어노테이션을 사용하여 구현할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772808412744&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface StockRepository extends JpaRepository&amp;lt;Stock, Long&amp;gt; {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query(&quot;select s from Stock s where s.id = :id&quot;)
    Stock findByIdWithPessimistic(Long id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;➡️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;@Lock(LockModeType.PESSIMISTIC_WRITE)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;해당 데이터에 대해 쓰기 잠금을 설정하여, 다른 트랜잭션이 해당 데이터를 읽거나 수정하지 못하게 막는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Optimistic Lock (낙관적 락)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;동시성 제어.drawio.png&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pyX48/dJMcab4eUDR/6zNhItgDLKFkj04pyZct4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pyX48/dJMcab4eUDR/6zNhItgDLKFkj04pyZct4k/img.png&quot; data-alt=&quot;출처 : https://minjooig.tistory.com/147#비관적%20락(Perssimistic%20Locking)이란%3F-1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pyX48/dJMcab4eUDR/6zNhItgDLKFkj04pyZct4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpyX48%2FdJMcab4eUDR%2F6zNhItgDLKFkj04pyZct4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;592&quot; height=&quot;392&quot; data-filename=&quot;동시성 제어.drawio.png&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://minjooig.tistory.com/147#비관적%20락(Perssimistic%20Locking)이란%3F-1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 읽고 수정할 때까지 락을 걸지 않고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;수정 시점에서 충돌 여부를 확인&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실제로 락을 이용하지 않고 version 을 이용&lt;/b&gt;해 데이터 정합성을 맞춤&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1) 특징 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%953-Lock#-3-named-lock%EB%84%A4%EC%9E%84%EB%93%9C-%EB%9D%BD&quot;&gt;참고 자료&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저 데이터를 읽은 후에 update를 할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;내가 읽은 버전이 맞는 확인&lt;/b&gt;하며 업데이트한다.&lt;/li&gt;
&lt;li&gt;일반적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;버전번호&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;타임스탬프를 활용해 변경 여부를 확인&lt;/li&gt;
&lt;li&gt;동시성이 높은 환경에서 유용하며, 충돌 가능성이 낮은 경우 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2) 장점 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue#named-lock&quot;&gt;참고 자료&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도의 Lock을 잡지 않아 Pessimistic Lock보다 성능상으로 이점이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3) 단점 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%953-Lock#-3-named-lock%EB%84%A4%EC%9E%84%EB%93%9C-%EB%9D%BD&quot;&gt;참고 자료 1&lt;/a&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue#named-lock&quot;&gt;참고 자료 2&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;충돌이 발생하면 다시 데이터를 읽고 재시도해야 한다.&lt;/li&gt;
&lt;li&gt;update가 실패했을 때 재시도 로직을 개발자가 직접 작성해야 하는 번거로움이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3-1) 예외처리를 하는 이유 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://girokeulhaja.tistory.com/107#%F0%9F%93%8CSynchronized%20%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-1&quot;&gt;참고 자료&lt;/a&gt;)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;사용자 A가 Stock 데이터를 조회 (SELECT)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;id=1, quantity=100, version=1&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;사용자 B가 동일한 Stock 데이터를 조회 (SELECT)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;, quantity=&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;, version=&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;1&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;사용자 A가 Stock을 업데이트 (UPDATE)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772808412749&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;UPDATE stock SET quantity=90, version=2 WHERE id=1 AND version=1;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-end=&quot;1198&quot; data-start=&quot;1167&quot;&gt;성공적으로 업데이트됨 (version=2로 증가)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;사용자 B가 Stock을 업데이트 시도 (UPDATE)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772808412750&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;UPDATE stock SET quantity=80, version=2 WHERE id=1 AND version=1;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-end=&quot;1392&quot; data-start=&quot;1327&quot;&gt;version=1을 가진 데이터가 없으므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;예외 발생&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(OptimisticLockException)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4) 사용 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stock Entity Class 에 version 컬럼 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772808412751&quot; class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stock {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private Long productId;

  private Long quantity;

  @Version
  private Long version;
  
  //생략...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;➡️ 주의 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;javax.persistence&lt;span&gt;&amp;nbsp;&lt;/span&gt;패키지에 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;@Version 어노테이션을 사용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Repository&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772808412752&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface StockRepository extends JpaRepository&amp;lt;Stock, Long&amp;gt; {

  @Lock(LockModeType.OPTIMISTIC)
  @Query(&quot; select s from Stock s where s.id = :id &quot;)
  Stock findByIdWithOptimisticLock(Long id);
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;➡️ Spring Data JPA에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;@Lock&lt;span&gt;&amp;nbsp;&lt;/span&gt;어노테이션을 사용하여 구현할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;facade 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772808412753&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class OptimisticLockStockFacade {

    private final OptimisticLockStockService optimisticLockStockService;

    public OptimisticLockStockFacade(OptimisticLockStockService optimisticLockStockService){
        this.optimisticLockStockService = optimisticLockStockService;
    }

    public void decrease(Long id, Long quantity) throws InterruptedException{
        while (true) {  //업데이트를 실패했을 때 재시도를 해야함
            try {
                optimisticLockStockService.decrease(id, quantity);

                //정상적으로 업데이트 되면 빠져나감
                break;
            } catch (Exception e) {
                //수량 감소 실패 시, 재시도
                Thread.sleep(50);
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;➡️ Optimistic Lock은 실패했을 경우 재시도를 해야하기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;facade(퍼사드)와 같은 재시도 로직을 생성해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Named Lock&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1-1) 특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름을 가진 메타데이터를 사용하는&amp;nbsp; metadata locking 방법이다. (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://dodop-blog.tistory.com/463&quot;&gt;참고 자료&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;이름을 가진 lock을 획득한 후 해제할 때까지 다른 세션이 이 lock을 획득할 수 없도록 한다. (&lt;a href=&quot;https://girokeulhaja.tistory.com/107#%F0%9F%93%8CSynchronized%20%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고 자료&lt;/a&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;트랜잭션이 종료 될 때 Lock이 자동으로 해제되지 않으므로 별도의 명령어로 해제가 필요하다. (&lt;a style=&quot;color: #0070d1; text-align: left;&quot; href=&quot;https://girokeulhaja.tistory.com/107#%F0%9F%93%8CSynchronized%20%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-1&quot;&gt;참고 자료&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;MySQL에서는 GET_LOCK() 함수를 사용해서 &lt;b&gt;사용자가 정의한 이름(name)을 기준으로 lock을 획득&lt;/b&gt;할 수 있다. (&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;1-2) Named Lock 데이터 적용 과정 (&lt;a href=&quot;https://velog.io/@kdmin0706/Java-Spring-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#3-named-lock&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고 자료&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn12k3/dJMcaaqWexn/pYLMGqrNzbduJsdGfEJxWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn12k3/dJMcaaqWexn/pYLMGqrNzbduJsdGfEJxWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn12k3/dJMcaaqWexn/pYLMGqrNzbduJsdGfEJxWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn12k3%2FdJMcaaqWexn%2FpYLMGqrNzbduJsdGfEJxWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;651&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;651&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Named Lock은 DB에 락을 걸지 않고, 별도의 공간에 락을 건다.&lt;/li&gt;
&lt;li&gt;session-1이 1이라는 이름으로 락을 건다면 session-1이 1을 해지한 후에 락을 얻을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2) 장점&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3) 단점 (&lt;a href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%953-Lock#-3-named-lock%EB%84%A4%EC%9E%84%EB%93%9C-%EB%9D%BD&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고 자료&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락을 획득하지 못하면 대기하거나 재시도 로직 필요&lt;/li&gt;
&lt;li&gt;트랜잭션 종료 시 자동으로 Lock 해제 되지 않아 Lock을 걸고 해제하는 별도 로직이 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4) 사용 방법 (&lt;a href=&quot;https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue#named-lock&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고 자료&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Named Lock 을 수행하는 repository 를 생성 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772975646893&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface LockRepository extends JpaRepository&amp;lt;Stock, Long&amp;gt; {
    @Query(value = &quot;select get_lock(:key, 3000)&quot;, nativeQuery = true)
    void getLock(String key);

    @Query(value = &quot;select release_lock(:key)&quot;, nativeQuery = true)
    void releaseLock(String key);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Lock 획득 해제하는 별도의 명령을 위해 Facade 패턴을 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772977912672&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class NamedLockStockFacade {

    private final LockRepository lockRepository;
    private final StockService stockService;

    public NamedLockStockFacade(LockRepository lockRepository, StockService stockService){
        this.lockRepository = lockRepository;
        this.stockService = stockService;
    }

    //decrease 메서드
    @Transactional
    public void decrease(Long id, Long quantity){
        try {
            //lock 획득
            lockRepository.getLock(id.toString());
            //재고 감소
            stockService.decrease(id, quantity);
        }finally {
            //모든 로직이 종료되었을 때, lock 해제
            lockRepository.releaseLock(id.toString());
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockService propagation 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772977951562&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional(propagation = Propagation.REQUIRES_NEW)
public synchronized void decrease(Long id, Long quantity){
    // Stock 조회
    Stock stock = stockRepository.findById(id).orElseThrow();

    // 재고를 감소
    stock.decrease(quantity);

    // 갱신된 값을 저장
    stockRepository.saveAndFlush(stock);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 부모의 트랜잭션과 별도로 실행되어야 하기 때문에 propagation을 변경해주어야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️Propagation(전파) 란?&lt;br /&gt;&lt;br /&gt;현재 메서드가 트랜잭션을 어떻게 사용할지 결정하는 규칙&lt;br /&gt;&lt;br /&gt;ex.&lt;br /&gt;@Transactional(propagation = Propagation.REQUIRED) -&amp;gt; 기존 트랜잭션 사용, 없으면 새로 생성&lt;br /&gt;@Transactional(propagation = Propagation.REQUIRES_NEW) -&amp;gt; 항상 새로운 트랜잭션 생성, 기존 트랜잭션은 일시 정지&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;connection pool size 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 데이터 소스를 사용하기 위해 connection pool size를 변경합니다.&lt;br /&gt;maximum-pool-size: 40&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 블로그 : SpringBoot 동시성 이슈 해결방법[3] - Lock&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%953-Lock#-1-pessimistic-lock%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@kiteof_park/SpringBoot-pessimistic-lock&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772809038307&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;SpringBoot 동시성 이슈 해결방법[3] - Lock&quot; data-og-description=&quot;synchronized의 한계와 Lock의 종류 - Pessimistic Lock, Optimistic Lock, Named Lock&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%953-Lock#-1-pessimistic-lock%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD&quot; data-og-url=&quot;https://velog.io/@kiteof_park/SpringBoot-동시성-이슈-해결방법3-Lock&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zN0yl/dJMb9kmaHZn/PqKV5XjdiWisXerXkLOYS1/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/bSOdA0/dJMb9gxjjBG/kvMWX5rSR1mLedtDUoaplk/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/rVmIR/dJMb9b3PYCR/QgoLb62ZUwSpHueqOI8Zzk/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398&quot;&gt;&lt;a href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%953-Lock#-1-pessimistic-lock%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%953-Lock#-1-pessimistic-lock%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zN0yl/dJMb9kmaHZn/PqKV5XjdiWisXerXkLOYS1/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/bSOdA0/dJMb9gxjjBG/kvMWX5rSR1mLedtDUoaplk/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/rVmIR/dJMb9b3PYCR/QgoLb62ZUwSpHueqOI8Zzk/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SpringBoot 동시성 이슈 해결방법[3] - Lock&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;synchronized의 한계와 Lock의 종류 - Pessimistic Lock, Optimistic Lock, Named Lock&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 블로그 : [Java/Spring] 재고 시스템으로 알아보는 동시성 이슈와 해결 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue#2-database-lock&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue#2-database-lock&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772809181158&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Java/Spring] 재고 시스템으로 알아보는 동시성 이슈와 해결 방법&quot; data-og-description=&quot;인프런 재고시스템으로 알아보는 동시성이슈 해결방법 강의를 듣고 작성한 글입니다.&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue#2-database-lock&quot; data-og-url=&quot;https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jZi19/dJMb82MA0wX/joVcKDaFSQCSK10Y6aUHU0/img.jpg?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/cPxk2F/dJMb88eYzAD/YzjiWNHQ23h3sdPWTUJh7K/img.jpg?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/eGfu3/dJMb83SgJYf/Gr10sSlSAsWCpyIM4ujd70/img.png?width=1812&amp;amp;height=490&amp;amp;face=0_0_1812_490&quot;&gt;&lt;a href=&quot;https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue#2-database-lock&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue#2-database-lock&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jZi19/dJMb82MA0wX/joVcKDaFSQCSK10Y6aUHU0/img.jpg?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/cPxk2F/dJMb88eYzAD/YzjiWNHQ23h3sdPWTUJh7K/img.jpg?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/eGfu3/dJMb83SgJYf/Gr10sSlSAsWCpyIM4ujd70/img.png?width=1812&amp;amp;height=490&amp;amp;face=0_0_1812_490');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Java/Spring] 재고 시스템으로 알아보는 동시성 이슈와 해결 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인프런 재고시스템으로 알아보는 동시성이슈 해결방법 강의를 듣고 작성한 글입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 블로그 : [Java/Spring] 재고시스템으로 알아보는 동시성 이슈 해결 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://girokeulhaja.tistory.com/107#%F0%9F%93%8COptimistic%20Lock(%EB%82%99%EA%B4%80%EC%A0%81%20%EB%9D%BD)%20%ED%99%9C%EC%9A%A9-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://girokeulhaja.tistory.com/107&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772809362275&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Java/Spring] 재고시스템으로 알아보는 동시성 이슈 해결 방법 &quot; data-og-description=&quot;❗해당 포스팅은 인프런에서 제공해 주는 강의 내용을 개인적으로 정리하였음을 알려드립니다.&amp;nbsp;재고시스템으로 알아보는 동시성이슈 해결방법 강의 | 최상용 - 인프런최상용 | , 동시성 이슈 &quot; data-og-host=&quot;girokeulhaja.tistory.com&quot; data-og-source-url=&quot;https://girokeulhaja.tistory.com/107#%F0%9F%93%8COptimistic%20Lock(%EB%82%99%EA%B4%80%EC%A0%81%20%EB%9D%BD)%20%ED%99%9C%EC%9A%A9-1&quot; data-og-url=&quot;https://girokeulhaja.tistory.com/107&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cVWFoe/dJMb8ZvznJQ/mDK1XioKKCrpxgab6CYO40/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/z2NDZ/dJMb8WexyKO/MHrm0kXuWtKaCythMdpT10/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/QjafP/dJMb8YXJpSN/SrjBUgvnFu9qeeOEAxjaKk/img.png?width=1080&amp;amp;height=1080&amp;amp;face=0_0_1080_1080&quot;&gt;&lt;a href=&quot;https://girokeulhaja.tistory.com/107#%F0%9F%93%8COptimistic%20Lock(%EB%82%99%EA%B4%80%EC%A0%81%20%EB%9D%BD)%20%ED%99%9C%EC%9A%A9-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://girokeulhaja.tistory.com/107#%F0%9F%93%8COptimistic%20Lock(%EB%82%99%EA%B4%80%EC%A0%81%20%EB%9D%BD)%20%ED%99%9C%EC%9A%A9-1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cVWFoe/dJMb8ZvznJQ/mDK1XioKKCrpxgab6CYO40/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/z2NDZ/dJMb8WexyKO/MHrm0kXuWtKaCythMdpT10/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/QjafP/dJMb8YXJpSN/SrjBUgvnFu9qeeOEAxjaKk/img.png?width=1080&amp;amp;height=1080&amp;amp;face=0_0_1080_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Java/Spring] 재고시스템으로 알아보는 동시성 이슈 해결 방법 &lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;❗해당 포스팅은 인프런에서 제공해 주는 강의 내용을 개인적으로 정리하였음을 알려드립니다.&amp;nbsp;재고시스템으로 알아보는 동시성이슈 해결방법 강의 | 최상용 - 인프런최상용 | , 동시성 이슈&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;girokeulhaja.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 유튜브 : &lt;span&gt;락의 두 종류: 비관적 락 vs 낙관적 락&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=oJrVl6QKzHw&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=oJrVl6QKzHw&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=oJrVl6QKzHw&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cNmSoQ/dJMb8WexyKa/iMiIXyUlXkH0T3iJ75P9m1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bSpl1n/dJMb8TB7v9N/kidMd61NOBGTPoyXy7t2Zk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/sHFvg/dJMb8SpFSay/FlPz31vHv5et5vZ9m878AK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;락의 두 종류: 비관적 락 vs 낙관적 락 ＃개발자개념장착&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/oJrVl6QKzHw&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) 블로그 : 인프런) 재고시스템으로 알아보는 동시성 이슈 해결 (1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dodop-blog.tistory.com/463&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dodop-blog.tistory.com/463&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772974515074&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;인프런) 재고시스템으로 알아보는 동시성 이슈 해결 (1)&quot; data-og-description=&quot;동시성 이슈 해결을 위한 인프런 강의를 듣고 실습해보았다.&amp;nbsp;https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard&amp;nbsp;재고시스템으로 알아보는 동&quot; data-og-host=&quot;dodop-blog.tistory.com&quot; data-og-source-url=&quot;https://dodop-blog.tistory.com/463&quot; data-og-url=&quot;https://dodop-blog.tistory.com/463&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/burOuT/dJMb87f4ufX/FYkbYhnlAf2HHddwMvpPeK/img.png?width=800&amp;amp;height=353&amp;amp;face=0_0_800_353,https://scrap.kakaocdn.net/dn/bADN4d/dJMb9lk5b39/rAGAaaob4PA8s8gCeBY131/img.png?width=800&amp;amp;height=353&amp;amp;face=0_0_800_353,https://scrap.kakaocdn.net/dn/kSe7t/dJMb9jOk0xf/essLrohcJClWvAhX6lOllk/img.png?width=2398&amp;amp;height=1220&amp;amp;face=0_0_2398_1220&quot;&gt;&lt;a href=&quot;https://dodop-blog.tistory.com/463&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dodop-blog.tistory.com/463&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/burOuT/dJMb87f4ufX/FYkbYhnlAf2HHddwMvpPeK/img.png?width=800&amp;amp;height=353&amp;amp;face=0_0_800_353,https://scrap.kakaocdn.net/dn/bADN4d/dJMb9lk5b39/rAGAaaob4PA8s8gCeBY131/img.png?width=800&amp;amp;height=353&amp;amp;face=0_0_800_353,https://scrap.kakaocdn.net/dn/kSe7t/dJMb9jOk0xf/essLrohcJClWvAhX6lOllk/img.png?width=2398&amp;amp;height=1220&amp;amp;face=0_0_2398_1220');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;인프런) 재고시스템으로 알아보는 동시성 이슈 해결 (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;동시성 이슈 해결을 위한 인프런 강의를 듣고 실습해보았다.&amp;nbsp;https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard&amp;nbsp;재고시스템으로 알아보는 동&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dodop-blog.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6) MySQL Docs : Locking Functions&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772977048753&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL :: MySQL 8.0 Reference Manual :: 14.14 Locking Functions&quot; data-og-description=&quot;This section describes functions used to manipulate user-level locks. Table&amp;nbsp;14.19&amp;nbsp;Locking Functions GET_LOCK(str,timeout) Tries to obtain a lock with a name given by the string str, using a timeout of timeout seconds. A negative timeout value means infin&quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot; data-og-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL :: MySQL 8.0 Reference Manual :: 14.14 Locking Functions&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This section describes functions used to manipulate user-level locks. Table&amp;nbsp;14.19&amp;nbsp;Locking Functions GET_LOCK(str,timeout) Tries to obtain a lock with a name given by the string str, using a timeout of timeout seconds. A negative timeout value means infin&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/537</guid>
      <comments>https://annovation.tistory.com/537#entry537comment</comments>
      <pubDate>Mon, 9 Mar 2026 03:09:41 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 - (1) Synchronized</title>
      <link>https://annovation.tistory.com/536</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;동시성&amp;nbsp;문제&amp;nbsp;(Race&amp;nbsp;Condition)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방법&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레이스 컨디션을 해결하는 방법에는 크게 3가지 관점이 있다.&lt;/li&gt;
&lt;li&gt;첫번째는 Java에서 지원하는 Synchronized 방법으로 해결하기&lt;/li&gt;
&lt;li&gt;두번째는 DB가 제공하는 Lock 을 이용하여 데이터 정합성 맞추기&lt;/li&gt;
&lt;li&gt;세번째는 Redis 를 활용하여 대표적인 라이브러리 Lettuce 와 Ressison 으로 해결하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;동시성 문제 (Race Condition) 이란?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://annovation.tistory.com/508&quot;&gt;https://annovation.tistory.com/508&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772788179007&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[동시성 처리] Java 에서 발생하는 동시성 문제란?&quot; data-og-description=&quot;Java 동시성 문제 (Race Condition, 레이스 컨디션) Java 동시성 문제란?Java에서 여러 스레드가 같은 데이터(필드/객체 상태) 를 공유하며 실행될 때, 올바른 제어 없이 접근하면 오류가 생긴다.이렇게&quot; data-og-host=&quot;annovation.tistory.com&quot; data-og-source-url=&quot;https://annovation.tistory.com/508&quot; data-og-url=&quot;https://annovation.tistory.com/508&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cDeAXH/dJMb8866KwP/16UyKvzOjY4StBiA5kaMw1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bSw4Ph/dJMb85vMPna/JuHUjAJkYcvlTv8zFFkhWk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/4cfsv/dJMb86nVq1P/0y7h6uYYA8KHVxxKOd7i70/img.jpg?width=736&amp;amp;height=736&amp;amp;face=0_0_736_736&quot;&gt;&lt;a href=&quot;https://annovation.tistory.com/508&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://annovation.tistory.com/508&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cDeAXH/dJMb8866KwP/16UyKvzOjY4StBiA5kaMw1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bSw4Ph/dJMb85vMPna/JuHUjAJkYcvlTv8zFFkhWk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/4cfsv/dJMb86nVq1P/0y7h6uYYA8KHVxxKOd7i70/img.jpg?width=736&amp;amp;height=736&amp;amp;face=0_0_736_736');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[동시성 처리] Java 에서 발생하는 동시성 문제란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Java 동시성 문제 (Race Condition, 레이스 컨디션) Java 동시성 문제란?Java에서 여러 스레드가 같은 데이터(필드/객체 상태) 를 공유하며 실행될 때, 올바른 제어 없이 접근하면 오류가 생긴다.이렇게&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;annovation.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Application Level&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java에서 제공하는 Synchronized 키워드를 사용하여 한 개의 스레드만 접근 가능하도록 제한할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Synchronized&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772788175325&quot; class=&quot;gradle&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//@Transactional
public synchronized void decrease(Long id, Long quantity) {
    // Stock 조회
    // 재고를 감소시킨 뒤
    // 갱신된 값을 저장
    Stock stock = stockRepository.findById(id).orElseThrow();
    stock.decrease(quantity);

    stockRepository.save(stock);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized&lt;span&gt;&amp;nbsp;&lt;/span&gt;키워드를 메서드 선언부에 붙여, 해당 메서드는 하나의 스레드만 접근 가능하도록 제한한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✅ @Transactional 을 주석처리하는 이유 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---java-synchronized&quot;&gt;출처&lt;/a&gt;)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;@Transactional의 동작 원리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Transactional은 Spring AOP 기반 프록시(proxy)를 통해 트랜잭션 경계를 생성한다. (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html&quot;&gt;공식 문서&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;Spring uses AOP proxies to apply transactional behavior.&lt;/blockquote&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;❗️개념 정리&lt;br /&gt;&lt;br /&gt;1) Spring AOP(Aspect Oriented Programming) 란? (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://docs.spring.io/spring-framework/reference/core/aop.html&quot;&gt;공식 문서&lt;/a&gt;)&lt;br /&gt;- 관점 지향 프로그래밍이란 뜻으로, 공통 기능을 비즈니스 로직에서 분리해서 자동으로 적용하는 기술이다.&lt;br /&gt;ex.&lt;br /&gt;공통 기능 (ex. 로그)&lt;br /&gt;&amp;darr;&lt;br /&gt;비즈니스 로직 실행&lt;br /&gt;// Spring은 실제 객체 대신 Proxy 객체를 먼저 호출하고, Proxy가 공통 기능을 실행한 뒤 실제 비즈니스 로직을 호출한다.&lt;br /&gt;&amp;darr;&lt;br /&gt;공통 기능 (ex. 로그)&lt;br /&gt;&lt;br /&gt;2) 프록시(proxy) 란?&lt;br /&gt;- Spring 에서 AOP 개념을 구현한 기능으로, 원래 객체 대신 호출을 받아서 중간에서 처리하는 객체&lt;br /&gt;ex. 원래 객체 UserService를 호출했다면, Spring이 만든 UserServiceProxy가 호출된다.&lt;br /&gt;&lt;br /&gt;3) 트랜잭션 이란?&lt;br /&gt;- 여러 데이터베이스 작업(DB 안의 데이터를 읽거나 변경하는 모든 행위, CRUD)을 하나의 논리적 작업 단위로 묶는 것이다.&lt;br /&gt;- 즉 여러 쿼리(데이터베이스에게 요청을 보내는 명령문)가 실행되더라도 전부 성공하거나 / 전부 실패해야 한다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #353638;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;Spring이 해당 메서드를 감싼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;프록시(proxy) 객체를 생성해 트랜잭션을 관리&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;한다.&lt;br /&gt;예를 들면, StockService 를 필드로 가지는 클래스를 새로 만들어서 실행 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---java-synchronized&quot;&gt;참고자료&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Proxy 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772788175326&quot; class=&quot;pgsql&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class OrderServiceProxy {

    OrderService target;

    public void order() {

        System.out.println(&quot;로그 시작&quot;);

        target.order();

        System.out.println(&quot;로그 종료&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #353638;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;실행 흐름&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772788175326&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Client
 &amp;darr;
Proxy.order()
 &amp;darr;
OrderService.order()&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;@Transactional 을 주석처리하는 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #353638;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;commit(트랜잭션의 모든 작업을 최종 확정해 DB에 영구 저장하는 것)은 트랜잭션 완료 시점에 이루어진다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #353638;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;commit 전에 DB에 기록되지만, 다른 트랜잭션에서는 그 변경사항을 볼 수 없다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #353638;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;따라서 재고 감소 메서드 decrease()가 완료되고 실제 DB에 반영 전에 다른 스레드가 decrease()를 호출하면, 다른 스레드는 갱신되기 전에 값을 가져가 이전과 동일한 문제가 발생한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #353638;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;즉, 현재 테스트 코드에서는 여러 스레드가 동시에 실행되지만, 하나의 트랜잭션 안에서 commit이 되지 않은채 데이터에 접근하는 구조이다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;synchronized 동작 방식&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized&lt;span&gt;&amp;nbsp;&lt;/span&gt;키워드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;같은 인스턴스에서 실행되는 여러 스레드&lt;/b&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;하나씩 순차적으로 메서드를 실행하도록 보장한다.&lt;/li&gt;
&lt;li&gt;같은 인스턴스에서 적용되는&lt;span&gt;&amp;nbsp;&lt;/span&gt;synchronized Lock 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772788175328&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;StockService service = new StockService();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 스레드&lt;/p&gt;
&lt;pre id=&quot;code_1772788175328&quot; class=&quot;autohotkey&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Thread A &amp;rarr; service.decrease()
Thread B &amp;rarr; service.decrease()&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 경우&lt;/p&gt;
&lt;pre id=&quot;code_1772788175328&quot; class=&quot;autohotkey&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Thread A &amp;rarr; lock(service)
Thread B &amp;rarr; 대기&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결과&lt;/p&gt;
&lt;pre id=&quot;code_1772788175328&quot; class=&quot;properties&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;A 실행
B 실행&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 인스턴스에서 적용되는&lt;span&gt;&amp;nbsp;&lt;/span&gt;synchronized Lock 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772788175328&quot; class=&quot;haxe&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;StockService service1 = new StockService();
StockService service2 = new StockService();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 스레드&lt;/p&gt;
&lt;pre id=&quot;code_1772788175328&quot; class=&quot;autohotkey&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Thread A &amp;rarr; service1.decrease()
Thread B &amp;rarr; service2.decrease()&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 경우&lt;/p&gt;
&lt;pre id=&quot;code_1772788175328&quot; class=&quot;stylus&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;lock(service1)
lock(service2)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결과&lt;/p&gt;
&lt;pre id=&quot;code_1772788175329&quot; class=&quot;&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;동시에 실행&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized는 객체 기준으로 Lock을 잡는데 Spring이 Proxy를 사용하면 Lock이&amp;nbsp;걸리는&amp;nbsp;객체와&amp;nbsp;호출&amp;nbsp;객체가&amp;nbsp;달라질&amp;nbsp;수&amp;nbsp;있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Transactional이 적용되면 Spring은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;원본 StockService 대신 프록시 객체를 통해 메서드를 실행&lt;/b&gt;하여 트랜잭션을 관리한다.&lt;/li&gt;
&lt;li&gt;하지만 synchronized는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;원본 객체 인스턴스를 기준으로 동기화&lt;/b&gt;되기 때문에, 프록시가 개입하는 구조에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;의도한 대로 동기화가 보장되지 않을 가능성이 있다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✅ synchronized 한계 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---java-synchronized&quot;&gt;출처&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized는 하나의 프로세스 안에서만 동기화를 보장한다.&lt;/li&gt;
&lt;li&gt;서버가 1대일 때는 데이터 접근을 서버 1대만 해서 괜찮겠지만,&lt;br /&gt;서버가 2대 혹은 그 이상인 경우 데이터 접근을 여러 곳에서 할 수 있게 된다.&lt;/li&gt;
&lt;li&gt;실제 운영 서비스는 대부분 2대 이상의 서버를 사용하므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;synchronized&lt;span&gt;&amp;nbsp;&lt;/span&gt;거의 사용❌&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Spring Docs : Using @Transactional&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772788304513&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Using @Transactional :: Spring Framework&quot; data-og-description=&quot;The @Transactional annotation is metadata that specifies that an interface, class, or method must have transactional semantics (for example, &amp;quot;start a brand new read-only transaction when this method is invoked, suspending any existing transaction&amp;quot;). The de&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Using @Transactional :: Spring Framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The @Transactional annotation is metadata that specifies that an interface, class, or method must have transactional semantics (for example, &quot;start a brand new read-only transaction when this method is invoked, suspending any existing transaction&quot;). The de&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 블로그 : 동시성 이슈 해결방법[2] - synchronized&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---java-synchronized&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@kiteof_park/SpringBoot-java-synchronized&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772788272670&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;동시성 이슈 해결방법[2] - synchronized&quot; data-og-description=&quot;synchronized로 동시성 이슈를 해결하지 못하는 이유와 @Transactional과의 충돌!&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---java-synchronized&quot; data-og-url=&quot;https://velog.io/@kiteof_park/SpringBoot-동시성-이슈-해결방법2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sNrCo/dJMb9fZtdB7/H8Y56dkpz8jKEzNA4oWFYK/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/cSA7NY/dJMb9kmaGtJ/iy19VlE8KM8GbqRi00EhG0/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/yQD10/dJMb9kmaGtI/KOHNDhkBHXMpzoxniUxnGk/img.png?width=1024&amp;amp;height=435&amp;amp;face=0_0_1024_435&quot;&gt;&lt;a href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---java-synchronized&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---java-synchronized&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sNrCo/dJMb9fZtdB7/H8Y56dkpz8jKEzNA4oWFYK/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/cSA7NY/dJMb9kmaGtJ/iy19VlE8KM8GbqRi00EhG0/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/yQD10/dJMb9kmaGtI/KOHNDhkBHXMpzoxniUxnGk/img.png?width=1024&amp;amp;height=435&amp;amp;face=0_0_1024_435');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;동시성 이슈 해결방법[2] - synchronized&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;synchronized로 동시성 이슈를 해결하지 못하는 이유와 @Transactional과의 충돌!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/536</guid>
      <comments>https://annovation.tistory.com/536#entry536comment</comments>
      <pubDate>Fri, 6 Mar 2026 18:12:12 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Redis 실습 2</title>
      <link>https://annovation.tistory.com/535</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;Spring Boot에서 Redis 사용해보기&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Redis의 Hash 자료구조로 저장되는 엔티티(Entity) 만들기&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;엔티티(Entity) : 저장소(DB, Redis 등)에 저장되는 도메인 객체(클래스)&lt;br /&gt;도메인 객체(Domain Object) : 서비스가 다루는 현실 세계의 개념(ex. 상품, 주문, 계좌 등) 들을 코드로 표현한 객체&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1) 요구사항&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아래와 같은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;도메인 객체의 속성(필드)을 가지는 주문 ID, 판매 물품, 갯수, 총액, 결제 여부에 대한 데이터를 지정하기 위한 ItemOrder 클래스를 RedisHash 만들기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772719852404&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 주문 ID - String
2. 판매 물품 - String
3. 갯수 - Integer
4. 총액 - Long
5. 주문 상태 - String&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2) 코드 구현&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ItemOrder.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772720666554&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RedisHash(&quot;order&quot;)
public class ItemOrder {
    @Id
    private String id;
    private String item;
    private Integer count;
    private Long totalPrice;
    private String status;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ @RedisHash&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이 클래스가 Redis의 Hash 자료구조로 저장되는 엔티티(Entity)라는 것을 Spring에게 알려준다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;주문에 대한 CRUD를 진행하는 기능&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 요구사항&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ItemOrder의 속성값들을 ID를 제외하고 클라이언트에서 전달해준다. (Spring Boot 서버에서 ID를 생성해야 함)&lt;/li&gt;
&lt;li&gt;성공하면 저장된 ItemOrder를 사용자에게 응답해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 코드 구현 : Spring Data Redis Repository 의 CrudRepository 활용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OrderRepository.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772723104065&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.springframework.data.repository.CrudRepository;

public interface OrderRepository
        extends CrudRepository&amp;lt;ItemOrder, String&amp;gt; {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ CrudRepository&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본적인 CURD 메서드를 제공하는 인터페이스 (save(), findById(), deleteById() ... )&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OrderController.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772723104066&quot; class=&quot;livescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping(&quot;orders&quot;)
public class OrderController {
    private final OrderRepository orderRepository;

    public OrderController(
            OrderRepository orderRepository
    ) {
        this.orderRepository = orderRepository;
    }

    @PostMapping
    public ItemOrder create(
            @RequestBody
            ItemOrder order
    ) {
        return orderRepository.save(order);
    }

    @GetMapping
    public List&amp;lt;ItemOrder&amp;gt; readAll() {
        List&amp;lt;ItemOrder&amp;gt; orders = new ArrayList&amp;lt;&amp;gt;();
        orderRepository.findAll()
                .forEach(orders::add);
        return orders;
    }

    @GetMapping(&quot;{id}&quot;)
    public ItemOrder readOne(
            @PathVariable(&quot;id&quot;)
            String id
    ) {
        return orderRepository.findById(id)
                .orElseThrow(() -&amp;gt; new ResponseStatusException(HttpStatus.NOT_FOUND));
    }

    @PutMapping(&quot;{id}&quot;)
    public ItemOrder update(
            @PathVariable(&quot;id&quot;)
            String id,
            @RequestBody
            ItemOrder order
    ) {
        ItemOrder target = orderRepository
                .findById(id)
                .orElseThrow(() -&amp;gt; new ResponseStatusException(HttpStatus.NOT_FOUND));
        target.setItem(order.getItem());
        target.setCount(order.getCount());
        target.setTotalPrice(order.getTotalPrice());
        target.setStatus(order.getStatus());
        return orderRepository.save(target);
    }

    @DeleteMapping(&quot;{id}&quot;)
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(
            @PathVariable(&quot;id&quot;)
            String id
    ) {
        orderRepository.deleteById(id);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 코드 구현 : RedisTemplate 을 활용해 직접 Redis 명령을 사용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RedisConfig.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772724021063&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate&amp;lt;String, Integer&amp;gt; articleTemplate(
            RedisConnectionFactory redisConnectionFactory
    ) {
        RedisTemplate&amp;lt;String, Integer&amp;gt; template = new RedisTemplate&amp;lt;&amp;gt;();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(RedisSerializer.string());
        template.setValueSerializer(new GenericToStringSerializer&amp;lt;&amp;gt;(Integer.class));
        return template;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ RedisTemplate Bean 생성하는 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-1) 각 코드 의미&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Configuration : Spring&amp;nbsp;Bean&amp;nbsp;설정을&amp;nbsp;하는&amp;nbsp;클래스라는 의미&lt;/li&gt;
&lt;li&gt;@Bean
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;해당 어노테이션이 붙은 메서드가 반환하는 객체를 Spring Bean 으로 등록 한다.&lt;/li&gt;
&lt;li&gt;즉, 위 코드에서는 RedisTemplate 을 Spring 이 관리하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RedisConnectionFactory : Redis 서버와 연결을 만드는 객체&lt;/li&gt;
&lt;li&gt;RedisTemplate&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772724229436&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RedisTemplate&amp;lt;String, Integer&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ Redis&amp;nbsp;명령을&amp;nbsp;실행하는&amp;nbsp;클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 예시&lt;/p&gt;
&lt;pre id=&quot;code_1772724258141&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;redisTemplate.opsForValue().increment(&quot;article:1&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Serializer 설정 (중요)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772724311067&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;template.setValueSerializer(
    new GenericToStringSerializer&amp;lt;&amp;gt;(Integer.class)
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ Redis 는 문자열 기반 저장소이기 때문에 Java 객체를 Redis 저장 가능 형태로 변환 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 Serializer 라고 하고 위 코드는 Integer &amp;rarr; String 변환하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-2) ArticleController.java&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RedisConfig로 설정한 RedisTemplate 클래스를 Controller에서 사용하는 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772724552163&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(&quot;articles&quot;)
public class ArticleController {
    private final ValueOperations&amp;lt;String, Integer&amp;gt; ops;

    public ArticleController(
            RedisTemplate&amp;lt;String, Integer&amp;gt; articleTemplate
    ) {
        ops = articleTemplate.opsForValue();
    }

    @GetMapping(&quot;{id}&quot;)
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void read(
            @PathVariable(&quot;id&quot;)
            Long id
    ) {
        ops.increment(&quot;articles:%d&quot;.formatted(id));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-6-2-2242dc3ef51481f3a285e1abb7bd48e8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://teamsparta.notion.site/1-6-2-2242dc3ef51481f3a285e1abb7bd48e8&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772716235467&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;챕터1-6 : 실습2 | Notion&quot; data-og-description=&quot;Spring Boot에서 Redis 사용해보기&quot; data-og-host=&quot;teamsparta.notion.site&quot; data-og-source-url=&quot;https://teamsparta.notion.site/1-6-2-2242dc3ef51481f3a285e1abb7bd48e8&quot; data-og-url=&quot;https://teamsparta.notion.site/1-6-2-2242dc3ef51481f3a285e1abb7bd48e8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dXVVcw/dJMb86OZC8a/ssapD3nY9cU2xmJvzQlcSK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dyGHru/dJMb81GU11c/1crlwSUTuYkif93Dpj2gm1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-6-2-2242dc3ef51481f3a285e1abb7bd48e8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://teamsparta.notion.site/1-6-2-2242dc3ef51481f3a285e1abb7bd48e8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dXVVcw/dJMb86OZC8a/ssapD3nY9cU2xmJvzQlcSK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dyGHru/dJMb81GU11c/1crlwSUTuYkif93Dpj2gm1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;챕터1-6 : 실습2 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot에서 Redis 사용해보기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;teamsparta.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>심화/DB</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/535</guid>
      <comments>https://annovation.tistory.com/535#entry535comment</comments>
      <pubDate>Thu, 5 Mar 2026 23:11:58 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Redis 실습 1</title>
      <link>https://annovation.tistory.com/534</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;블로그 별 조회수를 Redis로 확인하기&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;구현 기능&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래와 같은 조건의 '내 블로그 글 별 조회수'를 Redis로 확인하는 기능을 만드려고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772638756503&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 블로그 URL의 PATH는 /articles/{id} 형식이다.
2. 로그인 여부와 상관없이 새로고침 될때마다 조회수가 하나 증가한다.
3. 이를 관리하기 위해 적당한 데이터 타입을 선정하고,
4. 사용자가 임의의 페이지에 접속할 때 실행될 명령을 작성해보자.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Redis 명령어&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Redis의 &lt;/span&gt;String 타입은 숫자 카운터로 사용할 수 있고 값을 증가(INCR) 또는 감소(DECR)시키는 연산을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 특정 게시글 조회수 증가&lt;/p&gt;
&lt;pre id=&quot;code_1772639226440&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INCR articles:{id}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 특정 게시글 조회수 증가 예시&lt;/p&gt;
&lt;pre id=&quot;code_1772639265725&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INCR articles:1
INCR articles:2
INCR articles:3&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1번 게시글 조회수 +1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 오늘의 조회수 따로 관리&lt;/p&gt;
&lt;pre id=&quot;code_1772639302055&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INCR articles:1:today&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1번 게시글의 오늘 조회수 +1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 하루가 지나면 날짜 키로 변경&lt;/p&gt;
&lt;pre id=&quot;code_1772639357795&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RENAME articles:1:today articles:2026-03-05&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오늘 카운터를 날짜 기준 기록으로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring 사용 예시&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조회수 증가 Service&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772639759534&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class ArticleViewService {

    private final StringRedisTemplate redisTemplate;

    public Long increaseView(Long articleId) {

        String key = &quot;articles:&quot; + articleId;

        return redisTemplate
                .opsForValue()
                .increment(key);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;redisTemplate&lt;/b&gt; : Spring이 제공하는 API (RedisTemplate)를 통해 Redis 명령을 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;opsForValue()&lt;/b&gt; : &lt;span&gt;Redis의 &lt;/span&gt;String 자료형을 다루는 API&lt;/li&gt;
&lt;li&gt;&lt;b&gt;increment()&lt;/b&gt; : 내부적으로 Redis 명령 'INCR key'를 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;블로그에 로그인 한 사람들의 조회수와 가장 많은 조회수를 기록한 글을 Redis로 확인하기&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;구현 기능&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래와 같은 조건의 '블로그에 로그인한 사람들의 조회수와 가장 많은 조회수를 기록한 글'을 Redis로 확인하는 기능을 만드려고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772639583836&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 블로그 URL의 PATH는 `/articles/{id}` 형식이다.
2. 로그인 한 사람들의 계정은 영문으로만 이뤄져 있다.
3. 이를 관리하기 위해 적당한 데이터 타입을 선정하고,
4. 사용자가 임의의 페이지에 접속할 때 실행될 명령을 작성해보자.
5. 만약 상황에 따라 다른 명령이 실행되어야 한다면, 주석으로 추가해보자.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Redis 명령어&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복을 허용하지 않는 자료형 Set을 활용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) &lt;span&gt;Set에 값 추가&lt;/span&gt; (중복 허용 안 됨)&lt;/p&gt;
&lt;pre id=&quot;code_1772640503332&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SADD articles:1 alex
SADD articles:1 brad
SADD articles:1 chad&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis 내부&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772640537477&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;articles:1 = {alex, brad, chad}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Set에 들어있는 원소 개수 반환&lt;/p&gt;
&lt;pre id=&quot;code_1772640509174&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SCARD articles:1&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772640557536&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 중복된 값 저장하는 경우 : SADD 반환값은 0&lt;/p&gt;
&lt;pre id=&quot;code_1772640729514&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SADD articles:1 alex brad chad&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 내부에 alex, brad, chad 가 저장되어 있다면, 반환값은 0이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 따라서 SADD의 결과에 따라 명령어 실행 여부 결정 가능&lt;/p&gt;
&lt;pre id=&quot;code_1772640883232&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# SADD의 결과에 따라 명령 실행
# 0이면 스킵
# 1이면 Sorted Set에 추가

ZADD articles:ranks 1 articles:1

ZINCRBY articles:ranks 1 articles:1&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ZADD&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772640937696&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Sorted Set &quot;articles:ranks&quot;에
member = articles:1
score = 1
로 추가&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ZINCRBY&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772640958831&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;articles:1의 score를 1 증가&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 높은 조회수의 글을 보고 싶다면&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772641257785&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZRANGE articles:ranks 0 0 REV&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring 사용 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772641085894&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ArticleLikeService {

    private final StringRedisTemplate redis;

    public ArticleLikeService(StringRedisTemplate redis) {
        this.redis = redis;
    }

    /**
     * userId가 articleId에 좋아요를 누르면:
     * 1) Set에 userId 추가 (중복이면 0)
     * 2) 새로 추가된 경우에만 Sorted Set 랭킹 점수 +1
     */
    public boolean like(Long articleId, String userId) {
        String likeSetKey = &quot;article:&quot; + articleId + &quot;:likes&quot;;     // Set
        String rankKey = &quot;articles:ranks&quot;;                         // ZSet
        String member = &quot;articles:&quot; + articleId;                   // ZSet member

        Long added = redis.opsForSet().add(likeSetKey, userId);    // Redis: SADD
        if (added == null) added = 0L;

        if (added == 1L) {
            redis.opsForZSet().incrementScore(rankKey, member, 1); // Redis: ZINCRBY
            return true; // 처음 좋아요 성공
        }
        return false;     // 이미 좋아요 눌렀음
    }

    public Long likeCount(Long articleId) {
        String likeSetKey = &quot;article:&quot; + articleId + &quot;:likes&quot;;
        return redis.opsForSet().size(likeSetKey);                 // Redis: SCARD
    }

    public Double rankScore(Long articleId) {
        String rankKey = &quot;articles:ranks&quot;;
        String member = &quot;articles:&quot; + articleId;
        return redis.opsForZSet().score(rankKey, member);          // Redis: ZSCORE
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-5-1-2242dc3ef51481478520c62135a0e3c9&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://teamsparta.notion.site/1-5-1-2242dc3ef51481478520c62135a0e3c9&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772631881749&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;챕터1-5 : 실습1 | Notion&quot; data-og-description=&quot;Redis 설치 및 기본 명령어 사용해보기&quot; data-og-host=&quot;teamsparta.notion.site&quot; data-og-source-url=&quot;https://teamsparta.notion.site/1-5-1-2242dc3ef51481478520c62135a0e3c9&quot; data-og-url=&quot;https://teamsparta.notion.site/1-5-1-2242dc3ef51481478520c62135a0e3c9&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/betaFQ/dJMb9aKCQey/QUKni6OE3ne62engVYatg1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dvqMqb/dJMb9fZs2CY/l9Wjs8mTxPL8ekEoM9WF7k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-5-1-2242dc3ef51481478520c62135a0e3c9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://teamsparta.notion.site/1-5-1-2242dc3ef51481478520c62135a0e3c9&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/betaFQ/dJMb9aKCQey/QUKni6OE3ne62engVYatg1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dvqMqb/dJMb9fZs2CY/l9Wjs8mTxPL8ekEoM9WF7k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;챕터1-5 : 실습1 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Redis 설치 및 기본 명령어 사용해보기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;teamsparta.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>심화/DB</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/534</guid>
      <comments>https://annovation.tistory.com/534#entry534comment</comments>
      <pubDate>Wed, 4 Mar 2026 23:54:16 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Spring에서 Redis 사용해보기 : RedisTemplate 써보기</title>
      <link>https://annovation.tistory.com/533</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;RedisTemplate&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;RedisTemplate 이란?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Spring Data Redis&lt;/b&gt;&lt;/span&gt; 에서 제공하는 핵심 클래스 (&lt;a href=&quot;https://docs.spring.io/spring-data-redis/reference/api/java/org/springframework/data/redis/core/RedisTemplate.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Key를 직접 설정하고 자료형도 직접 선택해 가면서 Redis를 활용하고 싶다면, RedisTemplate 을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;RedisTemplate &lt;/span&gt;을 정의하면서 Key와 Value로 사용될 Java 클래스를 정하고, 이후 사용할 세부 작업을 RedisOperations 를 이용해 가져오는 방식이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;StringRedisTemplate&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;StringRedisTemplate 이란?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RedisTemplate을 상속받은 클래스로, 복잡한 작업없이 Java의 문자열만 다루는 경우, StringRedisTemplate 이 기본으로 만들어진다.&lt;/li&gt;
&lt;li&gt;이는 Key와 Value를 전부 Java의 문자열이라고 가정, 문자열 데이터를 주고받기 위한 작업들을 준비하며, 기본 설정을 가지고 자동으로 만들어져 주입되는 Spring Bean 이다.&lt;/li&gt;
&lt;li&gt;Redis의 String 자료형만 사용하는것이 아닌, Redis와 Spring Boot 사이에 데이터를 주고받는 과정에서 직렬화 - 역직렬화 할때 Java의 String으로 취급되는 클래스이다.&lt;/li&gt;
&lt;li&gt;Redis List에 넣을 때 Java 문자열을 넣고, Redis Set에 넣을 때 Java 문자열을 넣는다는 의미이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199; color: #333333;&quot;&gt;RedisTemplateTests.java&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772548645862&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootTest
public class RedisTemplateTests {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    @Test
    public void stringOpsTest() {

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772547047283&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;챕터1-4 : Spring에서 Redis 사용해보기 | Notion&quot; data-og-description=&quot;Spring Boot 프로젝트 준비&quot; data-og-host=&quot;teamsparta.notion.site&quot; data-og-source-url=&quot;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&quot; data-og-url=&quot;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bH3ZKR/dJMb8XkcYXX/8kd85csfqUApyplG1Lk9J0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/XkgBF/dJMb8SpFyZ9/OfTFdk6KHmLbcZqdO37zf1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bH3ZKR/dJMb8XkcYXX/8kd85csfqUApyplG1Lk9J0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/XkgBF/dJMb8SpFyZ9/OfTFdk6KHmLbcZqdO37zf1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;챕터1-4 : Spring에서 Redis 사용해보기 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 프로젝트 준비&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;teamsparta.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>심화/DB</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/533</guid>
      <comments>https://annovation.tistory.com/533#entry533comment</comments>
      <pubDate>Tue, 3 Mar 2026 23:36:29 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Spring에서 Redis 사용해보기</title>
      <link>https://annovation.tistory.com/532</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;의존성 추가&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;새 프로젝트 생성 시&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Data Redis (Access+Driver) 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Spring Initializr 로 추가할 때&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKxrZx/dJMcabi1V0t/o839upTnigt5O1p70T1Rz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKxrZx/dJMcabi1V0t/o839upTnigt5O1p70T1Rz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKxrZx/dJMcabi1V0t/o839upTnigt5O1p70T1Rz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKxrZx%2FdJMcabi1V0t%2Fo839upTnigt5O1p70T1Rz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1700&quot; height=&quot;850&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;850&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) IntelliJ IDE 로 추가할 때&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-03-02 at 12.12.27 AM.png&quot; data-origin-width=&quot;2278&quot; data-origin-height=&quot;1876&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0euiL/dJMcagxPl01/EeX1aFJlUK1JUhx2kTPaz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0euiL/dJMcagxPl01/EeX1aFJlUK1JUhx2kTPaz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0euiL/dJMcagxPl01/EeX1aFJlUK1JUhx2kTPaz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0euiL%2FdJMcagxPl01%2FEeX1aFJlUK1JUhx2kTPaz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2278&quot; height=&quot;1876&quot; data-filename=&quot;Screenshot 2026-03-02 at 12.12.27 AM.png&quot; data-origin-width=&quot;2278&quot; data-origin-height=&quot;1876&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;기존 프로젝트에 추가 시&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;build.gradle에 아래 코드 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772378222041&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
  ...
  implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;application.yml 추가&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;src/main/resources 경로에 application.yml 추가&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772379313804&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  data:
    redis:
      host: &amp;lt;서버 주소&amp;gt;
      port: &amp;lt;포트 번호&amp;gt;
      username: &amp;lt;사용자 계정, 기본값 default&amp;gt;
      password: &amp;lt;사용자 비밀번호&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;host와 port를 작성하지 않으면 docker-compose 로 설정해둔 값 혹은 설정한 값이 없다면, 기본값인 localhost:6379에 연결을 시도한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;실습 : Redis에 데이터 저장하기&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;도메인 객체 만들기 : Item.java&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772546840396&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// package, import 생략

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RedisHash(&quot;item&quot;)
public class Item implements Serializable {
    @Id
    private Long id;
    private String name;
    private String description;
    private Integer price;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772542449731&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;챕터1-4 : Spring에서 Redis 사용해보기 | Notion&quot; data-og-description=&quot;Spring Boot 프로젝트 준비&quot; data-og-host=&quot;teamsparta.notion.site&quot; data-og-source-url=&quot;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&quot; data-og-url=&quot;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bH3ZKR/dJMb8XkcYXX/8kd85csfqUApyplG1Lk9J0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/XkgBF/dJMb8SpFyZ9/OfTFdk6KHmLbcZqdO37zf1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://teamsparta.notion.site/1-4-Spring-Redis-2242dc3ef5148183b059f5f71a15b28c&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bH3ZKR/dJMb8XkcYXX/8kd85csfqUApyplG1Lk9J0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/XkgBF/dJMb8SpFyZ9/OfTFdk6KHmLbcZqdO37zf1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;챕터1-4 : Spring에서 Redis 사용해보기 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 프로젝트 준비&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;teamsparta.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>심화/DB</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/532</guid>
      <comments>https://annovation.tistory.com/532#entry532comment</comments>
      <pubDate>Mon, 2 Mar 2026 23:59:42 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Redis 타입 살펴보기 (공부중..)</title>
      <link>https://annovation.tistory.com/530</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Redis 타입 별 명령어&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Redis는 Key - Value 데이터베이스 이다. 그래서 대부분의 명령이 Key를 바탕으로 동작하게 되며, Value로 사용하고자 하는 자료형에 따라 다른 명령어를 사용하게 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;또한 대부분의 명령이 존재하지 않는 Key를 이용해도 정상적으로 동작하며, 없는 Key에 데이터를 저장하면 Key를 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;단, 이미 만든 Key에 해당하는 데이터와 다른 자료형의 명령을 사용하는 경우 오류가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;String&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 기본적인 자료형이며, Redis가 Java의 Map&amp;lt;String, String&amp;gt; 처럼 동작한다고 생각할 수 있다.&lt;/li&gt;
&lt;li&gt;단순 문자열이지만, 실제 메모리에는 바이트 배열 (ex. [68 65 6C 6C 6F]) 로 저장된다.&lt;/li&gt;
&lt;li&gt;따라서 바이트로 저장되는 이미지, 음성, 영상, 파일 또는 이메일 본문 등도 보관이 가능하다.&lt;/li&gt;
&lt;li&gt;분산된 구조에서 비교적 큰 사이즈의 데이터를 주고받을 때 Key만 전달해서 데이터 위치를 전달하는 방식으로 활용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;GET, SET&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SET을 통해 저장할 수 있는 최대 크기는 512MB 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772198172158&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET user:email alex@example.com
GET user:email&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-27 at 10.21.40 PM.png&quot; data-origin-width=&quot;2207&quot; data-origin-height=&quot;926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cacuDR/dJMcaduhErQ/x5eUckIYs4thmvncHmU3Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cacuDR/dJMcaduhErQ/x5eUckIYs4thmvncHmU3Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cacuDR/dJMcaduhErQ/x5eUckIYs4thmvncHmU3Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcacuDR%2FdJMcaduhErQ%2Fx5eUckIYs4thmvncHmU3Sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2207&quot; height=&quot;926&quot; data-filename=&quot;Screenshot 2026-02-27 at 10.21.40 PM.png&quot; data-origin-width=&quot;2207&quot; data-origin-height=&quot;926&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SET &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;lt;key&amp;gt; &amp;lt;value&amp;gt; : key에 value 문자열 데이터를 저장, &quot; 으로 공백 구분&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;GET &amp;lt;key&amp;gt; : key에 저장된 문자열 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;INCR, DECR&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 저장된 데이터가 정수 데이터라면 데이터를 바로 증가, 감소가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772198765402&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET user:count 1
INCR user:count
DECR user:count&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-27 at 10.26.28 PM.png&quot; data-origin-width=&quot;2196&quot; data-origin-height=&quot;1096&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVwgsy/dJMcaibmRvS/CdT4RdKKC24ZgYXGxaJqWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVwgsy/dJMcaibmRvS/CdT4RdKKC24ZgYXGxaJqWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVwgsy/dJMcaibmRvS/CdT4RdKKC24ZgYXGxaJqWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVwgsy%2FdJMcaibmRvS%2FCdT4RdKKC24ZgYXGxaJqWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2196&quot; height=&quot;1096&quot; data-filename=&quot;Screenshot 2026-02-27 at 10.26.28 PM.png&quot; data-origin-width=&quot;2196&quot; data-origin-height=&quot;1096&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;INCR key : key에 저장된 데이터를 1 증가&lt;/li&gt;
&lt;li&gt;DECR key : key에 저장된 데이터를 1 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;MSET, MGET&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 Key - Value를 한번에 다루고 싶다면 MSET, MGET 을 활용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772199181682&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MSET user:name alex user:email alex@example.com
MGET user:name user:email&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MSET key value [key value &amp;hellip;] : key value의 형태로 주어진 인자들을 각 key에 value를 저장&lt;/li&gt;
&lt;li&gt;MGET key [key] : 주어진 모든 key에 해당하는 데이터를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;List&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Linked List 구현체&lt;/li&gt;
&lt;li&gt;Linked List이기 때문에, 중간의 데이터를 활용하는 용도 보다는 양끝의 데이터, 즉 스택 또는 큐 처럼 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;예를 들어, A, B, C 데이터가 Linked List 로 저장되어 있을 때, 양 끝에 데이터를 삽입하고 조회하는 방식으로 활용 가능하다.&lt;/li&gt;
&lt;li&gt;List의 경우 소셜 네트워크에서 많이 사용하는 자료형이다.&lt;br /&gt;ex. List는 데이터가 순서가 있으므로 최신글 -&amp;gt; 오래된글 의 시간 순서로 정렬된 데이터를 저장할 때 유용&lt;/li&gt;
&lt;li&gt;대표적으로 X (구 트위터)가 List를 바탕으로 Timeline을 구성했다고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772605457551&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;X &amp;rarr; A &amp;rarr; B &amp;rarr; C&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis는 Key - Value 형태로 저장되므로 Map&amp;lt;String, List&amp;lt;String&amp;gt;&amp;gt; 의 형태로 사용한다고 생각하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;LPUSH, RPUSH, LPOP, RPOP&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PUSH 또는 POP을 L 또는 R과 조합하여, 왼쪽 또는 오른쪽에 데이터를 추가 / 제거가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772605835371&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LPUSH user:list alex  # [alex]
LPUSH user:list brad  # [brad, alex]
RPUSH user:list chad  # [brad, alex, chad]
RPUSH user:list dave  # [brad, alex, chad, dave]

LPOP user:list        # brad
RPOP user:list        # chad&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LPUSH key value : key에 저장된 리스트의 앞쪽에 value를 저장&lt;/li&gt;
&lt;li&gt;RPUSH key value : key에 저장된 리스트의 뒤쪽에 value를 저장&lt;/li&gt;
&lt;li&gt;LPOP key : key에 저장된 리스트의 앞쪽에서 값을 반환 및 제거&lt;/li&gt;
&lt;li&gt;RPOP key : key에 저장된 리스트의 뒤쪽에서 값을 반환 및 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;LLEN, LRANGE&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리스트를 사용하면서 흔히 사용하는 길이 구하기, 범위 내 원소 반환하기 등의 기능도 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772685437211&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LLEN user:list
LRANGE user:list 0 3
LRANGE user:list 0 -1&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLEN key : key에 저장된 리스트의 길이를 반환
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;없는 Key를 대상으로 하면 0이 되지만, 다른 자료형을 저장한 Key를 대상으로 하면 &lt;span data-token-index=&quot;1&quot;&gt;오류가 발생&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LRANGE key start end : key의 start부터 end까지 원소들을 반환
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;시작을 0으로 생각하고,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;end가 실제 길이를 벗어나도 오류가 발생하진 않는다.&lt;/li&gt;
&lt;li&gt;start &amp;gt; end일 경우 빈 결과가 반환된다.&lt;/li&gt;
&lt;li&gt;음수의 경우 리스트의 뒤에서부터 데이터를 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Set&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Set은 &lt;/span&gt;순서가 없고 중복되지 않는 문자열들의 집합이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;SADD, SREM, SMEMBERS, SISMEMBER, SCARD&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772697915001&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SADD user:java alex  # [alex]
SADD user:java brad  # [alex, brad]
SADD user:java chad  # [alex, brad, chad]
SREM user:java alex  # [brad, chad]

SMEMBERS user:java        # [alex, brad, chad]
SISMEMBER user:java brad  # true
SISMEMBER user:java dave  # false&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SADD key value : key에 저장된 집합에 value를 추가&lt;/li&gt;
&lt;li&gt;SREM key value : key에 저장된 집합의 value를 제거&lt;/li&gt;
&lt;li&gt;SMEMBERS key : key에 저장된 집합의 모든 원소를 반환&lt;/li&gt;
&lt;li&gt;SISMEMBER key value : key에 저장된 집합에 value가 존재하는지 반환&lt;/li&gt;
&lt;li&gt;SCARD key : key에 저장된 집합의 크기를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199; color: #333333;&quot;&gt;SINTER, SUNION, SINTERCARD&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기에 복수의 집합이 있다면, 교집합, 합집합 등의 기능을 제공한다.&lt;/li&gt;
&lt;li&gt;결과 집합 자체를 반환하기도, 결과 집합의 크기를 반환하기도 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772697950926&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 다른 Set에 추가한 뒤,
SADD user:python alex
SADD user:python dave

SINTER user:java user:python        # [alex]
SUNION user:java user:python        # [alex, brad, chad, dave]
SINTERCARD 2 user:java user:python  # 1&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SINTER key1 key2 : key1과 key2에 저장된 집합들의 교집합의 원소들을 반환&lt;/li&gt;
&lt;li&gt;SUNION key1 key2 : key1과 key2에 저장된 집합들의 합집합의 원소들을 반환&lt;/li&gt;
&lt;li&gt;SINTERCARD number key1 [key2 ...] : number개의 key에 저장된 집합들의 교집합의 크기를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199; color: #333333;&quot;&gt;SISMEMBER&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772698115936&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SADD user:java alex   # [alex]
SADD user:java brad   # [alex, brad]
SADD user:java chad   # [alex, brad, chad]

SISMEMBER user:java brad   # true
SISMEMBER user:java dave   # false&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복을 허용하지 않으며, 어떤 데이터의 존재 여부를 확인하는 SISMEMBER 같은 경우 O(1)의 시간복잡도를 가지고 있다.&lt;/li&gt;
&lt;li&gt;그래서 중복 없는 방문 수, 인증 토큰 블랙리스트 등을 구현할때 활용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️단, 매우 높은 방문수를 기록하는 서비스의 경우 실제 데이터가 필요하지 않다면, 근사값을 돌려주는 확률형 자료형을 사용하는게 권장됩니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Hash&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Field - Value 쌍으로 이뤄진 자료형&lt;/li&gt;
&lt;li&gt;Hash 데이터를 가져오기 위해 Key를 사용하고, 이후 다시 Key에 저장된 Hash 데이터에 Field - Value 쌍을 넣어주는 형식으로 동작&lt;/li&gt;
&lt;li&gt;즉, Redis 전체가 Map이라면 Hash는 Map&amp;lt;String, Map&amp;lt;String, String&amp;gt;&amp;gt;의 형식이라고 생각할 수 있다.&lt;/li&gt;
&lt;li&gt;Hash는 본래 하나의 키에 복잡한 데이터 (객체의 데이터 라던지)를 하나의 키에 저장하는 용도로 주로 활용되고, 공식 문서에서도 여러 Key에 걸쳐 객체의 데이터를 표현하기 보단 Hash를 자주 활용할 것을 권장하고 있다. (&lt;a href=&quot;https://redis.io/docs/latest/develop/data-types/hashes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;장바구니 같은 기능은 사용자별로, 어떤 물품이 몇개나 담겨있는지와 같은 정보가 포함되어야 한다. 사용자 마다 Hash 데이터를 생성하고, &lt;span data-token-index=&quot;1&quot;&gt;물품 - 갯수&lt;/span&gt; 형식으로 데이터를 저장하면 사용자별 장바구니를 쉽게 저장할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;While hashes are handy to represent objects ...&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199; color: #333333;&quot;&gt;HSET, HGET, HMGET, HGETALL, HKEYS, HLEN&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772698510897&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HSET user:alex name alex age 25
HSET user:alex major CSE

HGET user:alex name
HGET user:alex age

HMGET user:alex age major
HGETALL user:alex

HKEYS user:alex
HLEN user:alex&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HSET key field value [field value] : key의 Hash에 field에 value를 넣는다. 한번에 여러 field - value 쌍을 넣어줄 수 있다.&lt;/li&gt;
&lt;li&gt;HGET key field:&amp;nbsp; key에 저장된 Hash의 field에 저장된 value를 반환. 없는 field의 경우 null&lt;/li&gt;
&lt;li&gt;HMGET key field [field] : key에 저장된 Hash에서 복수의 field에 저장된 value를 반환&lt;/li&gt;
&lt;li&gt;HGETALL key : key에 저장된 Hash에 저장된 field - value를 전부 반환&lt;/li&gt;
&lt;li&gt;HKEYS key : key에 저장된 Hash에 저장된 field를 전부 반환&lt;/li&gt;
&lt;li&gt;HLEN key : key에 저장된 Hash에 저장된 field의 갯수를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Sorted Set&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름처럼 정렬된 집합이다.&lt;/li&gt;
&lt;li&gt;Set과 동일하게, 유일한 값들만 유지하지만 여기에 더해 각 값들에 score라고하는 실수를 함께 보관한다.&lt;/li&gt;
&lt;li&gt;그리고 데이터를 가져올 때, &lt;span data-token-index=&quot;1&quot;&gt;score를 바탕으로 정렬&lt;/span&gt;하여 값들을 가져올 수 있다.&lt;/li&gt;
&lt;li&gt;대표적으로 리더보드나 Rate Limiter, 즉 순위와 관련된 기능을 만드는데 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✅ Rate Limiter란, API 등을 제공할 때 짧은 시간에 지나치게 많은 요청을 막기 위한 기능을 의미합니다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199; color: #333333;&quot;&gt;ZADD, ZINCRBY, ZRANK, ZRANGE, ZREVRANK, ZREVRANGE&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772698991790&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZADD user:ranks 10 alex
ZADD user:ranks 9 brad 11 chad
ZADD user:ranks 8 dave

ZINCRBY user:ranks 2 alex

ZRANK user:ranks alex
ZRANGE user:ranks 0 3

ZREVRANK user:ranks alex
ZREVRANGE user:ranks 0 3&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ZADD key score member [score member ...] : key의 Sorted Set에 score를 점수로 가진 member를 추가, 이미 있는 member의 경우 새로운 score를 설정&lt;/li&gt;
&lt;li&gt;ZRANK key member : key의 Sorted Set의 member의 순위를 오름차순 기준으로 0에서 부터 세서 반환&lt;/li&gt;
&lt;li&gt;ZRANGE key start stop : key의 Sorted Set의 member들을 start 부터 stop 순위까지 오름차순 기준으로 반환&lt;/li&gt;
&lt;li&gt;ZREVRANK key member : key의 Sorted Set의 member의 순위를 내림차순 기준으로 0에서 부터 세서 반환&lt;/li&gt;
&lt;li&gt;ZREVRANGE key start stop : key의 Sorted Set의 member들을 start 부터 stop 순위까지 내림차순 기준으로 반환&lt;/li&gt;
&lt;li&gt;ZINCRBY key increment member : key의 Sorted Set의 member의 score를 increment 만큼 증가 (음수를 전달하면 감소)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;그 외 공용 명령&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그 외 자료형과 상관없이 사용할 수 있는 명령들이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;DEL, EXPIRE, EXPIRETIME&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대표적으로 많이 사용하는 건, Key를 제거하기 위한 DEL, 만료시각 설정을 위한 EXPIRE 등이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772699122427&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET somekey &quot;to be deleted&quot;
DEL somekey

SET expirekey &quot;to be expired&quot;
EXPIRE expirekey 5
EXPIRETIME expirekey&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DEL key : key(와 저장된 데이터)를 제거&lt;/li&gt;
&lt;li&gt;EXPIRE key seconds : key의 TTL(유효시각)을 seconds로 설정, seconds초가 지나면 key 제거&lt;/li&gt;
&lt;li&gt;EXPIRETIME key : key가 만료되는 시각을 Unix Timestamp로 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199; color: #333333;&quot; data-token-index=&quot;0&quot;&gt;KEYS *&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 Key를 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772699171699&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;KEYS *&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;glob 패턴 형식을 전달하여, 패턴에 일치하는 키를 반환하는 메서드이다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #eb5757; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;*&lt;/span&gt; &lt;/span&gt;은 임의갯수의 글자를 대체하는 와일드카드입니다. 지금은 * 만 있으므로, 모든 Key와 일치하는 패턴이기 때문에 모든 Key가 반환된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✅ glob 패턴 이란?&lt;br /&gt;Linux 파일시스템 등에서 흔히 사용하며, 와일드카드(wildcard)를 이용해 문자열을 매칭하는 간단한 패턴 규칙이다.&lt;br /&gt;&lt;br /&gt;ex. &lt;br /&gt;* 모든 문자열&lt;br /&gt;? 한 글자 &lt;br /&gt;[abc] 특정 문자 &lt;br /&gt;[a-z] 문자 범위&lt;br /&gt;&lt;br /&gt;ex.&lt;br /&gt;h?llo - hello, hallo, hxllo&lt;br /&gt;h*llo - hllo, heeeello &lt;br /&gt;h[ae]llo - hello, hallo (not hillo) &lt;br /&gt;h[^e]llo - hallo, hbllo, ... (not hello) &lt;br /&gt;h[a-b]llo - hallo, hbllo&lt;br /&gt;&lt;br /&gt;✅ 와일드카드(wildcard) 란?&lt;br /&gt;임의의 문자나 문자열을 대신해서 매칭되는 특수 문자&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199; color: #333333;&quot; data-token-index=&quot;0&quot;&gt;FLUSHDB&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 Key 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772699515038&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FLUSHDB&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-3-Redis-2242dc3ef5148154a4f6c97065cf1b51&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://teamsparta.notion.site/1-3-Redis-2242dc3ef5148154a4f6c97065cf1b51&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772195275628&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;챕터1-3 : Redis 타입 살펴보기 | Notion&quot; data-og-description=&quot;Redis를 설치하고 성공적으로 연결했다면, 이제 데이터를 저장해 봅시다.&quot; data-og-host=&quot;teamsparta.notion.site&quot; data-og-source-url=&quot;https://teamsparta.notion.site/1-3-Redis-2242dc3ef5148154a4f6c97065cf1b51&quot; data-og-url=&quot;https://teamsparta.notion.site/1-3-Redis-2242dc3ef5148154a4f6c97065cf1b51&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RLzQU/dJMb84XV21Q/mQl1EEvMGvlz5fAVWHwXB0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/AEnP5/dJMb85vMdF9/vTEzFJXu5bCMmLZDjkaKGK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-3-Redis-2242dc3ef5148154a4f6c97065cf1b51&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://teamsparta.notion.site/1-3-Redis-2242dc3ef5148154a4f6c97065cf1b51&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RLzQU/dJMb84XV21Q/mQl1EEvMGvlz5fAVWHwXB0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/AEnP5/dJMb85vMdF9/vTEzFJXu5bCMmLZDjkaKGK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;챕터1-3 : Redis 타입 살펴보기 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Redis를 설치하고 성공적으로 연결했다면, 이제 데이터를 저장해 봅시다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;teamsparta.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>심화/DB</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/530</guid>
      <comments>https://annovation.tistory.com/530#entry530comment</comments>
      <pubDate>Fri, 27 Feb 2026 22:42:54 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Redis 설치하기 (MacOS)</title>
      <link>https://annovation.tistory.com/529</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;로컬 환경에 설치하기&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Homebrew를 이용해 Redis 설치&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Homebrew를 설치한 적 없다면, Homebrew 먼저 설치&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Homebrew 설치 페이지 : &lt;a href=&quot;https://brew.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://brew.sh/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1772109644469&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Homebrew&quot; data-og-description=&quot;The Missing Package Manager for macOS (or Linux).&quot; data-og-host=&quot;brew.sh&quot; data-og-source-url=&quot;https://brew.sh/&quot; data-og-url=&quot;https://brew.sh/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/5FSQh/dJMb85WQuu9/XXMqa0KAEX4yBtwieRc9VK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ceCysS/dJMb88654KY/KRNKUuinI3iuWo3ZAFZ28k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://brew.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://brew.sh/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/5FSQh/dJMb85WQuu9/XXMqa0KAEX4yBtwieRc9VK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ceCysS/dJMb88654KY/KRNKUuinI3iuWo3ZAFZ28k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Homebrew&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Missing Package Manager for macOS (or Linux).&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;brew.sh&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Homebrew 설치 명령어&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772109661315&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Homebrew를 이용해 Redis를 설치&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래 명령어를 터미널에 입력&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772109690511&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install redis&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 백그라운드에서 Redis 실행&lt;/p&gt;
&lt;pre id=&quot;code_1772109727920&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services start redis&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Docker에 설치하기 (권장)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Docker 사용을 권장하는 이유&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너화된 환경을 제공함으로써 OS 차이, 라이브러리 버전 차이 등으로 인한 오류를 예방할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;docker-compose.yml 이 필요한 이유&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 개의 컨테이너로 구성된 애플리케이션을 YAML 파일로 정의하고 한 번의 명령어로 실행할 수 있게 해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Docker 이용해 Redis 설치하기&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker가 설치되어 있다면 명령어 하나로 Redis를 실행해볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;1114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvcTKn/dJMcah4zUmn/fmbHXxX2Gi2ZfK5ym6KIr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvcTKn/dJMcah4zUmn/fmbHXxX2Gi2ZfK5ym6KIr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvcTKn/dJMcah4zUmn/fmbHXxX2Gi2ZfK5ym6KIr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvcTKn%2FdJMcah4zUmn%2FfmbHXxX2Gi2ZfK5ym6KIr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;814&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;1114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;redis&lt;/b&gt;&lt;/span&gt; : Redis의 기본(Core) 서버 이미지로, 가장 표준적인 Redis 기능만 포함된 최소 구성 버전입니다. 단순 캐시&amp;middot;세션&amp;middot;Pub/Sub 등 기본 기능 실습에 적합&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;redis/redis-stack-server&lt;/b&gt;&lt;/span&gt; : 기본 Redis에 Redis Stack 모듈(ex. JSON, Search, Bloom, TimeSeries 등)이 포함된 서버 이미지이다. JSON 문서 저장, 전문 검색, 확률형 자료구조 등을 사용할 때 선택한다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;redis/redis-stack&lt;/b&gt;&lt;/span&gt; : &lt;span&gt;redis/redis-stack-server&lt;/span&gt;에 Redis Insight(웹 기반 관리 GUI)가 추가된 통합 이미지이다. 서버 기능과 함께 시각적 관리 도구까지 함께 사용하려는 경우 적합하다.&lt;/li&gt;
&lt;li&gt;Docker Hub에서 Redis를 찾아보면 세가지 이미지가 상단에 나오게 되는데, 이중 한가지를 선택해서 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️Docker Hub 란?&lt;br /&gt;&lt;br /&gt;컨테이너 이미지를 저장하고(push) 검색하고 다운로드(pull) 할 수 있는 공식 이미지 레지스트리(registry) 서비스이다.&lt;br /&gt;&lt;br /&gt;컨테이너(Container) : 이미지를 실행한 인스턴스로, 격리된 환경에서 애플리케이션이 동작하는 실행 단위&lt;br /&gt;이미지(Image) : 컨테이너를 생성하기 위한 실행 파일과 라이브러리, 설정을 포함한 불변 템플릿&lt;br /&gt;레지스트리(Registry) : Docker 이미지를 저장&amp;middot;관리하고 네트워크를 통해 배포하는 저장소 시스템&lt;br /&gt;도커 허브(Docker Hub) : 컨테이너 이미지를 저장하고 공유하는 Docker 공식 public registry&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Run Redis Stack on Docker (&lt;a href=&quot;https://redis.io/docs/latest/operate/oss_and_stack/install/archive/install-stack/docker/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis 공식 문서에 Docker 를 통해 Redis Stack 이미지를 다운 받는 방법이 안내되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 프로젝트 루트에 docker-compose.yml 을 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1772114473642&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  redis-stack:
    image: redis/redis-stack
    container_name: redis-stack-compose
    restart: always
    environment:
      REDIS_ARGS: &quot;--requirepass systempass&quot;
    ports:
      - 6379:6379
      - 8001:8001&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 예시의 systempass가 비밀번호의 역할을 한다. 이 부분을 수정하면 접속할 때 비밀번호가 바뀌니 주의!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. docker-compose.yml 이 있는 폴더에서 터미널은 열어 아래 명령어를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1772115157908&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자동으로 최신 버전의 Redis Stack 이미지를 사용하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Redis 컨테이너가 정상적으로 실행되었는지 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1772115234291&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose ps&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Up 3 minutes : 컨테이너 프로세스가 살아 있음을 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QhlS3/dJMcaih6kIN/1ItJJliDf29TuAPsoXKCZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QhlS3/dJMcaih6kIN/1ItJJliDf29TuAPsoXKCZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QhlS3/dJMcaih6kIN/1ItJJliDf29TuAPsoXKCZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQhlS3%2FdJMcaih6kIN%2F1ItJJliDf29TuAPsoXKCZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;181&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;181&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;Intellij IDEA UE에서 연결해보기&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;color: #333333;&quot;&gt;Database 탭&lt;/span&gt;을 찾아 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tgsDq/dJMcaca745Q/hzSuKGiwZqVmqTKkkGKrOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tgsDq/dJMcaca745Q/hzSuKGiwZqVmqTKkkGKrOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tgsDq/dJMcaca745Q/hzSuKGiwZqVmqTKkkGKrOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtgsDq%2FdJMcaca745Q%2FhzSuKGiwZqVmqTKkkGKrOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;603&quot; height=&quot;327&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2&lt;span style=&quot;color: #333333;&quot;&gt;. + 를 누른 다음, Data Source &amp;gt; Redis를 찾아거나, 검색을 통해 Data Source를 선택한 다음, Redis를 입력하면 쉽게 찾을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3HUI3/dJMcacCcCeP/k0hdyMRKnCTkuiZlAkOHnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3HUI3/dJMcacCcCeP/k0hdyMRKnCTkuiZlAkOHnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3HUI3/dJMcacCcCeP/k0hdyMRKnCTkuiZlAkOHnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3HUI3%2FdJMcacCcCeP%2Fk0hdyMRKnCTkuiZlAkOHnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;908&quot; height=&quot;505&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;span style=&quot;color: #333333;&quot;&gt;자신의 Redis 인스턴스의 Host와 Port(위의 과정을 따랐다면 localhost, 6379)를, 이후 User는 default, Password는 로컬에 설치한 경우 생략, Docker로 설치한 경우 자신이 password에 넣은 값을 넣어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q0l3d/dJMcadA153R/vGZPcfmZJIGfloNIHDx7XK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q0l3d/dJMcadA153R/vGZPcfmZJIGfloNIHDx7XK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q0l3d/dJMcadA153R/vGZPcfmZJIGfloNIHDx7XK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq0l3d%2FdJMcadA153R%2FvGZPcfmZJIGfloNIHDx7XK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;471&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;471&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이후&lt;span style=&quot;color: #333333;&quot;&gt; Test Connection을 클릭해서 잘 연결되면, OK를 누른다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm1Rvi/dJMcafZX83N/yl3G5AbVkKwIUBzIwEOuuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm1Rvi/dJMcafZX83N/yl3G5AbVkKwIUBzIwEOuuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm1Rvi/dJMcafZX83N/yl3G5AbVkKwIUBzIwEOuuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm1Rvi%2FdJMcafZX83N%2Fyl3G5AbVkKwIUBzIwEOuuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;326&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. console 탭에서 명령어들을 사용해서 데이터를 가져올 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명령어 예시 (Command + Enter 로 명령어 한 줄 씩 실행 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772116007128&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET start &quot;hello world&quot;
GET start&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;console 탭 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;227&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J8i1w/dJMcagYS2jE/q7pEhD74zFqpNh6NUM2ej0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J8i1w/dJMcagYS2jE/q7pEhD74zFqpNh6NUM2ej0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J8i1w/dJMcagYS2jE/q7pEhD74zFqpNh6NUM2ej0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ8i1w%2FdJMcagYS2jE%2Fq7pEhD74zFqpNh6NUM2ej0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;227&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;227&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명령어를 통해 가져온 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nfEAS/dJMcab4lEWU/GKg8wKn3u07XHGyHlR2XeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nfEAS/dJMcab4lEWU/GKg8wKn3u07XHGyHlR2XeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nfEAS/dJMcab4lEWU/GKg8wKn3u07XHGyHlR2XeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnfEAS%2FdJMcab4lEWU%2FGKg8wKn3u07XHGyHlR2XeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;529&quot; height=&quot;258&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️console 탭이 사라지면 Database 탭에서 다시 열 수 있다&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T6sRw/dJMcafMtOro/vh1JP0BlV204XFbMIKINs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T6sRw/dJMcafMtOro/vh1JP0BlV204XFbMIKINs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T6sRw/dJMcafMtOro/vh1JP0BlV204XFbMIKINs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT6sRw%2FdJMcafMtOro%2Fvh1JP0BlV204XFbMIKINs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;475&quot; height=&quot;300&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;Redis Insight를 이용해 연결해보기&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Redis Insight 란?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis 전용 IDE (ex. MySQL Workbench)&lt;/li&gt;
&lt;li&gt;간단한 기능 테스트만 할 때는 엄청 필요한 도구는 아닐 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;App Store 에서 설치하기&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VMH7d/dJMcaaEoJKD/fP0J3uTkKavMQMf7shF8nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VMH7d/dJMcaaEoJKD/fP0J3uTkKavMQMf7shF8nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VMH7d/dJMcaaEoJKD/fP0J3uTkKavMQMf7shF8nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVMH7d%2FdJMcaaEoJKD%2FfP0J3uTkKavMQMf7shF8nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1068&quot; height=&quot;649&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;649&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MacOS의 경우 App Store 에 등록된 Redis Insight 를 다운로드 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Docker 로 설치하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 위에 소개된 docker-compose.yml 를 사용한 경우, Redis Insight 도 같이 설치된다.&lt;/p&gt;
&lt;pre id=&quot;code_1772116572048&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  redis-stack:
    image: redis/redis-stack
    container_name: redis-stack-compose
    restart: always
    environment:
      REDIS_ARGS: &quot;--requirepass systempass&quot;
    ports:
      - 6379:6379
      - 8001:8001&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;redis/redis-stack : 이 이미지는 Redis 서버 + Redis Stack 모듈 + Redis Insight까지 포함된 통합 이미지이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 따라서 바로 localhost:8001 로 들어가면, 웹 애플리케이션으로 확인 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-26 at 11.41.27 PM.png&quot; data-origin-width=&quot;2195&quot; data-origin-height=&quot;1231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TX6pv/dJMcagkhtHD/fRdbb3TUwUbpkGvFSixEqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TX6pv/dJMcagkhtHD/fRdbb3TUwUbpkGvFSixEqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TX6pv/dJMcagkhtHD/fRdbb3TUwUbpkGvFSixEqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTX6pv%2FdJMcagkhtHD%2FfRdbb3TUwUbpkGvFSixEqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2195&quot; height=&quot;1231&quot; data-filename=&quot;Screenshot 2026-02-26 at 11.41.27 PM.png&quot; data-origin-width=&quot;2195&quot; data-origin-height=&quot;1231&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Username 과 Password를 입력 후 Test Connection 을 진행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-26 at 11.45.42 PM.png&quot; data-origin-width=&quot;2189&quot; data-origin-height=&quot;1231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csaKh6/dJMcagYS2x3/3CuDzexGgSL6ZsNiqqbZh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csaKh6/dJMcagYS2x3/3CuDzexGgSL6ZsNiqqbZh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csaKh6/dJMcagYS2x3/3CuDzexGgSL6ZsNiqqbZh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsaKh6%2FdJMcagYS2x3%2F3CuDzexGgSL6ZsNiqqbZh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2189&quot; height=&quot;1231&quot; data-filename=&quot;Screenshot 2026-02-26 at 11.45.42 PM.png&quot; data-origin-width=&quot;2189&quot; data-origin-height=&quot;1231&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Connection이 성공했다면, Apply Changes 클릭하면 왼쪽 메뉴 바에 새로운 기능들이 활성화 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-2-Redis-2242dc3ef51481d0b7a7d2f163d2df05&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://teamsparta.notion.site/1-2-Redis-2242dc3ef51481d0b7a7d2f163d2df05&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772109077739&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;챕터1-2 : Redis 설치하기 | Notion&quot; data-og-description=&quot;코드스니펫&quot; data-og-host=&quot;teamsparta.notion.site&quot; data-og-source-url=&quot;https://teamsparta.notion.site/1-2-Redis-2242dc3ef51481d0b7a7d2f163d2df05&quot; data-og-url=&quot;https://teamsparta.notion.site/1-2-Redis-2242dc3ef51481d0b7a7d2f163d2df05&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cFfok7/dJMb8QejpNE/KOMkCBJqYgbAetJ6Fgftb0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/zVEDV/dJMb9jgupj5/eF6b1Mvn2F1wAh4OQyMe8k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-2-Redis-2242dc3ef51481d0b7a7d2f163d2df05&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://teamsparta.notion.site/1-2-Redis-2242dc3ef51481d0b7a7d2f163d2df05&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cFfok7/dJMb8QejpNE/KOMkCBJqYgbAetJ6Fgftb0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/zVEDV/dJMb9jgupj5/eF6b1Mvn2F1wAh4OQyMe8k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;챕터1-2 : Redis 설치하기 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드스니펫&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;teamsparta.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>심화/DB</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/529</guid>
      <comments>https://annovation.tistory.com/529#entry529comment</comments>
      <pubDate>Thu, 26 Feb 2026 22:41:43 +0900</pubDate>
    </item>
    <item>
      <title>[DB] 인메모리 저장소와 Redis 기초</title>
      <link>https://annovation.tistory.com/528</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;인메모리 저장소&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;In-Memory = 메모리 안에서 동작한다는 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;인메모리 저장소의 필요성&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot 기반의 애플리케이션에서 일반적으로 사용하는 데이터 저장소는 관계형 데이터베이스(RDBMS)이다.&lt;/li&gt;
&lt;li&gt;관계형 데이터베이스의 주된 목적은 &lt;span&gt;&lt;b&gt;영속성(Persistence)&lt;/b&gt;&lt;/span&gt; 보장이다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;영속성 데이터란, &lt;/span&gt;&lt;b&gt;애플리케이션이 종료되더라도 유지되어야 하는 데이터&lt;/b&gt;&lt;span&gt;를 의미한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;예를 들어, 회원 정보, 주문 내역, 결제 기록, 게시글과 같은 데이터들은 파일시스템에 데이터를 저장함으로서 서비스가 종료되어도 유지되어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️파일시스템(File System)이란?&lt;br /&gt;&lt;br /&gt;파일시스템은 운영체제가 디스크(SSD, HDD)에 데이터를 저장하고 관리하는 구조이다.&lt;br /&gt;데이터를 파일 단위로 저장한다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 서비스를 만들다보면 영구 보관이 필요없는 일시적인 데이터를 저장해야 하는 상황이 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;예를 들어, 로그인 정보, 장바구니, 조회수 카운팅, 실시간 랭킹과 같은 기능은 사용자의 행동에 따라 비번하게 데이터의 수정이 발생한다.&lt;/li&gt;
&lt;li&gt;이런 상황에서 파일시스템에 데이터를 저장하는 것은 특성상 속도가 느려진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️파일시스템(File System) 기반 저장이 상대적으로 느린 이유?&lt;br /&gt;&lt;br /&gt;디스크 기반 저장이기 때문이다. 디스크에 데이터를 쓰는 행위는 물리적인 I/O 작업이고 메모리 접근에 비해 속도가 매우 느리다.&lt;br /&gt;이에 반해 데이터를 디스크가 아닌 RAM에 저장하는 인메모리 저장 방식은 디스크에 저장하는 파일시스템 방식에 비해 속도가 매우 빠르다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;대표적인 인메모리 DB : Redis&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Redis 란?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis는 REmote DIctionary Server를 줄인말로, Java의 Map과 같은 방식으로 데이터를 저장하는 데이터베이스이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772034774894&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;user:1&quot;
    ├── name &amp;rarr; sati
    └── age  &amp;rarr; 30&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Redis 특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 관계형 데이터베이스와 다른 가장 큰 특징은 In-Memory 데이터베이스이다.&lt;/li&gt;
&lt;li&gt;메모리, 즉 RAM에 데이터를 저장하기 때문에 복잡한 입출력 과정 필요 없다.&lt;/li&gt;
&lt;li&gt;RDBMS(관계형 데이터베이스)에 비해 더 빠르게 동작하는 대신, 언제든 사라질 수 있는 데이터를 다룬다는 차이를 가진다.&lt;/li&gt;
&lt;li&gt;특정 게시글의 조회수와 같이 빠르게 업데이트되는 데이터, 사용자 세션, 장바구니와 같은 시간이 지나면 삭제되는 데이터 등을 저장하기 위해 가장 많이 사용된다.&lt;/li&gt;
&lt;li&gt;대표적인 NoSQL 데이터베이스로, 데이터를 단순히 Key, Value를 저장하는 것이 아닌 자료 구조 (data structure)를 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;NoSQL&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;NoSQL 이란?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NoSQL은 데이터베이스를 만드는 접근법의 일종으로, 'Not Only SQL'을 의미한다.&lt;/li&gt;
&lt;li&gt;RDBMS(관계형 데이터베이스)는 일반적으로 테이블 형식으로 데이터를 저장하고, 그 데이터를 회수하기 위해 SQL을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772035765433&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM users;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반면 Redis는 단순 문자열(String)부터, 리스트, 집합, Hash 등 다양한 형태의 데이터를 저장하며, 이 데이터를 회수하기 위해 SQL을 사용하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772035804913&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET greeting &quot;Hello, Redis!&quot;
GET greeting&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 관계형 데이터베이스가 약점을 가진 확장성, 유연성, 성능에 대한 문제를 해결하기 위해 사용되는 경우가 많으며, 컴퓨터 기술의 발전과 웹의 활성화로 인해 비정형 데이터를 더 높은 성능으로 사용하는데 초점이 맞춰진 경우가 많다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;NoSQL의 데이터 관리 방식&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/exGDAp/dJMcahQ1AX5/dAIwbnWMEvKUuQ8NGxNzY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/exGDAp/dJMcahQ1AX5/dAIwbnWMEvKUuQ8NGxNzY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/exGDAp/dJMcahQ1AX5/dAIwbnWMEvKUuQ8NGxNzY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FexGDAp%2FdJMcahQ1AX5%2FdAIwbnWMEvKUuQ8NGxNzY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;716&quot; height=&quot;179&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;미리 정의된 구조(schema : ex. 데이터 타입, NULL 허용 여부, 기본값, 제약 조건, 인덱스, 외래키 등)와 SQL을 사용하는 RDBMS와 달리, NoSQL은 데이터를 관리하는 방법이 다양하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Key-Value (ex. Redis)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 단순한 형태의 데이터베이스로, Key에 Value를 저장하는 형태이다.&lt;/li&gt;
&lt;li&gt;JSON, Python의 Dictionary, Java의 Map의 형태로 데이터를 관리한다고 생각할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772037073012&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;user:1&quot; &amp;rarr; &quot;{ name: 'sati', age: 30 }&quot;
&quot;order:1&quot; &amp;rarr; &quot;{ userId: 1, items: ['apple', 'banana'] }&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Document (ex. MongoDB)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체를 표현하는 Document라는 단위로 데이터를 저장하는 형태이다.&lt;/li&gt;
&lt;li&gt;Key - Value에서 발전했다고 볼 수 있으며, JSON, XML 등 복잡한 데이터를 저장하고 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772037087328&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;userId&quot;: 1,
  &quot;name&quot;: &quot;sati&quot;,
  &quot;orders&quot;: [
    {
      &quot;orderId&quot;: 1,
      &quot;items&quot;: [
        { &quot;product&quot;: &quot;apple&quot;, &quot;qty&quot;: 2 },
        { &quot;product&quot;: &quot;banana&quot;, &quot;qty&quot;: 1 }
      ]
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Column-Family (ex. cassandra)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 Row의 Column이 고정되어있지 않고, 필요한 데이터 Column을 이름, 데이터, Timestamp와 함께 저장하는 형태의 데이터베이스 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772037100303&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PRIMARY KEY (user_id, order_id)

user_id | order_id | product | qty
-----------------------------------
1       | 1        | apple   | 2
1       | 1        | banana  | 1&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Redis 활용 사례&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis는 NoSQL 중에서도 Key-Value Store로 작동하는 인메모리 데이터베이스이며, 지연이 적은 읽기 / 쓰기 성능을 가졌다.&lt;/li&gt;
&lt;li&gt;그렇기 때문에 일시적인 데이터, 변경이 잦은 데이터를 다뤄야 되는 상황에서 많이 활용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Session Clustering : 여러 애플리케이션 인스턴스에서 같은 세션 정보를 사용할 수 있도록 도와준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Caching : 자주 사용되는 데이터를 저장해두어, 데이터베이스 조회를 줄이고 전반적인 응답속도를 개선한다. (웹 어플리케이션의 성능에 많은 영향을 미친다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 지원하는 다양한 자료구조를 바탕으로 리더보드, 방문수 트래킹, 좌표 기반 검색 등의 기능을 쉽게 구현할 수 있게 해준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-1-Redis-2242dc3ef514811a85e0e562dfcae13c&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://teamsparta.notion.site/1-1-Redis-2242dc3ef514811a85e0e562dfcae13c&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772030457087&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;챕터1-1 : 인메모리 저장소와 Redis 기초 | Notion&quot; data-og-description=&quot;인메모리 저장소의 필요성&quot; data-og-host=&quot;teamsparta.notion.site&quot; data-og-source-url=&quot;https://teamsparta.notion.site/1-1-Redis-2242dc3ef514811a85e0e562dfcae13c&quot; data-og-url=&quot;https://teamsparta.notion.site/1-1-Redis-2242dc3ef514811a85e0e562dfcae13c&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Ja5I4/dJMb9b3Pbdf/FoSHKuJJQTQqW1jQo3KTOk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/wYmMH/dJMb9hCYqAp/1j1k0yXF80h09GM3w6uftk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://teamsparta.notion.site/1-1-Redis-2242dc3ef514811a85e0e562dfcae13c&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://teamsparta.notion.site/1-1-Redis-2242dc3ef514811a85e0e562dfcae13c&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Ja5I4/dJMb9b3Pbdf/FoSHKuJJQTQqW1jQo3KTOk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/wYmMH/dJMb9hCYqAp/1j1k0yXF80h09GM3w6uftk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;챕터1-1 : 인메모리 저장소와 Redis 기초 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인메모리 저장소의 필요성&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;teamsparta.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>심화/DB</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/528</guid>
      <comments>https://annovation.tistory.com/528#entry528comment</comments>
      <pubDate>Wed, 25 Feb 2026 23:58:05 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] 재고 감소 통합 테스트 코드 6 - Optimistic Lock(낙관적 락) 멀티 스레드 테스트 코드</title>
      <link>https://annovation.tistory.com/527</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Optimistic Lock(낙관적 락) 테스트 코드&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Optimistic Lock(낙관적 락) 을 적용한 동시성 처리 테스트 코드를 분석해보자 !&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;테스트 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771812555642&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;재고 감소 - 100개 동시 요청 시 정확히 차감&quot;)
void decreaseStock_when100ConcurrentRequests_thenSuccess() throws Exception {

    // given
    int threadCount = 100;

    ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

    CountDownLatch countDownLatch = new CountDownLatch(threadCount);

    List&amp;lt;Future&amp;lt;?&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();

	// when
    for (int i = 0; i &amp;lt; threadCount; i++) {
        futures.add(executorService.submit(() -&amp;gt; {
            try {
                stockRetryFacade.decreaseWithRetry(StockFixture.decreaseRequest(product, 1));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            } finally {
                countDownLatch.countDown();
            }
        }));
    }

    countDownLatch.await();

    for (Future&amp;lt;?&amp;gt; future : futures) {
        future.get(); // 스레드 내부 예외를 테스트 실패로 반영
    }

    executorService.shutdown();

    // then
    Stock updatedStock = jpaStockRepository
            .findByProductId(product.getProductId())
            .orElseThrow();

    assertEquals(0, updatedStock.getQuantity());
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;given&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;given : 테스트 환경 준비&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 동시에 실행할 스레드 개수 설정&lt;/p&gt;
&lt;pre id=&quot;code_1771934493587&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int threadCount = 100;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 스레드 풀 생성 (100개의 스레드를 동시에 실행할 수 있는 환경)&lt;/p&gt;
&lt;pre id=&quot;code_1771934518763&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ExecutorService executorService = Executors.newFixedThreadPool(threadCount);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;newFixedThreadPool(100): 최대 100개의 작업을 동시에 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) CountDownLatch : 모든 스레드가 작업을 완료할 때까지 대기하기 위한 동기화 도구&lt;/p&gt;
&lt;pre id=&quot;code_1771934580558&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CountDownLatch countDownLatch = new CountDownLatch(threadCount);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;100으로 초기화 &amp;rarr; 각 스레드가 countDown()을 호출할 때마다 1씩 감소&lt;/li&gt;
&lt;li&gt;0이 되면 await()에서 대기 중인 메인 스레드가 계속 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) Future 리스트 : 각 스레드의 실행 결과를 추적하기 위한 컨테이너&lt;/p&gt;
&lt;pre id=&quot;code_1771934598657&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Future&amp;lt;?&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Future를 통해 스레드 내부에서 발생한 예외를 감지할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;when&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;when : 실제 테스트 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771934763824&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 100번 반복하면서 100개의 재고 감소 작업을 동시에 제출
for (int i = 0; i &amp;lt; threadCount; i++) {

    // executorService.submit(): 작업을 스레드 풀에 제출
    // submit()은 Future 객체를 반환 &amp;rarr; 나중에 결과 확인 가능
    futures.add(executorService.submit(() -&amp;gt; {
        try {
            // 실제 재고 감소 로직 실행 (재시도 로직 포함)
            // 각 스레드는 재고를 1개씩 감소시키려고 시도
            stockRetryFacade.decreaseWithRetry(
                StockFixture.decreaseRequest(product, 1)
            );

        } catch (InterruptedException e) {
            // InterruptedException 발생 시
            // 1. 현재 스레드의 인터럽트 상태를 복원
            Thread.currentThread().interrupt();

            // 2. RuntimeException으로 감싸서 다시 던짐
            //    (Runnable은 checked exception을 던질 수 없기 때문)
            throw new RuntimeException(e);

        } finally {
            // 작업 성공/실패 여부와 관계없이 항상 실행
            // countDownLatch를 1 감소시켜 &quot;이 스레드는 작업 완료했다&quot;고 알림
            countDownLatch.countDown();
        }
    }));
}

// 모든 스레드가 countDown()을 호출할 때까지 메인 스레드를 여기서 대기
// countDownLatch가 0이 될 때까지 블로킹
// &amp;rarr; 100개 스레드가 모두 작업을 완료하면 다음으로 진행
countDownLatch.await();

// Future 리스트를 순회하면서 각 스레드의 실행 결과 확인
for (Future&amp;lt;?&amp;gt; future : futures) {
    // future.get(): 해당 스레드가 완료될 때까지 대기하고 결과를 가져옴
    // 스레드 내부에서 예외가 발생했다면 여기서 ExecutionException 발생
    // &amp;rarr; 테스트 실패로 처리됨
    future.get();
}

// 스레드 풀 종료 (더 이상 새 작업을 받지 않음)
// 리소스 정리를 위해 항상 호출해야 함
executorService.shutdown();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Future 가 필요한 이유는 뭘까용?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Future 가 없으면 스레드 내부 예외가 숨겨진다.&lt;/li&gt;
&lt;li&gt;예를 들어 :&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771936557318&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test() {
    ExecutorService executor = Executors.newFixedThreadPool(2);
    CountDownLatch latch = new CountDownLatch(2);

    // 스레드 1
    executor.submit(() -&amp;gt; {
        try {
            throw new RuntimeException(&quot;에러!&quot;);  // &amp;larr; 예외 발생
        } finally {
            latch.countDown();
        }
    });

    // 스레드 2
    executor.submit(() -&amp;gt; {
        try {
            System.out.println(&quot;정상 실행&quot;);
        } finally {
            latch.countDown();
        }
    });

    latch.await();
    executor.shutdown();
    
    // 테스트는 통과! (예외를 몰라서)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1771935981096&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;스레드 1: 성공 (재고 99)
스레드 2: 성공 (재고 98)
...
스레드 50: NullPointerException 발생! &amp;larr; 하지만 테스트는 계속 진행
...
스레드 100: 성공

결과: 재고가 50 (50번만 성공)

하지만 assertEquals(0, stock.getQuantity()) 에서 실패
&amp;rarr; &quot;왜 50이지?&quot; 원인을 알 수 없음!&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Future 를 사용하면 스레드 내부 예외가 명확하게 드러난다.&lt;/li&gt;
&lt;li&gt;예외를 객체에 저장해 get()을 통해 호출 가능하기 때문에 스레드 안에서 발생한 예외를 호출한 쪽에서 확실하게 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;예를 들어 :&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771936508850&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test() throws Exception {
    ExecutorService executor = Executors.newFixedThreadPool(2);
    CountDownLatch latch = new CountDownLatch(2);
    List&amp;lt;Future&amp;lt;?&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();

    // 스레드 1
    futures.add(executor.submit(() -&amp;gt; {
        try {
            throw new RuntimeException(&quot;에러!&quot;);
        } finally {
            latch.countDown();
        }
    }));

    // 스레드 2
    futures.add(executor.submit(() -&amp;gt; {
        try {
            System.out.println(&quot;정상 실행&quot;);
        } finally {
            latch.countDown();
        }
    }));

    latch.await();

    // Future로 예외 확인
    for (Future&amp;lt;?&amp;gt; future : futures) {
        future.get();  // &amp;larr; ExecutionException 발생! 테스트 실패
    }

    executor.shutdown();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 왜 자료 구조를 ArrayList 로 썼을까용?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순차 접근이 주 목적&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771938339343&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 순차적으로 모든 Future를 확인
for (Future&amp;lt;?&amp;gt; future : futures) {
    future.get();  // 하나씩 차례대로 확인
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 인덱스 기반 빠른 접근 : O(1)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;크기를 미리 알수 있고, 데이터 추가에 용이하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771938395717&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int threadCount = 100;  // 크기 고정

// 정확히 100개의 Future가 들어갈 것을 알고 있음
List&amp;lt;Future&amp;lt;?&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;(threadCount);  // 초기 용량 지정 가능

for (int i = 0; i &amp;lt; threadCount; i++) {
    futures.add(executor.submit(...));  // 끝에 추가만 함
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 데이터 추가(append) : O(1) (평균)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-1) LinkedList 랑은 무슨 차인데용?&lt;/p&gt;
&lt;pre id=&quot;code_1771938482542&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// LinkedList
List&amp;lt;Future&amp;lt;?&amp;gt;&amp;gt; futures = new LinkedList&amp;lt;&amp;gt;();
for (int i = 0; i &amp;lt; 100; i++) {
    futures.add(executor.submit(...));  // O(1)
}
for (Future&amp;lt;?&amp;gt; future : futures) {
    future.get();  // 순회는 괜찮지만 메모리 오버헤드
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 오버헤드(실제 데이터 저장 외에 추가로 소비되는 메모리)가 크다. (각 노드마다 prev, next 포인터)&lt;/li&gt;
&lt;li&gt;인덱스 접근 느림 : O(n)&lt;/li&gt;
&lt;li&gt;캐시 지역성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771938906149&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ArrayList: 연속된 메모리 &amp;rarr; CPU 캐시 히트율 높음
for (Future&amp;lt;?&amp;gt; future : futures) {
    future.get();  // &amp;larr; 빠름
}

// LinkedList: 흩어진 메모리 &amp;rarr; CPU 캐시 미스 많음
for (Future&amp;lt;?&amp;gt; future : futures) {
    future.get();  // &amp;larr; 상대적으로 느림
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 실행 흐름&lt;/p&gt;
&lt;pre id=&quot;code_1771935375596&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;## 실행 흐름

1. 스레드 풀 생성 (100개)
   
2. 100개 작업 제출 (각각 재고 1씩 감소)
   
3. 모든 작업 동시 실행 (경쟁 상태 발생 가능)
   
4. countDownLatch.await() - 모든 작업 완료 대기
   
5. future.get() - 각 스레드 예외 확인
   
6. DB 조회 - 최종 재고 확인
   
7. assertEquals(0, ...) - 재고가 정확히 0인지 검증&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;then&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;then : 결과 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) DB에서 최신 재고 데이터 조회&lt;/p&gt;
&lt;pre id=&quot;code_1771935085130&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stock updatedStock = jpaStockRepository
            .findByProductId(product.getProductId())  // productId로 재고 조회
            .orElseThrow();  // 없으면 예외 발생&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@BeforeEach에서 생성한 stock 객체가 아닌, DB에서 다시 가져와야 함!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 검증&lt;/p&gt;
&lt;pre id=&quot;code_1771935126762&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;assertEquals(0, updatedStock.getQuantity());&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;100개 스레드가 각각 1씩 감소시켰으므로 초기 재고 100 - (100번 &amp;times; 1씩 감소) = 0이어야 함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;만약 동시성 제어가 제대로 안 되었다면 0보다 큰 값이 나올 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;핵심 개념 정리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. ExecutorService (&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1771935242098&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ExecutorService executorService = Executors.newFixedThreadPool(100);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 작업을 병렬로 실행하기 위한 스레드 풀&lt;/li&gt;
&lt;li&gt;newFixedThreadPool(100): 최대 100개의 작업을 동시에 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. CountDownLatch&lt;/p&gt;
&lt;pre id=&quot;code_1771935260042&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CountDownLatch latch = new CountDownLatch(100);
latch.countDown();  // -1 감소
latch.await();      // 0이 될 때까지 대기&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 스레드의 작업 완료를 동기화&lt;/li&gt;
&lt;li&gt;카운터가 0이 되면 대기 중인 스레드가 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Future (&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/Future.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1771935305126&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Future&amp;lt;?&amp;gt; future = executorService.submit(...);
future.get();  // 결과 대기 + 예외 확인&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;Java 표준 라이브러리&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;비동기 작업의 결과를 나타내는 객체 &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;get() :&lt;/span&gt;&lt;span&gt; 작업 완료 대기 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;+&lt;/span&gt;&lt;span&gt; 예외가 있으면 던짐&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;Java 8 이후부터는 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;CompletableFuture 사용을 권장한다고 한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Oracle Docs : Future&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/Future.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/Future.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771937023670&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Future (Java SE 21 &amp;amp; JDK 21)&quot; data-og-description=&quot;Type Parameters: V - The result type returned by this Future's get method All Known Subinterfaces: RunnableFuture , RunnableScheduledFuture , ScheduledFuture All Known Implementing Classes: CompletableFuture, CountedCompleter, ForkJoinTask, FutureTask, Rec&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/Future.html&quot; data-og-url=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/Future.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/Future.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/Future.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Future (Java SE 21 &amp;amp; JDK 21)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Type Parameters: V - The result type returned by this Future's get method All Known Subinterfaces: RunnableFuture , RunnableScheduledFuture , ScheduledFuture All Known Implementing Classes: CompletableFuture, CountedCompleter, ForkJoinTask, FutureTask, Rec&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Oracle Docs : ExecutorService&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771939255972&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ExecutorService (Java Platform SE 8 )&quot; data-og-description=&quot;An Executor that provides methods to manage termination and methods that can produce a Future for tracking progress of one or more asynchronous tasks. An ExecutorService can be shut down, which will cause it to reject new tasks. Two different methods are p&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ExecutorService (Java Platform SE 8 )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An Executor that provides methods to manage termination and methods that can produce a Future for tracking progress of one or more asynchronous tasks. An ExecutorService can be shut down, which will cause it to reject new tasks. Two different methods are p&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) Oracle Docs : CountDownLatch&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771939297283&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;CountDownLatch (Java Platform SE 8 )&quot; data-og-description=&quot;A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes. A CountDownLatch is initialized with a given count. The await methods block until the current count reaches zero due to invo&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CountDownLatch (Java Platform SE 8 )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes. A CountDownLatch is initialized with a given count. The await methods block until the current count reaches zero due to invo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/527</guid>
      <comments>https://annovation.tistory.com/527#entry527comment</comments>
      <pubDate>Tue, 24 Feb 2026 21:17:09 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] Optimistic Lock (낙관적 락) 재시도 로직</title>
      <link>https://annovation.tistory.com/526</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Optimistic&amp;nbsp;Lock&amp;nbsp;(낙관적&amp;nbsp;락)&amp;nbsp;재시도&amp;nbsp;로직&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Optimistic Lock (낙관적 락) 에서는 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;실패했을 경우 재시도를 해야하는데,&lt;/span&gt;&amp;nbsp;아래 다양한 방법들을 통해 구현할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring Retry&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Facade 패턴&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771812457694&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class StockRetryFacade {

    private final StockService stockService; // @Transactional 메서드 가진 서비스

    public StockResult decreaseWithRetry(StockRequests.Decrease request)
            throws InterruptedException {
        int maxRetries = 100;
        int attempt = 0;

        while (true) {
            try {
                return stockService.decreaseStock(request); // 1회 시도 (새 트랜잭션)
            } catch (OptimisticLockingFailureException e) {
                if (++attempt &amp;gt;= maxRetries) {
                    throw e;
                }
                Thread.sleep(50);
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;재시도 구현 시 검토해야 할 4가지 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;낙관적 락에서 발생하는 Conflict는 시스템 장애가 아니다. &lt;/span&gt;오히려 &lt;b&gt;동시성 환경에서 자연스럽게 발생하는 정상적인 제어 흐름&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이다. &lt;/span&gt;따라서 모든 충돌을 '에러'로 취급해 무조건 재시도하는 것은 올바른 전략이 아니다. 서비스 특성과 트래픽 패턴에 맞는 재시도 설계가 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Fresh Read의 원칙 (최신 데이터 재조회)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재시도 시에는 반드시 &lt;b&gt;Primary DB에서 최신 버전 데이터를 다시 조회&lt;/b&gt;해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;메모리에 로드되어 있던 기존 객체를 그대로 들고 재시도하는 것은 무의미한 연쇄 충돌을 야기하고 CPU 자원을 낭비한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771941064823&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;충돌 발생
   &amp;darr;
Primary DB에서 최신 상태 재조회
   &amp;darr;
비즈니스 로직 재계산
   &amp;darr;
다시 CAS 시도&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ CAS 란?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compare And Swap 의 약자로, &lt;span&gt;현재 값이 expected 값과 같으면 &lt;/span&gt;&lt;span&gt;새로운 값으로 교체 &lt;/span&gt;&lt;span&gt;아니면 &lt;/span&gt;&lt;span&gt;아무것도 하지 않고 실패&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;예를 들면 :&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771941142020&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UPDATE character_state
SET current_sal = ?, update_version = update_version + 1
WHERE character_id = ?
  AND update_version = ?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;지수 백오프와 지터&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;충돌 직후 즉시 재시도하면 또다시 충돌할 가능성이 매우 높다.&lt;/li&gt;
&lt;li&gt;예를 들면 :&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772089427891&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 모두 같은 값을 읽음

A &amp;rarr; SAL=1000, version=1

B &amp;rarr; SAL=1000, version=1

C &amp;rarr; SAL=1000, version=1

2. A가 먼저 성공

UPDATE ... WHERE version=1

✔ 성공

SAL = 1100

version = 2

3. 나머지는 실패

여기서 B와 C가 즉시 재시도를 한다면?

4. B와 C가 바로 같은 값을 다시 읽음

B &amp;rarr; SAL=1100, version=2

C &amp;rarr; SAL=1100, version=2

5. 앞선 상황과 같이 B와 C 중 하나만 성공하고 실패한 요청은 다시 데이터를 읽고 또 동시에 업데이트를 시도하는 상황 반복

6. 따라서 실패한 요청을 흩어놓으면 또다시 동시에 부딪히는 상황을 예방할 수 있다.

B &amp;rarr; 50ms 기다림

C &amp;rarr; 120ms 기다림&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;따라서 재시도 간격을 2^n&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;형태로 늘리는 지수 백오프를 적용하고, 여기에 무작위 값인 지터(Jitter)를 섞어 요청 시점을 분산시켜야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771941486230&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;delay = base * 2^n + random_jitter&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들면 :&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;시도 횟수&lt;/td&gt;
&lt;td&gt;대기 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1회&lt;/td&gt;
&lt;td&gt;50ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2회&lt;/td&gt;
&lt;td&gt;100ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3회&lt;/td&gt;
&lt;td&gt;200ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4회&lt;/td&gt;
&lt;td&gt;400ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이는 수많은 요청이 한꺼번에 몰리는 현상을 방지하여 DB의 병목을 막아주는 핵심 기술이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;멱등성(Idempotency) 보장&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 내부 재시도 (OptimisticLockException 이후 retry)가 중복 처리되거나, 네트워크 이슈로 응답 받지 못한 클라이언트가 재요청을 보낼 경우, 동일한 비즈니스 요청이 2번 이상 실행될 가능성이 생긴다.&lt;/li&gt;
&lt;li&gt;즉, Optimistic Lock 은 동시성 제어만 보장할 뿐, 중복 실행 방지는 보장하지 않는다.&lt;/li&gt;
&lt;li&gt;이 경우, 클라이언트가 모든 요청에 대한 고유한 ID인 request_id를 생성하고 이를 기반으로 멱등성 테이블을 운용하여, 동일한 요청에 대해서는 실제 DB 쓰기 없이 이전의 성공 결과를 반환하는 안전장치가 병행되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772093651869&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE idempotency (
    request_id VARCHAR(64) PRIMARY KEY,
    response_payload JSON,
    created_at DATETIME
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 해당 테이블에 동일한 request_id가 존재한다면 이미 처리된 요청이고, 존재하지 않으면 해당 요청은 처음 처리하는 것임을 나타낸다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️멱등성(Idempotency)&amp;nbsp; 이란?&lt;br /&gt;&lt;br /&gt;같은 요청을 여러 번 보내도 결과가 한 번 보낸 것과 동일하게 유지되는 성질이다.&lt;br /&gt;어떤 연산을 한 번 수행하든 여러 번 수행하든 최종 상태가 동일하면 멱등하다고 한다. (&lt;a href=&quot;https://docs.tosspayments.com/blog/what-is-idempotency&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고 자료&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;예를 들어, HTTP 메서드를 기준으로 보면 GET은 여러 번 호출해도 같은 결과가 돌아오고, 리소스에 변화를 일으키지 않기 때문에 멱등성이 보장된 메서드이다.&lt;br /&gt;&lt;br /&gt;PUT /users/1로 이름을 '홍길동'으로 설정하는 요청은 여러 번 보내도 결과는 동일 &amp;rarr; 멱등&lt;br /&gt;POST /orders는 호출할 때마다 주문이 새로 생성 &amp;rarr; 멱등하지 않음&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;최대 재시도 횟수 제한&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;낙관적 락에서의 재시도는 일시적인 충돌을 완화하기 위한 전략일 뿐, 무한히 반복되어서는 안된다.&lt;/li&gt;
&lt;li&gt;충돌이 일정 횟수 이상 반복된다는 것은 단순한 타이밍 문제를 넘어, 특정 데이터에 과도한 경합(Hot Row Contention)이 발생하고 있다는 신호일 수 있다.&lt;/li&gt;
&lt;li&gt;예를 들어 3~5회 이상 충돌이 반복된다면 이는 단순한 일시적 충돌이 아니라 설계적 병목 가능성을 의심해야 할 상황이다.&lt;/li&gt;
&lt;li&gt;이 경우, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;사용자에게 명확한 에러를 반환하거나, 비즈니스 로직에 따라 비관적 락 등으로의 전환을 검토해야 하는 신호일 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 뱅크샐러드 블로그 : 뱅크샐러드가 게임을 만들 때 데이터 정합성을 유지하는 법 (feat. 낙관적 락)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.banksalad.com/tech/banksalad-optimistic-lock/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.banksalad.com/tech/banksalad-optimistic-lock/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771831976046&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;뱅크샐러드가 게임을 만들 때 데이터 정합성을 유지하는 법 (feat. 낙관적 락) | 뱅크샐러드&quot; data-og-description=&quot;안녕하세요 금융쇼핑 PA&amp;hellip;&quot; data-og-host=&quot;blog.banksalad.com&quot; data-og-source-url=&quot;https://blog.banksalad.com/tech/banksalad-optimistic-lock/&quot; data-og-url=&quot;https://blog.banksalad.com/tech/banksalad-optimistic-lock/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmaNsa/dJMb87NTgB8/IGkZYDlNH8H3SWKH3sUK71/img.jpg?width=2400&amp;amp;height=1600&amp;amp;face=0_0_2400_1600,https://scrap.kakaocdn.net/dn/ru569/dJMb8RROLWd/FpZTFGIGVkjkY4kWoyhjWK/img.png?width=650&amp;amp;height=366&amp;amp;face=0_0_650_366,https://scrap.kakaocdn.net/dn/tQIzB/dJMb8865KJ2/hk05oPnNp22ZiBiZNdBnn0/img.png?width=650&amp;amp;height=366&amp;amp;face=0_0_650_366&quot;&gt;&lt;a href=&quot;https://blog.banksalad.com/tech/banksalad-optimistic-lock/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.banksalad.com/tech/banksalad-optimistic-lock/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmaNsa/dJMb87NTgB8/IGkZYDlNH8H3SWKH3sUK71/img.jpg?width=2400&amp;amp;height=1600&amp;amp;face=0_0_2400_1600,https://scrap.kakaocdn.net/dn/ru569/dJMb8RROLWd/FpZTFGIGVkjkY4kWoyhjWK/img.png?width=650&amp;amp;height=366&amp;amp;face=0_0_650_366,https://scrap.kakaocdn.net/dn/tQIzB/dJMb8865KJ2/hk05oPnNp22ZiBiZNdBnn0/img.png?width=650&amp;amp;height=366&amp;amp;face=0_0_650_366');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;뱅크샐러드가 게임을 만들 때 데이터 정합성을 유지하는 법 (feat. 낙관적 락) | 뱅크샐러드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 금융쇼핑 PA&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.banksalad.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 토스 블로그 : 멱등성이 뭔가요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.tosspayments.com/blog/what-is-idempotency&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.tosspayments.com/blog/what-is-idempotency&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772094738508&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;멱등성이 뭔가요? | 토스페이먼츠 개발자센터&quot; data-og-description=&quot;생소한 표현이지만 알고 보면 쉬워요. 멱등성에 대해 이해하고 API를 멱등하게 제공하기 위한 방법도 함께 알아봐요.&quot; data-og-host=&quot;docs.tosspayments.com&quot; data-og-source-url=&quot;https://docs.tosspayments.com/blog/what-is-idempotency&quot; data-og-url=&quot;https://docs.tosspayments.com/blog/what-is-idempotency&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/1t6B6/dJMb8TB6MUh/YGjOFZmnGzXLkGXJSBPQT1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/caoklu/dJMb82MAgnE/LZvVfravESjdezgdGyFjUk/img.png?width=4659&amp;amp;height=2367&amp;amp;face=0_0_4659_2367&quot;&gt;&lt;a href=&quot;https://docs.tosspayments.com/blog/what-is-idempotency&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.tosspayments.com/blog/what-is-idempotency&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/1t6B6/dJMb8TB6MUh/YGjOFZmnGzXLkGXJSBPQT1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/caoklu/dJMb82MAgnE/LZvVfravESjdezgdGyFjUk/img.png?width=4659&amp;amp;height=2367&amp;amp;face=0_0_4659_2367');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;멱등성이 뭔가요? | 토스페이먼츠 개발자센터&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;생소한 표현이지만 알고 보면 쉬워요. 멱등성에 대해 이해하고 API를 멱등하게 제공하기 위한 방법도 함께 알아봐요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.tosspayments.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/526</guid>
      <comments>https://annovation.tistory.com/526#entry526comment</comments>
      <pubDate>Mon, 23 Feb 2026 23:53:49 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] DB Lock : Optimistic Lock (낙관적 락) - 실습</title>
      <link>https://annovation.tistory.com/525</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Optimistic Lock (낙관적 락)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;Optimistic Lock (낙관적 락) 이란?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://annovation.tistory.com/509&quot;&gt;https://annovation.tistory.com/509&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771488485331&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 (실습중..)&quot; data-og-description=&quot;동시성 문제 (Race Condition) 해결 방법레이스 컨디션을 해결하는 방법에는 크게 3가지 관점이 있다.첫번째는 Java에서 지원하는 Synchronized 방법으로 해결하기두번째는 DB가 제공하는 Lock 을 이용하&quot; data-og-host=&quot;annovation.tistory.com&quot; data-og-source-url=&quot;https://annovation.tistory.com/509&quot; data-og-url=&quot;https://annovation.tistory.com/509&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bX10OE/dJMb86nT4ES/KK5CsiUEzGktYrbneba7i0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/m6j0e/dJMb86OYl4k/xzIBL8FcCaMaDfQVurpLK0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dBQd55/dJMb8TB57dv/6W6G39YgpNYmVLGtE90wX0/img.jpg?width=736&amp;amp;height=736&amp;amp;face=0_0_736_736&quot;&gt;&lt;a href=&quot;https://annovation.tistory.com/509&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://annovation.tistory.com/509&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bX10OE/dJMb86nT4ES/KK5CsiUEzGktYrbneba7i0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/m6j0e/dJMb86OYl4k/xzIBL8FcCaMaDfQVurpLK0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dBQd55/dJMb8TB57dv/6W6G39YgpNYmVLGtE90wX0/img.jpg?width=736&amp;amp;height=736&amp;amp;face=0_0_736_736');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 (실습중..)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;동시성 문제 (Race Condition) 해결 방법레이스 컨디션을 해결하는 방법에는 크게 3가지 관점이 있다.첫번째는 Java에서 지원하는 Synchronized 방법으로 해결하기두번째는 DB가 제공하는 Lock 을 이용하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;annovation.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;동시성 처리&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;동시성 처리 전 코드&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockController&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771812901168&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Operation(summary = &quot;재고 감소 API&quot;, description = &quot;상품 주문시 재고가 감소한다.&quot;)
@PutMapping
public ResponseEntity&amp;lt;ApiResponse&amp;lt;StockResponse&amp;gt;&amp;gt; decreaseStock(
        @Valid @RequestBody StockRequests.Decrease request) {

    // 재고 감소 로직 호출
    StockResult result = stockService.decreaseStock(request);

    return ResponseEntity.status(HttpStatus.OK)
            .body(ApiResponse.success(StockResponse.from(result)));
}

@Operation(summary = &quot;재고 복원 API&quot;, description = &quot;상품 취소시 재고가 복원된다.&quot;)
@PutMapping(&quot;/restore&quot;)
public ResponseEntity&amp;lt;ApiResponse&amp;lt;StockResponse&amp;gt;&amp;gt; restoreStock(
        @Valid @RequestBody StockRequests.Restore request) {

    // 재고 복원 로직 호출
    StockResult result = stockService.restoreStock(request);

    return ResponseEntity.status(HttpStatus.OK)
            .body(ApiResponse.success(StockResponse.from(result)));
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockServiceImpl.decreaseStock&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771488483888&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
@Transactional
public StockResult decreaseStock(StockRequests.Decrease request) {

    Product product = getProductOrThrow(request.productId());

    Stock stock = getStockOrThrow(request.productId());

    stock.decreaseQuantity(request.quantity());

    return StockResult.from(stock, product.getName());
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stock&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771488483889&quot; class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void decreaseQuantity(int decreaseQuantity) {
    validateDecreaseQuantity(decreaseQuantity);

    if (this.quantity &amp;lt; decreaseQuantity) {
        throw new GlobalException(INSUFFICIENT_STOCK);
    }

    this.quantity -= decreaseQuantity;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;JpaStockRepository&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771678095446&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface JpaStockRepository extends JpaRepository&amp;lt;Stock, UUID&amp;gt; {

    Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;StockRepository&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771678109381&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface StockRepository {

	Stock save(Stock stock);

	Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;StockRepositoryAdapter&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771678120415&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class StockRepositoryAdapter implements StockRepository {

	private final JpaStockRepository jpaStockRepository;

	@Override
	public Stock save(Stock stock) {
		return jpaStockRepository.save(stock);
	}

	@Override
	public Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId) {
		return jpaStockRepository.findByProductId(productId);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockCurrencyTest&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771678199881&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try {
    stockServiceImpl.decreaseStock(
            StockFixture.decreaseRequest(product, 1)
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;동시성 처리 전 테스트 코드 결과&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-19 at 12.25.02 AM.png&quot; data-origin-width=&quot;2154&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq27Fu/dJMcabwoxaI/K8PE5Rk9TkjltkdLp9GnU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq27Fu/dJMcabwoxaI/K8PE5Rk9TkjltkdLp9GnU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq27Fu/dJMcabwoxaI/K8PE5Rk9TkjltkdLp9GnU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq27Fu%2FdJMcabwoxaI%2FK8PE5Rk9TkjltkdLp9GnU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2154&quot; height=&quot;560&quot; data-filename=&quot;Screenshot 2026-02-19 at 12.25.02 AM.png&quot; data-origin-width=&quot;2154&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재고 100개에 대해 1개씩 100번 동시 차감했을 때 0이 되어야 하는데, 실제로는 88이 남아 동시성 제어가 제대로 되지 않아 일부 차감이 반영되지 않은 상태에서 발생한 AssertionFailedError가 발생했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;동시성 처리 한 코드&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockController&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771812973822&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PutMapping
public ResponseEntity&amp;lt;ApiResponse&amp;lt;StockResponse&amp;gt;&amp;gt; decreaseStock(
	@Valid @RequestBody StockRequests.Decrease request) {
    
    StockResult result = stockRetryFacade.decreaseWithRetry(request);
    
    return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(StockResponse.from(result)));
}

@PutMapping(&quot;/restore&quot;)
public ResponseEntity&amp;lt;ApiResponse&amp;lt;StockResponse&amp;gt;&amp;gt; restoreStock(
	@Valid @RequestBody StockRequests.Restore request) {
    
    StockResult result = stockRetryFacade.restoreWithRetry(request);
    
    return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(StockResponse.from(result)));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 낙관적 락 재시도 로직을 사용할 수 있도록 Facade 클래스를 Controller에서 반영하도록 수정&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockServiceImpl&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771677967816&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private Stock getStockWithLockOrThrow(UUID productId) {
        return stockRepository
                .findByProductWithOptimisticLock(productId)
                .orElseThrow(() -&amp;gt; new GlobalException(STOCK_NOT_FOUND));
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;➡️&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Lock 조회 메서드 추가&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stock&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771597817540&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Entity
@Table(name = &quot;p_stock&quot;)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stock extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(name = &quot;stock_id&quot;, nullable = false)
    private UUID stockId;

    @Column(name = &quot;product_id&quot;, nullable = false)
    private UUID productId;

    @Column(name = &quot;company_id&quot;, nullable = false)
    private UUID companyId;

    @Column(name = &quot;hub_id&quot;, nullable = false)
    private UUID hubId;

    @Column(name = &quot;quantity&quot;, nullable = false)
    private int quantity;

    @Version
    private Long version;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ Stock Entity 에 Version 필드 추가&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JpaStockRepository&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771597952470&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface JpaStockRepository extends JpaRepository&amp;lt;Stock, UUID&amp;gt; {

    Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId);

    @Lock(LockModeType.OPTIMISTIC)
    @Query(&quot;SELECT s FROM Stock s WHERE s.productId = :productId&quot;)
    Optional&amp;lt;Stock&amp;gt; findByProductWithOptimisticLock(@Param(&quot;productId&quot;) UUID productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockRepository&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771598022206&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface StockRepository {

	Stock save(Stock stock);

	Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId);
	
	Optional&amp;lt;Stock&amp;gt; findByProductWithOptimisticLock(UUID productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockRepositoryAdapter&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771598033527&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class StockRepositoryAdapter implements StockRepository {

    private final JpaStockRepository jpaStockRepository;

    @Override
    public Stock save(Stock stock) {
        return jpaStockRepository.save(stock);
    }

    @Override
    public Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId) {
        return jpaStockRepository.findByProductId(productId);
    }

    @Override
    public Optional&amp;lt;Stock&amp;gt; findByProductWithOptimisticLock(UUID productId) {
        return jpaStockRepository.findByProductWithOptimisticLock(productId);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;applicaiton/service/StockRetryFacade.java 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771597740697&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class StockRetryFacade {

    private final StockService stockService; // @Transactional 메서드 가진 서비스

    public StockResult decreaseWithRetry(StockRequests.Decrease request)
            throws InterruptedException {
        int maxRetries = 100;
        int attempt = 0;

        while (true) {
            try {
                return stockService.decreaseStock(request); // 1회 시도 (새 트랜잭션)
            } catch (OptimisticLockingFailureException e) {
                if (++attempt &amp;gt;= maxRetries) {
                    throw e;
                }
                Thread.sleep(50);
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ Optimistic Lock은 실패했을 경우 재시도를 해야하기 때문에&lt;span&gt; 이를 구현하기 위한 방법 중 강의에 소개된&amp;nbsp;&lt;/span&gt;facade(퍼사드)를 통해 생성하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockCurrencyTest&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771678403110&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;재고 감소 - 100개 동시 요청 시 정확히 차감&quot;)
void decreaseStock_when100ConcurrentRequests_thenSuccess() throws Exception {

    // given
    int threadCount = 100;

    ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

    CountDownLatch countDownLatch = new CountDownLatch(threadCount);

    List&amp;lt;Future&amp;lt;?&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();

	// when
    for (int i = 0; i &amp;lt; threadCount; i++) {
        futures.add(executorService.submit(() -&amp;gt; {
            try {
                stockRetryFacade.decreaseWithRetry(StockFixture.decreaseRequest(product, 1));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            } finally {
                countDownLatch.countDown();
            }
        }));
    }

    countDownLatch.await();

    for (Future&amp;lt;?&amp;gt; future : futures) {
        future.get(); // 스레드 내부 예외를 테스트 실패로 반영
    }

    executorService.shutdown();

    // then
    Stock updatedStock = jpaStockRepository
            .findByProductId(product.getProductId())
            .orElseThrow();

    assertEquals(0, updatedStock.getQuantity());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;➡️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;테스트에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048);&quot;&gt;StockServiceImpl&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048);&quot;&gt;StockRetryFacade&lt;/span&gt;를 주입해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048);&quot;&gt;decreaseWithRetry&lt;/span&gt;를 호출하도록 수정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;동시성 처리 후 테스트 코드 결과&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-21 at 10.15.00 PM.png&quot; data-origin-width=&quot;2882&quot; data-origin-height=&quot;1622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUQ0Yg/dJMcahDpAyo/BKl1iZ1fJ6URcSO17d9oW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUQ0Yg/dJMcahDpAyo/BKl1iZ1fJ6URcSO17d9oW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUQ0Yg/dJMcahDpAyo/BKl1iZ1fJ6URcSO17d9oW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUQ0Yg%2FdJMcahDpAyo%2FBKl1iZ1fJ6URcSO17d9oW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2882&quot; height=&quot;1622&quot; data-filename=&quot;Screenshot 2026-02-21 at 10.15.00 PM.png&quot; data-origin-width=&quot;2882&quot; data-origin-height=&quot;1622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/525</guid>
      <comments>https://annovation.tistory.com/525#entry525comment</comments>
      <pubDate>Fri, 20 Feb 2026 23:29:16 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] Trouble Shooting : Pessimistic Lock 적용 오류</title>
      <link>https://annovation.tistory.com/524</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;진행 환경&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;진행 환경&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Java 17&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Spring Boot 3.5.7&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;MySQL 8.0.44&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Gradle&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;문제 상황&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 상황&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pessimistic Lock (낙관적 락) 적용을 위해&lt;span style=&quot;color: #333333;&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;@Lock(LockModeType.PESSIMISTIC_WRITE)&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;추가 후 아래와 같은 오류 발생&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-19 at 5.11.20 PM.png&quot; data-origin-width=&quot;2100&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZwHhh/dJMcaduaKqb/elNw332RvpbEAEf1lPAHtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZwHhh/dJMcaduaKqb/elNw332RvpbEAEf1lPAHtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZwHhh/dJMcaduaKqb/elNw332RvpbEAEf1lPAHtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZwHhh%2FdJMcaduaKqb%2FelNw332RvpbEAEf1lPAHtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2100&quot; height=&quot;622&quot; data-filename=&quot;Screenshot 2026-02-19 at 5.11.20 PM.png&quot; data-origin-width=&quot;2100&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;➡️ &lt;/span&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048);&quot;&gt;TransactionRequiredException: Query requires transaction be in progress&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt; &lt;span style=&quot;background-color: #f6e199; color: #222222; text-align: start;&quot;&gt;문제 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;증상&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;락 쿼리를 트랜잭션 없이 실행해서 난 오류&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;원인 분석&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;원인 1 : 테스트 코드에서 단순히 재고 데이터를 읽어오는 작업이더라도 트랜잭션이 걸려있어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 테스트 코드에서 레이스 컨디션 검증 포인트는 아래 stockServiceImpl 에서 decreaseStock 을 통해 동시 호출 하는 구간이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771563194012&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (int i = 0; i &amp;lt; threadCount; i++) {
            executorService.submit(() -&amp;gt; {

                try {
                    stockServiceImpl.decreaseStock(
                            StockFixture.decreaseRequest(product, 1)
                    );
                } finally {
                    countDownLatch.countDown();
                }
            });
        }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그런데, &lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;JpaStockRepository.findByProductId&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;에&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;@Lock(PESSIMISTIC_WRITE) 을 통해 동시성 처리를 하고 있기 때문에 findByProductId 와 같은 단순 조회 로직이더라도 &lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;가 아니라 락 쿼리(&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;FOR UPDATE&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;)로 실행되고 이는 트랜잭션 환경이 필수적이다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771563463102&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface JpaStockRepository extends JpaRepository&amp;lt;Stock, UUID&amp;gt; {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 StockServiceImpl 메서드들은 트랜잭션이 걸려있지만, 테스트 코드에서는 StockServiceImpl 을 거치지 않고 테스트가 Repository를 직접 호출 했기 때문에 해당 호출 자체에는 트랜잭션이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771564531035&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stock updatedStock = jpaStockRepository
                .findByProductId(product.getProductId())
                .orElseThrow();

        assertEquals(0, updatedStock.getQuantity());&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방안 검토&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방안 1 : Lock 없는 메서드로 읽기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 새롭게 발견한 문제점 : 단순 조회 API 까지 Lock 조회가 되어 성능이 떨어진다.&lt;/p&gt;
&lt;pre id=&quot;code_1771584775953&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
@Transactional(readOnly = true)
public StockResult getStockByProductId(UUID productId) {

    Product product = getProductOrThrow(productId);

    Stock stock = getStockOrThrow(productId);

    return StockResult.from(stock, product.getName());
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서 조회용으로 Lock 이 없는 메서드를 따로 분리하여 설계한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771591883215&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface JpaStockRepository extends JpaRepository&amp;lt;Stock, UUID&amp;gt; {

    Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId); // 일반 조회

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query(&quot;select s from Stock s where s.productId = :productId&quot;)
    Optional&amp;lt;Stock&amp;gt; findByProductIdForUpdate(@Param(&quot;productId&quot;) UUID productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;결과&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;테스트 코드 정상적으로 통과!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-20 at 10.28.16 PM.png&quot; data-origin-width=&quot;2876&quot; data-origin-height=&quot;1636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9SsHX/dJMcafr3yCp/cyOe9KPkruI2NK0R8PWwrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9SsHX/dJMcafr3yCp/cyOe9KPkruI2NK0R8PWwrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9SsHX/dJMcafr3yCp/cyOe9KPkruI2NK0R8PWwrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9SsHX%2FdJMcafr3yCp%2FcyOe9KPkruI2NK0R8PWwrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2876&quot; height=&quot;1636&quot; data-filename=&quot;Screenshot 2026-02-20 at 10.28.16 PM.png&quot; data-origin-width=&quot;2876&quot; data-origin-height=&quot;1636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>왜안되지?</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/524</guid>
      <comments>https://annovation.tistory.com/524#entry524comment</comments>
      <pubDate>Thu, 19 Feb 2026 16:33:27 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] DB Lock : Pessimistic Lock (비관적 락) - 실습</title>
      <link>https://annovation.tistory.com/523</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Pessimistic Lock (비관적 락)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;Pessimistic Lock (비관적 락) 이란?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://annovation.tistory.com/509&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://annovation.tistory.com/509&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771428628703&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 (실습중..)&quot; data-og-description=&quot;동시성 문제 (Race Condition) 해결 방법레이스 컨디션을 해결하는 방법에는 크게 3가지 관점이 있다.첫번째는 Java에서 지원하는 Synchronized 방법으로 해결하기두번째는 DB가 제공하는 Lock 을 이용하&quot; data-og-host=&quot;annovation.tistory.com&quot; data-og-source-url=&quot;https://annovation.tistory.com/509&quot; data-og-url=&quot;https://annovation.tistory.com/509&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vtJsQ/dJMb8YXHV5s/WNukZySJfEyp8iqyvOv28K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/LaRlD/dJMb8T9VUPd/dV5kNKlNrRzzswKoPUMh81/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/DDZFB/dJMb8YpRTsH/ZTBz9bYr4GT977HbUcGlX0/img.jpg?width=736&amp;amp;height=736&amp;amp;face=0_0_736_736&quot;&gt;&lt;a href=&quot;https://annovation.tistory.com/509&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://annovation.tistory.com/509&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vtJsQ/dJMb8YXHV5s/WNukZySJfEyp8iqyvOv28K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/LaRlD/dJMb8T9VUPd/dV5kNKlNrRzzswKoPUMh81/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/DDZFB/dJMb8YpRTsH/ZTBz9bYr4GT977HbUcGlX0/img.jpg?width=736&amp;amp;height=736&amp;amp;face=0_0_736_736');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 (실습중..)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;동시성 문제 (Race Condition) 해결 방법레이스 컨디션을 해결하는 방법에는 크게 3가지 관점이 있다.첫번째는 Java에서 지원하는 Synchronized 방법으로 해결하기두번째는 DB가 제공하는 Lock 을 이용하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;annovation.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;동시성 처리&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;동시성 처리 전 코드&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockServiceImpl&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771427824295&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
@Transactional
public StockResult decreaseStock(StockRequests.Decrease request) {

    Product product = getProductOrThrow(request.productId());

    Stock stock = getStockOrThrow(request.productId());

    stock.decreaseQuantity(request.quantity());

    return StockResult.from(stock, product.getName());
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stock&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771427854727&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void decreaseQuantity(int decreaseQuantity) {
    validateDecreaseQuantity(decreaseQuantity);

    if (this.quantity &amp;lt; decreaseQuantity) {
        throw new GlobalException(INSUFFICIENT_STOCK);
    }

    this.quantity -= decreaseQuantity;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JpaStockRepository&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771592777718&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface JpaStockRepository extends JpaRepository&amp;lt;Stock, UUID&amp;gt; {

    Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockRepository&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771592797749&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface StockRepository {

	Stock save(Stock stock);

	Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockRepositoryAdapter&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771592828080&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class StockRepositoryAdapter implements StockRepository {

	private final JpaStockRepository jpaStockRepository;

	@Override
	public Stock save(Stock stock) {
		return jpaStockRepository.save(stock);
	}

	@Override
	public Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId) {
		return jpaStockRepository.findByProductId(productId);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;동시성 처리 전 테스트 코드 결과&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-19 at 12.25.02 AM.png&quot; data-origin-width=&quot;2154&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq27Fu/dJMcabwoxaI/K8PE5Rk9TkjltkdLp9GnU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq27Fu/dJMcabwoxaI/K8PE5Rk9TkjltkdLp9GnU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq27Fu/dJMcabwoxaI/K8PE5Rk9TkjltkdLp9GnU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq27Fu%2FdJMcabwoxaI%2FK8PE5Rk9TkjltkdLp9GnU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2154&quot; height=&quot;560&quot; data-filename=&quot;Screenshot 2026-02-19 at 12.25.02 AM.png&quot; data-origin-width=&quot;2154&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재고 100개에 대해 1개씩 100번 동시 차감했을 때 0이 되어야 하는데, 실제로는 88이 남아 동시성 제어가 제대로 되지 않아 일부 차감이 반영되지 않은 상태에서 발생한 AssertionFailedError가 발생했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;동시성 처리 한 코드&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JpaStockRepository&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771593411047&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface JpaStockRepository extends JpaRepository&amp;lt;Stock, UUID&amp;gt; {

    Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId);

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query(&quot;select s from Stock s where s.productId = :productId&quot;)
    Optional&amp;lt;Stock&amp;gt; findByProductIdForUpdate(@Param(&quot;productId&quot;) UUID productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️&lt;span style=&quot;color: #333333;&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;@Lock(PESSIMISTIC_WRITE)&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;가 붙어서 실행 시점에&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;FOR UPDATE&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;가 추가된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockRepository&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771593537448&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface StockRepository {

	Stock save(Stock stock);

	Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId);
	
	Optional&amp;lt;Stock&amp;gt; findByProductIdForUpdate(UUID productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockRepositoryAdapter&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771593575578&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class StockRepositoryAdapter implements StockRepository {

	private final JpaStockRepository jpaStockRepository;

	@Override
	public Stock save(Stock stock) {
		return jpaStockRepository.save(stock);
	}

	@Override
	public Optional&amp;lt;Stock&amp;gt; findByProductId(UUID productId) {
		return jpaStockRepository.findByProductId(productId);
	}
	
	@Override
	public Optional&amp;lt;Stock&amp;gt; findByProductIdForUpdate(UUID productId) {
		return jpaStockRepository.findByProductIdForUpdate(productId);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockServiceImpl&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771593713428&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private Stock getStockForUpdateOrThrow(UUID productId) {
    return stockRepository.findByProductIdForUpdate(productId)
            .orElseThrow(() -&amp;gt; new GlobalException(STOCK_NOT_FOUND));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt; Lock 조회 메서드 추가&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;동시성 처리 후 테스트 코드 결과&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-20 at 10.23.57 PM.png&quot; data-origin-width=&quot;2876&quot; data-origin-height=&quot;1636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Ntpi/dJMcabb7bCk/oHJVoHDRiOSexpGaF3VIUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Ntpi/dJMcabb7bCk/oHJVoHDRiOSexpGaF3VIUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Ntpi/dJMcabb7bCk/oHJVoHDRiOSexpGaF3VIUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Ntpi%2FdJMcabb7bCk%2FoHJVoHDRiOSexpGaF3VIUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2876&quot; height=&quot;1636&quot; data-filename=&quot;Screenshot 2026-02-20 at 10.23.57 PM.png&quot; data-origin-width=&quot;2876&quot; data-origin-height=&quot;1636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트가 성공적으로 통과되었다 !&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/523</guid>
      <comments>https://annovation.tistory.com/523#entry523comment</comments>
      <pubDate>Wed, 18 Feb 2026 23:56:03 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] Trouble Shooting : 멀티스레드 재고 감소 통합 테스트 코드 Eureka Server Connection refused</title>
      <link>https://annovation.tistory.com/522</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;진행 환경&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;진행 환경&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Java 17&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Spring Boot 3.5.7&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;MySQL 8.0.44&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Gradle&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;문제 상황&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 상황&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재고 감소 시 동시에 100개의 요청을 정확히 차감하는지 테스트 하는 코드에서 오류 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt; &lt;span style=&quot;background-color: #f6e199; color: #222222; text-align: start;&quot;&gt;문제 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;증상&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;org.springframework.web.client.ResourceAccessException: I/O error on GET request for &quot;http://localhost:8761/eureka/apps/&quot;: Connect to http://localhost:8761 failed: Connection refused&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Eureka 서버(8761 포트)에 연결하려고 했는데 해당 포트에 서버가 떠 있지 않아서 거절당한 것&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;원인 분석&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;원인 1 : 테스트 실행 중 Config Client가 활성화되어 Eureka를 통해 Config Server를 찾으려 함&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@SpringBootTest 가 전체 ApplicationContext를 다 올리고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;@SpringBootTest&lt;span&gt;&amp;nbsp;동작 흐름&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;706&quot; data-start=&quot;671&quot;&gt;테스트 클래스에서 @SpringBootTest 발견&lt;/li&gt;
&lt;li data-end=&quot;748&quot; data-start=&quot;707&quot;&gt;Spring Test Framework가 &lt;b&gt;테스트 컨텍스트 생성&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;804&quot; data-start=&quot;749&quot;&gt;SpringApplication.run 을 통해 &lt;b&gt;ApplicationContext 생성&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;854&quot; data-start=&quot;805&quot;&gt;Spring Boot auto-configuration + Test 환경 설정 적용&lt;/li&gt;
&lt;li data-end=&quot;864&quot; data-start=&quot;855&quot;&gt;테스트 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ ApplicationContext 개념 및 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) ApplicationContext 란? (&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1973&quot; data-start=&quot;1892&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1914&quot; data-start=&quot;1892&quot;&gt;Spring Container&lt;/li&gt;
&lt;li data-end=&quot;1946&quot; data-start=&quot;1915&quot;&gt;Bean 정의, 의존성 주입, 라이프사이클 관리 제공&lt;/li&gt;
&lt;li data-end=&quot;1973&quot; data-start=&quot;1947&quot;&gt;환경, 프로퍼티, 메시지, 이벤트 처리 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;It extends the BeanFactory interface, so it provides all the functionality of BeanFactory and more.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Context 란?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Context = &lt;b&gt;환경 + 설정 + Bean들의 집합&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;현재 애플리케이션이 동작하는 설정 환경 전체&lt;/li&gt;
&lt;li&gt;즉, 현재 실행 중인 Spring 컨테이너의 전체 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;A Spring ApplicationContext is essentially a container of beans.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방안 검토&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방안 1 : 기존 Config Server 트러블슈팅 해결 방안을 적용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://annovation.tistory.com/512&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://annovation.tistory.com/512&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771253260424&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[동시성 처리] Trouble Shooting : 테스트 코드 Config Client 오류&quot; data-og-description=&quot;문제 상황  문제 상황Stock 테스트 코드에서 yml 설정으로 config server 를 false 설정 했음에도 관련 오류로 인해 테스트 실행이 안되는 상황 발생  문제 코드 - StockServiceTest.java@ActiveProfiles(&amp;quot;test&amp;quot;)@Sp&quot; data-og-host=&quot;annovation.tistory.com&quot; data-og-source-url=&quot;https://annovation.tistory.com/512&quot; data-og-url=&quot;https://annovation.tistory.com/512&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ckp12Y/dJMb8SpEhX3/yoaCYXiPHhOKCdxr8FxSsK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/p9JRN/dJMb8XkbJYo/SkWlK6gpyYB32ejLNWFiD0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bOBTqV/dJMb8RROf1R/FpmoGck6RI3uSWaC9uBEi1/img.jpg?width=736&amp;amp;height=736&amp;amp;face=0_0_736_736&quot;&gt;&lt;a href=&quot;https://annovation.tistory.com/512&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://annovation.tistory.com/512&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ckp12Y/dJMb8SpEhX3/yoaCYXiPHhOKCdxr8FxSsK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/p9JRN/dJMb8XkbJYo/SkWlK6gpyYB32ejLNWFiD0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bOBTqV/dJMb8RROf1R/FpmoGck6RI3uSWaC9uBEi1/img.jpg?width=736&amp;amp;height=736&amp;amp;face=0_0_736_736');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[동시성 처리] Trouble Shooting : 테스트 코드 Config Client 오류&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;문제 상황  문제 상황Stock 테스트 코드에서 yml 설정으로 config server 를 false 설정 했음에도 관련 오류로 인해 테스트 실행이 안되는 상황 발생  문제 코드 - StockServiceTest.java@ActiveProfiles(&quot;test&quot;)@Sp&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;annovation.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방법&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방법 : &lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;해당 트러블슈팅으로 인해 기존에 작성해둔&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;application-test.yml 을 적용한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771253352818&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ActiveProfiles(&quot;test&quot;)
@SpringBootTest
public class StockCurrencyTest {

...(생략)...

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ @ActiveProfiles(&quot;test&quot;) 역할 (&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-activeprofiles.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2737&quot; data-start=&quot;2714&quot;&gt;테스트에서 &lt;b&gt;사용할 프로필을 지정&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;2765&quot; data-start=&quot;2738&quot;&gt;지정된 프로필만 적용해서 context를 로딩&lt;/li&gt;
&lt;li data-end=&quot;2788&quot; data-start=&quot;2766&quot;&gt;테스트 환경 분리/커스터마이징에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 여기서 말하는 프로필(profile) 이란?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서로 다른 환경(dev, test, prod)에 따라 다른 Bean을 등록하기 위한 기능 (&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/environment.html#beans-definition-profiles&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Bean definition profiles provide a way to register different beans for different environments.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ application-test.yml 을 사용하는데 프로필(profile) 개념이 연관되는 이유?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) profile-specific&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;application-test.yml 과 같은 형식으로 설정값(Property)을 분리해 환경마다 다른 Bean 을 주입시킬 수 있다. (&lt;a href=&quot;https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config.files.profile-specific&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;You can specify profile-specific configuration files by using the application-{profile}.yml format.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Profile 은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2280&quot; data-start=&quot;2261&quot;&gt;어떤 Bean을 등록할지 결정하면서 동시에&lt;/li&gt;
&lt;li data-end=&quot;2302&quot; data-start=&quot;2281&quot;&gt;어떤 yml 파일을 로드할지 결정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;yml은 설정값이고, profile은 Bean과 설정을 모두 제어하는 환경 스위치다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;참고 자료&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Spring Docs : ActiveProfiles&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-activeprofiles.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-activeprofiles.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771254798142&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;@ActiveProfiles :: Spring Framework&quot; data-og-description=&quot;@ActiveProfiles is an annotation that can be applied to a test class to declare which bean definition profiles should be active when loading an ApplicationContext for an integration test.&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-activeprofiles.html?utm_source=chatgpt.com&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-activeprofiles.html?utm_source=chatgpt.com&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-activeprofiles.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-activeprofiles.html?utm_source=chatgpt.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;@ActiveProfiles :: Spring Framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;@ActiveProfiles is an annotation that can be applied to a test class to declare which bean definition profiles should be active when loading an ApplicationContext for an integration test.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>왜안되지?</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/522</guid>
      <comments>https://annovation.tistory.com/522#entry522comment</comments>
      <pubDate>Mon, 16 Feb 2026 23:32:52 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] synchronized vs Reentrant Lock - 실습</title>
      <link>https://annovation.tistory.com/521</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;실습&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;synchronized&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://annovation.tistory.com/509&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://annovation.tistory.com/509&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771161819666&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;동시성 이슈 해결방법[2] - synchronized&quot; data-og-description=&quot;synchronized로 동시성 이슈를 해결하지 못하는 이유와 @Transactional과의 충돌!&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---java-synchronized&quot; data-og-url=&quot;https://velog.io/@kiteof_park/SpringBoot-동시성-이슈-해결방법2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/birq8p/dJMb8UHLsHs/lEpIkxRJzUKNPqHYmo1qt0/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/bB2T4i/dJMb8XR1TcE/dykuw2tLKkRKCxjIE0tyM1/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/bZLVzY/dJMb8WMlRlK/IZ7OrWXZPAJSPjThujChk1/img.png?width=1024&amp;amp;height=435&amp;amp;face=0_0_1024_435&quot;&gt;&lt;a href=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---java-synchronized&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@kiteof_park/SpringBoot-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%952#-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---java-synchronized&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/birq8p/dJMb8UHLsHs/lEpIkxRJzUKNPqHYmo1qt0/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/bB2T4i/dJMb8XR1TcE/dykuw2tLKkRKCxjIE0tyM1/img.png?width=764&amp;amp;height=398&amp;amp;face=0_0_764_398,https://scrap.kakaocdn.net/dn/bZLVzY/dJMb8WMlRlK/IZ7OrWXZPAJSPjThujChk1/img.png?width=1024&amp;amp;height=435&amp;amp;face=0_0_1024_435');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;동시성 이슈 해결방법[2] - synchronized&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;synchronized로 동시성 이슈를 해결하지 못하는 이유와 @Transactional과의 충돌!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Reentrant&amp;nbsp;Lock&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 사용법&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771163521275&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private final ReentrantLock lock = new ReentrantLock();

public void method() {
    lock.lock();
    try {
        // 비즈니스 로직
    } finally {
        lock.unlock();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HubEleven 프로젝트 적용 실습&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771162578793&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class StockServiceImpl implements StockService {

    private final StockRepository stockRepository;
    private final ProductRepository productRepository;

    private final ReentrantLock lock = new ReentrantLock();

	... (생략) ...

    @Override
    @Transactional
    public StockResult decreaseStock(StockRequests.Decrease request) {

        lock.lock();
        try {
            Product product = getProductOrThrow(request.productId());

            Stock stock = getStockOrThrow(request.productId());

            stock.decreaseQuantity(request.quantity());

            return StockResult.from(stock, product.getName());
        } finally {
            lock.unlock();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 한계 : MSA 와 같은 멀티 서버에서는 무용지물&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 서버 구성 예시&lt;/p&gt;
&lt;pre id=&quot;code_1771163289700&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Server A &amp;rarr; JVM A &amp;rarr; Heap A &amp;rarr; Object A(모니터 A)
Server B &amp;rarr; JVM B &amp;rarr; Heap B &amp;rarr; Object B(모니터 B)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Server A에서 synchronized/ReentrantLock으로 임계영역을 막아도&lt;/li&gt;
&lt;li&gt;Server B에서는 &lt;span&gt;&lt;b&gt;완전히 별개의 락&lt;/b&gt;&lt;/span&gt;이라 동시에 들어올 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Oracle Docs : ReentrantLock&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/17/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html&quot;&gt;https://docs.oracle.com/javase/17/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771163586903&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Overview (Java SE 17 &amp;amp; JDK 17)&quot; data-og-description=&quot;This document is divided into two sections: Java SE The Java Platform, Standard Edition (Java SE) APIs define the core Java platform for general-purpose computing. These APIs are in modules whose names start with java. JDK The Java Development Kit (JDK) AP&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/17/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html&quot; data-og-url=&quot;https://docs.oracle.com/en/java/javase/17/docs/api/index.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/17/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/17/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Overview (Java SE 17 &amp;amp; JDK 17)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This document is divided into two sections: Java SE The Java Platform, Standard Edition (Java SE) APIs define the core Java platform for general-purpose computing. These APIs are in modules whose names start with java. JDK The Java Development Kit (JDK) AP&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/521</guid>
      <comments>https://annovation.tistory.com/521#entry521comment</comments>
      <pubDate>Sun, 15 Feb 2026 22:50:57 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] JVM Lock : synchronized VS Reentrant Lock (실습중..)</title>
      <link>https://annovation.tistory.com/520</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;JVM 동시성 처리&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;synchronized&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;를 쓰면 &lt;/span&gt;&lt;b&gt;동시에 여러 스레드가 접근하는 상황(Thread interference)&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 을 막는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 일관성 문제(memory consistency)&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 도 예방한다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Synchronized methods enable a simple strategy for preventing thread interference and memory consistency errors.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized는 모니터 락(intrinsic lock) 을 사용하는 동기화 메커니즘이다.&lt;/li&gt;
&lt;li&gt;한 객체에 대해 한 번에 한 스레드만 락을 획득할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Each object is associated with a monitor, which a thread can lock or unlock. Only one thread at a time may hold a lock on a monitor.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;synchronized 메서드/블록은 &lt;/span&gt;&lt;b&gt;이 모니터 락을 획득 &amp;rarr; 코드 실행 &amp;rarr; 자동 해제&lt;/b&gt;&lt;span&gt; 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;JVM은 락을 자동으로 획득/해제한다(프로그램이 직접 unlock 호출 X) &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Reentrant&amp;nbsp;Lock&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 동작은 &lt;span&gt;synchronized&lt;/span&gt;와 같다(상호 배제).&lt;/li&gt;
&lt;li&gt;&lt;span&gt;그러나 기능이 &lt;/span&gt;&lt;b&gt;더 많고 유연하다&lt;/b&gt;&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) &lt;b&gt;재진입성 (Reentrant)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 스레드가 같은 락을 여러 번 얻을 수 있다 &amp;rarr; &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;재진입 가능(Reentrant)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;A ReentrantLock is owned by the thread last successfully locking, but not yet unlocking it&amp;hellip; The method will return immediately if the current thread already owns the lock.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771163388049&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lock.lock();
methodA();
lock.unlock();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ methodA 내부에서 또 lock.lock()을 호출 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) &lt;b&gt;공정성 설정 (fair)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;new ReentrantLock(true)&lt;span&gt; &amp;rarr; 오래 기다린 스레드 먼저!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;반면 &lt;span&gt;synchronized&lt;/span&gt;는 기본적으로 공정성 보장을 제공하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) &lt;b&gt;Memory Sync&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;ReentrantLock&lt;/span&gt;&lt;span&gt; 역시 JVM의 &lt;/span&gt;&lt;span&gt;synchronized&lt;/span&gt;&lt;span&gt;와 동일한 &lt;/span&gt;&lt;b&gt;메모리 가시성 보장(happens-before)&lt;/b&gt;&lt;span&gt; 을 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;All Lock implementations must enforce the same memory synchronization semantics as provided by the built-in monitor lock &amp;hellip;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;비교&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;synchronized&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Reentrant&amp;nbsp;Lock&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 블로그 : [JAVA] synchronized VS Reentrant Lock (차이점)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@may_yun/JAVA-synchronized-VS-Reentrant-Lock-차이점&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@may_yun/JAVA-synchronized-VS-Reentrant-Lock-차이점&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771073309470&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[JAVA] synchronized VS Reentrant Lock (차이점)&quot; data-og-description=&quot;상호 배제를 통한 동기화의 개념을 선행하고 보면 이해하는데 도움이 됩니다.현재 데이터를 사용하고 있는 해당 스레드를 제외하고 나머지 스레드들은 데이터에 접근 할 수 없도록 막는 개념Jav&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@may_yun/JAVA-synchronized-VS-Reentrant-Lock-차이점&quot; data-og-url=&quot;https://velog.io/@may_yun/JAVA-synchronized-VS-Reentrant-Lock-차이점&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bF5o2e/dJMb9fZrx5c/LS9NG2bDe6ERBW90615EX0/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500&quot;&gt;&lt;a href=&quot;https://velog.io/@may_yun/JAVA-synchronized-VS-Reentrant-Lock-차이점&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@may_yun/JAVA-synchronized-VS-Reentrant-Lock-차이점&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bF5o2e/dJMb9fZrx5c/LS9NG2bDe6ERBW90615EX0/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[JAVA] synchronized VS Reentrant Lock (차이점)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;상호 배제를 통한 동기화의 개념을 선행하고 보면 이해하는데 도움이 됩니다.현재 데이터를 사용하고 있는 해당 스레드를 제외하고 나머지 스레드들은 데이터에 접근 할 수 없도록 막는 개념Jav&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Oracle Docs : Synchronized&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771073688454&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Synchronized Methods (The Java&amp;trade; Tutorials &amp;gt;        
            Essential Java Classes &amp;gt; Concurrency)&quot; data-og-description=&quot;The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Dev.java for updated tutorials taking advantag&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Synchronized Methods (The Java&amp;trade; Tutorials &amp;gt; Essential Java Classes &amp;gt; Concurrency)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Dev.java for updated tutorials taking advantag&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) Oracle Docs : ReentrantLock&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771074269165&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ReentrantLock (Java Platform SE 8 )&quot; data-og-description=&quot;Acquires the lock only if it is not held by another thread at the time of invocation. Acquires the lock if it is not held by another thread and returns immediately with the value true, setting the lock hold count to one. Even when this lock has been set to&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ReentrantLock (Java Platform SE 8 )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Acquires the lock only if it is not held by another thread at the time of invocation. Acquires the lock if it is not held by another thread and returns immediately with the value true, setting the lock hold count to one. Even when this lock has been set to&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 블로그 : 처음부터 다시 배우는 Java 동시성 제어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://myvelop.tistory.com/262&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://myvelop.tistory.com/262&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772025405909&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;처음부터 다시 배우는 Java 동시성 제어&quot; data-og-description=&quot;이 글에서는 synchronized 키워드의 JVM 모니터 락이 실제로 어떻게 동작하는지 살펴보고, ReentrantLock이 제공하는 동시성 처리 기능을 알아본 뒤, 동시성 프로그래밍을 할 때 발생할 수 있는 생산자-&quot; data-og-host=&quot;myvelop.tistory.com&quot; data-og-source-url=&quot;https://myvelop.tistory.com/262&quot; data-og-url=&quot;https://myvelop.tistory.com/262&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bC9s8O/dJMb9lL8F1y/QjTF1q2hPXuCkuDvEy3TzK/img.png?width=800&amp;amp;height=355&amp;amp;face=0_0_800_355,https://scrap.kakaocdn.net/dn/bCjmRO/dJMb9c9uZ2G/PTr7kqzjorYJHMpgibff00/img.png?width=800&amp;amp;height=355&amp;amp;face=0_0_800_355,https://scrap.kakaocdn.net/dn/cyhW1H/dJMb8WewJLL/EFvuAKVIfhKHhkQxDmSBJ1/img.png?width=1495&amp;amp;height=668&amp;amp;face=0_0_1495_668&quot;&gt;&lt;a href=&quot;https://myvelop.tistory.com/262&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://myvelop.tistory.com/262&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bC9s8O/dJMb9lL8F1y/QjTF1q2hPXuCkuDvEy3TzK/img.png?width=800&amp;amp;height=355&amp;amp;face=0_0_800_355,https://scrap.kakaocdn.net/dn/bCjmRO/dJMb9c9uZ2G/PTr7kqzjorYJHMpgibff00/img.png?width=800&amp;amp;height=355&amp;amp;face=0_0_800_355,https://scrap.kakaocdn.net/dn/cyhW1H/dJMb8WewJLL/EFvuAKVIfhKHhkQxDmSBJ1/img.png?width=1495&amp;amp;height=668&amp;amp;face=0_0_1495_668');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 다시 배우는 Java 동시성 제어&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는 synchronized 키워드의 JVM 모니터 락이 실제로 어떻게 동작하는지 살펴보고, ReentrantLock이 제공하는 동시성 처리 기능을 알아본 뒤, 동시성 프로그래밍을 할 때 발생할 수 있는 생산자-&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;myvelop.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/520</guid>
      <comments>https://annovation.tistory.com/520#entry520comment</comments>
      <pubDate>Sat, 14 Feb 2026 22:10:56 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] Trouble Shooting : 테스트 코드 .env 환경 변수 적용</title>
      <link>https://annovation.tistory.com/518</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;문제 상황&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 상황&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;환경 변수(DB_USERNAME)가 정상적으로 치환되지 않아 실제 DB 계정이 아닌 &lt;span&gt;${DB_USERNAME}&lt;/span&gt; 문자열로 접속을 시도하면서 인증에 실패한 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt; &lt;span style=&quot;background-color: #f6e199; color: #222222; text-align: start;&quot;&gt;문제 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;증상&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770947622961&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;org.hibernate.exception.GenericJDBCException: 
unable to obtain isolated JDBC connection 
[Access denied for user '${DB_USERNAME}'@'localhost' (using password: YES)]&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;원인 분석&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;원인 1 : 루트에 위치한 .env 환경 변수 값을 읽어오지 못하는 상황&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048); color: #333333; text-align: start;&quot;&gt;본 프로젝트의 .env 파일은 루트 디렉터리(/Users/ann/Programming/Java Projects/hub-eleven/.env)에 위치하며, 루트는 settings.gradle 기준으로 정의한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048); color: #333333; text-align: start;&quot;&gt;해당 .env 파일의 DB_USERNAME 과 DB_PASSWORD 값을 읽어오지 못해 테스트용 로컬 MySQL 에 접속하지 못하는 에러 발생&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방안 검토&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방안 1 : .env 환경 변수란?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;환경변수(Environment Variables)는 운영체제에서 제공하는 동적인 값으로, 애플리케이션이 실행되는 환경에 따라 설정 값을 다르게 적용할 수 있도록 도와준다. Spring Boot 프로젝트에서도 환경변수를 활용하면 코드 수정 없이 설정을 변경할 수 있어 유지보수성과 보안성이 향상된다. (&lt;a href=&quot;https://msj9965.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방안 2 : 왜 문제가 되었나요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 방식별로 .env 주입 주체가 다르다 (IDE 실행 vs 테스트 실행)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;앱 실행(Run) &lt;/b&gt;&lt;/span&gt;: IntelliJ의 EnvFile 플러그인, Docker compose, 쉘 스크립트 등이 &lt;span&gt;.env&lt;/span&gt;를 읽어 환경변수로 넣어줌 (&lt;a href=&quot;https://www.jetbrains.com/help/idea/run-debug-configuration-spring-boot.html#more_options_before_launch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;테스트(Test) &lt;/b&gt;&lt;/span&gt;: Gradle/Maven 테스트 JVM은 별도 프로세스라서 위 주입이 적용되지 않음 (특히 IntelliJ에서 Run 구성과 Test 구성이 따로일 때)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 해결&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IntelliJ라면 &lt;span&gt;&lt;b&gt;Run/Debug Configurations에서 &amp;ldquo;테스트 구성&amp;rdquo;에도&lt;/b&gt;&lt;/span&gt; EnvFile(또는 Environment variables) 적용&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Gradle 사용 시 &lt;/span&gt;test { environment &quot;KEY&quot;,&quot;VALUE&quot; }&lt;span&gt;로 주입&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방법&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방법 : &lt;/span&gt;&lt;span style=&quot;color: #333333; background-color: #f6e199;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;테스트 프로필 설정에&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;.env&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;를 명시적으로 import 하도록 설정&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;import: &quot;optional:file:.env[.properties],optional:file:../.env[.properties]&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;file:.env : 현재 실행 디렉터리 기준&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048);&quot;&gt;file:../.env&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 한 단계 상위(루트) 기준&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048);&quot;&gt;[.properties]&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048);&quot;&gt;.env&lt;/span&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048);&quot;&gt;KEY=VALUE&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형식으로 파싱&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.999994 0.0000455678 0.0000200868 / 0.048);&quot;&gt;optional:&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 파일이 없어도 앱 시작 실패 방지(디버깅 시 제거 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;그 외 더 다양한 해결 방법들도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://imdeepskyblue.tistory.com/66&quot;&gt;https://imdeepskyblue.tistory.com/66&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770950564772&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;SpringBoot 애플리케이션에 .env 통합하는 방법 세가지&quot; data-og-description=&quot;안녕하세요. 저는 요즘에 합세랑 솦커톤, 과제로 인해서 프로젝트를 새로 세팅해야 하는 상황이 많았는데요!특히 환경 설정을 하면서 환경 변수를 분리하게 되는데 저는 분리하는 방법이 다양&quot; data-og-host=&quot;imdeepskyblue.tistory.com&quot; data-og-source-url=&quot;https://imdeepskyblue.tistory.com/66&quot; data-og-url=&quot;https://imdeepskyblue.tistory.com/66&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bquLRD/dJMb86OXV6O/Zl4ZeIYVqFbBRhQ4mfIpHK/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/re1Ku/dJMb8Xkbvx8/ka6I1CPpLzYGN88nRsMiKK/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225&quot;&gt;&lt;a href=&quot;https://imdeepskyblue.tistory.com/66&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://imdeepskyblue.tistory.com/66&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bquLRD/dJMb86OXV6O/Zl4ZeIYVqFbBRhQ4mfIpHK/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/re1Ku/dJMb8Xkbvx8/ka6I1CPpLzYGN88nRsMiKK/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SpringBoot 애플리케이션에 .env 통합하는 방법 세가지&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 저는 요즘에 합세랑 솦커톤, 과제로 인해서 프로젝트를 새로 세팅해야 하는 상황이 많았는데요!특히 환경 설정을 하면서 환경 변수를 분리하게 되는데 저는 분리하는 방법이 다양&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;imdeepskyblue.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;참고 자료&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 블로그 : [Spring Boot] 프로젝트에서 환경변수 설정하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://msj9965.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://msj9965.tistory.com/5&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770902415514&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring Boot] 프로젝트에서 환경변수 설정하는 방법&quot; data-og-description=&quot;1. 환경변수란?환경변수(Environment Variables)는 운영체제에서 제공하는 동적인 값으로, 애플리케이션이 실행되는 환경에 따라 설정 값을 다르게 적용할 수 있도록 도와준다. Spring Boot 프로젝트에서&quot; data-og-host=&quot;msj9965.tistory.com&quot; data-og-source-url=&quot;https://msj9965.tistory.com/5&quot; data-og-url=&quot;https://msj9965.tistory.com/5&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cty7Wg/dJMb88eWIjT/Iy0zEk7JOgIA7m93RyDEhK/img.png?width=800&amp;amp;height=800&amp;amp;face=395_372_493_480,https://scrap.kakaocdn.net/dn/Z1QPN/dJMb84XUPR3/1ZJ5GwaqqwW1YMxex2jZ80/img.png?width=800&amp;amp;height=800&amp;amp;face=395_372_493_480,https://scrap.kakaocdn.net/dn/v4ZWi/dJMb84p4O6p/WXkBmq9wwpEQDZHEPMWrd0/img.png?width=1024&amp;amp;height=1024&amp;amp;face=521_491_617_595&quot;&gt;&lt;a href=&quot;https://msj9965.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://msj9965.tistory.com/5&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cty7Wg/dJMb88eWIjT/Iy0zEk7JOgIA7m93RyDEhK/img.png?width=800&amp;amp;height=800&amp;amp;face=395_372_493_480,https://scrap.kakaocdn.net/dn/Z1QPN/dJMb84XUPR3/1ZJ5GwaqqwW1YMxex2jZ80/img.png?width=800&amp;amp;height=800&amp;amp;face=395_372_493_480,https://scrap.kakaocdn.net/dn/v4ZWi/dJMb84p4O6p/WXkBmq9wwpEQDZHEPMWrd0/img.png?width=1024&amp;amp;height=1024&amp;amp;face=521_491_617_595');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring Boot] 프로젝트에서 환경변수 설정하는 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 환경변수란?환경변수(Environment Variables)는 운영체제에서 제공하는 동적인 값으로, 애플리케이션이 실행되는 환경에 따라 설정 값을 다르게 적용할 수 있도록 도와준다. Spring Boot 프로젝트에서&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;msj9965.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>왜안되지?</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/518</guid>
      <comments>https://annovation.tistory.com/518#entry518comment</comments>
      <pubDate>Fri, 13 Feb 2026 12:48:12 +0900</pubDate>
    </item>
    <item>
      <title>[DBeaver] Access denied for user 'root@localhost' (using password : YES)</title>
      <link>https://annovation.tistory.com/517</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;문제 상황&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 상황&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-10 at 4.45.48 PM.png&quot; data-origin-width=&quot;2592&quot; data-origin-height=&quot;1706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmZlsb/dJMcacaYgSK/zHkMFzyfvE1s0FOMPAS3f1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmZlsb/dJMcacaYgSK/zHkMFzyfvE1s0FOMPAS3f1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmZlsb/dJMcacaYgSK/zHkMFzyfvE1s0FOMPAS3f1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmZlsb%2FdJMcacaYgSK%2FzHkMFzyfvE1s0FOMPAS3f1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2592&quot; height=&quot;1706&quot; data-filename=&quot;Screenshot 2026-02-10 at 4.45.48 PM.png&quot; data-origin-width=&quot;2592&quot; data-origin-height=&quot;1706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;allowPublicKeyRetrieval = true 설정 이후, Access denied for user 'root@localhost' (using password : YES) 에러 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;원인 분석&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;원인 1 : 잘못된 Password 입력&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dmg 파일과 충돌로 인해 HomeBrew MySQL 을 재설치 했더니 Password 오류가 난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방법&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방법 : Password 변경&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 실행중인 MySQL 종료&lt;/p&gt;
&lt;pre id=&quot;code_1770728329702&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql.server stop&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;brew services start mysql 로 서버를 켰다면 아래 명령어 입력&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770728841272&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services stop mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Password 없이 MySQL에 접속하기 위해 아래 명령어 입력&lt;/p&gt;
&lt;pre id=&quot;code_1770728353131&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysqld_safe --skip-grant-tables&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) root 유저로 MySQL 접속&lt;/p&gt;
&lt;pre id=&quot;code_1770728368995&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql -u root&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 터미널을 켜서 입력해야 들어가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) mysql DB 사용하도록 설정&lt;/p&gt;
&lt;pre id=&quot;code_1770728388920&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use mysql;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) 권한 초기화&lt;/p&gt;
&lt;pre id=&quot;code_1770728418238&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FLUSH PRIVILEGES;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6) 비밀번호 변경&lt;/p&gt;
&lt;pre id=&quot;code_1770728433441&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER USER 'root'@'localhost' IDENTIFIED BY '새로운비밀번호';&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i&gt;cf. 각 명령어는 반드시 세미콜론(;)으로 끝내야 하며, '새로운비밀번호' 부분에는 변경할 비밀번호를 입력하면 된다.&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7) 다시 권한 초기화&lt;/p&gt;
&lt;pre id=&quot;code_1770728447531&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FLUSH PRIVILEGES;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8) &lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;다음 exit 명령어를 통해 나갔다가 다시 접속하면 문제 없이 초기화 되어있는 것을 확인 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770728519770&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;exit;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9) 새 비밀번호로 접속 테스트&lt;/p&gt;
&lt;pre id=&quot;code_1770728538909&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql -u root -p&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10) 터미널 종료&lt;/p&gt;
&lt;pre id=&quot;code_1770729150566&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pkill mysqld
pkill mysqld_safe&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이걸 해줘야 'mysqld_safe --skip-grant-tables' 로 켜놨던 터미널이 종료된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11) MySQL 서버 재실행&lt;/p&gt;
&lt;pre id=&quot;code_1770729267712&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services start mysql&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;드디어&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;됐다..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-10 at 10.17.53 PM.png&quot; data-origin-width=&quot;2680&quot; data-origin-height=&quot;1794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq7ywK/dJMcaaRLHEO/uSlezJkT4uPkC3ReYEVKG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq7ywK/dJMcaaRLHEO/uSlezJkT4uPkC3ReYEVKG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq7ywK/dJMcaaRLHEO/uSlezJkT4uPkC3ReYEVKG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq7ywK%2FdJMcaaRLHEO%2FuSlezJkT4uPkC3ReYEVKG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2680&quot; height=&quot;1794&quot; data-filename=&quot;Screenshot 2026-02-10 at 10.17.53 PM.png&quot; data-origin-width=&quot;2680&quot; data-origin-height=&quot;1794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;참고 자료&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 블로그 : 맥 환경에서 MySQL 비밀번호 재설정하기 (ver 9.0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yermxx.tistory.com/70&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yermxx.tistory.com/70&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770728569122&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;맥 환경에서 MySQL 비밀번호 재설정하기 (ver 9.0)&quot; data-og-description=&quot;비밀번호 까먹으면 손이 고생하더라.. 비슷한 문제를 겪고 계신 분들에게 도움이 되길 바란다.&amp;nbsp;참고로 맥 환경 + MySQL 9.0 버전에서 시도했다.&amp;nbsp;&amp;nbsp;&amp;nbsp;MySQL 서버 재시작하기  &amp;nbsp;- 먼저 MySQL 서버를 중&quot; data-og-host=&quot;yermxx.tistory.com&quot; data-og-source-url=&quot;https://yermxx.tistory.com/70&quot; data-og-url=&quot;https://yermxx.tistory.com/70&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bfNWWu/dJMb9lk23JD/7NMTnqx66wbu3buvhaacC0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cacd0E/dJMb88eWwqY/LBbK7K8gRDvi5trxvPoApk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://yermxx.tistory.com/70&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yermxx.tistory.com/70&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bfNWWu/dJMb9lk23JD/7NMTnqx66wbu3buvhaacC0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cacd0E/dJMb88eWwqY/LBbK7K8gRDvi5trxvPoApk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;맥 환경에서 MySQL 비밀번호 재설정하기 (ver 9.0)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;비밀번호 까먹으면 손이 고생하더라.. 비슷한 문제를 겪고 계신 분들에게 도움이 되길 바란다.&amp;nbsp;참고로 맥 환경 + MySQL 9.0 버전에서 시도했다.&amp;nbsp;&amp;nbsp;&amp;nbsp;MySQL 서버 재시작하기  &amp;nbsp;- 먼저 MySQL 서버를 중&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yermxx.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 블로그 : Mac Mysql root계정 비밀번호 분실 해결하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev-woody.tistory.com/75&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev-woody.tistory.com/75&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770728590587&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Mac Mysql root계정 비밀번호 분실 해결하기&quot; data-og-description=&quot;brew unistall mysql을 통해서 Mysql을 지우고 다시 깔았지만, 비밀번호는 그대로 남아있어서 사용이 불가능했었다. 우선 실행중인 상태의 Mysql 서버를 종료시켜준다. mysql.server stop 다음 비밀번호 없이 &quot; data-og-host=&quot;dev-woody.tistory.com&quot; data-og-source-url=&quot;https://dev-woody.tistory.com/75&quot; data-og-url=&quot;https://dev-woody.tistory.com/75&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vC8fp/dJMb8U8Pwm3/Kl47P3vdSKSuuyNyTd0pzK/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/beMyXU/dJMb8PGr3lM/9xk5DDrwqyI6dQtkKDZR51/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/dMpEct/dJMb8RRNNPF/1UeNudYa5FU0jKjabuSPJK/img.png?width=3000&amp;amp;height=2000&amp;amp;face=0_0_3000_2000&quot;&gt;&lt;a href=&quot;https://dev-woody.tistory.com/75&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev-woody.tistory.com/75&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vC8fp/dJMb8U8Pwm3/Kl47P3vdSKSuuyNyTd0pzK/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/beMyXU/dJMb8PGr3lM/9xk5DDrwqyI6dQtkKDZR51/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/dMpEct/dJMb8RRNNPF/1UeNudYa5FU0jKjabuSPJK/img.png?width=3000&amp;amp;height=2000&amp;amp;face=0_0_3000_2000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Mac Mysql root계정 비밀번호 분실 해결하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;brew unistall mysql을 통해서 Mysql을 지우고 다시 깔았지만, 비밀번호는 그대로 남아있어서 사용이 불가능했었다. 우선 실행중인 상태의 Mysql 서버를 종료시켜준다. mysql.server stop 다음 비밀번호 없이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev-woody.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Tips ✨</category>
      <category>왜안되지?</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/517</guid>
      <comments>https://annovation.tistory.com/517#entry517comment</comments>
      <pubDate>Thu, 12 Feb 2026 09:48:28 +0900</pubDate>
    </item>
    <item>
      <title>[DBeaver] Public Key Retrieval is not allowed</title>
      <link>https://annovation.tistory.com/516</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;문제 상황&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 상황&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-10 at 4.31.34 PM.png&quot; data-origin-width=&quot;2592&quot; data-origin-height=&quot;1706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p2fNC/dJMcajnyBDS/cmx2o6C7bC7ifHv7KTJfs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p2fNC/dJMcajnyBDS/cmx2o6C7bC7ifHv7KTJfs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p2fNC/dJMcajnyBDS/cmx2o6C7bC7ifHv7KTJfs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp2fNC%2FdJMcajnyBDS%2Fcmx2o6C7bC7ifHv7KTJfs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2592&quot; height=&quot;1706&quot; data-filename=&quot;Screenshot 2026-02-10 at 4.31.34 PM.png&quot; data-origin-width=&quot;2592&quot; data-origin-height=&quot;1706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DBeaver 에서 MySQL 연결 시도 중 Public Key Retrieval is not allowed 에러 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;원인 분석&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;원인 1 : MySQL 8.0 이상 보안 정책&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보안상의 이유로 MySQL 8.0 이상 부터 기본적으로 MySQL 서버는 클라이언트가 요청하는 공개키에 대해 허용하지 않도록 지원하기 떄문이라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방법&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방법 : Driver properties -&amp;gt; allowPublicKeyRetrieval -&amp;gt; true로 변경&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-10 at 4.33.15 PM.png&quot; data-origin-width=&quot;2800&quot; data-origin-height=&quot;1706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brRKVv/dJMcah4oTgQ/XOdNOTRCkHPiMyrQ2Hw63K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brRKVv/dJMcah4oTgQ/XOdNOTRCkHPiMyrQ2Hw63K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brRKVv/dJMcah4oTgQ/XOdNOTRCkHPiMyrQ2Hw63K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrRKVv%2FdJMcah4oTgQ%2FXOdNOTRCkHPiMyrQ2Hw63K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2800&quot; height=&quot;1706&quot; data-filename=&quot;Screenshot 2026-02-10 at 4.33.15 PM.png&quot; data-origin-width=&quot;2800&quot; data-origin-height=&quot;1706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;참고 자료&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 블로그 : DBeaver(디비버) Public Key Retrieval is not allowed 에러&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://computer-science-student.tistory.com/719&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://computer-science-student.tistory.com/719&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770708751179&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;DBeaver(디비버) Public Key Retrieval is not allowed 에러&quot; data-og-description=&quot;DBeaver(디비버) Public Key Retrieval is not allowed 에러 디비버에서 MySQL 데이터베이스 접속을 잘하다가 갑자기 Public Key Retrieval is not allowed 에러를 만났다. MySQL의 8.x 버전 이후부터 발생하는 문제라고 한&quot; data-og-host=&quot;computer-science-student.tistory.com&quot; data-og-source-url=&quot;https://computer-science-student.tistory.com/719&quot; data-og-url=&quot;https://computer-science-student.tistory.com/719&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gUXTE/dJMb82MyUrE/EM9hkMxpysYpUV61O4QnVK/img.png?width=685&amp;amp;height=545&amp;amp;face=0_0_685_545,https://scrap.kakaocdn.net/dn/bmX4N3/dJMb9dHjT5n/TuxcVGD3jzRjoXiwbBRFJ1/img.png?width=685&amp;amp;height=545&amp;amp;face=0_0_685_545,https://scrap.kakaocdn.net/dn/e3tSY/dJMb9gxhfAs/PxZt3oQPuBq9JrEnqe2uWk/img.png?width=685&amp;amp;height=545&amp;amp;face=0_0_685_545&quot;&gt;&lt;a href=&quot;https://computer-science-student.tistory.com/719&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://computer-science-student.tistory.com/719&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gUXTE/dJMb82MyUrE/EM9hkMxpysYpUV61O4QnVK/img.png?width=685&amp;amp;height=545&amp;amp;face=0_0_685_545,https://scrap.kakaocdn.net/dn/bmX4N3/dJMb9dHjT5n/TuxcVGD3jzRjoXiwbBRFJ1/img.png?width=685&amp;amp;height=545&amp;amp;face=0_0_685_545,https://scrap.kakaocdn.net/dn/e3tSY/dJMb9gxhfAs/PxZt3oQPuBq9JrEnqe2uWk/img.png?width=685&amp;amp;height=545&amp;amp;face=0_0_685_545');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;DBeaver(디비버) Public Key Retrieval is not allowed 에러&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;DBeaver(디비버) Public Key Retrieval is not allowed 에러 디비버에서 MySQL 데이터베이스 접속을 잘하다가 갑자기 Public Key Retrieval is not allowed 에러를 만났다. MySQL의 8.x 버전 이후부터 발생하는 문제라고 한&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;computer-science-student.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 블로그 : Public Key Retrieval is not allowed 오류 해결하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wookku.tistory.com/272&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wookku.tistory.com/272&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770709157598&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[DBeaver] Public Key Retrieval is not allowed 오류 해결하기&quot; data-og-description=&quot;안녕하세요. 어느덧 여름이 지나고 이제는 제법 쌀쌀한 날씨가 되어가고 있습니다.낮에는 그럼에도 덥고, 아침과 저녁에는 추운 날씨네요. 저는 살짝 콧물을 훌쩍이며 글을 적고 있습니다.모두 &quot; data-og-host=&quot;wookku.tistory.com&quot; data-og-source-url=&quot;https://wookku.tistory.com/272&quot; data-og-url=&quot;https://wookku.tistory.com/272&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eJkCx/dJMb81fOuln/tDrXKefCcULEBKeNpOlVF1/img.png?width=800&amp;amp;height=315&amp;amp;face=0_0_800_315,https://scrap.kakaocdn.net/dn/cF9jP1/dJMb87f2fJO/9VAL2CU6LywgtPAI69Itf0/img.png?width=800&amp;amp;height=315&amp;amp;face=0_0_800_315,https://scrap.kakaocdn.net/dn/507pj/dJMb86OXGnZ/YdhG8kYE9bxSzSQ1FMeM11/img.png?width=886&amp;amp;height=349&amp;amp;face=0_0_886_349&quot;&gt;&lt;a href=&quot;https://wookku.tistory.com/272&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wookku.tistory.com/272&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eJkCx/dJMb81fOuln/tDrXKefCcULEBKeNpOlVF1/img.png?width=800&amp;amp;height=315&amp;amp;face=0_0_800_315,https://scrap.kakaocdn.net/dn/cF9jP1/dJMb87f2fJO/9VAL2CU6LywgtPAI69Itf0/img.png?width=800&amp;amp;height=315&amp;amp;face=0_0_800_315,https://scrap.kakaocdn.net/dn/507pj/dJMb86OXGnZ/YdhG8kYE9bxSzSQ1FMeM11/img.png?width=886&amp;amp;height=349&amp;amp;face=0_0_886_349');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[DBeaver] Public Key Retrieval is not allowed 오류 해결하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 어느덧 여름이 지나고 이제는 제법 쌀쌀한 날씨가 되어가고 있습니다.낮에는 그럼에도 덥고, 아침과 저녁에는 추운 날씨네요. 저는 살짝 콧물을 훌쩍이며 글을 적고 있습니다.모두&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wookku.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) MySQL Docs&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-security.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-security.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770709414891&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL :: MySQL Connector/J Developer Guide :: 6.3.5 Security&quot; data-og-description=&quot;MySQL Connector/J Developer Guide &amp;nbsp;/&amp;nbsp; ... &amp;nbsp;/&amp;nbsp; Connector/J Reference &amp;nbsp;/&amp;nbsp; Configuration Properties &amp;nbsp;/&amp;nbsp; Security paranoid Take measures to prevent exposure sensitive information in error messages and clear data structures holding sensitive data when p&quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-security.html&quot; data-og-url=&quot;https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-security.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-security.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-security.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL :: MySQL Connector/J Developer Guide :: 6.3.5 Security&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MySQL Connector/J Developer Guide &amp;nbsp;/&amp;nbsp; ... &amp;nbsp;/&amp;nbsp; Connector/J Reference &amp;nbsp;/&amp;nbsp; Configuration Properties &amp;nbsp;/&amp;nbsp; Security paranoid Take measures to prevent exposure sensitive information in error messages and clear data structures holding sensitive data when p&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Tips ✨</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/516</guid>
      <comments>https://annovation.tistory.com/516#entry516comment</comments>
      <pubDate>Wed, 11 Feb 2026 09:00:52 +0900</pubDate>
    </item>
    <item>
      <title>[터미널 명령어] HomeBrew 명령어</title>
      <link>https://annovation.tistory.com/515</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1) brew 로 설치한 프로그램 리스트&lt;/p&gt;
&lt;pre id=&quot;code_1770706525374&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew ls&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) MySQL 서버 켜져있는지 확인&lt;/p&gt;
&lt;pre id=&quot;code_1770706724361&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services list | grep mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) MySQL 자동 실행 (재부팅 시 자동으로 다시 켜짐)&lt;/p&gt;
&lt;pre id=&quot;code_1770706550507&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services start mysql&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Mac &lt;/span&gt;부팅할 때 자동으로 켜지고&lt;/li&gt;
&lt;li&gt;터미널을 닫아도 안 꺼지고&lt;/li&gt;
&lt;li&gt;로그아웃해도 계속 살아있고&lt;/li&gt;
&lt;li&gt;명시적으로 끄기 전까지 계속 실행되는 상태 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) MySQL 직접 끄기&lt;/p&gt;
&lt;pre id=&quot;code_1770706673495&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services stop mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) MySQL 지금만 켜기 (재부팅 시 다시 안켜짐)&lt;/p&gt;
&lt;pre id=&quot;code_1770706774731&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql.server start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-1) 서버 실행 중인지 확인&lt;/p&gt;
&lt;pre id=&quot;code_1770706920983&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pgrep -fl mysqld&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️&lt;/p&gt;
&lt;pre id=&quot;code_1770707027912&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;3741 /.../opt/homebrew/.../mysqld_safe ...
3852 /opt/homebrew/.../mysqld ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-2) 직접 끄기&lt;/p&gt;
&lt;pre id=&quot;code_1770707169973&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql.server stop&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Tips ✨</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/515</guid>
      <comments>https://annovation.tistory.com/515#entry515comment</comments>
      <pubDate>Tue, 10 Feb 2026 16:04:00 +0900</pubDate>
    </item>
    <item>
      <title>[MSA] Spring Cloud Config Client 동작 구조 (with Spring Docs)</title>
      <link>https://annovation.tistory.com/514</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;구조&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Config Client와 Config Server 연동&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Config Client는 Spring Boot 앱이 Config Server와 통신하는 역할&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;455&quot; data-start=&quot;365&quot;&gt;Spring Boot 애플리케이션이 Config Client를 포함하면&lt;br /&gt;&amp;rarr; Config Server에 HTTP로 접근해서 설정 데이터를 받아온다.&lt;/li&gt;
&lt;li data-end=&quot;537&quot; data-start=&quot;456&quot;&gt;받아온 설정 데이터는 &lt;b&gt;Spring Environment&lt;/b&gt;에 반영된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;ldquo;A Spring Boot application can take immediate advantage of the Spring Config Server (or other external property sources)&amp;hellip; and initializes Spring Environment with remote property sources. (&lt;a href=&quot;https://cloud.spring.io/spring-cloud-config/multi/multi__spring_cloud_config_client.html?utm_source&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 즉, Config Client는 &lt;b&gt;원격 중앙 설정 저장소(Config Server)&lt;/b&gt; 에서 설정을 가져와 Spring 애플리케이션의 기존 설정 환경에 &amp;ldquo;로딩&amp;rdquo;하는 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Config Server 설정 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Config Server 데이터를 가져오려면 아래와 같은 설정이 기본 형태&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770702658061&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.config.import=optional:configserver:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 이 설정이 있으면 Config Server를 기본적으로 http://localhost:8888로 연결 시도&lt;br /&gt;&amp;rarr; Optional prefix를 제거하면, 서버 연결 실패 시 바로 예외를 발생시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 레거시 방식인 &lt;b&gt;bootstrap&lt;/b&gt; 기반 연결도 지원&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 경우 Config Server 주소를 bootstrap.yml에 설정하고 Config Client가 시작하면서 바로 서버와 연결된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;핵심 구조 요약&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1770702873432&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Spring Boot App
    │ (spring.cloud.config on classpath)
    ▼
Config Client 자동 구성
    │
    │ 1. spring.config.import 또는 bootstrap 설정으로 Config Server 위치 확인
    ▼
Config Server (HTTP endpoint, default localhost:8888)
    │
    │ 2. Remote config file(e.g., git repo)에서 설정 읽기
    ▼
Config Client
    │
    │ 3. Spring Environment에 remote properties merge
    ▼
ApplicationContext 완성&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-cloud-config/reference/client.html?utm_source&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770646123174&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Cloud Config Client :: Spring Cloud Config&quot; data-og-description=&quot;Retry works with the Spring Boot spring.config.import statement and the normal properties work. However, if the import statement is in a profile, such as application-prod.properties, then you need a different way to configure retry. Configuration needs to &quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html?utm_source=chatgpt.com&quot; data-og-url=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html?utm_source=chatgpt.com&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html?utm_source=chatgpt.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Cloud Config Client :: Spring Cloud Config&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Retry works with the Spring Boot spring.config.import statement and the normal properties work. However, if the import statement is in a profile, such as application-prod.properties, then you need a different way to configure retry. Configuration needs to&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>심화/MSA</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/514</guid>
      <comments>https://annovation.tistory.com/514#entry514comment</comments>
      <pubDate>Mon, 9 Feb 2026 23:22:47 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] MySQL 8.0.45-arm 64 켜졌다 꺼졌다 반복 on MacOs</title>
      <link>https://annovation.tistory.com/513</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;문제 상황&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 상황&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맥 8.0.45-arm 64 환경에서 MySQL 서버가 켜졌다 꺼졌다를 반복하는 오류 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt; &lt;span style=&quot;background-color: #f6e199; color: #222222; text-align: start;&quot;&gt;문제 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;증상&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-07 at 10.38.17 PM.png&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;1816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eryWU5/dJMcac22EMB/EiVWuM3BKLmcUEwmCE7rpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eryWU5/dJMcac22EMB/EiVWuM3BKLmcUEwmCE7rpk/img.png&quot; data-alt=&quot;ON&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eryWU5/dJMcac22EMB/EiVWuM3BKLmcUEwmCE7rpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeryWU5%2FdJMcac22EMB%2FEiVWuM3BKLmcUEwmCE7rpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1992&quot; height=&quot;1816&quot; data-filename=&quot;Screenshot 2026-02-07 at 10.38.17 PM.png&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;1816&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ON&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-07 at 10.37.56 PM.png&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;1816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3a4c8/dJMcaiIWVWs/qX2nvXSxYx2LKKqWac38Yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3a4c8/dJMcaiIWVWs/qX2nvXSxYx2LKKqWac38Yk/img.png&quot; data-alt=&quot;OFF&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3a4c8/dJMcaiIWVWs/qX2nvXSxYx2LKKqWac38Yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3a4c8%2FdJMcaiIWVWs%2FqX2nvXSxYx2LKKqWac38Yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1992&quot; height=&quot;1816&quot; data-filename=&quot;Screenshot 2026-02-07 at 10.37.56 PM.png&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;1816&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OFF&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;원인 분석&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;원인 1 : HomeBrew 로 설치한 MySQL 과 Community Server 에서 설치한 dmg 버전 충돌&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) bash : HomeBrew 에 MySQL 설치되어있는지 확인&lt;/p&gt;
&lt;pre id=&quot;code_1770471890952&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;which mysql
mysql --version
brew list | grep mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️&lt;/p&gt;
&lt;pre id=&quot;code_1770471925620&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/opt/homebrew/bin/mysql
mysql  Ver 9.0.1 for macos15.1 on arm64 (Homebrew)
mysql&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL 클라이언트는 Homebrew 버전(9.0.1)을 쓰고 있다는 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) bash : MySQL 서버가 동작 중인지 확인&lt;/p&gt;
&lt;pre id=&quot;code_1770471946793&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ps aux | grep mysqld&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 버전 2개&lt;/p&gt;
&lt;pre id=&quot;code_1770472929327&quot; class=&quot;dts&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;/usr/local/mysql/bin/mysqld ... --datadir=/usr/local/mysql/data ...
/opt/homebrew/opt/mysql/bin/mysqld ... --datadir=/opt/homebrew/var/mysql ...&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공식 dmg(installer)로 설치된 MySQL 서버와 Homebrew로 설치된 MySQL 서버가 동시에 동작 중임을 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;분석 : 같은 포트(기본 3306)을 동시에 사용하려고 함&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금 어떤 MySQL 서버를 켰는지/어떤 클라이언트를 실행했는지 / 어떤 소켓&amp;middot;포트를 보고 있는지가 섞이기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방안 검토&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방안 1 : dmg 버전 제거, HomeBrew 만 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 우선 충돌 멈추기위해 dmg 버전과 HomeBrew 버전 정지&lt;/p&gt;
&lt;pre id=&quot;code_1770472730840&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;launchctl bootout gui/$(id -u)/homebrew.mxcl.mysql 2&amp;gt;/dev/null
launchctl disable gui/$(id -u)/homebrew.mxcl.mysql 2&amp;gt;/dev/null
pkill -9 mysqld_safe
pkill -9 mysqld&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;bootout&lt;/span&gt; &amp;rarr; launchd에서 MySQL 자동 실행 &lt;span&gt;&lt;b&gt;언로드&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;disable&lt;/span&gt;&lt;span&gt; &amp;rarr; 재부팅해도 &lt;/span&gt;&lt;b&gt;다시 안 올라오게 차단&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;그 다음 &lt;span&gt;pkill&lt;/span&gt; &amp;rarr; 이제 감시자가 없으니 &lt;span&gt;&lt;b&gt;진짜로 종료&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 꺼졌다 켜졌다를 반복하기 때문에 PID (Process ID, 현재 실행중인 프로그램에 붙는 고유 번호) 를 알 수 없기 때문에 강제로 종료시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 제거 되었는지 확인&lt;/p&gt;
&lt;pre id=&quot;code_1770611197444&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pkill -9 mysqld
pgrep -fl mysqld || echo &quot;mysqld 없음&quot;
mysqld 없음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) dmg MySQL 완전 제거&lt;/p&gt;
&lt;pre id=&quot;code_1770472075455&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo /usr/local/mysql/bin/mysql.server stop

sudo rm -rf /usr/local/mysql
sudo rm -rf /usr/local/mysql-*

sudo rm -rf /Library/StartupItems/MySQLCOM

sudo rm -rf /Library/PreferencePanes/MySQL.prefPane

sudo rm -rf /Library/LaunchDaemons/com.oracle.oss.mysql.mysqld.plist

sudo rm -rf /var/db/receipts/com.mysql.*&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 의미는 차례대로 :
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;DMG(MySQL.pkg) 버전 서비스 중지&lt;/li&gt;
&lt;li&gt;DMG 버전 본체 디렉토리 삭제&lt;/li&gt;
&lt;li&gt;아주 옛날 DMG 버전에서 쓰던 &lt;span&gt;부팅 자동 실행 스크립트 삭제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;시스템 설정에 보이는 MySQL 설정 패널 제거&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Oracle DMG MySQL 자동 실행 launchd 데몬&lt;/li&gt;
&lt;li&gt;macOS의 설치 기록을 지우는 명령&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 비밀번호는 mac 로그인 password&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) dmg 버전 삭제되었는지 확인&lt;/p&gt;
&lt;pre id=&quot;code_1770611636227&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ls -d /usr/local/mysql*&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 아무것도 안나오면 삭제된 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770611653506&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo launchctl list | grep -i oracle || echo &quot;oracle mysql 데몬 없음&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) Homebrew 서비스로 실행&lt;/p&gt;
&lt;pre id=&quot;code_1770472150493&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services start mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6) 상태 확인&lt;/p&gt;
&lt;pre id=&quot;code_1770472167010&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services list
mysqladmin ping&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 실행이 안될 경우&lt;/p&gt;
&lt;pre id=&quot;code_1770612048747&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;error: 'Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)'&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에러 메세지&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770611981114&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ls -ld /opt/homebrew/var/mysql || mkdir -p /opt/homebrew/var/mysql&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 디렉토리 존재 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770612069934&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/opt/homebrew/opt/mysql/bin/mysqld_safe \
  --datadir=/opt/homebrew/var/mysql \
  --socket=/tmp/mysql.sock &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;에러가 &lt;/span&gt;/tmp/mysql.sock&lt;span&gt; 없다고 했으니, 서버 실행 (socket을 /tmp/mysql.sock로 고정)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770612158066&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/opt/homebrew/opt/mysql/bin/mysqladmin --socket=/tmp/mysql.sock ping&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;살아있는지 확인 (socket 지정)&lt;/li&gt;
&lt;li&gt;mysqld is alive&lt;span&gt; 나오면 성공&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770612201206&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/opt/homebrew/opt/mysql/bin/mysql --socket=/tmp/mysql.sock -u root&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;접속&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770612217308&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tail -n 200 /opt/homebrew/var/mysql/*.err&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안켜지면 에러 로그 보기&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방법&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방법 : HomeBrew MySQL 재설치 ^_^&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://juzdalua.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://juzdalua.tistory.com/127&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770640355512&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;MySQL) M1 macbook MySQL 완전 삭제&quot; data-og-description=&quot;mysql을 설치했다 지웠는데 재설치 후 에러가 난다면, 기존에 삭제되지 않은 설정 값 때문일 수 있다. # mysql 강제종료 ps -ef | grep mysql kill -9 [mysql PID] # 삭제 brew services stop mysql brew uninstall mysql # 파&quot; data-og-host=&quot;juzdalua.tistory.com&quot; data-og-source-url=&quot;https://juzdalua.tistory.com/127&quot; data-og-url=&quot;https://juzdalua.tistory.com/127&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/F0n6v/dJMb89x9knr/Xu2VmFIf8WgKQxB4jyzYiK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bGCPhz/dJMb86OXBBB/708MgkZlo8CEmvZRiT1qI1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://juzdalua.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://juzdalua.tistory.com/127&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/F0n6v/dJMb89x9knr/Xu2VmFIf8WgKQxB4jyzYiK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bGCPhz/dJMb86OXBBB/708MgkZlo8CEmvZRiT1qI1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL) M1 macbook MySQL 완전 삭제&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;mysql을 설치했다 지웠는데 재설치 후 에러가 난다면, 기존에 삭제되지 않은 설정 값 때문일 수 있다. # mysql 강제종료 ps -ef | grep mysql kill -9 [mysql PID] # 삭제 brew services stop mysql brew uninstall mysql # 파&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;juzdalua.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Tips ✨</category>
      <category>왜안되지?</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/513</guid>
      <comments>https://annovation.tistory.com/513#entry513comment</comments>
      <pubDate>Sat, 7 Feb 2026 22:41:12 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] Trouble Shooting : 테스트 코드 Config Client 오류</title>
      <link>https://annovation.tistory.com/512</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;문제 상황&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 상황&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stock 테스트 코드에서 yml 설정으로 config server 를 false 설정 했음에도 관련 오류로 인해 테스트 실행이 안되는 상황 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 코드&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- StockServiceTest.java&lt;/p&gt;
&lt;pre id=&quot;code_1770386299966&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ActiveProfiles(&quot;test&quot;)
@SpringBootTest
class StockServiceImplTest {

    ...
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- application-test.yml&lt;/p&gt;
&lt;pre id=&quot;code_1770386427380&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  cloud:
    config:
      enabled: false
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/hubeleven_test?useSSL=false&amp;amp;serverTimezone=Asia/Seoul&amp;amp;characterEncoding=UTF-8
    username: 비밀
    password: 비밀
  jpa:
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        format_sql: true
    show-sql: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199; color: #222222; text-align: start;&quot;&gt;문제&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;증상&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770386751498&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ConfigClientFailFastException: Could not locate PropertySource and the resource is not optional
ResourceAccessException: I/O error on GET request for &quot;http://localhost:8888/...&quot;: Connection refused&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;원인 분석&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;원인 1 : Config Server 연결 시도&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot 2.4+ / Spring Cloud&lt;span&gt; 조합에서 &lt;/span&gt;&lt;span&gt;spring.config.import=configserver:&lt;/span&gt;&lt;span&gt; 가 &lt;/span&gt;(application.yml 등 다른 파일에)&lt;span&gt; 있으면, &lt;/span&gt;application-local.yml이 다 로드되기 전에&lt;span&gt; configserver import를 먼저 처리하려고 시도한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;원인 2 : application-test.yml .env 환경 변수 읽어오기 실패&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770877754304&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/hubeleven_test?allowPublicKeyRetrieval=true&amp;amp;useSSL=false&amp;amp;serverTimezone=Asia/Seoul&amp;amp;characterEncoding=UTF-8
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 yml 에서 ${DB_USERNAME} 과 ${DB_PASSWORD} 을 불러오지 못한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방안 검토&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방안 1 : main/resources/application.yml&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래의 test/resources/application-test.yml 에 config server 를 불러오지 않도록 설정된 값이 작동할 수 있게 main yml 파일을 수정해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770878351659&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  cloud:
    config:
      enabled: false&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래와 같이 import 에 optional: 을 설정해줘야 test yml 에서 config server false 가 제대로 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770878463380&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  application:
    name: product-service
  config:
    import: &quot;optional:configserver:&quot;
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ optional: 은 Config Server 에서 설정을 못 가져와도 애플리케이션을 중단시키지 않고 계속 기동하게 만드는 방식 (&lt;a href=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ spring.config.import: 에 지정된 경로/서비스가 발견되면, Spring Boot 초기화 과정에서 추가적인 설정 데이터로 로딩되고 그 설정은 기존 설정보다 우선순위가 높게 적용된다. (&lt;a href=&quot;https://docs.spring.io/spring-boot/reference/features/external-config.html?utm_source=chatgpt.com#features.external-config.files.importing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방안 2 : 일단 루트에 있는 .env 파일을 못읽어와서 테스트용 username 과 password 는 변수처리 하지 않고 명시적으로 작성&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770901669831&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  cloud:
    config:
      enabled: false
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/hubeleven_test?allowPublicKeyRetrieval=true&amp;amp;useSSL=false&amp;amp;serverTimezone=Asia/Seoul&amp;amp;characterEncoding=UTF-8
    username: test
    password: test&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 yml 파일에서 루트에 있는 .env 파일 읽어오는 방법을 따로 찾아봐야할 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;해결 방법&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;해결 방법 : 근데 결국 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;${DB_USERNAME},&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;${DB_PASSWORD} 얘네가 문제였다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음에 optional 도 붙여서 실행했는데도 테스트 코드 오류 났었는데, 이걸 해결하니까 다 잘 작동 된다..&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span&gt;참고 자료&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Spring Docs&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-cloud-config/reference/client.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770898928815&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Cloud Config Client :: Spring Cloud Config&quot; data-og-description=&quot;Retry works with the Spring Boot spring.config.import statement and the normal properties work. However, if the import statement is in a profile, such as application-prod.properties, then you need a different way to configure retry. Configuration needs to &quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html&quot; data-og-url=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-cloud-config/reference/client.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Cloud Config Client :: Spring Cloud Config&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Retry works with the Spring Boot spring.config.import statement and the normal properties work. However, if the import statement is in a profile, such as application-prod.properties, then you need a different way to configure retry. Configuration needs to&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/512</guid>
      <comments>https://annovation.tistory.com/512#entry512comment</comments>
      <pubDate>Fri, 6 Feb 2026 22:55:29 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] Java 에서 발생하는 동시성 문제란?</title>
      <link>https://annovation.tistory.com/508</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Java 동시성 문제 (Race Condition, 레이스 컨디션)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Java 동시성 문제란?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java에서 여러 스레드가 &lt;b&gt;같은 데이터(필드/객체 상태)&lt;/b&gt; 를 공유하며 실행될 때, 올바른 제어 없이 접근하면 오류가 생긴다.&lt;/li&gt;
&lt;li&gt;이렇게 여러 스레드가 공유 메모리(객체, 필드, 배열 등)에 접근할 때 명확한 동기화 규칙이 없어서, 값이 다르게 보이거나 실행 순서가 뒤틀리는 현상을 뜻한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;동시성 문제가 발생하는 이유&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Thread interference (스레드 간 간섭)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 스레드가 같은 데이터에 접근/수정하면서 연산이 서로 끼어들어 결과가 깨지는 문제 (전형적인 race condition의 형태)&lt;/li&gt;
&lt;li&gt;Java의 모든 객체의 필드, static 변수, 배열 요소는 힙 메모리에 존재하는데, 이 영역은 여러 스레드가 동시에 읽고, 쓸수 있기 때문에 접근 순서를 규정하지 않으면 문제가 발생한다. (&amp;sect;17.4.1 Shared Variables)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Memory consistency errors (메모리 일관성 오류)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 스레드가 쓴 값이 다른 스레드에서 &lt;b&gt;제때 보이지 않거나(stale)&lt;/b&gt;, 실행 순서가 기대와 다르게 관찰되는 문제(가시성/순서 문제)&lt;/li&gt;
&lt;li&gt;(&amp;sect;17.4 Memory Model, &amp;sect;17.4-A/B 예제)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;멀티스레드로 처리할 때 레이스 컨디션이 일어나는 이유&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;예상 작업 순서&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 39.7674%;&quot;&gt;&lt;span&gt;&lt;b&gt;Thread-1&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;&lt;span&gt;&lt;b&gt;Stock&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;b&gt;Thread-2&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 39.7674%;&quot;&gt;&lt;span&gt;select *from stockwhere id = 1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;&lt;span&gt;{id: 1, quantity: 5}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 39.7674%;&quot;&gt;&lt;span&gt;update set quantity = 4from stockwhere id = 1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;&lt;span&gt;{id: 1, quantity: 4}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 39.7674%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;{id: 1, quantity: 4}&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;&lt;span&gt;select *from stockwhere id = 1&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 39.7674%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;{id: 1, quantity: 3}&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;&lt;span&gt;update set quantity = 3from stockwhere id = 1&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Thread-1 이 데이터를 가져가 update 한 값을 Thread-2 가 가져간 이후 update 하는 것을 예상&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;실제 작업 순서&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 39.7675%;&quot;&gt;&lt;span&gt;&lt;b&gt;Thread-1&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;&lt;span&gt;&lt;b&gt;Stock&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.3953%;&quot;&gt;&lt;span&gt;&lt;b&gt;Thread-2&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 39.7675%;&quot;&gt;&lt;span&gt;select *from stockwhere id = 1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;{id: 1, quantity: 5}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.3953%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 39.7675%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;&lt;span&gt;{id: 1, quantity: 5}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.3953%;&quot;&gt;&lt;span&gt;select *from stockwhere id = 1&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 39.7675%;&quot;&gt;&lt;span&gt;update set quantity = 4from stockwhere id = 1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;&lt;span&gt;{id: 1, quantity: 4}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.3953%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 39.7675%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;&lt;span&gt;{id: 1, quantity: 4}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.3953%;&quot;&gt;&lt;span&gt;update set quantity = 4from stockwhere id = 1&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만, 실제로는 Thread-1 이 데이터를 가져가 update 하기 전에 Thread-2 가 값을 읽고 각각 update 를 수행하면서 한 쪽 변경이 덮어써지는 Lost Update 가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;출처&lt;/span&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Oracle Docs : Synchronization&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html?&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770210103499&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Synchronization (The Java&amp;trade; Tutorials &amp;gt;        
            Essential Java Classes &amp;gt; Concurrency)&quot; data-og-description=&quot;The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Dev.java for updated tutorials taking advantag&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html?&quot; data-og-url=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html?&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html?&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html?&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Synchronization (The Java&amp;trade; Tutorials &amp;gt; Essential Java Classes &amp;gt; Concurrency)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Dev.java for updated tutorials taking advantag&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Oracle Docs : Chapter 17. Threads and Locks&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770210568834&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Chapter&amp;nbsp;17.&amp;nbsp;Threads and Locks&quot; data-og-description=&quot;class A { final int x; A() { x = 1; } int f() { return d(this,this); } int d(A a1, A a2) { int i = a1.x; g(a1); int j = a2.x; return j - i; } static void g(A a) { // uses reflection to change a.x to 2 } } In the d method, the compiler is allowed to reorder&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Chapter&amp;nbsp;17.&amp;nbsp;Threads and Locks&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;class A { final int x; A() { x = 1; } int f() { return d(this,this); } int d(A a1, A a2) { int i = a1.x; g(a1); int j = a2.x; return j - i; } static void g(A a) { // uses reflection to change a.x to 2 } } In the d method, the compiler is allowed to reorder&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 블로그 : 재고시스템으로 알아보는 동시성 이슈 해결방법 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@kdmin0706/Java-Spring-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%951&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@kdmin0706/Java-Spring&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770296707464&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Java &amp;amp; Spring] 재고시스템으로 알아보는 동시성이슈 해결방법(1)&quot; data-og-description=&quot;인프런 &amp;quot;재고시스템으로 알아보는 동시성이슈 해결방법&amp;quot; 강의를 듣고 작성한 글입니다. 동시성 문제란!? 동일한 데이터에 2개 이상의 스레드 혹은, 세션에서 가변 데이터를 동시에 제어할 때 나&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@kdmin0706/Java-Spring-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%951&quot; data-og-url=&quot;https://velog.io/@kdmin0706/Java-Spring-재고시스템으로-알아보는-동시성이슈-해결방법1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zkePs/dJMb9frAUsM/2tacx5Fk85q4hJtI0y83Yk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/LDNvC/dJMb9iICzGj/CucCtKTBr1wKhjhKiCryL0/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/UzJbE/dJMb9iaMAmi/Z3u5PVQkifpYyvaXdlFxKK/img.jpg?width=1080&amp;amp;height=1440&amp;amp;face=337_258_764_1075&quot;&gt;&lt;a href=&quot;https://velog.io/@kdmin0706/Java-Spring-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%951&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@kdmin0706/Java-Spring-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%951&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zkePs/dJMb9frAUsM/2tacx5Fk85q4hJtI0y83Yk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/LDNvC/dJMb9iICzGj/CucCtKTBr1wKhjhKiCryL0/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/UzJbE/dJMb9iaMAmi/Z3u5PVQkifpYyvaXdlFxKK/img.jpg?width=1080&amp;amp;height=1440&amp;amp;face=337_258_764_1075');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Java &amp;amp; Spring] 재고시스템으로 알아보는 동시성이슈 해결방법(1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인프런 &quot;재고시스템으로 알아보는 동시성이슈 해결방법&quot; 강의를 듣고 작성한 글입니다. 동시성 문제란!? 동일한 데이터에 2개 이상의 스레드 혹은, 세션에서 가변 데이터를 동시에 제어할 때 나&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 블로그 : &lt;span style=&quot;color: #000000; letter-spacing: -1px; text-align: center; background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;[재고시스템으로 알아보는 동시성 이슈 해결방법] #2 재고감소 로직 작성 및 테스트&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; letter-spacing: -1px; text-align: center; background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;&lt;a href=&quot;https://dev-rosiepoise.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev-rosiepoise.tistory.com/129&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770297546552&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[재고시스템으로 알아보는 동시성 이슈 해결방법] #2 재고감소 로직 작성 및 테스트&quot; data-og-description=&quot;들어가기 전 ..1. 이전 게시글과 이어지는 게시글이므로 프로젝트 환경 및 세팅은 아래 링크에서 확인해주세요!2. 전체 코드는 아래의 Git에서 확인 가능합니다!참고하면 좋을 이전 글&amp;nbsp;[재고시스&quot; data-og-host=&quot;dev-rosiepoise.tistory.com&quot; data-og-source-url=&quot;https://dev-rosiepoise.tistory.com/129&quot; data-og-url=&quot;https://dev-rosiepoise.tistory.com/129&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cX3UuM/dJMb9bvXKZ6/V0TjL5pICTFC4qXcvS7yhk/img.png?width=800&amp;amp;height=231&amp;amp;face=0_0_800_231,https://scrap.kakaocdn.net/dn/MaIIn/dJMb9gxgPgI/P35RMrGx639U3qMCqsvTGK/img.png?width=800&amp;amp;height=231&amp;amp;face=0_0_800_231,https://scrap.kakaocdn.net/dn/C4iod/dJMb8YpQRrE/qTlVXqW3HewyH9na9q6IbK/img.png?width=2643&amp;amp;height=649&amp;amp;face=0_0_2643_649&quot;&gt;&lt;a href=&quot;https://dev-rosiepoise.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev-rosiepoise.tistory.com/129&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cX3UuM/dJMb9bvXKZ6/V0TjL5pICTFC4qXcvS7yhk/img.png?width=800&amp;amp;height=231&amp;amp;face=0_0_800_231,https://scrap.kakaocdn.net/dn/MaIIn/dJMb9gxgPgI/P35RMrGx639U3qMCqsvTGK/img.png?width=800&amp;amp;height=231&amp;amp;face=0_0_800_231,https://scrap.kakaocdn.net/dn/C4iod/dJMb8YpQRrE/qTlVXqW3HewyH9na9q6IbK/img.png?width=2643&amp;amp;height=649&amp;amp;face=0_0_2643_649');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[재고시스템으로 알아보는 동시성 이슈 해결방법] #2 재고감소 로직 작성 및 테스트&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가기 전 ..1. 이전 게시글과 이어지는 게시글이므로 프로젝트 환경 및 세팅은 아래 링크에서 확인해주세요!2. 전체 코드는 아래의 Git에서 확인 가능합니다!참고하면 좋을 이전 글&amp;nbsp;[재고시스&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev-rosiepoise.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/508</guid>
      <comments>https://annovation.tistory.com/508#entry508comment</comments>
      <pubDate>Wed, 4 Feb 2026 22:04:10 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] 재고 감소 통합 테스트 코드 7 - 멀티 스레드</title>
      <link>https://annovation.tistory.com/507</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;멀티 스레드 테스트 코드&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;진행 환경&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 17&lt;/li&gt;
&lt;li&gt;Spring Boot 3.5.7&lt;/li&gt;
&lt;li&gt;MySQL 8.0.44&lt;/li&gt;
&lt;li&gt;Gradle&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;StockCurrencyTest.java&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770128784236&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootTest
public class StockCurrencyTest {

    @Autowired
    private StockServiceImpl stockServiceImpl;

    @Autowired
    private StockRepository stockRepository;

    @Autowired
    private JpaStockRepository jpaStockRepository;

    @Autowired
    private JpaProductRepository jpaProductRepository;

    private Product product;

    @BeforeEach
    void setUp() {
        product = ProductFixture.createDefault();
        jpaProductRepository.saveAndFlush(product);

        Stock stock = StockFixture.createFromProductWithQuantity(product, 100);
        jpaStockRepository.saveAndFlush(stock);
    }

    @AfterEach
    void tearDown() {
        jpaStockRepository.deleteAll();
        jpaProductRepository.deleteAll();
    }

    @Test
    @DisplayName(&quot;재고 감소 - 100개 동시 요청 시 정확히 차감&quot;)
    void decreaseStock_when100ConcurrentRequests_thenSuccess() throws InterruptedException {

        // given
        int threadCount = 100;

        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);

		// when
        for (int i = 0; i &amp;lt; threadCount; i++) {
            executorService.submit(() -&amp;gt; {

                try {
                    stockServiceImpl.decreaseStock(
                            StockFixture.decreaseRequest(product, 1)
                    );
                } finally {
                    countDownLatch.countDown();
                }
            });
        }

        countDownLatch.await();

		// then
        Stock updatedStock = stockRepository
                .findByProductId(product.getProductId())
                .orElseThrow();

        assertEquals(0, updatedStock.getQuantity());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재고가 100개인 상품에 대해&amp;nbsp;100개의 스레드가 동시에 재고 1개씩 감소 요청&lt;span&gt; &amp;rarr; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;최종 재고가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;0이 되는지&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;검증&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;코드 흐름&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;given, when, then&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) given : 테스트 준비&lt;/p&gt;
&lt;pre id=&quot;code_1770266715433&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int threadCount = 100;
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
CountDownLatch countDownLatch = new CountDownLatch(threadCount);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;threadCount = 100 &amp;rarr; 동시에 실행할 작업(요청) 수&lt;/li&gt;
&lt;li&gt;ExecutorService &amp;rarr; &lt;b&gt;100개의 스레드를 동시에 실행&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하기 위한 스레드 풀&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Java에서 멀티스레딩을 쉽게 관리하기 위한 인터페이스&lt;/li&gt;
&lt;li&gt;new Thread()를 여러 개 생성하는 대신 미리 만들어진 스레드 풀에서 스레드를 재사용&lt;/li&gt;
&lt;li&gt;execute(), submit() 등의 메서드를 사용해 비동기 작업 실행 가능 (&lt;a href=&quot;https://girokeulhaja.tistory.com/107&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CountDownLatch(100) &lt;span&gt;&amp;rarr; &lt;/span&gt;&lt;b&gt;모든 스레드가 작업을 끝낼 때까지 기다리기 위한 장치&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;각 스레드가 끝날 때마다 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;countDown()&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;latch.await()이 호출되면 요청이 모두 끝날 때까지 대기 후 다음 코드 실행 (&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://girokeulhaja.tistory.com/107&quot;&gt;출처&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;메인 스레드는 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;await()&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;로 대기&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;2) when : 동시 요청 실행&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770266866783&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (int i = 0; i &amp;lt; threadCount; i++) {
    executorService.submit(() -&amp;gt; {
        try {
            stockServiceImpl.decreaseStock(
                    StockFixture.decreaseRequest(product, 1)
            );
        } finally {
            countDownLatch.countDown();
        }
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;100번 반복 &amp;rarr; &lt;/span&gt;&lt;b&gt;100개의 작업 제출&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;즉, &lt;b&gt;100명이 동시에 같은 상품 재고를 1개씩 감소시키는 상황&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 작업은 :
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;decreaseStock(product, 1)&lt;span&gt; 호출&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;같은 상품&lt;/li&gt;
&lt;li&gt;재고 1개 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;finally&lt;span&gt;에서 &lt;/span&gt;countDownLatch.countDown()
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;예외가 나도 반드시 호출되게 보장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 모든 스레드 종료 대기&lt;/p&gt;
&lt;pre id=&quot;code_1770266970946&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;countDownLatch.await();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 테스트 스레드가 여기서 멈춤&lt;/li&gt;
&lt;li&gt;&lt;b&gt;100개의 재고 감소 요청이 모두 끝날 때까지 대기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이 줄이 없으면&amp;nbsp;&amp;rarr; 아직 작업이 끝나기 전에 결과를 조회해버릴 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) then : 결과 검증&lt;/p&gt;
&lt;pre id=&quot;code_1770267012345&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stock updatedStock = jpaStockRepository
        .findByProductId(product.getProductId())
        .orElseThrow();

assertEquals(0, updatedStock.getQuantity());&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB에서 최신 재고 조회&lt;/li&gt;
&lt;li&gt;최종 재고 수량이 &lt;span&gt;&lt;b&gt;0인지 확인&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/507</guid>
      <comments>https://annovation.tistory.com/507#entry507comment</comments>
      <pubDate>Tue, 3 Feb 2026 23:27:14 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 변수(Variables) 헷갈리는 문법 정리</title>
      <link>https://annovation.tistory.com/506</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;변수의 값&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수에 값을 대입하는 것은 변수에 들어있는 값을 복사해서 대입하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;기본형&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1)&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769955303789&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;int a = 10;
int b = a;

a = 20; // a 값 변경&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 b는 그대로 10이 나온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)&lt;/p&gt;
&lt;pre id=&quot;code_1769955428908&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
	int a = 10;
	changePrimitive(a);

	static void changePrimitive(int x) {
				x = 20;
				
		}
	
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;a = 10, x에 int 값 10이 복사되어 넘어가므로 x = 10&lt;/li&gt;
&lt;li&gt;changePrimitive에서 x = 20으로 바뀌므로 최종 결과는 a = 10, x = 20&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;참조형&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769955523408&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Data dataA = new Data();
dataA.value = 10;
Data dataB = dataA;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dataA의 값을 바꾸나, dataB의 값을 바꾸나, 참조하고 있는 메모리 주소값이 같기 때문에 dataA.value와 dataB.value의 값은 언제나 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;변수 초기화&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;멤버 변수 : 자동 초기화&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인스턴스의 멤버 변수는 인스턴스를 생성할 때 자동으로 초기화된다.&lt;/li&gt;
&lt;li&gt;int = 0, boolean = false, 참조형 = null&lt;/li&gt;
&lt;li&gt;개발자가 초기값을 직접 지정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;지역 변수 : 수동 초기화&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지역 변수는 항상 직접 초기화해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Java의 변수 타입 기본형(Primitive Type) vs 참조형 (Reference Type)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;기본형 (Primitive Type)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int, long, double, boolean 같이 변수에 직접 값을 넣을 수 있는 데이터 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;참조형 (Reference Type)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체와 배열같이 데이터에 접근하기 위해 참조 주소를 변수에 저장하는 데이터 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;형변환 (Type Casting)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 타입끼리 계산은 값을 타입 반환 ex. int + int = int, double + double = double&lt;/li&gt;
&lt;li&gt;서로 다른 타입의 계산은 큰 범위로 자동 형변환 ex. int + double = double + double&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;출처&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8/dashboard?cid=332505&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8/dashboard?cid=332505&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769955025858&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;[지금 무료]김영한의 자바 입문 - 코드로 시작하는 자바 첫걸음| 김영한 - 인프런 강의&quot; data-og-description=&quot;현재 평점 5.0점 수강생 54,098명인 강의를 만나보세요. 프로그래밍에 처음 입문하는 분들을 위한 자바 강의입니다. 코드를 따라하면서 손쉽게 자바를 배울 수 있습니다. 자바(Java) 프로그래밍 언&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8/dashboard?cid=332505&quot; data-og-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8?cid=332505&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qCTlM/dJMb8Zvwri5/e3chmAALFOtjLiTTgpBSBk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/cWDo5B/dJMb8XkarDr/1Ab9sTPwnya9XTiOfXLemk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dc3MzP/dJMb8SpCXDL/f4NC0QpRSZljRj0bdvMjP1/img.jpg?width=1439&amp;amp;height=1441&amp;amp;face=171_677_281_798&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8/dashboard?cid=332505&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8/dashboard?cid=332505&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qCTlM/dJMb8Zvwri5/e3chmAALFOtjLiTTgpBSBk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/cWDo5B/dJMb8XkarDr/1Ab9sTPwnya9XTiOfXLemk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dc3MzP/dJMb8SpCXDL/f4NC0QpRSZljRj0bdvMjP1/img.jpg?width=1439&amp;amp;height=1441&amp;amp;face=171_677_281_798');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[지금 무료]김영한의 자바 입문 - 코드로 시작하는 자바 첫걸음| 김영한 - 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;현재 평점 5.0점 수강생 54,098명인 강의를 만나보세요. 프로그래밍에 처음 입문하는 분들을 위한 자바 강의입니다. 코드를 따라하면서 손쉽게 자바를 배울 수 있습니다. 자바(Java) 프로그래밍 언&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Java/[인프런] 자바 입문</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/506</guid>
      <comments>https://annovation.tistory.com/506#entry506comment</comments>
      <pubDate>Sun, 1 Feb 2026 23:24:29 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] 재고 감소 통합 테스트 코드 6 - test DB</title>
      <link>https://annovation.tistory.com/505</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;테스트 코드를 위한 DB 연결&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;테스트 코드를 위한 DB 연결&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL 보다 가벼운 H2 DB 를 사용해도 되지만, 실제 서비스 환경에서는 MySQL 을 사용하기 때문에 테스트의 신뢰성을 높이기 위해 MySQL DB 를 연결하기로 했다.&lt;/li&gt;
&lt;li&gt;Docker 로 테스트용 MySQL 을 팀원 공용으로 사용하면 DB 버전이나 설정 차이 없이 동일한 환경에서 테스트 가능하지만, 동시에 테스트를 진행하게될 경우 간섭을 받을 수 있어 우선 로컬에서 MySQL 을 생성하여 테스트하기로 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;테스트 코드를 위한 MySQL 생성&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Local MySQL 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) MySQL 과 DBeaver 연결&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설치 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codingapple.com/unit/sql-mysql-dbeaver-install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codingapple.com/unit/sql-mysql-dbeaver-install/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769848854422&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL &amp;amp; DBeaver 설치하려면 (맥 / 윈도우)&quot; data-og-description=&quot;1:08 윈도우에 MySQL 설치는 3:09 맥북에 MySQL 설치는 5:11 DBeaver 설치 &amp;amp; 데이터베이스 연결 DBMS가 뭐냐면 Database Management System 의 약자인데 데이터베이스 조작을 쉽게 도와주는 프로그램입니다.&amp;nbsp; DBMS를&quot; data-og-host=&quot;codingapple.com&quot; data-og-source-url=&quot;https://codingapple.com/unit/sql-mysql-dbeaver-install/&quot; data-og-url=&quot;https://codingapple.com/unit/sql-mysql-dbeaver-install/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cAWHVu/dJMb9lL6xiw/hp1EBOdjEoP0ZeHABwf5PK/img.png?width=971&amp;amp;height=690&amp;amp;face=0_0_971_690,https://scrap.kakaocdn.net/dn/bWvGf1/dJMb9lk17l9/BGzHlyklnDUW2stzbI27ck/img.png?width=970&amp;amp;height=691&amp;amp;face=0_0_970_691,https://scrap.kakaocdn.net/dn/cHj93X/dJMb9frAoJp/ww9h6K6VhK7CSnAb5DlLj0/img.png?width=967&amp;amp;height=692&amp;amp;face=0_0_967_692&quot;&gt;&lt;a href=&quot;https://codingapple.com/unit/sql-mysql-dbeaver-install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codingapple.com/unit/sql-mysql-dbeaver-install/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cAWHVu/dJMb9lL6xiw/hp1EBOdjEoP0ZeHABwf5PK/img.png?width=971&amp;amp;height=690&amp;amp;face=0_0_971_690,https://scrap.kakaocdn.net/dn/bWvGf1/dJMb9lk17l9/BGzHlyklnDUW2stzbI27ck/img.png?width=970&amp;amp;height=691&amp;amp;face=0_0_970_691,https://scrap.kakaocdn.net/dn/cHj93X/dJMb9frAoJp/ww9h6K6VhK7CSnAb5DlLj0/img.png?width=967&amp;amp;height=692&amp;amp;face=0_0_967_692');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL &amp;amp; DBeaver 설치하려면 (맥 / 윈도우)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1:08 윈도우에 MySQL 설치는 3:09 맥북에 MySQL 설치는 5:11 DBeaver 설치 &amp;amp; 데이터베이스 연결 DBMS가 뭐냐면 Database Management System 의 약자인데 데이터베이스 조작을 쉽게 도와주는 프로그램입니다.&amp;nbsp; DBMS를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codingapple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 데이터 베이스 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-01-31 at 5.52.32 PM.png&quot; data-origin-width=&quot;2447&quot; data-origin-height=&quot;1510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdAUmi/dJMcaajP1Uy/KfCte1Xt28I77rutx92a1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdAUmi/dJMcaajP1Uy/KfCte1Xt28I77rutx92a1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdAUmi/dJMcaajP1Uy/KfCte1Xt28I77rutx92a1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdAUmi%2FdJMcaajP1Uy%2FKfCte1Xt28I77rutx92a1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2447&quot; height=&quot;1510&quot; data-filename=&quot;Screenshot 2026-01-31 at 5.52.32 PM.png&quot; data-origin-width=&quot;2447&quot; data-origin-height=&quot;1510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL 설치시 설정했던 Username 과 Password 입력&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-01-31 at 5.57.55 PM.png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;1330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xNMr2/dJMcahJ3kaz/q1Y27XuRs2Gzr6QumjOI2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xNMr2/dJMcahJ3kaz/q1Y27XuRs2Gzr6QumjOI2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xNMr2/dJMcahJ3kaz/q1Y27XuRs2Gzr6QumjOI2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxNMr2%2FdJMcahJ3kaz%2Fq1Y27XuRs2Gzr6QumjOI2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;1330&quot; data-filename=&quot;Screenshot 2026-01-31 at 5.57.55 PM.png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 테스트용 MySQL DB 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① SQL문으로 직접 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770363640704&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE DATABASE IF NOT EXISTS hubEleven_test&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② DBeaver 툴을 사용하여 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 1. 연결된 MySQL localhost 오른쪽 클릭 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;/span&gt; Create New Database&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-06 at 5.21.56 PM.png&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;1374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/barkxl/dJMcaia9SXi/xR4gWtIeKfMr003xn0736k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/barkxl/dJMcaia9SXi/xR4gWtIeKfMr003xn0736k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/barkxl/dJMcaia9SXi/xR4gWtIeKfMr003xn0736k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbarkxl%2FdJMcaia9SXi%2FxR4gWtIeKfMr003xn0736k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2080&quot; height=&quot;1374&quot; data-filename=&quot;Screenshot 2026-02-06 at 5.21.56 PM.png&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;1374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2. Database 이름 설정 후 OK&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-06 at 5.28.48 PM.png&quot; data-origin-width=&quot;2592&quot; data-origin-height=&quot;1706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceNQjx/dJMcafZLV12/7jxph2iOYPe0KRWx4i01Ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceNQjx/dJMcafZLV12/7jxph2iOYPe0KRWx4i01Ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceNQjx/dJMcafZLV12/7jxph2iOYPe0KRWx4i01Ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceNQjx%2FdJMcafZLV12%2F7jxph2iOYPe0KRWx4i01Ck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2592&quot; height=&quot;1706&quot; data-filename=&quot;Screenshot 2026-02-06 at 5.28.48 PM.png&quot; data-origin-width=&quot;2592&quot; data-origin-height=&quot;1706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 새로 생성한 로컬 MySQL 프로젝트 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;yml 설정&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770365257932&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  cloud:
    config:
      enabled: false
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/hubEleven_test?useSSL=false&amp;amp;serverTimezone=Asia/Seoul&amp;amp;characterEncoding=UTF-8
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        format_sql: true
    show-sql: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) &lt;b&gt;spring.cloud.config.enabled: false&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영 환경에서는 Config Server를 사용하지만, 로컬 테스트 환경에서는 &lt;b&gt;&lt;/b&gt;외부 인프라 의존성을 제거하기 위해 Config Client를 비활성화&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) &lt;b&gt;spring.datasource.*&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770365407687&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/hubEleven_test
username: ${DB_USERNAME}
password: ${DB_PASSWORD}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① 테스트 전용 DB 사용&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;hubEleven_test&lt;/span&gt;&lt;span&gt;를 사용해서 &lt;/span&gt;&lt;b&gt;운영 DB와 물리적으로 분리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;실수로 데이터 삭제/변경되는 사고 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② 환경변수로 계정 정보 관리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계정/비밀번호를 yml에 직접 쓰지 않음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안 + 환경별 설정 분리&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;프로젝트 연결&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) intelliJ 오른쪽 DB 메뉴에서 MySQL DB 연결&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-06 at 5.37.22 PM.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byBTxq/dJMcagEpYJS/XyIENrK10aULkwTukapYLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byBTxq/dJMcagEpYJS/XyIENrK10aULkwTukapYLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byBTxq/dJMcagEpYJS/XyIENrK10aULkwTukapYLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyBTxq%2FdJMcagEpYJS%2FXyIENrK10aULkwTukapYLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;721&quot; height=&quot;551&quot; data-filename=&quot;Screenshot 2026-02-06 at 5.37.22 PM.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;816&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) DBeaver 에서 생성한 test DB 연결해주면 끄읏!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-02-06 at 5.38.41 PM.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bM95Rx/dJMcabC59fZ/GkKPbbzF8vbMOV0JxhOJNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bM95Rx/dJMcabC59fZ/GkKPbbzF8vbMOV0JxhOJNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bM95Rx/dJMcabC59fZ/GkKPbbzF8vbMOV0JxhOJNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbM95Rx%2FdJMcabC59fZ%2FGkKPbbzF8vbMOV0JxhOJNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1824&quot; height=&quot;1584&quot; data-filename=&quot;Screenshot 2026-02-06 at 5.38.41 PM.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1584&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;개선되어야 할 점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 Docker로 띄우는 MySQL 포트 localhost:3306 과 테스트용 로컬 MySQL &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;localhost:3306&lt;span&gt; 가 같기 때문에 두 서버를 동시에 띄울 수 없다는 한계가 존재한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;이 경우 Docker MySQL 포트를 다른 서버에 포워딩하는 방식이 해결 방법 중 하나가 될 수 있을 것 같다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770364659873&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run --name mark-board-mysql -e MYSQL_ROOT_PASSWORD=root -p 3307:3306 -d mysql:8.0.38&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 코딩 애플 : MySQL &amp;amp; DBeaver 설치하려면 (맥 / 윈도우)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://codingapple.com/unit/sql-mysql-dbeaver-install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codingapple.com/unit/sql-mysql-dbeaver-install/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770363343546&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL &amp;amp; DBeaver 설치하려면 (맥 / 윈도우)&quot; data-og-description=&quot;1:08 윈도우에 MySQL 설치는 3:09 맥북에 MySQL 설치는 5:11 DBeaver 설치 &amp;amp; 데이터베이스 연결 DBMS가 뭐냐면 Database Management System 의 약자인데 데이터베이스 조작을 쉽게 도와주는 프로그램입니다.&amp;nbsp; DBMS를&quot; data-og-host=&quot;codingapple.com&quot; data-og-source-url=&quot;https://codingapple.com/unit/sql-mysql-dbeaver-install/&quot; data-og-url=&quot;https://codingapple.com/unit/sql-mysql-dbeaver-install/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RA6F1/dJMb9lk2GlV/mQWk0HVGncugqbQMMdMakK/img.png?width=971&amp;amp;height=690&amp;amp;face=0_0_971_690,https://scrap.kakaocdn.net/dn/2ugTE/dJMb9bvXPku/6G8OlOu3sQIkEK2YJhkrD1/img.png?width=970&amp;amp;height=691&amp;amp;face=0_0_970_691,https://scrap.kakaocdn.net/dn/hDzDR/dJMb9jOivar/R906MdCIOU8yyFnFO0Pu31/img.png?width=967&amp;amp;height=692&amp;amp;face=0_0_967_692&quot;&gt;&lt;a href=&quot;https://codingapple.com/unit/sql-mysql-dbeaver-install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codingapple.com/unit/sql-mysql-dbeaver-install/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RA6F1/dJMb9lk2GlV/mQWk0HVGncugqbQMMdMakK/img.png?width=971&amp;amp;height=690&amp;amp;face=0_0_971_690,https://scrap.kakaocdn.net/dn/2ugTE/dJMb9bvXPku/6G8OlOu3sQIkEK2YJhkrD1/img.png?width=970&amp;amp;height=691&amp;amp;face=0_0_970_691,https://scrap.kakaocdn.net/dn/hDzDR/dJMb9jOivar/R906MdCIOU8yyFnFO0Pu31/img.png?width=967&amp;amp;height=692&amp;amp;face=0_0_967_692');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL &amp;amp; DBeaver 설치하려면 (맥 / 윈도우)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1:08 윈도우에 MySQL 설치는 3:09 맥북에 MySQL 설치는 5:11 DBeaver 설치 &amp;amp; 데이터베이스 연결 DBMS가 뭐냐면 Database Management System 의 약자인데 데이터베이스 조작을 쉽게 도와주는 프로그램입니다.&amp;nbsp; DBMS를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codingapple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Docker MySQL 포트 충돌 문제 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hwangjoonyoung.com/posts/docker-mysql-port/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hwangjoonyoung.com/posts/docker-mysql-port/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770364698736&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Docker MySQL 포트 충돌 문제 해결 - Blog&quot; data-og-description=&quot;도커 환경을 구성하면서 마주친 문제&quot; data-og-host=&quot;hwangjoonyoung.com&quot; data-og-source-url=&quot;https://hwangjoonyoung.com/posts/docker-mysql-port/&quot; data-og-url=&quot;https://hwangjoonyoung.com/posts/docker-mysql-port/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/i6aPS/dJMb8Z3mMku/GqOomYjIoZkFn835Gj3jW1/img.png?width=1381&amp;amp;height=206&amp;amp;face=0_0_1381_206,https://scrap.kakaocdn.net/dn/c2lHl2/dJMb8XkaU4A/9GoxeOsHXAPhAVoLUVZ7A0/img.png?width=1023&amp;amp;height=162&amp;amp;face=0_0_1023_162,https://scrap.kakaocdn.net/dn/gWbSm/dJMb8SXtksw/pEsaFRbSvruU6WZAp17Fa0/img.png?width=1274&amp;amp;height=718&amp;amp;face=0_0_1274_718&quot;&gt;&lt;a href=&quot;https://hwangjoonyoung.com/posts/docker-mysql-port/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hwangjoonyoung.com/posts/docker-mysql-port/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/i6aPS/dJMb8Z3mMku/GqOomYjIoZkFn835Gj3jW1/img.png?width=1381&amp;amp;height=206&amp;amp;face=0_0_1381_206,https://scrap.kakaocdn.net/dn/c2lHl2/dJMb8XkaU4A/9GoxeOsHXAPhAVoLUVZ7A0/img.png?width=1023&amp;amp;height=162&amp;amp;face=0_0_1023_162,https://scrap.kakaocdn.net/dn/gWbSm/dJMb8SXtksw/pEsaFRbSvruU6WZAp17Fa0/img.png?width=1274&amp;amp;height=718&amp;amp;face=0_0_1274_718');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Docker MySQL 포트 충돌 문제 해결 - Blog&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;도커 환경을 구성하면서 마주친 문제&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hwangjoonyoung.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/505</guid>
      <comments>https://annovation.tistory.com/505#entry505comment</comments>
      <pubDate>Sat, 31 Jan 2026 17:10:53 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] 재고 감소 통합 테스트 코드 5 - 단일 스레드</title>
      <link>https://annovation.tistory.com/504</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;단일 스레드 환경 리팩토링 with TestFixture&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Test Fixture 도입 전 단일 스레드 테스트 코드&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) @BeforeEach&lt;/p&gt;
&lt;pre id=&quot;code_1769761543411&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@BeforeEach
    public void setUp() {

        // 테스트용 고정 ID 생성 (각 테스트마다 새로 생성)
        testProductId = UUID.randomUUID(); // TODO : Test Fixture
        testCompanyId = UUID.randomUUID();
        testHubId = UUID.randomUUID();

        Stock stock = Stock.create(testProductId, testCompanyId, testHubId, 100);
        jpaStockRepository.saveAndFlush(stock);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) decreaseStock_success&lt;/p&gt;
&lt;pre id=&quot;code_1769761588909&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Test
    @DisplayName(&quot;재고 감소 - 단일 요청 성공&quot;)
    void decreaseStock_success() {

        // given
        int decreaseAmount = 10;

        StockRequests.Decrease request = new StockRequests.Decrease(testProductId, decreaseAmount);

        // when
        StockResult result = stockServiceImpl.decreaseStock(request);

        // then - 반환값 검증
        assertThat(result.productId()).isEqualTo(testProductId);
        assertThat(result.productId()).isEqualTo(testCompanyId);
        assertThat(result.productId()).isEqualTo(testHubId);
        assertThat(result.quantity()).isEqualTo(90L);

        // then - 실제 DB 재고 검증
        Stock updatedStock =
                jpaStockRepository
                        .findByProductId(testProductId)
                        .orElseThrow(() -&amp;gt; new AssertionError(&quot;재고가 존재해야 합니다&quot;));

        assertThat(updatedStock.getQuantity()).isEqualTo(90L);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Test Fixture 도입 후 단일 스레드 테스트 코드&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) @BeforeEach&lt;/p&gt;
&lt;pre id=&quot;code_1769762385335&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@BeforeEach
    public void setUp() {

        product = ProductFixture.createDefault();
        jpaProductRepository.saveAndFlush(product);

        Stock stock = StockFixture.createFromProductWithQuantity(product, 100);
        jpaStockRepository.saveAndFlush(stock);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Fixture 로 책임 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) decreaseStock_success&lt;/p&gt;
&lt;pre id=&quot;code_1769762448767&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
    @DisplayName(&quot;재고 감소 - 단일 요청 성공&quot;)
    void decreaseStock_success() {

        // given
        int decreaseAmount = 10;

        StockRequests.Decrease request = StockFixture.decreaseRequest(
                product, decreaseAmount);

        // when
        StockResult result = stockServiceImpl.decreaseStock(request);

        // then - 반환값 검증
        assertThat(result.productId()).isEqualTo(product.getProductId());
        assertThat(result.companyId()).isEqualTo(product.getCompanyId());
        assertThat(result.hubId()).isEqualTo(product.getHubId());
        assertThat(result.quantity()).isEqualTo(90);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;ProductFixture.java&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;test/java/com.hubeleven.product/stock/application/fixtures/ProductFixture.java&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770207794796&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ProductFixture {

    // ===== ID =====

    public static final UUID COMPANY_ID = UUID.randomUUID();

    public static final UUID HUB_ID = UUID.randomUUID();

    // ===== Factory Methods =====

    public static Product createDefault() {
        return Product.create(
                &quot;Default Product&quot;,
                COMPANY_ID,
                HUB_ID
        );
    }
    
    private ProductFixture() {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stock 이 생성되려면 Product 가 먼저 만들어져야하기 때문에 ProductFixture 도 만들어줘야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;StockFixture.java&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;test/java/com.hubeleven.product/stock/application/fixtures/StockFixture.java&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769762575129&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class StockFixture {

    // ===== Factory Methods =====

    public static Stock createFromProductWithQuantity(Product product, int quantity) {
        return Stock.create(
                product.getProductId(),
                product.getCompanyId(),
                product.getHubId(),
                quantity
        );
    }

    public static StockRequests.Decrease decreaseRequest(Product product, int quantity) {
        return new StockRequests.Decrease(
                product.getProductId(),
                quantity
        );
    }

    private StockFixture() {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우선 현재 테스트에서 바뀌는 필드는 quantity 하나이기 때문에, Builder 패턴은 도입하지 않았다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/504</guid>
      <comments>https://annovation.tistory.com/504#entry504comment</comments>
      <pubDate>Fri, 30 Jan 2026 17:27:11 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] 재고 감소 통합 테스트 코드 4 - 단일 스레드</title>
      <link>https://annovation.tistory.com/503</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;단일 스레드 환경 with Test Fixture&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;test/java/com.hubeleven.product/stock/application/fixtures/StockFixture.java&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stock 도메인 테스트용 StockFixture 클래스 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1769698283363&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// fixtures/StockFixture.java
public class StockFixture {

    // ===== Factory Methods =====

    public static Stock createDefault() {
        return Stock.create(PRODUCT_ID, COMPANY_ID, HUB_ID, 100);
    }

    public static Stock createWithQuantity(int quantity) {
        return Stock.create(PRODUCT_ID, COMPANY_ID, HUB_ID, quantity);
    }

    public static Stock lowStock() {
        return createWithQuantity(5);
    }

    public static Stock outOfStock() {
        return createWithQuantity(0);
    }

    // ===== Builder =====

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private UUID productId = PRODUCT_ID;
        private UUID companyId = COMPANY_ID;
        private UUID hubId = HUB_ID;
        private int quantity = 100;

        public Builder productId(UUID productId) {
            this.productId = productId;
            return this;
        }

        public Builder companyId(UUID companyId) {
            this.companyId = companyId;
            return this;
        }

        public Builder hubId(UUID hubId) {
            this.hubId = hubId;
            return this;
        }

        public Builder quantity(int quantity) {
            this.quantity = quantity;
            return this;
        }

        public Stock build() {
            return Stock.create(productId, companyId, hubId, quantity);
        }
    }

    private StockFixture() {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Factory Methods&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769699493856&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ===== Factory Methods =====

    public static Stock createDefault() {
        return Stock.create(PRODUCT_ID, COMPANY_ID, HUB_ID, 100);
    }

    public static Stock createWithQuantity(int quantity) {
        return Stock.create(PRODUCT_ID, COMPANY_ID, HUB_ID, quantity);
    }

    public static Stock lowStock() {
        return createWithQuantity(5);
    }

    public static Stock outOfStock() {
        return createWithQuantity(0);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 테스트가 무엇을 검증하는지 명확히 드러나는 Factory 메서드 구현&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Builder&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769699524199&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ===== Builder =====

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private UUID productId = PRODUCT_ID;
        private UUID companyId = COMPANY_ID;
        private UUID hubId = HUB_ID;
        private int quantity = 100;

        public Builder productId(UUID productId) {
            this.productId = productId;
            return this;
        }

        public Builder companyId(UUID companyId) {
            this.companyId = companyId;
            return this;
        }

        public Builder hubId(UUID hubId) {
            this.hubId = hubId;
            return this;
        }

        public Builder quantity(int quantity) {
            this.quantity = quantity;
            return this;
        }

        public Stock build() {
            return Stock.create(productId, companyId, hubId, quantity);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 필요한 값으로 바꿔서 사용 가능한 Builder 패턴 구현&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;인스턴스 생성 차단&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769699602480&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private StockFixture() {}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StockFixture 클래스는 객체를 생성하기 위한 클래스가 아니기 때문에 new StockFixture() 는 의미가 없다.&lt;/li&gt;
&lt;li&gt;Java 에서는 생성자를 만들지 않으면 기본 생성자가 자동으로 만들어지지만, StockFixutre 는 인스턴스가 아무 역할도 하지 않기 때문에 쓸모 없는 객체 생성으로 인해 잘못된 사용 가능성이 있다.&lt;/li&gt;
&lt;li&gt;이를 위해 private 으로 인스턴스 생성을 차단한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/503</guid>
      <comments>https://annovation.tistory.com/503#entry503comment</comments>
      <pubDate>Thu, 29 Jan 2026 23:51:28 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 객체 지향 설계와 스프링</title>
      <link>https://annovation.tistory.com/502</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;객체 지향 설계와 Spring 연관성&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring 애기에 왜 객체 지향 이야기가 나오는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 은 아래 기술로 객체 지향의 특징인&amp;nbsp;&lt;b&gt;다형성&lt;/b&gt;과 &lt;b&gt;OCP&lt;/b&gt;, &lt;b&gt;DIP&lt;/b&gt; 를 가능하게 지원한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DI (&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;Dependency &lt;span style=&quot;text-align: left;&quot;&gt;Dependency) : 의존 관계, 의존성 주입&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;DI 컨테이너 제공&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;Java 객체들을 어떤 컨테이너 안에 넣어 놓고 이 안에서 의존 관계를 서로 연결해주고 주입해주는 기능&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;위와 같은 기술을 활용하면, 클라이언트 코드 변경 없이 기능 확장 가능&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;쉽게 부품을 교체하듯이 개발 가능&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Spring이 없던 시절..&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 개발자가 좋은 객체 지향 개발을 하려고 OCP, DIP 원칙을 지키며 개발을 해보니, 만들어야할 것들이 너무 많아서 배보다 배꼽이 큰 상황이 되어버렸다.&lt;/li&gt;
&lt;li&gt;그래서 OCP, DIP 원칙들을 프레임워크로 만들어버렸다..!&lt;/li&gt;
&lt;li&gt;순수한 Java 로 OCP, DIP 원칙들을 지키면서 개발을 해보면, 결국 Spring Framework 와 같은 기능을 만들게 된다. (더 장확히는 DI 컨테이너)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;정리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;정리&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 설계에 역할(인터페이스)과 구현(구현체)을 분리해야 한다.&lt;/li&gt;
&lt;li&gt;애플리케이션 설계도 공연을 설계하듯 배역만 만들어두고 배우는 언제든지 &lt;b&gt;유연&lt;/b&gt;하게 교체하고 &lt;b&gt;변경&lt;/b&gt;할 수 있도록 만드는 것이 좋은 객체 지향 설계이다.&lt;/li&gt;
&lt;li&gt;이것을 가능하게 하려면, &lt;b&gt;다형성&lt;/b&gt; 뿐만 아니라 &lt;b&gt;OCP&lt;/b&gt;, &lt;b&gt;DIP&lt;/b&gt; 원칙을 모두 준수해야 한다.&lt;/li&gt;
&lt;li&gt;다형성, OCP, DIP 를 가능하게 하려면 중간 역할이 필요한데, 그게 바로 Spring Container 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;이상적으로는 모든 설계에 인터페이스를 부여하는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이상적으로는 모든 설계에 인터페이스를 부여하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;장점 : 인터페이스를 먼저 작성 후 하부 구현 기술들에 대한 선택을 미룰 수 있다. &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;rarr; 변경 범위가 작아 유연&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;ex. 어떤 DB를 사용하지 미정일 때 (RDB, NoSQL, 관계형 DB 등), 인터페이스만 정의 해놓고 우선 다른 부분을 개발 가능하다.&lt;/li&gt;
&lt;li&gt;ex. 할인 정책을 개발해야 하는데 아직 구체적으로 정채지지 않았을 때, 할인 정책에 대한 인터페이스를 만들고 간단한 구현체만 작성해놓고 개발 가능하다. (0원 할인 처럼)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;실무론적 고민&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이상적으로는 인터페이스를 도입하는 것이 좋지만, 무분별한 도입은 '추상화'라는 비용이 발생한다.&lt;/li&gt;
&lt;li&gt;추상화가 되면, 개발자가 코드를 한번 더 열어봐야 하는 상황이 발생한다.&lt;/li&gt;
&lt;li&gt;추상화가 됨으로써 장점도 있지만, 발생하는 단점을 넘어설 때 선택을 해야한다.&lt;/li&gt;
&lt;li&gt;따라서 추천하는 방법은, 기능을 확장할 가능성이 없다면, 구체 클래스를 직접 사용하고, 향후 꼭 필요할 때 리팩토링해서 인터페이스를 도입하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;출처&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769437719871&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의&quot; data-og-description=&quot;현재 평점 5.0점 수강생 49,149명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/diAv7O/dJMb8QegE5q/Y7stOLqeaT14fku7X8hhQ0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/oaZHE/dJMb8Z3lIUS/QnvG7ON3EYB22BDyUt3Mz1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/deo2Zy/dJMb8YXFWL8/naEQl4Do5wVq7Kg7TO5DD1/img.png?width=960&amp;amp;height=555&amp;amp;face=0_0_960_555&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/diAv7O/dJMb8QegE5q/Y7stOLqeaT14fku7X8hhQ0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/oaZHE/dJMb8Z3lIUS/QnvG7ON3EYB22BDyUt3Mz1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/deo2Zy/dJMb8YXFWL8/naEQl4Do5wVq7Kg7TO5DD1/img.png?width=960&amp;amp;height=555&amp;amp;face=0_0_960_555');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;현재 평점 5.0점 수강생 49,149명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Java Framework/[인프런] 스프링 핵심 원리 - 기본편</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/502</guid>
      <comments>https://annovation.tistory.com/502#entry502comment</comments>
      <pubDate>Tue, 27 Jan 2026 22:26:55 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 좋은 객체 지향 설계의 5가지 원칙 (SOLID)</title>
      <link>https://annovation.tistory.com/498</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;SOLID&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;SOLID 란?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'클린코드' 저자&amp;nbsp;로버트 마틴(Robert Martin)이 정의한 좋은 객체 지향 설계의 5가지 원칙&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SRP : 단일 책임 원칙(Single Responsibility Principle)&lt;/li&gt;
&lt;li&gt;OCP : 개방 폐쇄 원칙(Open Closed Principle)&lt;/li&gt;
&lt;li&gt;LSP : 리스코프 치환 원칙(Liskov substitution Principle)&lt;/li&gt;
&lt;li&gt;ISP : 인터페이스 분리 원칙(Interface Segregation Principle)&lt;/li&gt;
&lt;li&gt;DIP : 의존관계 역전 원칙(Dependency Inversion Principle)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;SRP&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;SRP (Single Responsibility Principle, 단일 책임 원칙)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 클래스는 하나의 책임만 가져야 한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;여기서의 책임은 문맥과 상황에 따라 다르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SRP 원칙을 잘 지키며 설계하는 중요한 기준은 &lt;b&gt;변경&lt;/b&gt;이다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;변경이 있을 때 파급이 적으면 SRP 원칙을 잘 지킨 것&lt;br /&gt;ex. UI 하나 변경 할 때, SQL 코드부터 애플리케이션 코드 전부 고쳐야 한다면 잘못된 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변경이 있을 때 하나의 클래스나 하나의 지점만 고치면 SRP를 잘 따르는 설계라고 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;OCP (가장 중요한 원칙)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;OCP (Open/Closed Principle, 개방 폐쇄 원칙)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다형성&lt;/b&gt;을 활용하여 인터페이스를 구현한 새로운 클래스를 만들어 새로운 기능을 구현하면 OCP 원칙으로 설계 가능&lt;/li&gt;
&lt;li&gt;단, 다형성을 활용해도 코드 수정이 필요해 OCP 원칙이 지켜지지 않는 경우가 생길 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;다형성을 활용했지만, OCP 원칙이 지켜지지 않는 경우&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째 코드 (MemoryMemberRepository 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1769491168440&quot; class=&quot;haxe&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class MemberService {

    private MemberRepository memberRepository = new MemoryMemberRepository();

}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 번째 코드 (JdbcMemberRepository로 변경한 버전)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1769491199179&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MemberService {

    // private MemberRepository memberRepository = new MemoryMemberRepository();
    private MemberRepository memberRepository = new JdbcMemberRepository();

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 같은 타입(MemberRepository)으로 서로 다른 구현체를 만들 수 있는 '다형성'의 개념을 활용하고 있지만, 구현 객체를 변경하려면 클라이언트 코드(MemberService)를 변경해야 하므로 OCP 원칙은 지켜지지 않고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 이를 해결하기 위해서는, 객체를 생성하고 연관관계를 맺어주는 별도의 중간 역할이 필요한데, 이 역할을 바로 Spring이 지원해주는 것이다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Container 가 해주는 역할&lt;/li&gt;
&lt;li&gt;따라서, 이렇게 OCP 원칙을 지키기 위해 DI와 IoC 컨테이너가 필요하다 (다음 강의에서 코드로 직접 실습해봐야 이해되는 부분)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;LSP&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;LSP (Liskov Substitution Principle, 리스코프 치환 원칙)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브 타입은 언제나 상위 타입으로 교체할 수 있어야한다. 즉, 서브 타입은 상위 타입이 약속한 규약을 지켜야함을 강조&lt;/li&gt;
&lt;li&gt;프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.&lt;/li&gt;
&lt;li&gt;다형성을 지원하기 위한 원칙으로, 하위 클래스는 인터페이스 규약을 전부 지켜야 한다는 원칙이다.&lt;br /&gt;ex. 자동차 인터페이스의 엑셀은 앞으로 가는 기능인데, 뒤로 가도록 구현하면 LSP 위반&lt;/li&gt;
&lt;li&gt;인터페이스를 구현한 구현체를 믿고 사용하려면, 이 원칙이 필요하다.&lt;/li&gt;
&lt;li&gt;단순히 컴파일에 성공하는 것을 넘어서는 이야기다. 즉, 코드가 문법적으로 맞는지(컴파일)보다, 의미적으로 약속을 지켰는지가 중요한 원칙이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;ISP&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;ISP (Interface Segregation Principle, 인터페이스 분리 원칙)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 입장에서 인터페이스를 분리해야함을 강조&lt;/li&gt;
&lt;li&gt;특정 클라이언트를 위한 인터페이스 여러 개가, 범용 인터페이스 하나보다 낫다.&lt;br /&gt;ex. 자동차 인터페이스 &amp;rarr; 운전 인터페이스, 정비 인터페이스로 분리&lt;br /&gt;ex. 사용자 클라이언트 &amp;rarr; 운전자 클라이언트, 정비사 클라이언트로 분리&lt;/li&gt;
&lt;li&gt;특정 인터페이스 자체가 변해도 다른 클라이언트에 영향을 주지 않는다.&lt;br /&gt;ex. 정비 인터페이스 변경이 운전자 클라이언트에 영향을 주지 않음&lt;/li&gt;
&lt;li&gt;인터페이스가 명확해지고, 대체 가능성이 높아진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;DIP (중요한 원칙)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;DIP (Dependency Inversion Principle, 의존관계 역전 원칙)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;어떤 Class 를 참조해야하는 상황이 생기면, 그 Class 를 직접 참조하는 것이 아닌 대상의 상위 요소(추상 클래스 or 인터페이스)로 참조하라는 원칙 (&lt;a href=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 되며, 모두 추상화에 의존해야 함을 강조 (&lt;a href=&quot;https://www.maeil-mail.kr/question/110&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;추상화(인터페이스)에 의존하고 구체화(구현 클래스)에 의존하지 말 것&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;클라이언트가 인터페이스에 의존하여 구현체를 유연하게 변경할 수 있게된다. 구현체에 의존하면 변경이 어려워진다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;DIP 위반 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769501961388&quot; class=&quot;haxe&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MemberService {

    private MemberRepository memberRepository = new MemoryMemberRepository();

}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MemberService(상위 요소)가 MemoryMemberRepository(하위 요소)를 직접 의존(참조)하고 있으므로 DIP 위반&lt;/li&gt;
&lt;li&gt;구현체(MemoryMemberRepository) 변경 시 MemberSerivce의 코드 수정이 필요하기 때문 (다음 강의에 이어서 어떻게 해결할지에 대한 방법 안내)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;정리&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 지향의 핵심은 &lt;b&gt;다형성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;하지만 &lt;b&gt;다형성&lt;/b&gt;만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다형성&lt;/b&gt;만으로 OCP 와 DIP 같은 원칙을 준수하여 설계할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고하면 좋은 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 블로그 - SOLID&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769742867545&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;  객체 지향 설계의 5가지 원칙 - S.O.L.I.D&quot; data-og-description=&quot;객체 지향 설계의 5원칙 S.O.L.I.D 모든 코드에서 LSP를 지키기에는 어려움. 리스코프 치환 원칙에 따르면 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대신하더라도 의도에 맞게 작동되어&quot; data-og-host=&quot;inpa.tistory.com&quot; data-og-source-url=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID&quot; data-og-url=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cCTNBE/dJMb8Qeg05a/7gU8cQWqaMDWjtwLprRFyk/img.jpg?width=800&amp;amp;height=417&amp;amp;face=0_0_800_417,https://scrap.kakaocdn.net/dn/l4PnH/dJMb8XR0nHZ/qP6NBSKTkEaEszLKS7Tc6K/img.jpg?width=800&amp;amp;height=417&amp;amp;face=0_0_800_417,https://scrap.kakaocdn.net/dn/d9kzmL/dJMb87f1cfv/KAH5Crf0LFADFh4uyL1fsK/img.png?width=864&amp;amp;height=353&amp;amp;face=0_0_864_353&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cCTNBE/dJMb8Qeg05a/7gU8cQWqaMDWjtwLprRFyk/img.jpg?width=800&amp;amp;height=417&amp;amp;face=0_0_800_417,https://scrap.kakaocdn.net/dn/l4PnH/dJMb8XR0nHZ/qP6NBSKTkEaEszLKS7Tc6K/img.jpg?width=800&amp;amp;height=417&amp;amp;face=0_0_800_417,https://scrap.kakaocdn.net/dn/d9kzmL/dJMb87f1cfv/KAH5Crf0LFADFh4uyL1fsK/img.png?width=864&amp;amp;height=353&amp;amp;face=0_0_864_353');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;  객체 지향 설계의 5가지 원칙 - S.O.L.I.D&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;객체 지향 설계의 5원칙 S.O.L.I.D 모든 코드에서 LSP를 지키기에는 어려움. 리스코프 치환 원칙에 따르면 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대신하더라도 의도에 맞게 작동되어&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;inpa.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 매일메일 - SOLID&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.maeil-mail.kr/question/110&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.maeil-mail.kr/question/110&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769742886687&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;매일메일 - 기술 면접 질문 구독 서비스&quot; data-og-description=&quot;기술 면접 질문을 매일매일 메일로 보내드릴게요!&quot; data-og-host=&quot;www.maeil-mail.kr&quot; data-og-source-url=&quot;https://www.maeil-mail.kr/question/110&quot; data-og-url=&quot;https://www.maeil-mail.kr&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VWRrt/dJMb86nSmR5/PZHRXHZ31kX71RalMTTP3K/img.png?width=1134&amp;amp;height=918&amp;amp;face=0_0_1134_918&quot;&gt;&lt;a href=&quot;https://www.maeil-mail.kr/question/110&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.maeil-mail.kr/question/110&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VWRrt/dJMb86nSmR5/PZHRXHZ31kX71RalMTTP3K/img.png?width=1134&amp;amp;height=918&amp;amp;face=0_0_1134_918');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;매일메일 - 기술 면접 질문 구독 서비스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;기술 면접 질문을 매일매일 메일로 보내드릴게요!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.maeil-mail.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 매일메일 - 객체 지향 프로그래밍&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.maeil-mail.kr/question/258&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.maeil-mail.kr/question/258&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769742913017&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;매일메일 - 기술 면접 질문 구독 서비스&quot; data-og-description=&quot;기술 면접 질문을 매일매일 메일로 보내드릴게요!&quot; data-og-host=&quot;www.maeil-mail.kr&quot; data-og-source-url=&quot;https://www.maeil-mail.kr/question/258&quot; data-og-url=&quot;https://www.maeil-mail.kr&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bliLgr/dJMb8ZvweHH/NrhuFKvkdVF3vggxKrwQC0/img.png?width=1134&amp;amp;height=918&amp;amp;face=0_0_1134_918&quot;&gt;&lt;a href=&quot;https://www.maeil-mail.kr/question/258&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.maeil-mail.kr/question/258&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bliLgr/dJMb8ZvweHH/NrhuFKvkdVF3vggxKrwQC0/img.png?width=1134&amp;amp;height=918&amp;amp;face=0_0_1134_918');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;매일메일 - 기술 면접 질문 구독 서비스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;기술 면접 질문을 매일매일 메일로 보내드릴게요!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.maeil-mail.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;출처&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769171321468&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의&quot; data-og-description=&quot;현재 평점 5.0점 수강생 49,140명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TBYtk/dJMb88FY7ac/qRLdtIEZWkuKnSmyRrrunK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/qMuoB/dJMb84p2YFF/YUf6TtSbBIMVq8HEvd9NV0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/bMGe3r/dJMb895XPHp/tj0bEQAIDrNibQR1zyYMbK/img.png?width=960&amp;amp;height=555&amp;amp;face=0_0_960_555&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TBYtk/dJMb88FY7ac/qRLdtIEZWkuKnSmyRrrunK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/qMuoB/dJMb84p2YFF/YUf6TtSbBIMVq8HEvd9NV0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/bMGe3r/dJMb895XPHp/tj0bEQAIDrNibQR1zyYMbK/img.png?width=960&amp;amp;height=555&amp;amp;face=0_0_960_555');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;현재 평점 5.0점 수강생 49,140명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java Framework/[인프런] 스프링 핵심 원리 - 기본편</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/498</guid>
      <comments>https://annovation.tistory.com/498#entry498comment</comments>
      <pubDate>Mon, 26 Jan 2026 23:45:25 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 좋은 객체 지향 프로그래밍이란?</title>
      <link>https://annovation.tistory.com/499</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;객체 지향 프로그래밍&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;객체 지향 프로그래밍 이란?&lt;/span&gt;&lt;sup class=&quot;footnote&quot;&gt;&lt;a href=&quot;#footnote_499_1&quot; id=&quot;footnote_link_499_1&quot; onmouseover=&quot;tistoryFootnote.show(this, 499, 1)&quot; onmouseout=&quot;tistoryFootnote.hide(499, 1)&quot; style=&quot;color:#f9650d; font-family: Verdana, Sans-serif; display: inline;&quot;&gt;&lt;span style=&quot;display: none;&quot;&gt;[각주:&lt;/span&gt;1&lt;span style=&quot;display: none;&quot;&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;즉,&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;ldquo;객체&amp;rdquo;들의 모임&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;으로 파악하고자 하는 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;b&gt;(협력)&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;객체 지향 프로그래밍은 프로그램을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;유연&lt;/b&gt;하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;변경&lt;/b&gt;이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;유연하고 변경이 용이?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레고 블럭 조립 하듯 컴포넌트를 쉽고 유연하게 변경하며 개발할 수 있는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;객체 지향 특징 4가지&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추상화 (&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;Abstraction)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;캡슐화 (&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;Encapsulation)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;상속 (&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;Inheritance)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다형성 (&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;Polymorphism&lt;/span&gt;) : 객체 지향의 핵심&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;다형성 (Polymorphism)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;비유&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1) 운전자 - 자동차&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;751&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z481B/dJMcabwdTpl/iHReya8EOF0k6Xh0y1YD31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z481B/dJMcabwdTpl/iHReya8EOF0k6Xh0y1YD31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z481B/dJMcabwdTpl/iHReya8EOF0k6Xh0y1YD31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz481B%2FdJMcabwdTpl%2FiHReya8EOF0k6Xh0y1YD31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1468&quot; height=&quot;751&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;751&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운전자는 운전면허증만 있으면 자동차가 K3 에서 아반떼로 바뀌어도 운전이 가능하다.&lt;/li&gt;
&lt;li&gt;운전자(클라이언트)를 위해 자동차 역할(인터페이스)을 통해 새로운 자동차 기능을 제공할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2) 공연 무대 (로미오와 줄리엣 공연)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOmI9K/dJMcaaxkuqI/HV1AUpflLoPOFIOlmKfkh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOmI9K/dJMcaaxkuqI/HV1AUpflLoPOFIOlmKfkh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOmI9K/dJMcaaxkuqI/HV1AUpflLoPOFIOlmKfkh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOmI9K%2FdJMcaaxkuqI%2FHV1AUpflLoPOFIOlmKfkh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1373&quot; height=&quot;837&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;837&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;줄리엣(서버)의 배우(구현)이 바뀐다고 해서 로미오(클라이언트)에 영향을 주지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✅ 결론&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 예시 모두 유연하고 변경이 용이한 예시를 보여준다.&lt;/li&gt;
&lt;li&gt;즉, 다른 대상으로 대체 가능하다는 것을 뜻한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;역할과 구현을 분리&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 예시처럼 역할과 구현으로 구분하면 단순하면서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;유연&lt;/b&gt;해지고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;변경&lt;/b&gt;이 편리해진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구현체를 무한하게 확장할 수 있는 설계가 가능하다.&lt;/li&gt;
&lt;li&gt;따라서 인터페이스를 안정적으로 잘 설계하는 것이 매우 중요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✅ 장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;클라이언트&lt;/b&gt;는 대상의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;역할(인터페이스)&lt;/b&gt;만 알면 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트&lt;/b&gt;는 구현 대상의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;내부 구조를 몰라도&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트&lt;/b&gt;는 구현 대상의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;내부 구조가 변경&lt;/b&gt;되어도 영향을 받지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트&lt;/b&gt;는 구현&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;대상 자체를 변경&lt;/b&gt;해도 영향을 받지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Java에서의 다형성 (Polymorphism)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Java에서의 다형성 활용&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할과 구현을 분리
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;역할 = 인터페이스&lt;/li&gt;
&lt;li&gt;구현 = 인터페이스를 구현한 클래스, 구현 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체를 설계할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;역할&lt;/b&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;구현&lt;/b&gt;을 명확히 분리&lt;/li&gt;
&lt;li&gt;객체 설계시 역할(인터페이스)을 먼저 부여하고, 그 역할을 수행하는 구현 객체 만들기&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;객체의 협력이라는 관계부터 생각&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1351&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n9Vfe/dJMcag5nWgk/2vKTqWKxX0vr3kkkHgVyVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n9Vfe/dJMcag5nWgk/2vKTqWKxX0vr3kkkHgVyVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n9Vfe/dJMcag5nWgk/2vKTqWKxX0vr3kkkHgVyVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn9Vfe%2FdJMcag5nWgk%2F2vKTqWKxX0vr3kkkHgVyVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1351&quot; height=&quot;846&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1351&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수 많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가진다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;클라이언트 = 요청하는 사람&lt;/li&gt;
&lt;li&gt;서버 = 클라이언트의 요청을 받아 응답하는 사람&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;혼자 있는 객체는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Java에서의 활용 예시&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Overriding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1647&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8aREJ/dJMcaiIQWKa/Tz6CjboG5COowkTL4a2xZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8aREJ/dJMcaiIQWKa/Tz6CjboG5COowkTL4a2xZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8aREJ/dJMcaiIQWKa/Tz6CjboG5COowkTL4a2xZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8aREJ%2FdJMcaiIQWKa%2FTz6CjboG5COowkTL4a2xZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1647&quot; height=&quot;850&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1647&quot; data-origin-height=&quot;850&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다형성으로 인터페이스를 구현한 객체를 실행 시점에 유연하게 변경할 수 있다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;즉, 클라이언트는 코드의 인터페이스 타입만 알고 있고, 실제로 어떤 구현 클래스 객체를 쓸지는 프로그램 실행 중에 변경 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클래스와 상속 관계에서도 다형성, 오버라이딩 적용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;다형성의 본질&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스를 구현한 객체 인스턴스를 &lt;b&gt;실행 시점&lt;/b&gt;에 &lt;b&gt;유연&lt;/b&gt;하게 &lt;b&gt;변경&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;li&gt;다형성의 본질을 이해하려면 &lt;b&gt;협력&lt;/b&gt;이라는 객체 사이의 관계에서 시작해야한다.&lt;/li&gt;
&lt;li&gt;즉, 클라이언트와 서버의 협력 관계 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다는 것이 다형성의 본질이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;다형성의 한계&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할(인터페이스) 자체가 변하면 클라이언트, 서버 모두 큰 변경이 발생한다.&lt;br /&gt;ex. 자동차 역할을 비행기 역할로 변경해야 한다면? 연극에서 대본 자체가 변경되어야 한다면?&lt;/li&gt;
&lt;li&gt;따라서 가장 변화가 없는 방식으로 인터페이스를 안정적으로 설계하는 것이 가장 중요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Spring과 객체 지향&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래서 객체 지향의 특징인 '다형성'과 Spring 이 도대체 무슨 연관인거지?&lt;/li&gt;
&lt;li&gt;Spring은 객체 지향의 '다형성' 특징을 가장 극대화해서 사용할 수 있도록 도와주는 프레임워크이다.&lt;br /&gt;ex. DI (의존성 주입), IoC (제어의 역전)&lt;/li&gt;
&lt;li&gt;Spring을 사용하면 레고 블럭 조립하듯, 공연 무대의 배우를 선택하듯 구현을 편리하게 변경할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;출처&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769326443890&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의&quot; data-og-description=&quot;현재 평점 5.0점 수강생 49,145명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/brwdpS/dJMb89x7SQf/WKkksybx8htklfPeg3x2dk/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/c6i7fb/dJMb87f0HFh/kFTZiWBjbkdSj19ykCU9f1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/t7WvQ/dJMb83SdayQ/WEErN0CdDXUTSNpZKJPmi0/img.png?width=960&amp;amp;height=555&amp;amp;face=0_0_960_555&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/brwdpS/dJMb89x7SQf/WKkksybx8htklfPeg3x2dk/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/c6i7fb/dJMb87f0HFh/kFTZiWBjbkdSj19ykCU9f1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/t7WvQ/dJMb83SdayQ/WEErN0CdDXUTSNpZKJPmi0/img.png?width=960&amp;amp;height=555&amp;amp;face=0_0_960_555');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;현재 평점 5.0점 수강생 49,145명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;주석 출처&lt;/span&gt;&lt;/h3&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol class=&quot;footnotes&quot;&gt;
    &lt;li id=&quot;footnote_499_1&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/객체_지향_프로그래밍&quot;&gt;https://ko.wikipedia.org/wiki/객체_지향_프로그래밍&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; &lt;a href=&quot;#footnote_link_499_1&quot;&gt;[본문으로]&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</description>
      <category>Java Framework/[인프런] 스프링 핵심 원리 - 기본편</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/499</guid>
      <comments>https://annovation.tistory.com/499#entry499comment</comments>
      <pubDate>Fri, 23 Jan 2026 23:14:59 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring 이란?</title>
      <link>https://annovation.tistory.com/497</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Spring&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btcTgA/dJMcaia3hU4/Ioe6tSgEyaEqehjXKJaNwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btcTgA/dJMcaia3hU4/Ioe6tSgEyaEqehjXKJaNwk/img.png&quot; data-alt=&quot;출처 : https://www.edureka.co/blog/videos/building-web-application-using-spring-framework/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btcTgA/dJMcaia3hU4/Ioe6tSgEyaEqehjXKJaNwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtcTgA%2FdJMcaia3hU4%2FIoe6tSgEyaEqehjXKJaNwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;578&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://www.edureka.co/blog/videos/building-web-application-using-spring-framework/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring 이란?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 은 특정한 하나가 아니라 아래 나열된 여러가지 기술들의 모음이라고 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;필수&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Framework : Spring 의 가장 핵심&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-01-23 at 10.15.00 PM.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7GmNN/dJMcaia3hY9/ww1wJnA8rgrSc88bBld77k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7GmNN/dJMcaia3hY9/ww1wJnA8rgrSc88bBld77k/img.png&quot; data-alt=&quot;출처 : https://spring.io/projects&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7GmNN/dJMcaia3hY9/ww1wJnA8rgrSc88bBld77k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7GmNN%2FdJMcaia3hY9%2Fww1wJnA8rgrSc88bBld77k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;474&quot; height=&quot;426&quot; data-filename=&quot;Screenshot 2026-01-23 at 10.15.00 PM.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://spring.io/projects&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot : Spring Framework 를 편리하게 사용할 수 있게 해주는 프레임워크&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-01-23 at 10.14.23 PM.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QdmLh/dJMcadndNkG/WrVlkkoBtpXNm8JtoOiJL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QdmLh/dJMcadndNkG/WrVlkkoBtpXNm8JtoOiJL1/img.png&quot; data-alt=&quot;출처 : https://spring.io/projects&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QdmLh/dJMcadndNkG/WrVlkkoBtpXNm8JtoOiJL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQdmLh%2FdJMcadndNkG%2FWrVlkkoBtpXNm8JtoOiJL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;485&quot; height=&quot;249&quot; data-filename=&quot;Screenshot 2026-01-23 at 10.14.23 PM.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://spring.io/projects&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;선택&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Data
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;데이터베이스 CRUD 를 편리하게 사용할 수 있도록 도와주는 기술&lt;/li&gt;
&lt;li&gt;주로 Spring Data JPA 를 많이 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Session
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;세셔 기능을 편리하게 사용할 수 있도록 도와주는 기능&lt;/li&gt;
&lt;li&gt;ex. 세션 만료 시간 통합 관리, 세션 무효화, 사용자별 동시 로그인 제어, Spring Security 와 연동&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Security&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Spring Framework 로 만든 애플리케이션을 보호하기 위한 보안 프레임워크&lt;/li&gt;
&lt;li&gt;로그인, 회원관리, 권한 설정, 해킹 방지 등의 보안 기능을 개발자가 직접 만들고 관리할 필요 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Rest Docs
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;테스트를 통해 검증된 REST API 만 문서로 생성해주는 도구&lt;/li&gt;
&lt;li&gt;코드와 문서의 불일치 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Batch
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;대용량 데이터를 한 번에 묶어서(batch) 처리하기 위한 배치 처리 프레임워크&lt;/li&gt;
&lt;li&gt;정해진 시간, 조건에 따라 많은 데이터를 안정적으로 처리해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Cloud
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;MSA 환경에서 필요한 공통 인프라 관리 도구&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring 단어&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 은 문맥에 따라 다양하게 해석될 수 있다.&lt;/li&gt;
&lt;li&gt;좁게는 Spring 의 핵심 기술인 Spring DI Container 기술을 뜻한다.&lt;/li&gt;
&lt;li&gt;Spring Framework 자체를 의미하기도 한다.&lt;/li&gt;
&lt;li&gt;Spring Boot, Spring Framework 등을 모두 포함한 스프링 생태계로 사용되기도 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Spring Framework&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring Framework 구성&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;핵심 기술
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Spring DI 컨테이너&lt;/li&gt;
&lt;li&gt;AOP&lt;/li&gt;
&lt;li&gt;이벤트&lt;/li&gt;
&lt;li&gt;기타&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Web 기술
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Spring MVC&lt;/li&gt;
&lt;li&gt;Spring WebFlux&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 접근 기술
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;DB 트랜잭션&lt;/li&gt;
&lt;li&gt;JDBC&lt;/li&gt;
&lt;li&gt;ORM 지원&lt;/li&gt;
&lt;li&gt;XML 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기술 통합
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;캐시&lt;/li&gt;
&lt;li&gt;이메일&lt;/li&gt;
&lt;li&gt;원격 접근&lt;/li&gt;
&lt;li&gt;스케줄링&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Spring 기반 테스트 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;언어
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Kotlin&lt;/li&gt;
&lt;li&gt;Groovy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Spring Boot&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring Boot 란?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot 는 Spring Framework 를 편리하게 사용할 수 있도록 지원해주는 프레임워크이다.&lt;/li&gt;
&lt;li&gt;최근에는 프로젝트에서 Spring Boot 를 기본으로 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;특징&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tomcat 같은 웹 서버를 내장해서 별도의 웹 서버를 설치하지 않아도 됨&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;단독으로 실행할 수 있는 Spring 애플리케이션을 쉽게 생성&lt;/li&gt;
&lt;li&gt;Tomcat 설치나 서버 설정 따로 필요 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;손쉬운 빌드 구성을 위한 starter 종속성 제공
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;ex. 웹 API 를 만들고 싶을 때, &lt;br /&gt;&lt;b&gt;implementation 'org.springframework.boot:spring-boot-starter-web'&lt;/b&gt; &lt;br /&gt;을 통해서 해당 기능을 쓰려면 필요한 라이브러리들을 가져와준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 과 3rd party (외부) 라이브러리 자동 구성
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;ex. MySQL 을 사용하려고 하면,&lt;br /&gt;&lt;b&gt;implementation 'mysql:mysql-connector-j'&lt;br /&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;라이브러리를 추가하고,&lt;br /&gt;&lt;b&gt;spring:&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;datasource:&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url:&amp;nbsp;jdbc:mysql://localhost:3306/test&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username:&amp;nbsp;root&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password:&amp;nbsp;1234&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;yml 설정 파일을 작성하면 Spring 이 자동으로 연동해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;메트릭, 상태 확인, 외부 구성 같은 프로덕션 준비 기능 제공
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;implementation&amp;nbsp;'spring-boot-starter-actuator'&lt;/b&gt;&lt;br /&gt;을 통해 운영할 때 꼭 필요한 기능들을 기본으로 제공&lt;/li&gt;
&lt;li&gt;ex. 서버가 살아있는지, 메모리를 얼마나 쓰는지, DB 연결 상태는 어떤지 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;관례에 의한 간결한 설정
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;대부분의 사람들이 이렇게 쓸 거라 가정하고 기본값을 정해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Spring은 왜 만들었나요?&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 의 핵심 개념을 이해하면 기술을 이해하기 쉬워지기 때문에 Spring 이 만들어진 이유에 대해 알아보자&lt;/li&gt;
&lt;li&gt;이 기술은 왜 만들었는지? 핵심 컨셉은 무엇인지?&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring 핵심 개념&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 언어 기반의 프레임워크&lt;/li&gt;
&lt;li&gt;Java 언어의 가장 큰 특징은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;객체 지향 언어&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Spring 은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크&lt;/li&gt;
&lt;li&gt;Spring 은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;좋은 객체 지향&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;애플리케이션을 개발할 수 있게 도와주는 프레임워크&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;그래서 그게 뭔데?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 이전에는 EJB 라는 기술이 있었지만, 굉장히 의존적이고 복잡하고 어려운 단점이 있었다.&lt;/li&gt;
&lt;li&gt;이렇게 의존적일 경우, EJB에 종속되어 EJB 스탕이로만 개발해야하기 때문에 객체 지향이 가진 좋은 장점들을 잃게 된다.&lt;/li&gt;
&lt;li&gt;이러한 단점을 극복하고 객체 지향 본래의 특성을 활용하기 위해 순수한 Java 로 돌아가기 위한 POJO 개념이 등장하기도 했다.&lt;/li&gt;
&lt;li&gt;이러한 POJO 기반 개발을 실제로 가능하게 만들기 위해 Spring은 객체 생성과 의존성 관리를 컨테이너가 담당하는 IoC와 DI 개념을 도입하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;결론&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 이 제대로 된 객체지향 프로그래밍을 할 수 있게 도와주는 도구라는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;어렵네&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 객체 지향 프로그램이란 뭘까를 이해해야 Spring Framework 를 제대로 이해할 수 있다.&lt;/li&gt;
&lt;li&gt;이것이 모든 것의 출발점 !&lt;/li&gt;
&lt;li&gt;Spring 의 본질을 알면 객체 지향 프로그래밍에 얼마나 최적화 되어있는지 이해할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;출처&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769092801088&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의&quot; data-og-description=&quot;현재 평점 5.0점 수강생 49,133명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xt7dT/dJMb8PGqidN/e5geL2Ooi2Jh6jaKyyMxI0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/rfIDU/dJMb9g45hzI/kLye9lZguFTftniAg4kHz1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/bvPuB3/dJMb8WetIwp/eS8rilr1sYVY3RVCpcuXV0/img.png?width=920&amp;amp;height=456&amp;amp;face=0_0_920_456&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xt7dT/dJMb8PGqidN/e5geL2Ooi2Jh6jaKyyMxI0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/rfIDU/dJMb9g45hzI/kLye9lZguFTftniAg4kHz1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/bvPuB3/dJMb8WetIwp/eS8rilr1sYVY3RVCpcuXV0/img.png?width=920&amp;amp;height=456&amp;amp;face=0_0_920_456');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;현재 평점 5.0점 수강생 49,133명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Java Framework/[인프런] 스프링 핵심 원리 - 기본편</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/497</guid>
      <comments>https://annovation.tistory.com/497#entry497comment</comments>
      <pubDate>Thu, 22 Jan 2026 23:41:39 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 권한 별 기능 제한 로직 구현 (3) (업데이트 중..)</title>
      <link>https://annovation.tistory.com/492</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Controller&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@PreAuthorize&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768553447244&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PreAuthorize(&quot;hasRole('MASTER')&quot;)
@PutMapping(&quot;/users/{userId}&quot;)
public void update(
        @PathVariable Long userId,
        @AuthenticationPrincipal CustomUserDetails user
) {
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;userRole 역할 별로 필터링 하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@AuthenticationPrincipal&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768553196690&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/profile&quot;)
public ResponseEntity&amp;lt;?&amp;gt; profile(
        @AuthenticationPrincipal CustomUserDetails userDetails
) {
    profileService.getProfile(userDetails.getUserId());
    return ResponseEntity.ok().build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;user 정보 받아오기&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Projects/hub-eleven</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/492</guid>
      <comments>https://annovation.tistory.com/492#entry492comment</comments>
      <pubDate>Fri, 16 Jan 2026 17:51:28 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 공통 모듈 수정으로 인한 리팩토링</title>
      <link>https://annovation.tistory.com/491</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;공통 모듈 repo 위치 변경&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;build.gradle 변경&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;repositories&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768468104213&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;repositories {
    mavenCentral()
    maven { url 'https://jitpack.io' }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;implementation&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;implementation 'com.github.ElevenHub:HubEleven-common:v0.0.1'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;변경 사유&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 common (공통 모듈) 이 팀장님 개인 repo 위치에 있어, 팀원 모두 공통 모듈 변경 가능하도록 팀 organization repo로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;응답값 변경&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;기존 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768467750486&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Operation(summary = &quot;주문 생성 API&quot;, description = &quot;새로운 주문을 생성한다.&quot;)
    @PostMapping
    public ResponseEntity&amp;lt;ApiResponse&amp;lt;OrderResponse&amp;gt;&amp;gt; create(
            @Valid @RequestBody OrderRequests.Create request) {

        OrderResult result = orderService.create(request);

        return ApiResponseEntity.success(OrderResponse.from(result));
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;return&amp;nbsp;ApiResponseEntity.success(OrderResponse.from(result));&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;변경 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768467833563&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Operation(summary = &quot;주문 생성 API&quot;, description = &quot;새로운 주문을 생성한다.&quot;)
    @PostMapping
    public ResponseEntity&amp;lt;ApiResponse&amp;lt;OrderResponse&amp;gt;&amp;gt; create(
            @Valid @RequestBody OrderRequests.Create request) {

        OrderResult result = orderService.create(request);

        return ResponseEntity.status(HttpStatus.CREATED)
                .body(ApiResponse.success(OrderResponse.from(result)));
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(OrderResponse.from(result)));&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;변경 사유&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;ErrorCode&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;기존 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768468167197&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum OrderErrorCode implements StatusCode {
    // Company
    REQUESTOR_COMPANY_NOT_FOUND(HttpStatus.NOT_FOUND, &quot;업체를 찾을 수 없습니다.&quot;),
    RECIPIENT_COMPANY_NOT_FOUND(HttpStatus.NOT_FOUND, &quot;업체를 찾을 수 없습니다.&quot;),

    // Product
    PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, &quot;상품을 찾을 수 없습니다.&quot;),

    // Stock
    STOCK_RESTORE_FAILED(HttpStatus.BAD_REQUEST, &quot;재고 복구에 실패했습니다.&quot;),
    STOCK_INSUFFICIENT(HttpStatus.BAD_REQUEST, &quot;재고가 부족합니다.&quot;),

    // Order
    ORDER_NOT_FOUND(HttpStatus.NOT_FOUND, &quot;주문을 찾을 수 없습니다.&quot;),

    // Authorization
    MASTER_ONLY(HttpStatus.FORBIDDEN, &quot;MASTER 권한이 있어야 수행할 수 있습니다.&quot;),
    HUB_MANAGER_ONLY(HttpStatus.FORBIDDEN, &quot;HUB_MANAGER 권한이 있어야 수행할 수 있습니다.&quot;),
    DELIVERY_MANAGER_ONLY(HttpStatus.FORBIDDEN, &quot;DELIVERY_MANAGER 권한이 있어야 수행할 수 있습니다.&quot;),
    COMPANY_MANAGER_ONLY(HttpStatus.FORBIDDEN, &quot;COMPANY_MANAGER 권한이 있어야 수행할 수 있습니다.&quot;);

    private final HttpStatus httpStatus;
    private final String message;

    @Override
    public String getName() {
        return name();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;변경 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768468205916&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@RequiredArgsConstructor
public enum OrderErrorCode implements ErrorCode {
    // Company
    REQUESTOR_COMPANY_NOT_FOUND(HttpStatus.NOT_FOUND, &quot;업체를 찾을 수 없습니다.&quot;),
    RECIPIENT_COMPANY_NOT_FOUND(HttpStatus.NOT_FOUND, &quot;업체를 찾을 수 없습니다.&quot;),

    // Product
    PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, &quot;상품을 찾을 수 없습니다.&quot;),

    // Stock
    STOCK_RESTORE_FAILED(HttpStatus.BAD_REQUEST, &quot;재고 복구에 실패했습니다.&quot;),
    STOCK_INSUFFICIENT(HttpStatus.BAD_REQUEST, &quot;재고가 부족합니다.&quot;),

    // Order
    ORDER_NOT_FOUND(HttpStatus.NOT_FOUND, &quot;주문을 찾을 수 없습니다.&quot;),

    // Authorization
    MASTER_ONLY(HttpStatus.FORBIDDEN, &quot;MASTER 권한이 있어야 수행할 수 있습니다.&quot;),
    HUB_MANAGER_ONLY(HttpStatus.FORBIDDEN, &quot;HUB_MANAGER 권한이 있어야 수행할 수 있습니다.&quot;),
    DELIVERY_MANAGER_ONLY(HttpStatus.FORBIDDEN, &quot;DELIVERY_MANAGER 권한이 있어야 수행할 수 있습니다.&quot;),
    COMPANY_MANAGER_ONLY(HttpStatus.FORBIDDEN, &quot;COMPANY_MANAGER 권한이 있어야 수행할 수 있습니다.&quot;);

    private final HttpStatus httpStatus;
    private final String message;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;implements 를 ErrorCode 로 변경&lt;/li&gt;
&lt;li&gt;기존 StatusCode 의 Override 메서드 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;변경 사유&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Projects/hub-eleven</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/491</guid>
      <comments>https://annovation.tistory.com/491#entry491comment</comments>
      <pubDate>Thu, 15 Jan 2026 18:14:10 +0900</pubDate>
    </item>
    <item>
      <title>[BAEKJOON / Java] 25494. 단순한 문제 (Small) (업데이트 중..)</title>
      <link>https://annovation.tistory.com/490</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Question &lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/25494&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/25494&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Algorithm&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Code&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1756560869927&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    private void solution() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int t = Integer.parseInt(br.readLine());
        StringBuilder sb = new StringBuilder();
        while (t--&amp;gt;0) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            int c = Integer.parseInt(st.nextToken());
            int cnt = 0;
            for (int i = 1; i &amp;lt;= a; i++) {
                for (int j = 1; j &amp;lt;= b; j++) {
                    for (int k = 1; k &amp;lt;= c; k++) {
                        if (i%j==j%k &amp;amp;&amp;amp; j%k==k%i &amp;amp;&amp;amp; i%j==k%i) {
                            cnt++;
                        }
                    }
                }
            }
            sb.append(cnt).append('\n');
        }
        System.out.println(sb);
    }

    public static void main(String[] args) throws Exception {
        new Main().solution();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;시간 복잡도&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Coding Test/[BAEKJOON] Java</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/490</guid>
      <comments>https://annovation.tistory.com/490#entry490comment</comments>
      <pubDate>Tue, 13 Jan 2026 23:52:03 +0900</pubDate>
    </item>
    <item>
      <title>코딩 테스트 유형</title>
      <link>https://annovation.tistory.com/489</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zN0TK/dJMcaiveBB2/QMasQJV0s6E9ZlrAEL4fTk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zN0TK/dJMcaiveBB2/QMasQJV0s6E9ZlrAEL4fTk/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zN0TK/dJMcaiveBB2/QMasQJV0s6E9ZlrAEL4fTk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzN0TK%2FdJMcaiveBB2%2FQMasQJV0s6E9ZlrAEL4fTk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;710&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://littlemobs.com/blog/coding-test-algorithms-top-down-overview/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://littlemobs.com/blog/coding-test-algorithms-top-down-overview/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768229575654&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;코딩 테스트 준비 전 모르면 큰일나는 알고리즘 문제 유형 파악 및 꿀팁 정리 - LittleMobs&quot; data-og-description=&quot;코딩 테스트를 준비할 시간이 부족하거나, 어디서부터 공부를 시작해야 할지 막막한가? 일단 무작정 알고리즘 문제풀이 사이트에 접속하여 문제를 풀고 있지만 잘하고 있는 것이 맞는지 의문이&quot; data-og-host=&quot;littlemobs.com&quot; data-og-source-url=&quot;https://littlemobs.com/blog/coding-test-algorithms-top-down-overview/&quot; data-og-url=&quot;https://littlemobs.com/blog/coding-test-algorithms-top-down-overview/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/byTuw0/hyZRkxJvUm/O7BjT98exuAivKVU58Htn0/img.png?width=1536&amp;amp;height=852&amp;amp;face=0_0_1536_852&quot;&gt;&lt;a href=&quot;https://littlemobs.com/blog/coding-test-algorithms-top-down-overview/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://littlemobs.com/blog/coding-test-algorithms-top-down-overview/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/byTuw0/hyZRkxJvUm/O7BjT98exuAivKVU58Htn0/img.png?width=1536&amp;amp;height=852&amp;amp;face=0_0_1536_852');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코딩 테스트 준비 전 모르면 큰일나는 알고리즘 문제 유형 파악 및 꿀팁 정리 - LittleMobs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코딩 테스트를 준비할 시간이 부족하거나, 어디서부터 공부를 시작해야 할지 막막한가? 일단 무작정 알고리즘 문제풀이 사이트에 접속하여 문제를 풀고 있지만 잘하고 있는 것이 맞는지 의문이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;littlemobs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Coding Test/자료구조 &amp;amp; 알고리즘</category>
      <category>CodingTest</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/489</guid>
      <comments>https://annovation.tistory.com/489#entry489comment</comments>
      <pubDate>Mon, 12 Jan 2026 23:50:38 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 권한 별 기능 제한 로직 구현 (2)</title>
      <link>https://annovation.tistory.com/486</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Controller &amp;rarr; Service&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Controller 에서 User 정보 받아오기&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767876672812&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Operation(summary = &quot;주문 전체 조회 API&quot;, description = &quot;주문 전체 목록을 조회한다.&quot;)
    @GetMapping
    public ResponseEntity&amp;lt;ApiResponse&amp;lt;CommonPageResponse&amp;lt;OrderResponse&amp;gt;&amp;gt;&amp;gt; getOrders(
            CommonPageRequest request,
            @RequestHeader(&quot;X-User-Id&quot;) Long userId,
            @RequestHeader(&quot;X-User-Role&quot;) String userRole
    ) {&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Header에서 받아온 userId 와 userRole 을 활용해 권한 관리 로직을 구현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;User Role 을 통해 Controller 에서 1차 검증&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;User ID 를 통해 Service 에서 2차 검증&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;AuthorizationValidator&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;AuthorizationValidator 클래스로 User Role 검증 로직 분리&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767876782468&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.hubEleven.order.application.validator;

import com.commonLib.common.exception.GlobalException;
import com.hubEleven.order.domain.exception.OrderErrorCode;
import org.springframework.stereotype.Component;

@Component
public class AuthorizationValidator {

    public void validateMaster(String userRole) {
        if (!&quot;MASTER&quot;.equalsIgnoreCase(userRole)) {
            throw new GlobalException(OrderErrorCode.MASTER_ONLY);
        }
    }

    public void validateHubManager(String userRole) {
        if (!&quot;HUB_MANAGER&quot;.equalsIgnoreCase(userRole)) {
            throw new GlobalException(OrderErrorCode.HUB_MANAGER_ONLY);
        }
    }

    public void validateDeliveryManager(String userRole) {
        if (!&quot;DELIVERY_MANAGER&quot;.equalsIgnoreCase(userRole)) {
            throw new GlobalException(OrderErrorCode.DELIVERY_MANAGER_ONLY);
        }
    }

    public void validateCompanyMaanger(String userRole) {
        if (!&quot;COMPANY_MANAGER&quot;.equalsIgnoreCase(userRole)) {
            throw new GlobalException(OrderErrorCode.COMPANY_MANAGER_ONLY);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;application/validaor 패키지에 Controller 에서 활용할 메서드를 클래스로 분리&lt;/li&gt;
&lt;li&gt;해당 클래스를 통해 Controller 에서는 User Role 을 통해 권한을 검증할 수 있는 로직&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/ElevenHub/HubEleven/pull/89/files&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/ElevenHub/HubEleven/pull/89/files&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768393158237&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;[refactor] 허브(Hub) 권한 및 userId 연결 by hinoyat &amp;middot; Pull Request #89 &amp;middot; ElevenHub/HubEleven&quot; data-og-description=&quot;#️⃣연관된 이슈 #88  작업 내용 gateway에서 받아온 사용자 정보 사용하도록 리팩토링 했습니다.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/ElevenHub/HubEleven/pull/89/files&quot; data-og-url=&quot;https://github.com/ElevenHub/HubEleven/pull/89&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dC4H8H/dJMb87NPGJr/ZbWbyNyC96l4goSsgThXZ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=946_153_989_199,https://scrap.kakaocdn.net/dn/ddFseq/dJMb82Mwqrm/El3WwI8jm9MfxDSJk8qN91/img.png?width=1200&amp;amp;height=600&amp;amp;face=946_153_989_199&quot;&gt;&lt;a href=&quot;https://github.com/ElevenHub/HubEleven/pull/89/files&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/ElevenHub/HubEleven/pull/89/files&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dC4H8H/dJMb87NPGJr/ZbWbyNyC96l4goSsgThXZ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=946_153_989_199,https://scrap.kakaocdn.net/dn/ddFseq/dJMb82Mwqrm/El3WwI8jm9MfxDSJk8qN91/img.png?width=1200&amp;amp;height=600&amp;amp;face=946_153_989_199');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[refactor] 허브(Hub) 권한 및 userId 연결 by hinoyat &amp;middot; Pull Request #89 &amp;middot; ElevenHub/HubEleven&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;#️⃣연관된 이슈 #88  작업 내용 gateway에서 받아온 사용자 정보 사용하도록 리팩토링 했습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/486</guid>
      <comments>https://annovation.tistory.com/486#entry486comment</comments>
      <pubDate>Thu, 8 Jan 2026 21:56:59 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] 권한 별 기능 제한 로직 구현 (1) (업데이트 중..)</title>
      <link>https://annovation.tistory.com/485</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;권한 관리&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;상품 (Product) 도메인 권한&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-01-07 at 10.51.26 PM.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QV2gl/dJMcacV5Zt1/ZvkKV3mUTxOHQsycYv7mtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QV2gl/dJMcacV5Zt1/ZvkKV3mUTxOHQsycYv7mtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QV2gl/dJMcacV5Zt1/ZvkKV3mUTxOHQsycYv7mtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQV2gl%2FdJMcacV5Zt1%2FZvkKV3mUTxOHQsycYv7mtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1250&quot; height=&quot;702&quot; data-filename=&quot;Screenshot 2026-01-07 at 10.51.26 PM.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;주문 (Order) 도메인 권한&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-01-07 at 10.51.39 PM.png&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQWWr9/dJMcaiPvnKn/rsDJSQauaBvbjYC7S2m10k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQWWr9/dJMcaiPvnKn/rsDJSQauaBvbjYC7S2m10k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQWWr9/dJMcaiPvnKn/rsDJSQauaBvbjYC7S2m10k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQWWr9%2FdJMcaiPvnKn%2FrsDJSQauaBvbjYC7S2m10k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1234&quot; height=&quot;654&quot; data-filename=&quot;Screenshot 2026-01-07 at 10.51.39 PM.png&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;권한 로직&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Controller&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767793975697&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping
public ResponseEntity&amp;lt;ApiResponse&amp;lt;HubResponseDto&amp;gt;&amp;gt; createHub(
        @Valid @RequestBody HubCreateRequestDto request,
        @RequestHeader(&quot;X-User-Id&quot;) Long userId,
        @RequestHeader(&quot;X-User-Role&quot;) String userRole,
        @RequestHeader(&quot;X-Username&quot;) String userNameString) {
    if (!&quot;MASTER&quot;.equalsIgnoreCase(userRole)) {
        throw new GlobalException(HubErrorCode.MASTER_ONLY);
    }
    CreateHubCommand command = request.toCommand(userId);
    HubResult result = hubService.createHub(command);
    HubResponseDto response = HubResponseDto.from(result);
    return ApiResponseEntity.success(response);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;전체 실행 흐름&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 클라이언트가 HTTP 요청 보냄&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;POST /hubs (예시)&lt;/li&gt;
&lt;li&gt;Body
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;HubCreateRequestDto에 매핑될 JSON&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;301&quot; data-start=&quot;221&quot;&gt;Headers
&lt;ul style=&quot;list-style-type: circle;&quot; data-end=&quot;301&quot; data-start=&quot;234&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-end=&quot;252&quot; data-start=&quot;234&quot;&gt;X-User-Id : 123&lt;/li&gt;
&lt;li data-end=&quot;278&quot; data-start=&quot;255&quot;&gt;X-User-Role : MASTER&lt;/li&gt;
&lt;li data-end=&quot;301&quot; data-start=&quot;281&quot;&gt;X-Username : sati&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;301&quot; data-start=&quot;281&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여기서 핵심은, 이 헤더들은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Spring이 자동으로 꺼내서 파라미터에 주입&lt;/b&gt;한다는 점&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 스프링이 매핑되는 컨트롤러 메서드 찾음&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@PostMapping이 붙은 메서드가 해당 경로/HTTP 메서드와 매칭되면 실행 대상으로 선택됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/ElevenHub/HubEleven/pull/89/files?diff=split&amp;amp;w=0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/ElevenHub/HubEleven/pull/89/files?diff=split&amp;amp;w=0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767791912816&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;[refactor] 허브(Hub) 권한 및 userId 연결 by hinoyat &amp;middot; Pull Request #89 &amp;middot; ElevenHub/HubEleven&quot; data-og-description=&quot;#️⃣연관된 이슈 #88  작업 내용 gateway에서 받아온 사용자 정보 사용하도록 리팩토링 했습니다.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/ElevenHub/HubEleven/pull/89/files?diff=split&amp;amp;w=0&quot; data-og-url=&quot;https://github.com/ElevenHub/HubEleven/pull/89&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c4D6JG/hyZQ4hGMde/I0JxdNww5nGWq0TKFASSFK/img.png?width=1200&amp;amp;height=600&amp;amp;face=946_153_989_199,https://scrap.kakaocdn.net/dn/bzavWv/hyZRbuh6iN/Tlx2E50EIXnocvjUumdEgk/img.png?width=1200&amp;amp;height=600&amp;amp;face=946_153_989_199&quot;&gt;&lt;a href=&quot;https://github.com/ElevenHub/HubEleven/pull/89/files?diff=split&amp;amp;w=0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/ElevenHub/HubEleven/pull/89/files?diff=split&amp;amp;w=0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c4D6JG/hyZQ4hGMde/I0JxdNww5nGWq0TKFASSFK/img.png?width=1200&amp;amp;height=600&amp;amp;face=946_153_989_199,https://scrap.kakaocdn.net/dn/bzavWv/hyZRbuh6iN/Tlx2E50EIXnocvjUumdEgk/img.png?width=1200&amp;amp;height=600&amp;amp;face=946_153_989_199');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[refactor] 허브(Hub) 권한 및 userId 연결 by hinoyat &amp;middot; Pull Request #89 &amp;middot; ElevenHub/HubEleven&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;#️⃣연관된 이슈 #88  작업 내용 gateway에서 받아온 사용자 정보 사용하도록 리팩토링 했습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/485</guid>
      <comments>https://annovation.tistory.com/485#entry485comment</comments>
      <pubDate>Wed, 7 Jan 2026 23:15:56 +0900</pubDate>
    </item>
    <item>
      <title>[리팩토링] Trouble Shooting : Swagger Request DTO 메서드 충돌</title>
      <link>https://annovation.tistory.com/484</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;문제 상황&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 상황&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot + springdoc-openapi 환경에서 API 문서를 Swagger UI로 확인하던 중, &lt;b&gt;서로 다른 Request DTO임에도 Swagger에서 하나의 Schema로 인식되는 문제&lt;/b&gt;&lt;span&gt;가 발생했다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- ProductRequests.Create&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767699771498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ProductRequests {

	public record Create(
			@NotBlank(message = &quot;상품명은 필수 입력 항목입니다.&quot;) @Size(max = 150, message = &quot;상품명은 150자 이하여야 합니다.&quot;)
					String name,
			@NotNull(message = &quot;업체 ID는 필수 입력 항목입니다.&quot;) UUID companyId,
			@NotNull(message = &quot;허브 ID는 필수 입력 항목입니다.&quot;) UUID hubId) {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- StockRequests.Create&lt;/p&gt;
&lt;pre id=&quot;code_1767699826271&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class StockRequests {

	public record Create(
			@NotNull(message = &quot;수량은 필수 입력 항목입니다.&quot;) @Min(value = 0, message = &quot;수량은 0 이상이어야 합니다.&quot;)
					int quantity,
			@NotNull(message = &quot;상품 ID는 필수 입력 항목입니다.&quot;) UUID productId,
			@NotNull(message = &quot;업체 ID는 필수 입력 항목입니다.&quot;) UUID companyId,
			@NotNull(message = &quot;허브 ID는 필수 입력 항목입니다.&quot;) UUID hubId) {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 도메인 모두 &lt;span&gt;Create&lt;/span&gt;라는 이름의 record를 사용하고 있었고, Controller에서는 다음과 같이 사용 중이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ProductController&lt;/p&gt;
&lt;pre id=&quot;code_1767699894519&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/products&quot;)
public void createProduct(@RequestBody ProductRequests.Create request) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- StockController&lt;/p&gt;
&lt;pre id=&quot;code_1767699854049&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/stocks&quot;)
public void createStock(@RequestBody StockRequests.Create request) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 증상&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Product / Stock API가 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;같은 Request 구조로 표시&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1463&quot; data-origin-height=&quot;1098&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpnwAi/dJMcahpzQRx/gpsMDKZ0kwFXfgtOO6YkQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpnwAi/dJMcahpzQRx/gpsMDKZ0kwFXfgtOO6YkQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpnwAi/dJMcahpzQRx/gpsMDKZ0kwFXfgtOO6YkQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpnwAi%2FdJMcahpzQRx%2FgpsMDKZ0kwFXfgtOO6YkQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1463&quot; height=&quot;1098&quot; data-origin-width=&quot;1463&quot; data-origin-height=&quot;1098&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;원인 분석&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Swagger(OpenAPI)의 schema 생성 방식 문제&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;springdoc-openapi는 기본적으로 &lt;span&gt;&lt;b&gt;클래스의 단순 이름(Simple Name)&lt;/b&gt;&lt;/span&gt; 을 기준으로 Schema 이름을 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1767700562786&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;schemaName = Create&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ProductRequests.Create&lt;/li&gt;
&lt;li&gt;StockRequests.Create&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;➡️ &lt;/span&gt;&lt;b&gt;둘 다 Swagger 입장에서는 같은 Create&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 &lt;span&gt;&lt;b&gt;Schema 이름 충돌&lt;/b&gt;&lt;/span&gt;이 발생했다. (&lt;a href=&quot;https://github.com/springdoc/springdoc-openapi/issues/548&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;해결 방안 검토&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. DTO 이름 변경&lt;/p&gt;
&lt;pre id=&quot;code_1767700665684&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;StockRequests.CreateStock&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 경우 Controller에서 다음과 같은 중복 표현이 발생한다.&lt;/p&gt;
&lt;pre id=&quot;code_1767700688674&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;StockRequests.CreateStock  // Stock이 두 번 반복됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;➡️ &lt;/span&gt;&lt;b&gt;도메인 구조 가독성이 나빠짐&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 코드 구조는 유지하고, Swagger 문서에서만 Schema 이름을 분리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 OpenAPI 전용 어노테이션인 &lt;span&gt;@Schema&lt;/span&gt;를 사용&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;해결 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@Schema 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 적용 코드&lt;/p&gt;
&lt;pre id=&quot;code_1767700821438&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import io.swagger.v3.oas.annotations.media.Schema;

public class StockRequests {

    @Schema(name = &quot;StockCreateRequest&quot;, description = &quot;재고 생성 요청&quot;)
    public record Create(
        int quantity,
        UUID productId,
        UUID companyId,
        UUID hubId
    ) {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Product 쪽도 동일하게 적용&lt;/p&gt;
&lt;pre id=&quot;code_1767700844111&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Schema(name = &quot;ProductCreateRequest&quot;, description = &quot;상품 생성 요청&quot;)
public record Create(
    String name,
    UUID companyId,
    UUID hubId
) {}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;@Schema 어노테이션&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Schema는 Swagger/OpenAPI 문서 생성 시 데이터 모델(스키마)의 메타 정보(이름, 설명, 예시 등)를 정의하기 위한 어노테이션으로, 서버 로직이나 validation에는 영향을 주지 않고 API 문서를 더 명확하게 만들어 준다. (&lt;a href=&quot;https://docs.swagger.io/swagger-core/v2.2.8/apidocs/io/swagger/v3/oas/annotations/media/Schema.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;생각해볼 것&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;규모가 더 커져서 다양한 Request 들이 필요해질 때, @Schema 사용만으로는 유지보수가 어려워질 것 같다.&lt;/li&gt;
&lt;li&gt;하나의 클래스에 관리하고 있는 Request 를 따로 분리하는 방법도 고려해봐야할 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) springdoc-openapi github&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/springdoc/springdoc-openapi/issues/548&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/springdoc/springdoc-openapi/issues/548&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767701057260&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Fully qualified names &amp;middot; Issue #548 &amp;middot; springdoc/springdoc-openapi&quot; data-og-description=&quot;Hi, Is it possible to show fully qualified java type names in the schemas? Its quite possible there might be multiple classes with same class simple name but different package names used . R&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/springdoc/springdoc-openapi/issues/548&quot; data-og-url=&quot;https://github.com/springdoc/springdoc-openapi/issues/548&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://github.com/springdoc/springdoc-openapi/issues/548&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/springdoc/springdoc-openapi/issues/548&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Fully qualified names &amp;middot; Issue #548 &amp;middot; springdoc/springdoc-openapi&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Hi, Is it possible to show fully qualified java type names in the schemas? Its quite possible there might be multiple classes with same class simple name but different package names used . R&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/484</guid>
      <comments>https://annovation.tistory.com/484#entry484comment</comments>
      <pubDate>Tue, 6 Jan 2026 21:04:22 +0900</pubDate>
    </item>
    <item>
      <title>[동시성 처리] Trouble Shooting : 테스트 코드 런타임 에러 발생</title>
      <link>https://annovation.tistory.com/483</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;테스트 코드 런타임 에러&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시성 처리 테스트 진행을 위해 재고 감소 로직 테스트 코드 작성 중 런타임 에러 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;런타임 에러 내용&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767616845647&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ConfigClientFailFastException: Could not locate PropertySource ... failing&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1767616858644&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ResourceAccessException: I/O error ... http://localhost:8888/... Connection refused&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;테스트가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;@SpringBootTest&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;ApplicationContext를 띄우는 순간 Spring Cloud Config Client&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;가 설정을 가져오려고 http://localhost:8888/product-service/default&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 GET 요청을 보냄&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;그런데&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;localhost:8888(Config Server)가 안 떠있어서 Connection refused&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Spring Cloud Config Client&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Spring Cloud Config Client 란?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Spring Cloud Config provides client-side and server-side support for externalized configuration in a distributed system.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Spring Cloud Config는 분산 시스템에서 외부화된 설정을 관리하기 위해 &lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;클라이언트 측과 서버 측 지원을 제공한다. &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;(&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://docs.spring.io/spring-cloud-config/docs/current/reference/html/&quot;&gt;출처&lt;/a&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Config Client는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;애플리케이션 안에 포함되는 라이브러리이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;애플리케이션이 시작될 때 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;외부 Config Server로부터 설정을 가져오는 역할을 한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;트러블 슈팅&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;해결 방법&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Config Client 비활성화&lt;/p&gt;
&lt;pre id=&quot;code_1770385326116&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  cloud:
    config:
      enabled: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Config Client optional 처리&lt;/p&gt;
&lt;pre id=&quot;code_1770385657385&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  config:
    import: optional:configserver:&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;요약&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Spring Cloud Config Client는 애플리케이션 시작 시 외부 Config Server에서 설정을 로드하는 선택적 컴포넌트이며,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;테스트 환경에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;spring.cloud.config.enabled=false&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;optional:configserver:&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;설정을 통해 &lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;로컬 설정만 사용하도록 구성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Spring docs&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-config/docs/current/reference/html/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-cloud-config/docs/current/reference/html/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767617476999&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Cloud Config&quot; data-og-description=&quot;Spring Cloud Config provides server-side and client-side support for externalized configuration in a distributed system. With the Config Server, you have a central place to manage external properties for applications across all environments. The concepts o&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-cloud-config/docs/current/reference/html/&quot; data-og-url=&quot;https://docs.spring.io/spring-cloud-config/docs/current/reference/html/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-config/docs/current/reference/html/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-cloud-config/docs/current/reference/html/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Cloud Config&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Cloud Config provides server-side and client-side support for externalized configuration in a distributed system. With the Config Server, you have a central place to manage external properties for applications across all environments. The concepts o&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Projects/hub-eleven</category>
      <category>til</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/483</guid>
      <comments>https://annovation.tistory.com/483#entry483comment</comments>
      <pubDate>Mon, 5 Jan 2026 21:51:19 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스 / MySQL] Lv.1 조건에 맞는 도서 리스트 출력하기</title>
      <link>https://annovation.tistory.com/482</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Question &lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/144853&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/144853&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767533016316&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/144853&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bj15LK/hyZRa2IRHI/vlxPKLmYJsVyUSPRLWSRH0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bExVX7/hyZQUEGjFU/DsVObDDng4gJM8UnDijzTK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/144853&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/144853&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bj15LK/hyZRa2IRHI/vlxPKLmYJsVyUSPRLWSRH0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bExVX7/hyZQUEGjFU/DsVObDDng4gJM8UnDijzTK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Algorithm&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 날짜 형식&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PUBLISHED_DATE&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;컬럼의 날짜 형식을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;YYYY-MM-DD&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형태로 변환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767534240692&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;date_format(PUBLISHED_DATE, '%Y-%m-%d') AS PUBLISHED_DATE&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 년도, 카테고리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;416&quot; data-start=&quot;352&quot;&gt;YEAR(PUBLISHED_DATE) = 2021&lt;br /&gt;&amp;rarr; 출판 연도가&lt;span&gt;&amp;nbsp;&lt;/span&gt;2021년인 데이터만 필터링&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;465&quot; data-start=&quot;417&quot;&gt;CATEGORY = '인문'&lt;br /&gt;&amp;rarr; 카테고리가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lsquo;인문&amp;rsquo;인 도서만 선택&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;465&quot; data-start=&quot;417&quot;&gt;AND&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;조건이므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;두 조건을 모두 만족하는 데이터만 조회&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 오름차순 정렬&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ASC&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;는 오름차순 (생략해도 기본값은 ASC)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;Code&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1756560835145&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BOOK_ID, date_format(PUBLISHED_DATE, '%Y-%m-%d') AS PUBLISHED_DATE
FROM BOOK
WHERE YEAR(PUBLISHED_DATE) = 2021 AND CATEGORY = '인문'
ORDER BY PUBLISHED_DATE ASC;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;border-bottom: 12px solid #dcf1fb; padding: 0 0 0 0.2em; font-weight: bold;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lungfish.tistory.com/entry/프로그래머스MYSQL-조건에-맞는-도서-리스트-출력하기&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://lungfish.tistory.com/entry/프로그래머스MYSQL-조건에-맞는-도서-리스트-출력하기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767533728398&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[프로그래머스/MYSQL] 조건에 맞는 도서 리스트 출력하기&quot; data-og-description=&quot;코딩테스트 연습 - 조건에 맞는 도서 리스트 출력하기 | 프로그래머스 스쿨&amp;nbsp;프로그래머스코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고&quot; data-og-host=&quot;lungfish.tistory.com&quot; data-og-source-url=&quot;https://lungfish.tistory.com/entry/프로그래머스MYSQL-조건에-맞는-도서-리스트-출력하기&quot; data-og-url=&quot;https://lungfish.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4MYSQL-%EC%A1%B0%EA%B1%B4%EC%97%90-%EB%A7%9E%EB%8A%94-%EB%8F%84%EC%84%9C-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d8Kwzm/hyZPO0oQah/aHqV5nkkMII5tCfeuhJiIK/img.png?width=800&amp;amp;height=1447&amp;amp;face=0_0_800_1447,https://scrap.kakaocdn.net/dn/dYtAuP/hyZPIy7jcN/7PjuV0HHRROjvRjF2GXp4K/img.png?width=800&amp;amp;height=1447&amp;amp;face=0_0_800_1447,https://scrap.kakaocdn.net/dn/uF6oO/hyZPIy7joh/9MK8M3KUMO8svwhts17nc1/img.png?width=975&amp;amp;height=1764&amp;amp;face=0_0_975_1764&quot;&gt;&lt;a href=&quot;https://lungfish.tistory.com/entry/프로그래머스MYSQL-조건에-맞는-도서-리스트-출력하기&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lungfish.tistory.com/entry/프로그래머스MYSQL-조건에-맞는-도서-리스트-출력하기&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d8Kwzm/hyZPO0oQah/aHqV5nkkMII5tCfeuhJiIK/img.png?width=800&amp;amp;height=1447&amp;amp;face=0_0_800_1447,https://scrap.kakaocdn.net/dn/dYtAuP/hyZPIy7jcN/7PjuV0HHRROjvRjF2GXp4K/img.png?width=800&amp;amp;height=1447&amp;amp;face=0_0_800_1447,https://scrap.kakaocdn.net/dn/uF6oO/hyZPIy7joh/9MK8M3KUMO8svwhts17nc1/img.png?width=975&amp;amp;height=1764&amp;amp;face=0_0_975_1764');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[프로그래머스/MYSQL] 조건에 맞는 도서 리스트 출력하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코딩테스트 연습 - 조건에 맞는 도서 리스트 출력하기 | 프로그래머스 스쿨&amp;nbsp;프로그래머스코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;lungfish.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Coding Test/[프로그래머스] SQL</category>
      <category>CodingTest</category>
      <author>annovation</author>
      <guid isPermaLink="true">https://annovation.tistory.com/482</guid>
      <comments>https://annovation.tistory.com/482#entry482comment</comments>
      <pubDate>Sun, 4 Jan 2026 22:45:41 +0900</pubDate>
    </item>
  </channel>
</rss>