<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Tamagotchi_Dev.log</title>
        <link>https://velog.io/</link>
        <description>"개발자는 해결사이자 발견자이다✨" - Michael C. Feathers</description>
        <lastBuildDate>Wed, 03 Sep 2025 18:34:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Tamagotchi_Dev.log</title>
            <url>https://velog.velcdn.com/images/kiteof_park/profile/9358b514-1d6d-43b4-a360-96b50e32bad6/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Tamagotchi_Dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kiteof_park" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[따맵] 헥사고날 아키텍처와 도메인 주도 설계(Hexagonal Architecture & Domain-Driven Design)]]></title>
            <link>https://velog.io/@kiteof_park/%EB%94%B0%EB%A7%B5-%ED%97%A5%EC%82%AC%EA%B3%A0%EB%82%A0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90%EC%99%80-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A3%BC%EB%8F%84-%EC%84%A4%EA%B3%84Hexagonal-Architecture-Domain-Driven-Design</link>
            <guid>https://velog.io/@kiteof_park/%EB%94%B0%EB%A7%B5-%ED%97%A5%EC%82%AC%EA%B3%A0%EB%82%A0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90%EC%99%80-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A3%BC%EB%8F%84-%EC%84%A4%EA%B3%84Hexagonal-Architecture-Domain-Driven-Design</guid>
            <pubDate>Wed, 03 Sep 2025 18:34:35 GMT</pubDate>
            <description><![CDATA[<p>따맵 백엔드를 구현하고 코드를 통합하면서 클로드가 헥사고날과 DDD 방향으로
코드를 통합한 잇슈 .. 패키지 구조를 하나도 못알아보겠는 까막눈 잇슈 ..</p>
<h3 id="헥사고날-아키텍처와-ddd">헥사고날 아키텍처와 DDD</h3>
<p>헥사고날 아키텍처와 DDD는 <strong>둘 다 도메인(비지니스 로직)을 중심에 둔다는 공통점</strong>이 있지만,
헥사고날 아키텍처는 <strong>&quot;시스템을 어떻게 계층화하고 의존성을 배치할까?&quot;</strong>에 초점
DDD는 <strong>&quot;도메인을 어떻게 모델링할까?&quot;</strong>에 초점</p>
<h1 id="헥사고날-아키텍처">헥사고날 아키텍처</h1>
<h2 id="hexagonal-architecture">Hexagonal Architecture</h2>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/14e80db2-44ca-4013-bfe1-1f278c36ed26/image.png" width = 90%>


<h2 id="🍀-헥사고날-아키텍처의-필요성">🍀 헥사고날 아키텍처의 필요성</h2>
<p>기존에 주로 개발하던 계층형 구조는 단순하고 직관적이다.</p>
<ul>
<li><code>Controller</code> : 외부 요청을 받는 역할</li>
<li><code>Service</code> : 비지니스 로직</li>
<li><code>Repository</code> : DB 접근</li>
</ul>
<p>하지만 계층형 구조의 문제점은 <strong>의존성 방향</strong>에 있다.
<code>Controller</code> → <code>Service</code> → <code>Repository</code> 순서로
아래로 내려가야 하고, <code>Service</code> 레이어는 DB같은 <strong>외부 인프라에 강하게 의존</strong>하게 된다.
따라서 <strong>비지니스 로직이 DB나 프레임워크에 종속</strong>되는 상황이 발생할 수 있다 ! !</p>
<h2 id="🍀-헥사고날-아키텍처의-핵심-아이디어">🍀 헥사고날 아키텍처의 핵심 아이디어</h2>
<ul>
<li><strong>비지니스 로직(=도메인)을 중심</strong>에 둔다.</li>
<li>DB, UI, 외부 API 등은 <strong>바깥쪽</strong>에 둔다.</li>
<li>도메인이 바깥쪽 인프라에 <strong>직접 의존하지 않는다.</strong></li>
</ul>
<p>따라서 <strong>어떤 프레임워크나 DB를 쓰더라도 비지니스 로직은 흔들리지 않는게 핵심</strong>이다.</p>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/de07d9aa-7b7c-438a-9093-0295f28b3175/image.png">

<ul>
<li><strong>[규칙서]</strong> <code>Domain</code> : 순수한 핵심 규칙(비지니스 로직), DB/웹/프레임워크를 모름</li>
<li><strong>[지시자]</strong> <code>Application</code> : 유스케이스(Service 역할), <code>Domain</code>을 이용해 시나리오 실행</li>
<li><strong>[계약서]</strong> <code>Port</code> : Application이 외부에 기대는 인터페이스(Ex. UserRepository)</li>
<li><strong>[실행자]</strong> <code>Adapter</code> : Port를 구현한 구체체(Ex. JPA 기반 JpaUserRepository)</li>
</ul>
<p>즉, <strong>헥사고날 아키텍처는 비지니스 로직을 외부 기술에서 독립시키는 구조</strong></p>
<hr>
<h1 id="도메인-주도-설계">도메인 주도 설계</h1>
<h2 id="ddd-domain-driven-design">DDD, Domain-Driven Design</h2>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/e8ac7cad-fa13-4548-b4c2-d868b90173b4/image.png">

<h2 id="🍀-ddd의-필요성">🍀 DDD의 필요성</h2>
<p>보통 개발을 하면 발생하는 문제점:</p>
<ul>
<li>코드가 기능 단위로만 작성 → 시간이 지나면 스파게티 코드</li>
<li>비지니스 규칙이 흩어져서 어디서 처리되는지 불명확</li>
<li>요구사항 변경 시 수정해야 할 코드가 여러 군데 </li>
</ul>
<p><strong>DDD는 도메인(비지니스 문제)를 중심에 두고, 이를 코드에 직접 반영하려는 접근</strong>
(뭔 소릴까 ..?)</p>
<h2 id="🍀-ddd의-핵심-아이디어">🍀 DDD의 핵심 아이디어</h2>
<ul>
<li><strong>도메인에 집중한다.</strong><ul>
<li>도메인 = 해결하려는 문제 영역</li>
<li>Ex. 따맵 : 자전거, 대여소, 사용자 / 은행 시스템 : 계좌, 송금, 대출</li>
</ul>
</li>
<li>유비쿼터스 언어<ul>
<li>개발자, 기획자, 현업 담당자 모두 같은 용어를 사용</li>
<li>&#39;결제 승인&#39;이라는 말을 코드에도 그대로 <code>approvePayment()</code>로 반영</li>
</ul>
</li>
</ul>
<h2 id="🍀-ddd의-기본-빌딩-블록">🍀 DDD의 기본 빌딩 블록</h2>
<p>DDD에서는 코드를 도메인 개념에 맞게 나눈다.</p>
<h3 id="1-entity-엔티티">(1) Entity (엔티티)</h3>
<ul>
<li>고유한 <code>ID</code>로 구별되는 객체</li>
<li>값이 변해도 동일성을 유지</li>
<li>Ex. <code>User(id=1, name=&quot;의연&quot;)</code> → 이름 바뀌어도 id=1이면 같은 User</li>
</ul>
<h3 id="2-value-object-값-객체">(2) Value Object (값 객체)</h3>
<ul>
<li>속성 값 자체로만 의미가 있는 객체 (<code>ID</code> 없음❌)</li>
<li>불변(immutable)으로 다루는 게 일반적</li>
<li>Ex. <code>Money(1000, &quot;KRW&quot;)</code>, <code>Email(&quot;velog@gmail.com&quot;)</code></li>
</ul>
<h3 id="3-aggregate">(3) Aggregate</h3>
<ul>
<li>Entity + VO 묶음 → 하나의 비즈니스 단위</li>
<li>항상 <strong>일관성</strong>을 유지해야 함</li>
<li>대표 엔티티 = Aggregate Root</li>
<li>Ex. <code>Order (root)</code> → <code>OrderItem(entity)</code>, <code>Money(VO)</code></li>
</ul>
<h3 id="4-repository">(4) Repository</h3>
<ul>
<li>Aggregate 저장/조회 담당</li>
<li>DB 접근을 추상화 (interface)</li>
<li>Ex. OrderRepository.save(order)</li>
</ul>
<h3 id="5-domain-service">(5) Domain Service</h3>
<ul>
<li>특정 Entity 하나에 속하지 않는 비즈니스 규칙을 담는 곳</li>
<li>Ex. 환율 계산, 결제 승인</li>
</ul>
<p>어렵땅 ...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[매일메일CS] 외부 API 장애 대응 전략 - 타임아웃, 벌크헤드, 서킷 브레이커]]></title>
            <link>https://velog.io/@kiteof_park/%EB%A7%A4%EC%9D%BC%EB%A9%94%EC%9D%BCCS-%EC%99%B8%EB%B6%80-API-%EC%9E%A5%EC%95%A0-%EB%8C%80%EC%9D%91-%EC%A0%84%EB%9E%B5-%ED%83%80%EC%9E%84%EC%95%84%EC%9B%83-%EB%B2%8C%ED%81%AC%ED%97%A4%EB%93%9C-%EC%84%9C%ED%82%B7-%EB%B8%8C%EB%A0%88%EC%9D%B4%EC%BB%A4-c9ytt3i4</link>
            <guid>https://velog.io/@kiteof_park/%EB%A7%A4%EC%9D%BC%EB%A9%94%EC%9D%BCCS-%EC%99%B8%EB%B6%80-API-%EC%9E%A5%EC%95%A0-%EB%8C%80%EC%9D%91-%EC%A0%84%EB%9E%B5-%ED%83%80%EC%9E%84%EC%95%84%EC%9B%83-%EB%B2%8C%ED%81%AC%ED%97%A4%EB%93%9C-%EC%84%9C%ED%82%B7-%EB%B8%8C%EB%A0%88%EC%9D%B4%EC%BB%A4-c9ytt3i4</guid>
            <pubDate>Fri, 01 Aug 2025 07:45:38 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.maeil-mail.kr/question/74">동기 방식으로 외부 서비스를 호출할 때 외부 서비스 장애가 나면 어떻게 조치할 수 있나요?</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[따맵] #1. 따릉이 대여소 저장 및 스케줄링]]></title>
            <link>https://velog.io/@kiteof_park/%EB%94%B0%EB%A7%B5-1.-%EB%94%B0%EB%A6%89%EC%9D%B4-%EB%8C%80%EC%97%AC%EC%86%8C-%EC%A0%80%EC%9E%A5-%EB%B0%8F-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81-5eusbawj</link>
            <guid>https://velog.io/@kiteof_park/%EB%94%B0%EB%A7%B5-1.-%EB%94%B0%EB%A6%89%EC%9D%B4-%EB%8C%80%EC%97%AC%EC%86%8C-%EC%A0%80%EC%9E%A5-%EB%B0%8F-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81-5eusbawj</guid>
            <pubDate>Thu, 31 Jul 2025 11:54:48 GMT</pubDate>
            <description><![CDATA[<h2 id="task">Task</h2>
<ul>
<li>✅ 따릉이 대여소 API 호출 → DB 저장</li>
<li>✅ 신규 및 폐쇄 대여소를 고려해 하루 1회 스케줄링</li>
</ul>
<hr>
<h2 id="외부-api를-호출하려면">외부 API를 호출하려면?</h2>
<p>우선 따릉이 대여소 API를 호출해 우리쪽 DB에 저장하는게 첫 번째 목표였다.
외부 API를 호출하는 방법은 대표적으로 5가지가 있는데,
그 중에서 가장 권장되는 방식인 <code>WebClient</code>를 사용했다.</p>
<p>✨ <a href="https://velog.io/@kiteof_park/Spring-Boot-%EC%99%B8%EB%B6%80-API%EB%A5%BC-%ED%98%B8%EC%B6%9C%ED%95%A0-%EB%95%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-HTTP-Client">외부 API를 호출할 때 사용하는 HTTP Client 정리 포스팅</a></p>
<h3 id="1-applicationyml-작성">1. application.yml 작성</h3>
<p><code>application.yml</code>은 <strong>스프링 부트 애플리케이션이 동작할 때 필요한 설정 값을 선언</strong>하는 곳으로,<br>따릉이 대여소 API를 호출하는 <code>baseUrl</code>과 <code>key</code> 작성했다.
(이 외에 서버, DB, JPA, 로그 설정 등을 <code>yml</code>에 작성한다.)</p>
<pre><code class="language-yml">bikeseoul:
  static:
    key: ${bikeseoul.static.key}
    baseUrl: http://openapi.seoul.go.kr:8088/{apiKey}/json/tbCycleStationInfo

  realtime:
    key: ${bikeseoul.realtime.key}
    baseUrl: http://openapi.seoul.go.kr:8088/{apiKey}/json/bikeList
</code></pre>
<p>✨ 발급받은 <code>key</code>는 <code>application-secret.yml</code>에 별도 저장</p>
<h3 id="2-configurationproperties">2. @ConfigurationProperties</h3>
<p><code>@ConfigurationProperties</code> 어노테이션은 <code>application.yml</code>에 정의된 설정 값을
<strong>자바 객체의 필드로 자동 바인딩</strong>해주는 어노테이션이다.</p>
<p>즉, <code>yml</code>의 설정 값 → <code>@ConfigurationProperties</code>가 붙은 클래스의 객체로 매핑
→ 필요한 클래스(외부 API를 호출하는 클래스)에서 주입받아 사용</p>
<pre><code class="language-java">@Component
@ConfigurationProperties(prefix = &quot;bikeseoul.static&quot;)
@Getter
@Setter
public class StationProperties {
    private String key;
    private String baseUrl;
}</code></pre>
<p>✨ 추후에 <code>StationApiClient</code>에서 <code>StationProperties</code>를 주입받아 외부 API 호출에 사용</p>
<h3 id="3-webclient-설정">3. WebClient 설정</h3>
<p>외부 API를 호출하기 위해서는 다른 서버로 HTTP 요청을 보내고 응답을 받아오는
라이브러리가 필요한데, 그 중 하나가 <code>WebClient</code>다. </p>
<p>✨ 스프링에서는 <strong>재사용 가능한 객체(빈)는 직접 생성하지 않고,
<code>@Configuration</code>에서 설정해놓고 필요할 때 의존성 주입을 통해 사용</strong>한다.</p>
<pre><code class="language-java">@Configuration        // 설정 클래스 명시
public class StationConfig {

    @Bean            // 스프링 빈 객체 등록 대상
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder()
                .exchangeStrategies(ExchangeStrategies.builder()
                        .codecs(configurer -&gt; configurer
                                .defaultCodecs()
                                .maxInMemorySize(10 * 1024 * 1024)) // 10MB
                        .build());
    }
}</code></pre>
<h3 id="4-외부-api-응답-dto-작성">4. 외부 API 응답 DTO 작성</h3>
<p>외부 API를 호출한 뒤에 받는 응답</p>
<h3 id="5-외부-api-호출-클라이언트-구현">5. 외부 API 호출 클라이언트 구현</h3>
<h3 id="6-db-저장">6. DB 저장</h3>
<h2 id="스케줄링">스케줄링</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[[매일메일CS] 동시성 제어와 공유 락(Shared Lock), 배타 락(Exclusive), 데드락(Deadlock) ]]></title>
            <link>https://velog.io/@kiteof_park/%EB%A7%A4%EC%9D%BC%EB%A9%94%EC%9D%BCCS-%EA%B3%B5%EC%9C%A0-%EB%9D%BD%EA%B3%BC-%EB%B0%B0%ED%83%80-%EB%9D%BD-hnkkvcs1</link>
            <guid>https://velog.io/@kiteof_park/%EB%A7%A4%EC%9D%BC%EB%A9%94%EC%9D%BCCS-%EA%B3%B5%EC%9C%A0-%EB%9D%BD%EA%B3%BC-%EB%B0%B0%ED%83%80-%EB%9D%BD-hnkkvcs1</guid>
            <pubDate>Thu, 31 Jul 2025 11:18:24 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.maeil-mail.kr/question/80">공유 락과 배타 락에 대해서 설명해주세요.</a></p>
<h1 id="동시성-제어와-락lock">동시성 제어와 락(Lock)</h1>
<h2 id="♻️동시성-제어concurrency-control">♻️동시성 제어(Concurrency Control)</h2>
<ul>
<li>동시성 제어란 <strong>여러 트랜잭션에 동시에 같은 데이터베이스에 접근</strong>할 때,</li>
<li><em>데이터의 정합성과 일관성을 보장*</em>하는 기법</li>
<li>데이터베이스는 성능을 위해 동시 처리(병행 실행)를 최대한 허용하지만,</li>
<li><em>동시에 실행되면 문제가 발생*</em>할 수 있음</li>
</ul>
<h3 id="대표적인-동시성-문제1---dirty-read">대표적인 동시성 문제1 <em>- Dirty Read</em></h3>
<ul>
<li>다른 트랜잭션이 <strong>아직 커밋하지 않은 변경 데이터를 읽음</strong></li>
<li>만약 T1이 롤백한다면, T2가 읽은 <code>var = 100</code>은 존재하지 않는 값이 되버림</li>
</ul>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/d0ce4a35-5fce-4f48-b6c2-04c42f73bb25/image.jpg">


<h3 id="대표적인-동시성-문제2---non-repeatable-read">대표적인 동시성 문제2 <em>- Non-Repeatable Read</em></h3>
<ul>
<li>한 트랜잭션에서 <strong>같은 데이터를 두 번 읽을 때 값이 다름</strong></li>
<li>첫 번째 <code>SELECT</code>와 두 번째 <code>SELECT</code> 사이에 다른 트랜잭션이 값을 변경</li>
</ul>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/a9014ccc-28e4-4372-85fd-41aeec6de794/image.jpg">


<h3 id="대표적인-동시성-문제3---phantom-read">대표적인 동시성 문제3 <em>- Phantom Read</em></h3>
<ul>
<li>조건에 맞는 행의 개수나 집합이 바뀜</li>
<li>첫 번째 <code>SELECT</code> 후 다른 트랜잭션이 <code>INSERT</code>하여 두 번째 <code>SELECT</code> 결과가 달라짐</li>
</ul>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/1c73aa7a-77ec-4c60-b17c-be83510a978c/image.jpg">


<h3 id="락lock의-필요성">락(Lock)의 필요성</h3>
<ul>
<li><strong>트랜잭션 간 동시 접근으로 인한 데이터 충돌을 방지</strong></li>
<li>DBMS는 <strong>동시성과 일관성 사이의 균형</strong>을 유지 <ul>
<li>락을 너무 강하게 걸면 → 동시성이 떨어져 성능 저하</li>
<li>락을 너무 약하게 걸면 → 데이터 정합성이 깨짐</li>
</ul>
</li>
<li>필요한 데이터 범위와 작업 성격에 따라 락을 선택적으로 걸어줌</li>
</ul>
<h2 id="🔒락의-두-가지-기본-형태">🔒락의 두 가지 기본 형태</h2>
<h3 id="공유-락s-lock-shared-lock">공유 락(S Lock, Shared Lock)</h3>
<ul>
<li><strong>읽기 전용 락</strong></li>
<li><strong>여러 트랜잭션이 동시에 같은 데이터에 공유 락</strong>을 걸 수 있음</li>
<li>공유 락이 걸린 데이터에 쓰기(<code>UPDATE</code>/<code>DELETE</code>)는 불가능</li>
<li>주로 <code>SELECT .. LOCK IN SHARE MODE</code> 같은 읽기 작업에서 사용</li>
</ul>
<pre><code class="language-SQL">T1: SELECT * FROM account WHERE id = 1 LOCK IN SHARE MODE;      -- S Lock 획득
T2: SELECT ... LOCK IN SHARE MODE;                              -- 가능
T3: UPDATE account SET balance = 0 WHERE id = 1;                 -- 대기</code></pre>
<h3 id="쓰기-락x-lock-exclusive-lock">쓰기 락(X Lock, Exclusive Lock)</h3>
<ul>
<li><strong>읽기/쓰기 모두 차단하는 락</strong></li>
<li>해당 데이터는 <strong>오직 락을 가진 트랜잭션만</strong> 읽고 쓸 수 있음</li>
<li>주로 <code>UPDATE</code>, <code>DELETE</code>, <code>INSERT</code>, 또는 <code>SELECT ... FOR UPDATE</code> 시 걸림</li>
<li>데이터 정합성을 위해 읽기조차 막음(Dirty Read 방지)</li>
</ul>
<p>💡<code>SELECT .. FOR UPDATE</code>는 트랜잭션에서 <code>SELECT</code>한 행에 대해 배타 락을 거는 문법으로
<strong>읽는 동시에 그 행을 수정할 권리를 독점</strong>(즉, SELECT 한 뒤에 UPDATE 할거야!)</p>
<pre><code class="language-SQL">T1: UPDATE account SET balance = 0 WHERE id = 1;                   -- X Lock 획득
T2: SELECT ... LOCK IN SHARE MODE;                                 -- 대기
T3: UPDATE account SET balance = 100 WHERE id = 1;                 -- 대기
</code></pre>
<h2 id="⏳-락과-데드락의-관계">⏳ 락과 데드락의 관계</h2>
<ul>
<li>락(Lock)은 동시성 제어를 위해 필요하지만, 잘못 사용되면</li>
<li><em>서로가 서로의 락을 기다리는 교착 상태(Deadlock)*</em>를 만들 수 있음</li>
<li>데드락은 <strong>두 개 이상의 트랜잭션이 서로가 보유한 자원을 기다리며 무한 대기에 빠지는 상황</strong></li>
</ul>
<pre><code>T1: account.id = 1 행의 S Lock → 그 다음 account.id = 2 행을 X Lock 하려고 함
T2: account.id = 2 행의 S Lock → 그 다음 account.id = 1 행을 X Lock 하려고 함
</code></pre><h3 id="데드락-해결예방-방법1---트랜잭션-설계-단계에서-예방">데드락 해결/예방 방법1 <em>- 트랜잭션 설계 단계에서 예방</em></h3>
<ul>
<li><strong>항상 동일한 순서로 자원을 잠금(락 획득 순서를 일관적으로 정해둠)</strong>
(Ex. 모든 트랜잭션에서 1번 데이터, 2번 데이터 순으로 락을 획득)</li>
<li>트랜잭션을 최소화 해 락을 오래 점유하지 못하도록 함</li>
<li>조건절 최적화, 범위 줄이기를 통해 불필요한 락 범위 줄이기</li>
</ul>
<h3 id="데드락-해결예방-방법2---db-설정으로-완화">데드락 해결/예방 방법2 <em>- DB 설정으로 완화</em></h3>
<ul>
<li><strong>Lock Timeout 설정</strong> : 일정 시간 기다렸다가 락이 안풀리면 ROLLBACK</li>
<li><strong>격리 수준 낮추기</strong> : READ COMMITED나 MVCC를 활용해 불필요한 대기 감소</li>
</ul>
<h3 id="데드락-해결예방-방법3---데드락-감지자동-해제">데드락 해결/예방 방법3 <em>- 데드락 감지&amp;자동 해제</em></h3>
<ul>
<li><code>InnoDB</code>, <code>PostgreSQL</code>, <code>Oracle</code> 등 대부분 DB는 주기적으로 <strong>데드락 감지 스레드</strong>가 돌아감</li>
<li>감지 시 둘 중 하나의 트랜잭션을 강제 ROLLBACK</li>
</ul>
<h3 id="next-step">NEXT STEP</h3>
<p>트랜잭션에 대해서 더 깊게 학습이 필요하다 ..!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[매일메일CS] 데이터베이스 커넥션 풀(Connection Pool)]]></title>
            <link>https://velog.io/@kiteof_park/%EB%A7%A4%EC%9D%BC%EB%A9%94%EC%9D%BCCS-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80Connection-Pool-zgkzmqp1</link>
            <guid>https://velog.io/@kiteof_park/%EB%A7%A4%EC%9D%BC%EB%A9%94%EC%9D%BCCS-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80Connection-Pool-zgkzmqp1</guid>
            <pubDate>Thu, 31 Jul 2025 11:17:33 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.maeil-mail.kr/question/88">데이터베이스 커넥션 풀(Connection Pool)을 사용하지 않으면 어떤 문제가 발생할 수 있나요?</a></p>
<h2 id="🔗-데이터베이스-커넥션">🔗 데이터베이스 커넥션</h2>
<ul>
<li>서버는 DB와 통신할 때 <code>Connection</code> 이라는 <strong>연결 통로</strong>가 필요하다.</li>
<li>즉, 커넥션은 <strong>애플리케이션 서버와 데이터베이스 사이의 통신선</strong></li>
<li>커넥션을 통해 SQL 쿼리를 전송하고 결과를 받아온다.</li>
<li>즉 <strong>애플리케이션과 DB 사이의 연결 세션으로, 쿼리를 보내고 응답을 받기 위한 통신 통로</strong></li>
<li>하나의 커넥션은 DB와의 <strong>TCP 커넥션 + 세션 컨텍스트</strong>를 의미한다.<strong>(TCP + DB 세션 연결)</strong></li>
<li><strong>커넥션을 열고 닫는 데는 네트워크 연결, 인증, 세션 설정 등의 작업으로 비용이 크다.</strong></li>
</ul>
<h3 id="커넥션의-특징과-문제점">커넥션의 특징과 문제점</h3>
<p><strong>1. 비싼 비용💸</strong></p>
<ul>
<li>매 요청 마다 커넥션을 열고 닫으면, DB와 네트워크에 부하가 크다.</li>
</ul>
<p><strong>2. 제한된 커넥션 수🚫</strong></p>
<ul>
<li>DB 서버는 동시에 처리할 수 있는 커넥션 수가 정해져 있어,
웹 서버 트래픽이 몰릴 때 커넥션이 부족한 문제가 발생할 수 있다.
(<code>Connection refused</code>, <code>Too many connections</code>)</li>
</ul>
<h2 id="🔗-커넥션-풀">🔗 커넥션 풀</h2>
<p><img src="https://velog.velcdn.com/images/kiteof_park/post/bf88f3c7-fc2e-4b63-864d-5896d3f62769/image.png" alt=""></p>
<ul>
<li>커넥션의 특징/문제점으로 인해 필요한 개념이 <code>Connection Pool</code>이다.</li>
<li>커넥션 풀은 <strong>미리 커넥션을 만들어두고 재사용</strong>해서 <strong>커넥션 생성/삭제 비용을 줄이고,
커넥션 수를 안정적으로 관리</strong>하는 방식이다.</li>
<li>대표적인 구현체로는 Java/Spring의 <code>HikariCP</code> <em>- 자주봤죵~?</em></li>
</ul>
<h3 id="커넥션-풀-주요-설정">커넥션 풀 주요 설정</h3>
<ul>
<li><code>maximumPoolSize</code> : 풀에 유지할 최대 커넥션 수</li>
<li><code>minimumIdle</code> : 유휴 상태로 유지할 커넥션 수</li>
<li><code>connectionTimeout</code> : 커넥션을 못 얻어올 떄까지 기다릴 최대 시간</li>
<li><code>idleTimeout</code> : 유휴 커넥션을 얼마나 오래 유지할지</li>
<li><code>maxLifetime</code> : 커넥션 객체가 살아있을 수 있는 최대 시간</li>
</ul>
<p>💡 <strong>커넥션 유휴 상태</strong>란 DB 커넥션이 열려있지만, 아무 작업도 하지 않는 <strong>대기 상태</strong></p>
<h3 id="커넥션-풀-작동-방식">커넥션 풀 작동 방식</h3>
<ul>
<li>서버 시작 시 DB 커넥션을 미리 일정 개수 열어둠</li>
<li>HTTP 요청이 들어오면 풀에서 커넥션을 꺼내서 사용</li>
<li>처리 후 커넥션을 닫는게 아니라 <strong>풀에 반환</strong></li>
</ul>
<h3 id="커넥션-누수">커넥션 누수</h3>
<ul>
<li>커넥션을 반환하지 않으면 누수가 발생해 커넥션 고갈 문제 발생</li>
<li>이슈 발생 시 <code>Hikari Pool - Connection timeout</code>, <code>connection leak detected</code> 경고</li>
</ul>
<h2 id="🍀-커넥션-풀을-사용하지-않는다면">🍀 커넥션 풀을 사용하지 않는다면?</h2>
<h3 id="1-매-요청마다-커넥션-생성종료-→-성능저하">1. 매 요청마다 커넥션 생성/종료 → 성능저하</h3>
<ul>
<li><strong>커넥션 생성 과정</strong>은<ol>
<li><code>TCP</code> 소켓 연결</li>
<li><code>DB</code> 인증</li>
<li>세션 초기화</li>
</ol>
</li>
<li>이 과정은 수 ms ~ 수백 ms 걸릴 수 있는 과정</li>
<li><strong>매 HTTP 요청마다 이 과정을 반복하면 응답 속도가 급격히 느려짐</strong></li>
<li><strong>TPS가 떨어져서 같은 서버 성능으로 처리할 수 있는 요청 수 감소</strong></li>
</ul>
<h3 id="2-db-부하-증가">2. DB 부하 증가</h3>
<ul>
<li>매번 커넥션을 새로 만들면 DB 서버는 계속 새 세션을 생성/삭제해야 함</li>
<li>세션 생성/삭제도 DB의 CPU, 메모리 자원을 많이 소모</li>
<li><strong>동시에 수백~수천 요청이 들어오면 DB가 세션 생성 처리만 하다가 병목 발생</strong></li>
</ul>
<h3 id="3-커넥션-폭주--제한-초과">3. 커넥션 폭주 &amp; 제한 초과</h3>
<ul>
<li>DB는 동시 접속 수(<code>max_connections</code>)가 제한됨</li>
<li><strong>커넥션 풀 없이 무제한 생성은 순간적으로 DB 동시 접속 한도를 초과</strong></li>
<li>결과적으로 <code>Too many connections</code> 오류로 서비스 전체 장애</li>
</ul>
<h3 id="4-커넥션-재사용-불가-→-리소스-낭비">4. 커넥션 재사용 불가 → 리소스 낭비</h3>
<ul>
<li>커넥션은 만들기 비싼 자원인데, 매번 만들고 바로 버리면 낭비</li>
<li>풀을 쓰면 한 번 만든 커넥션을 여러 요청이 재사용 가능</li>
<li><strong>재사용이 없으면 네트워크, DB, CPU 낭비 + 응답속도 지연</strong></li>
</ul>
<p>❗ 이 외에도 커넥션 풀을 사용하지 않아 커넥션을 관리하지 않으면
<strong>확장성(Scalability) 문제, 장애 복구 및 안정성 저하 문제</strong>를 초래할 수 있음</p>
<h2 id="정리">정리</h2>
<p>매일메일 스터디에서 <code>커넥션 타임아웃(Connection Timeout)</code>과 관련된 질문을 받았을 때,
명확한 답변을 하지 못했는데, 그 이유는 커넥션을 DB에 중점을 두고 생각했기 때문이다.</p>
<p><strong>커넥션은 애플리케이션과 DB간의 통신선</strong>이라고 위에서 정리했는데,
그 통신의 기반이 <code>TCP</code>라는 부분을 간과했던 것 같다.</p>
<p>다시 정리하자면,</p>
<p>✅ <strong>커넥션 = 애플리케이션 ↔ DB 간의 통신선</strong>
✅ <strong>DB 커넥션은 TCP 연결 + DB 세션 상태까지 포함한 개념</strong>
✅ <strong>애플리케이션과 DB의 통신 기반은 TCP(TCP 소켓 연결)</strong></p>
<pre><code class="language-markdown">[애플리케이션]                  [DB 서버]
    |                               |
    | 1. TCP 연결 (소켓 open)       |
    |------------------------------&gt;|
    |                               |
    | 2. DB 인증 &amp; 세션 생성        |
    |------------------------------&gt;|
    |                               |
    | ← 커넥션(=TCP + 세션) -------- |
    |                               |
    ▼                               ▼
 ┌─────────────────────────────────────────┐
 │             커넥션 풀(Connection Pool)  │
 │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
 │ │Conn #1    │ │Conn #2    │ │Conn #3    │ │  ← 미리 만든 커넥션들
 │ │TCP + 세션 │ │TCP + 세션 │ │TCP + 세션 │ │
 │ └───────────┘ └───────────┘ └───────────┘ │
 └─────────────────────────────────────────┘
         ▲                 ▲
         |                 |
   요청 시 꺼내서 사용   요청 끝나면 반환
</code></pre>
<h3 id="next-step">NEXT STEP</h3>
<p>DB 세션을 공부해야겠다 ..!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] 외부 API를 호출할 때 사용하는 HTTP Client]]></title>
            <link>https://velog.io/@kiteof_park/Spring-Boot-%EC%99%B8%EB%B6%80-API%EB%A5%BC-%ED%98%B8%EC%B6%9C%ED%95%A0-%EB%95%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-HTTP-Client</link>
            <guid>https://velog.io/@kiteof_park/Spring-Boot-%EC%99%B8%EB%B6%80-API%EB%A5%BC-%ED%98%B8%EC%B6%9C%ED%95%A0-%EB%95%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-HTTP-Client</guid>
            <pubDate>Thu, 17 Jul 2025 18:57:36 GMT</pubDate>
            <description><![CDATA[<h3 id="외부-api와-httpclient">외부 API와 HttpClient</h3>
<p>각종 프로젝트에서 서비스를 개발하다보면 외부 API를 호출해야하는 일이
심심치 않게 발생하는데, 이렇게 Spring Boot에서 외부 API를 호출할 때 사용하는
다양한 <code>Http Client</code>에 대해 알아보자!</p>
<h2 id="http-client란">HTTP Client란?</h2>
<ul>
<li><code>HTTP Client</code>는 다른 서버(보통 HTTP 서버)로 <strong>HTTP 요청을 보내고, 
응답을 받아오는</strong> 프로그램 또는 라이브러리를 의미한다.</li>
<li>다시 말해 Java나 Spring 진영에서 <strong>클라이언트 역할</strong>을 하는게 <code>Http Client</code>다.</li>
</ul>
<h3 id="http-client의-작업">HTTP Client의 작업</h3>
<table>
<thead>
<tr>
<th>역할</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>요청 구성(method, header, body)</td>
<td>GET, POST, PUT 같은 메서드, 요청 헤더, 요청 본문 지정</td>
</tr>
<tr>
<td>요청 전송</td>
<td>네트워크를 통해 외부 서버로 전송</td>
</tr>
<tr>
<td>응답 수신</td>
<td>응답 코드(200, 404 등)와 본문(body) 등을 수신</td>
</tr>
<tr>
<td>응답 파싱</td>
<td>JSON, XML을 Java 객체로 변환 가능</td>
</tr>
</tbody></table>
<h2 id="🌼1-resttemplatedeprecated-예정">🌼1. RestTemplate(Deprecated 예정)</h2>
<ul>
<li>동기적 요청 처리</li>
<li>Spring에서 오래된 기본 <code>Http Client</code></li>
<li>사용법이 간단하지만 deprecated 예정 <em>- <code>WebClient</code>로 대체되는 추세</em></li>
</ul>
<pre><code class="language-java">@RestController
public class ApiController {

    private final RestTemplate restTemplate = new RestTemplate();

    @GetMapping(&quot;/call-api&quot;)
    public String callApi() {
        String url = &quot;https://api.example.com/data&quot;;
        // 해당 url로 GET 요청
        ResponseEntity&lt;String&gt; response = restTemplate.getForEntity(url, String.class);
        return response.getBody();
    }
}
</code></pre>
<h2 id="🌼2-webclient">🌼2. WebClient</h2>
<ul>
<li><strong>비동기(Reactive)&amp; 논블로킹 지원 <em>- Mono, Flux</em></strong></li>
<li>동기 방식으로도 사용 가능 <em>- <code>.block()</code></em></li>
<li>Spring이 권장하는 최신 방식</li>
</ul>
<h3 id="비동기-방식">비동기 방식</h3>
<pre><code class="language-java">@RestController
public class ApiController {

    private final WebClient webClient = WebClient.create();

    @GetMapping(&quot;/call-api&quot;)
    public Mono&lt;String&gt; callApi() {
        return webClient.get()                // GET 요청
                .uri(&quot;https://api.example.com/data&quot;)
                .retrieve()                    // 응답 처리
                .bodyToMono(String.class);    // Mono로 래핑된 결과
    }
}
</code></pre>
<h3 id="동기-방식">동기 방식</h3>
<pre><code class="language-java">@GetMapping(&quot;/call-api-blocking&quot;)
public String callApiBlocking() {
    return webClient.get()
            .uri(&quot;https://api.example.com/data&quot;)
            .retrieve()
            .bodyToMono(String.class)
            .block(); // 동기 방식
}</code></pre>
<h3 id="mono가-모에요">Mono가 모에요?</h3>
<ul>
<li><code>Mono</code>는 Reactor(비동기, 논블로킹 라이브러리)에서 제공하는 <strong>비동기 데이터 처리용 객체</strong></li>
<li><strong>0개 또는 1개의 데이터를 비동기적으로 반환</strong></li>
</ul>
<h2 id="🌼3-httpclient">🌼3. HttpClient</h2>
<ul>
<li>동기 방식</li>
<li>Apache <code>HttpComponents</code>, <code>CloseableHttpClient</code></li>
<li>Apache의 라이브러리 (spring-core에는 포함되지 않음❌) <em>- 외부 의존성 필요</em></li>
<li>커넥션 풀, 타임아웃 등 세밀한 설정이 가능</li>
<li><code>RestTemplate</code>이나 <code>WebClient</code>의 하위 엔진으로도 사용됨</li>
</ul>
<pre><code class="language-java">@RestController
public class ApiController {

    @GetMapping(&quot;/call-api-apache&quot;)
    public String callApiApache() throws IOException {
        CloseableHttpClient client = HttpClients.createDefault();
        HttpGet request = new HttpGet(&quot;https://api.example.com/data&quot;);
        CloseableHttpResponse response = client.execute(request);
        return EntityUtils.toString(response.getEntity());
    }
}</code></pre>
<h2 id="🌼4-okhttp">🌼4. OkHttp</h2>
<ul>
<li>비동기 방식, 동기 방식 모두 지원</li>
<li>Square사에서 만든 경량 <code>HTTP client</code> <em>- 외부 라이브러리 필요</em></li>
<li>안드로이드나 네이티브 자바에서 많이 사용</li>
<li>간결하고 빠름</li>
</ul>
<pre><code class="language-java">@RestController
public class ApiController {

    private final OkHttpClient client = new OkHttpClient();

    @GetMapping(&quot;/call-api-okhttp&quot;)
    public String callApiOkHttp() throws IOException {
        Request request = new Request.Builder()
                .url(&quot;https://api.example.com/data&quot;)
                .build();
        try (Response response = client.newCall(request).execute()) {
            return response.body().string();
        }
    }
}</code></pre>
<h2 id="🌼5-restclient">🌼5. RestClient</h2>
<ul>
<li>동기 방식</li>
<li><code>RestTemplate</code>의 진화 버전</li>
<li>Java 17 이상의 <code>HttpClient</code> 기반</li>
<li>타입 세이프한 요청 방식, 간결한 코드</li>
<li>Spring Boot 3.2 이상에서 정식 지원</li>
</ul>
<pre><code class="language-java">@RestController
public class ApiController {

    private final RestClient restClient = RestClient.create();

    @GetMapping(&quot;/call-api-restclient&quot;)
    public String callApiRestClient() {
        return restClient.get()
                .uri(&quot;https://api.example.com/data&quot;)
                .retrieve()
                .body(String.class);
    }
}</code></pre>
<hr>
<h2 id="🍀-http-client의-동기비동기-방식">🍀 Http Client의 동기/비동기 방식</h2>
<h3 id="동기-방식resttemplate-webclientblock">동기 방식(RestTemplate, WebClient.block())</h3>
<ul>
<li><strong>요청을 보낸 스레드는 응답이 올 때까지 대기</strong></li>
<li>응답이 온 뒤에 다음 코드 를 실행</li>
<li>요청/응답이 순차적이고 직관적이지만, <strong>많은 요청 처리 시 스레드 낭비 우려</strong></li>
</ul>
<pre><code class="language-java">String body = restTemplate.getForObject(url, String.class)
// ❗ 응답이 올 때까지 기다림(동기 방식)
// ❗ 이 다음 코드는 응답을 받은 후에 실행됨</code></pre>
<h3 id="비동기-방식webclient-mono-okhttp-async-completablefuture">비동기 방식(WebClient, Mono, OkHttp async, CompletableFuture)</h3>
<ul>
<li><strong>요청을 보낸 후 스레드는 바로 다음 코드로 넘어가 실행</strong></li>
<li>응답은 나중에 도착하고, <strong>콜백이나 리액티브 방식</strong>으로 처리됨</li>
<li>응답이 나중에 오더라도 처리할 준비만 해두고,
그동안 CPU는 다른 일을 처리할 수 있어 효율적</li>
</ul>
<pre><code class="language-java">WebClient.create()
    .get()
    .uri(url)
    .retrieve()
    .bodyToMono(String.class)
    .subscribe(body -&gt; System.out.println(&quot;응답: &quot; + body));
// ❗ 응답 기다리지 않고 바로 다음 코드 실행됨</code></pre>
<h2 id="🍀-http-client별-동기비동기-방식-정리">🍀 Http Client별 동기/비동기 방식 정리</h2>
<table>
<thead>
<tr>
<th><strong>Http Client</strong></th>
<th><strong>기본 동작 방식</strong></th>
<th><strong>비동기 지원 여부</strong></th>
<th><strong>비고</strong></th>
</tr>
</thead>
<tbody><tr>
<td>RestTemplate</td>
<td>동기</td>
<td>❌ 지원 안함</td>
<td>Deprecated 예정</td>
</tr>
<tr>
<td>WebClient</td>
<td>비동기(Mono/Flux 기반)</td>
<td>✅ block()으로 동기도 가능</td>
<td>Spring 5+ 추천</td>
</tr>
<tr>
<td>HttpClient</td>
<td>동기</td>
<td>⚠️ async 버전은 따로 있음</td>
<td>설정 유연</td>
</tr>
<tr>
<td>OkHttp</td>
<td>동기</td>
<td>✅ <code>.enqueue()</code>로 비동기 가능</td>
<td>경량 &amp; 빠름</td>
</tr>
<tr>
<td>RestClient</td>
<td>동기</td>
<td>❌ 아직은 비동기 지원 제한</td>
<td>Spring 6+ 추천</td>
</tr>
</tbody></table>
<h3 id="동기비동기-방식에서-고민점">동기/비동기 방식에서 고민점</h3>
<p>사용자가 서비스를 이용하는 UX적인 관점에서 비동기 방식으로 요청을 처리하는게 뭐가 있을까? </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 핵심 원리 기본] Section02. 객체 지향 설계와 스프링]]></title>
            <link>https://velog.io/@kiteof_park/%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-Section02.-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81</link>
            <guid>https://velog.io/@kiteof_park/%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-Section02.-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81</guid>
            <pubDate>Fri, 20 Jun 2025 16:59:11 GMT</pubDate>
            <description><![CDATA[<h2 id="06-좋은-객체-지향-프로그래밍이란">06. 좋은 객체 지향 프로그래밍이란?</h2>
<h3 id="🍀-역할과-구현을-분리">🍀 역할과 구현을 분리</h3>
<ul>
<li><strong>역할과 구현</strong>으로 구분하면 <strong>단순</strong>해지고, <strong>유연</strong>해지며 <strong>변경</strong>도 편리해진다.</li>
<li>역할과 구현의 분리에서 자바 언어의 <strong>다형성</strong>을 활용<ul>
<li>역할 = 인터페이스</li>
<li>구현 = 인터페이스를 구현한 클래스, 구현 객체</li>
</ul>
</li>
<li>객체를 설계할 때 역할과 구현을 명확히 분리</li>
<li><strong>객체 설계 시 역할(인터페이스)을 먼저 부여하고, 그 역할을 수행하는 구현 객체 만들기</strong></li>
</ul>
<h3 id="🍀-역할과-구현-분리의-장점">🍀 역할과 구현 분리의 장점</h3>
<ul>
<li>클라이언트는 대상의 역할(인터페이스)만 알면 된다</li>
<li>클라이언트는 구현 대상의 <strong>내부 구조를 몰라도 된다</strong></li>
<li>클라이언트는 구현 대상의 <strong>내부 구조가 변경되어도 영향을 받지 않는다</strong></li>
<li>클라이언트는 구현 대상 <strong>자체를 변경해도 영향을 받지 않는다</strong></li>
</ul>
<h3 id="🍀-객체의-협력이라는-관계부터-생각">🍀 객체의 협력이라는 관계부터 생각</h3>
<ul>
<li>혼자 있는 객체는 없다</li>
<li><strong>수 많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가짐</strong></li>
</ul>
<h3 id="🍀-다형성의-본질">🍀 다형성의 본질</h3>
<ul>
<li>인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경 가능</li>
<li>다형성의 본질을 이해하려면 <strong>협력</strong>이라는 객체 사이의 관계에서 시작</li>
<li><strong>클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있음</strong></li>
</ul>
<h3 id="🍀-정리역할과-구현의-분리">🍀 [정리]역할과 구현의 분리</h3>
<ul>
<li>실세계의 역할과 구현이라는 컨셉 → <strong>다형성</strong>을 통해 객체 세상에 적용</li>
<li>유연성, 변경 용이, 확장 가능한 설계</li>
<li>클라이언트에 영향을 주지 않는 변경 가능</li>
<li>인터페이스를 안정적으로 잘 설계하는 것이 중요</li>
</ul>
<h3 id="🍀-한계역할과-구현의-분리">🍀 [한계]역할과 구현의 분리</h3>
<ul>
<li>역할(인터페이스) 자체가 변하면 클라이언트, 서버 모두에 큰 변경이 발생</li>
<li><strong>✨ 인터페이스를 안정적으로 잘 설계하는 것이 중요</strong></li>
</ul>
<h3 id="🍀-스프링과-객체-지향">🍀 스프링과 객체 지향</h3>
<ul>
<li><strong>다형성이 가장 중요</strong>, 스프링은 다형성을 극대화해 이용할 수 있도록 도와줌</li>
<li><strong>제어의 역전(<code>IoC</code>), 의존관계 주입(<code>DI</code>)은 다형성을 활용해<br>역할과 구현을 편리하게 다룰 수 있도록 지원</strong></li>
</ul>
<hr>
<h2 id="07-좋은-객체-지향-설계의-5가지-원칙solid">07. 좋은 객체 지향 설계의 5가지 원칙(SOLID)</h2>
<blockquote>
<p>OCP(개방 폐쇄 원칙), DIP(의존관계 역전 원칙)이 중요!</p>
</blockquote>
<h3 id="🍀-단일-책임-원칙-srpsingle-responsibility-principle">🍀 단일 책임 원칙, SRP(Single Responsibility Principle)</h3>
<ul>
<li>한 클래스는 하나의 책임만<ul>
<li>하나의 책임이라는 것은 모호하다</li>
<li>클 수도, 작을 수도 있고 문맥과 상황에 따라 다르다</li>
</ul>
</li>
<li><strong>✨ 중요한 기준은 변경, 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른것</strong></li>
<li>범위를 적절히 조절하는게 객체 지향 설계의 묘미</li>
</ul>
<h3 id="🍀-개방-폐쇄-원칙-ocp-open-close-principle">🍀 개방 폐쇄 원칙, OCP, Open Close Principle</h3>
<ul>
<li>✨ 소프트웨어 요소는 <strong>확장에는 열려있으나, 변경에는 닫혀있어야 한다.</strong></li>
<li>다형성, 역할과 구현의 분리를 의미</li>
<li>인터페이스를 구현한 클래스를 하나 만들어서 새로운 기능을 구현</li>
</ul>
<p><strong>개방 폐쇄 원칙의 문제점</strong></p>
<pre><code class="language-java">public class MemberService {
        private MemberRepository memberRepository = new MemoryMemberRepository();
}</code></pre>
<pre><code class="language-java">public class MemberService {
 // private MemberRepository memberRepository = new MemoryMemberRepository();
        private MemberRepository memberRepository = new JdbcMemberRepository();
}</code></pre>
<ul>
<li><code>MemberService</code> 클라이언트가 구현 클래스(레포지토리를 <code>new</code>를 통해 생성)를 직접 선택</li>
<li><strong>구현 객체(<code>MemoryRepository</code> → <code>JdbcRepository</code>)를 변경하려면 클라이언트 코드에 변경이 필요</strong></li>
<li>다형성을 사용했지만 OCP 원칙을 지킬 수 없다.<ul>
<li>이 문제를 해결하려면 객체를 생성하고, 연관관계를 맺어주는 별도의 조립/설정자가 필요</li>
<li>이 역할을 <strong>스프링 컨테이너</strong>가 해줌</li>
</ul>
</li>
</ul>
<h3 id="🍀-리스코프-치환-원칙-lsp-liskov-substitution-principle">🍀 리스코프 치환 원칙, LSP, <strong>Liskov Substitution Principle</strong></h3>
<ul>
<li>프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서,</li>
<li><em>하위 타입의 인스턴스로 바꿀 수 있어야 한다.*</em></li>
<li>다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것</li>
<li>다형성을 지원하기 위한 원칙</li>
<li>인터페이스를 구현한 구현체는 믿고 사용하려면 이 원칙이 필요</li>
</ul>
<h3 id="🍀-인터페이스-분리-원칙-isp-interface-segregation-principle">🍀 인터페이스 분리 원칙, ISP, Interface Segregation Principle</h3>
<ul>
<li>특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다</li>
<li>인터페이스가 명확해지고, 대체 가능성이 높아진다.</li>
</ul>
<h3 id="🍀-의존관계-역전-원칙-dip-dependency-inversion-principle">🍀 의존관계 역전 원칙, DIP, Dependency Inversion Principle</h3>
<ul>
<li>추상화에 의존하되 구체화에 의존하면 안된다. <em>- 의존성 주입은 이 원칙을 따르는 방법 중 하나</em></li>
<li><strong>구현 클래스에 의존❌, 인터페이스에 의존⭕</strong></li>
<li>역할에 의존하게 해야 한다는 것</li>
<li><strong>클라이언트가 인터페이스에 의존해야 유연하게 구현체 변경 가능</strong>
구현체에 의존할 경우 변경이 매우 어려워짐</li>
</ul>
<pre><code class="language-java">public class MemberService {
        private MemberRepository memberRepository = new MemoryMemberRepository();
}</code></pre>
<pre><code class="language-java">public class MemberService {
 // private MemberRepository memberRepository = new MemoryMemberRepository();
        private MemberRepository memberRepository = new JdbcMemberRepository();
}</code></pre>
<ul>
<li>이 코드에서 MemberService는 인터페이스에 의존하지만, <strong>구현 클래스도 동시에 의존한다.</strong><ul>
<li>인터페이스 : <code>MemberRepository</code></li>
<li>구현 클래스 : <code>MemoryMemberRepository</code>, <code>JdbcMemberRepository</code></li>
</ul>
</li>
<li>MemberService 클라이언트가 구현 클래스를 직접 선택(직접 의존)<ul>
<li>MemberService가 구현 클래스의 내부 구조를 <strong>알고 있다</strong></li>
</ul>
</li>
</ul>
<h3 id="🍀정리">🍀정리</h3>
<ul>
<li>객체 지향의 핵심은 다형성</li>
<li>다형성만으로 쉽게 부품을 갈아끼우듯이 개발할 수 없다.</li>
<li>다형성만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경해야 한다.</li>
<li>다형성만으로는 <code>OCP</code>, <code>DIP</code>를 지킬 수 없다. 그래서 뭔가가 더 필요하다! <strong><em>- 스프링</em></strong></li>
</ul>
<hr>
<h2 id="08-객체-지향-설계와-스프링">08. 객체 지향 설계와 스프링</h2>
<h3 id="🍀-객체-지향과-스프링">🍀 객체 지향과 스프링</h3>
<ul>
<li>스프링은 <strong>다형성 + <code>OCP</code>, <code>DIP</code></strong>를 가능하도록 기술을 지원<ul>
<li><strong>DI</strong> : 의존관계, 의존성 주입</li>
<li><strong>DI 컨테이너 제공</strong> : 이 컨테이너 내부에서 객체 간 의존 관계 연결 및 주입</li>
</ul>
</li>
<li>클라이언트 코드의 변경없이 기능 확장</li>
<li>쉽게 부품을 교체하듯이 개발</li>
</ul>
<h3 id="🍀-정리">🍀 정리</h3>
<ul>
<li><strong>모든 설계에 역할과 구현을 분리</strong></li>
<li><strong>이상적으로 모든 설계에 인터페이스를 부여</strong></li>
<li>하지만 인터페이스를 도입하면 <strong>추상화라는 비용</strong>이 발생(한번 더 까봐야 된다)<ul>
<li><strong>✅ 장점이 단점을 넘어설 때 선택</strong></li>
</ul>
</li>
<li>기능을 확장할 가능성이 없다면 구체 클래스를 직접 사용하고 향후 인터페이스 도입</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OS] 프로세스]]></title>
            <link>https://velog.io/@kiteof_park/OS-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4</link>
            <guid>https://velog.io/@kiteof_park/OS-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4</guid>
            <pubDate>Wed, 18 Jun 2025 19:52:24 GMT</pubDate>
            <description><![CDATA[<h2 id="프로세스의-개념">프로세스의 개념</h2>
<p>프로세스(<code>Process</code>)란 컴퓨터에서 연속적으로 <strong>실행되고 있는 프로그램</strong>이다.<br>종종 스케줄링의 대상이 되는 작업(<code>Task</code>)이라는 용어와 거의 같은 의미로 쓰인다.
<em>- 위키백과</em></p>
<h3 id="프로세스의-문맥context">프로세스의 문맥(Context)</h3>
<img src ="https://velog.velcdn.com/images/kiteof_park/post/3227574d-62a6-4081-b6f4-406e9178ef0b/image.png">

<ul>
<li><code>CPU</code> 수행 상태를 나타내는 하드웨어 문맥</li>
<li>프로세스의 <strong>현재 상태</strong>를 의미 <em>- 뭘 해서 지금의 상태까지 왔는가?</em></li>
<li><strong>특정 시점에서 어디까지 수행을 했는지</strong> 규명하는데 필요한 요소</li>
<li>프로세스의 문맥을 나타내기 위해서는 <code>PC</code>(Program Counter)가 가리키는 부분을 참조<ul>
<li><code>PC</code>는 CPU가 다음에 실행할 명령어의 주소를 저장하고 있는 레지스터</li>
<li>현재 프로세스의 실행 위치(명령어)를 추적</li>
</ul>
</li>
<li>프로세스의 문맥은 <code>code</code>, <code>data</code>, <code>stack</code> 의 상태를 저장함<ul>
<li>코드는 어디까지 수행? 어떤 데이터를 저장? 어떤 메서드를 호출?</li>
</ul>
</li>
<li>프로세스는 실행 시 독립적인 주소 공간을 생성한 뒤<br><code>CPU</code>를 할당받으면 PC가 프로세스의 <code>code</code> 영역의 어느 한 부분을 가리키고,<br>매 순간 명령어를 읽어 실행(레지스터에 값을 넣고 연산하고 저장하고 등)</li>
</ul>
<h3 id="프로세스-문맥의-구성요소">프로세스 문맥의 구성요소</h3>
<h4 id="1-하드웨어-문맥hardware-context">1. 하드웨어 문맥(Hardware Context)</h4>
<p>프로세스가 CPU를 사용하던 상태를 저장한 정보</p>
<ul>
<li><code>PC(Program Counter)</code> : 다음에 실행할 명령어의 주소</li>
<li><code>Register</code> : 현재 작업 중인 값들이 저장되어 있음</li>
</ul>
<h4 id="2-주소-공간-문맥memory-context">2. 주소 공간 문맥(Memory Context)</h4>
<p>프로세스의 독립적인 메모리 공간 상태를 나타냄</p>
<ul>
<li><code>Code</code> 영역 : 실행할 명령어</li>
<li><code>Date</code> 영역 : 전역 변수, 정적 변수</li>
<li><code>Stack</code> 영역 : 함수 호출, 지역 변수 등 실행 흐름과 관련된 정보</li>
</ul>
<h4 id="3-커널-문맥kernel-context">3. 커널 문맥(Kernel Context)</h4>
<p>운영체제가 프로세스를 관리하기 위해 유지하는 정보</p>
<ul>
<li><code>PCB(Process Control Block)</code> : 프로세스 ID, 상태, 우선순위 등 관리 정보</li>
<li><code>Kernel Stack</code> : 시스템 콜 처리 중에 사용하는 커널 스택<ul>
<li><strong>프로세스마다 별도의 커널 스택이 존재</strong></li>
<li>시스템 콜 진입 시, PC는 사용자 영역이 아닌 커널 코드 영역을 가리킴</li>
</ul>
</li>
</ul>
<hr>
<h2 id="프로세스의-상태">프로세스의 상태</h2>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/1a40efbf-fd29-4909-aaea-4a5aeb405725/image.png">

<ul>
<li><code>Running</code> : <code>CPU</code>를 잡고 명령어를 수행 중인 상태<ul>
<li>운영체제의 상태를 <code>Running</code>으로 표현하지 않음</li>
</ul>
</li>
<li><code>Ready</code> : (다른 조건을 모두 만족하고) CPU 할당을 기다리는 상태</li>
<li><code>Blocked(wait, sleep)</code> : CPU를 줘도 당장 명령어를 실행할 수 없는 상태<ul>
<li>자신이 요청한 이벤트(I/O 요청 등)가 즉시 만족되지 않아 대기 중인 상태</li>
<li>Ex. 디스크에서 파일을 읽어와야 하는 경우, 메모리에 로드되지 않은 경우</li>
<li>자신이 요청한 이벤트가 만족되면 <code>Ready</code> 상태</li>
</ul>
</li>
<li><code>Suspended(Stopped)</code> : 외부적인 이유로 프로세스 수행이 정지된 상태, 강제 종료<ul>
<li>프로세스는 통째로 디스크에 Swap Out</li>
<li>메모리에 너무 많은 프로세스가 올라와있을 때</li>
<li>외부에서 resume 해줘야 Active 상태로 변화</li>
</ul>
</li>
<li><code>New</code> : 프로세스가 생성 중인 상ㅌ애</li>
<li><code>Terminated</code> : 수행(execution)이 끝난 상태</li>
</ul>
<h2 id="pcbprocess-control-block">PCB(Process Control Block)</h2>
<ul>
<li>운영체제가 각 프로세스를 관리하기 위해 프로세스마다 유지하는 정보</li>
<li>프로세스마다 하나씩 1:1 매핑</li>
<li>구조체로 유지<h3 id="pcb-구성-요소">PCB 구성 요소</h3>
<img src = "https://scaler.com/topics/images/structure-of-process-control-block.webp" width = 80%>

</li>
</ul>
<p><a href="https://www.scaler.com/topics/operating-system/process-control-block-in-os/">이미지 출처</a></p>
<h4 id="1-os가-관리상-사용하는-정보">1. OS가 관리상 사용하는 정보</h4>
<ul>
<li>Process State(<code>Ready</code>, <code>Blocked</code> .. ), Process Id</li>
<li>Scheduling Information, Priority</li>
</ul>
<h4 id="2-cpu-수행-관련-하드웨어-값---프로세스-문맥">2. CPU 수행 관련 하드웨어 값 <em>- 프로세스 문맥</em></h4>
<ul>
<li>Program Counter(<code>PC</code>), Register</li>
</ul>
<h4 id="3-메모리-관련">3. 메모리 관련</h4>
<ul>
<li><code>code</code>, <code>data</code>, <code>stack</code> 위치 정보</li>
</ul>
<h4 id="4-파일-관련">4. 파일 관련</h4>
<ul>
<li>Open File Descriptors, ..</li>
</ul>
<h2 id="context-switch문맥-교환">Context Switch(문맥 교환)</h2>
<img src ="https://velog.velcdn.com/images/yoonuk/post/ba5772b3-73ca-40fa-b7f1-640e53e9d483/image.png">

<p><a href="https://velog.io/@yoonuk/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-PCB%EC%99%80-TCB">이미지 출처</a></p>
<ul>
<li><code>CPU</code>를 <strong>한 프로세스에서 다른 프로세스를 넘겨주는 과정</strong></li>
<li>CPU가 다른 프로세스에게 넘어갈 때 운영체제는 :<ul>
<li><ol>
<li><code>CPU</code>를 뺏기는 프로세스의 상태를 그 프로세스의 <code>PCB</code>에 저장</li>
</ol>
</li>
<li><ol start="2">
<li><code>CPU</code>를 새롭게 얻는 프로세스의 상태를 <code>PCB</code>에서 읽어옴  </li>
</ol>
</li>
</ul>
</li>
<li>⚠️ <strong>시스템 콜이나 인터럽트 발생 시 반드시 문맥 교환이 일어나는 것은 아님</strong><ul>
<li><code>CPU</code>제어권이 사용자 프로세스 → <code>OS</code>로 넘어가는 과정이 문맥교환❌</li>
<li>문맥교환은 사용자 프로세스A ↔ 사용자 프로세스B </li>
</ul>
</li>
</ul>
<h3 id="프로세스를-스케줄링하기-위한-큐">프로세스를 스케줄링하기 위한 큐</h3>
<p>운영체제가 프로세를 효율적으로 관리하고 스케줄링하기 위해 사용하는 대기열</p>
<ul>
<li><code>Job Queue</code> : 현재 시스템 내에 있는 <strong>모든 프로세스</strong>의 집합</li>
<li><code>Ready Queue</code> : <code>CPU</code>를 기다리는 모든 프로세스들의 대기열, 실행 가능한 상태</li>
<li><code>Deviced Queue</code> : I/O 장치의 사용을 기다리는 프로세스들의 대기열</li>
</ul>
<h3 id="큐-간-전이">큐 간 전이</h3>
<ul>
<li>프로그램 시작 → <code>Job Queue</code>에 등록</li>
<li>메모리에 올라오면 → <code>Ready Queue</code>로 이동</li>
<li>CPU 할당 → 실행 (<code>Running</code> 상태)</li>
<li>I/O 요청 → <code>Device Queue</code>로 이동</li>
<li>I/O 완료 → 다시 <code>Ready Queue</code>로 이동</li>
</ul>
<h3 id="스케줄러">스케줄러</h3>
<img src ="https://velog.velcdn.com/images/kiteof_park/post/1a39f0a5-fe27-41ba-b1f2-460bb8ac4944/image.png">

<h4 id="long-term--장기-스케줄러-job-스케줄러">Long-term  (장기 스케줄러, Job 스케줄러)</h4>
<ul>
<li>시작 프로세스 중 어떤 것들을 <code>Ready queue</code>에 보낼지 결정<ul>
<li><code>New</code> → <code>Ready</code></li>
</ul>
</li>
<li>프로세스에 <strong>메모리를 주는</strong> 문제 <em>- 멀티 프로그래밍을 제어</em><ul>
<li>메모리에 몇 개 올릴래?</li>
</ul>
</li>
<li><strong>✨ 시분할 시스템(Time Sharing System)에는 보통 장기 스케줄러가 없고 무조건 <code>ready</code> 상태</strong><ul>
<li>그래서 <code>Medium-term</code> 스케줄러가 필요</li>
</ul>
</li>
</ul>
<h4 id="short-term-scheduler단기-스케줄러-cpu-스케줄러">Short-term scheduler(단기 스케줄러, CPU 스케줄러)**</h4>
<ul>
<li>어떤 프로세스를 다음번에 <code>Running</code> 시킬지 결정</li>
<li>프로세스에 <strong>CPU를 주는</strong> 문제</li>
<li>충분히 빨라야 함</li>
</ul>
<h4 id="medium-term-scheduler중기-스케줄러-swapper">Medium-term scheduler(중기 스케줄러, Swapper)</h4>
<ul>
<li>여유 공간 마련을 위해 프로세스 통째로 메모리에서 디스크로 쫓아냄 <em>- Swap Out</em></li>
<li>프로세스에게서 <strong>메모리를 뺏는</strong> 문제 <em>- 멀티 프로그래밍을 제어</em></li>
<li>중기 스케줄러로 인해 <code>Suspended(stopped)</code>상태가 추가</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Network] 프로토콜과 캡슐화[2]]]></title>
            <link>https://velog.io/@kiteof_park/Network-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EA%B3%BC-%EC%BA%A1%EC%8A%90%ED%99%942</link>
            <guid>https://velog.io/@kiteof_park/Network-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EA%B3%BC-%EC%BA%A1%EC%8A%90%ED%99%942</guid>
            <pubDate>Wed, 18 Jun 2025 04:06:20 GMT</pubDate>
            <description><![CDATA[<h3 id="패킷-교환-네트워크">패킷 교환 네트워크</h3>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/5b79ccc7-1dc6-4cd4-af3c-c6f6c60d8946/image.png">

<img src ="https://velog.velcdn.com/images/kiteof_park/post/d0e89cf8-ef6c-424e-a99a-02ed678fa61e/image.png">

<ul>
<li>주고받는 정보를 <strong>패킷(packet)</strong>단위로 주고받는 네트워크</li>
<li><strong>패킷</strong>:  패킷 교환 네트워크에서 주고받는 데이터 단위</li>
<li>정보를 <strong>패킷 단위로 분할</strong>해서 송신하고, 수신 측에서 패킷을 재조립</li>
<li><strong>패킷 교환기</strong>: 패킷을 어디로 전송할지(라우팅), 패킷의 손상 유무를 판별</li>
</ul>
<h3 id="회선-교환-네트워크">회선 교환 네트워크</h3>
<img src ="https://velog.velcdn.com/images/kiteof_park/post/0dfdb8cb-bd78-457b-b261-ffa88eb28bd3/image.png">

<ul>
<li><strong>정해진 회선(circuit)으로만 통신</strong>하는 네트워크</li>
<li><strong>사전에 연결 수립 작업</strong></li>
<li>다른 호스트는 도중에 끼어들 수 없음❌</li>
<li><strong>전송률을 보장</strong>하지만 <strong>회선 이용률 저하</strong></li>
</ul>
<h3 id="패킷-구성-요소">패킷 구성 요소</h3>
<ul>
<li><code>Header</code> : 패킷에 붙일 부가 정보</li>
<li><code>Payload</code> : 패킷에 보낼 정보</li>
<li><code>Trailer</code> : 패킷 뒤에 붙일 부가정보</li>
</ul>
<h3 id="프로토콜">프로토콜</h3>
<img src ="https://velog.velcdn.com/images/kiteof_park/post/87bc2b05-b5e9-4b3e-ac28-bfb835a37283/image.png">

<ul>
<li>네트워크에 참여한 장비 간 정보를 주고받을 규칙하는 방법</li>
<li>호스트 간에 합의된 의사소통 규칙 <strong><em>- 노드 간의 언어</em></strong></li>
<li>✨ <strong>패킷 헤더의 내용은 프로토콜의 영향</strong>을 받는다<ul>
<li>헤더는 패킷에 붙일 부가 정보</li>
<li>프로토콜이 달라지면 헤더의 내용이 달라진다 <strong><em>- 프로토콜마다 목적이 다름</em></strong></li>
<li><strong>TCP 프로토콜 헤더와 vs UDP 프로토콜 헤더</strong><ul>
<li><code>TCP</code> : 신뢰성이 보장된 전송</li>
<li><code>UDP</code> : 빠른 전송</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="네트워크-참조-모델---tcpip-4-layer">네트워크 참조 모델 - TCP/IP 4 Layer</h3>
<table>
<thead>
<tr>
<th><strong>계층</strong></th>
<th><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td>어플리케이션 계층</td>
<td><strong>응용 프로그램</strong>으로부터 송수신하고자 하는 정보를 주고 받는 과정</td>
</tr>
<tr>
<td>전송 계층</td>
<td>정보 송수신 과정에서 <strong>안정성</strong>을 더하는 과정</td>
</tr>
<tr>
<td>인터넷 계층</td>
<td>송수신 하고자 하는 정보의 <strong>발신지/목적지를 결정</strong>하는 과정</td>
</tr>
<tr>
<td>네트워크 액세스 계층</td>
<td>송수신 하고자 하는 정보를 <strong>유/무선 매체</strong>를 통해 주고받는 과정</td>
</tr>
</tbody></table>
<img src ="https://velog.velcdn.com/images/kiteof_park/post/32432acf-985b-4a48-98c5-165539d97401/image.png">

<img src = "https://velog.velcdn.com/images/kiteof_park/post/ae37ef84-f495-4501-aecd-3a6bd4b944ac/image.png">

<h3 id="캡슐화와-역캡슐화">캡슐화와 역캡슐화</h3>
<img src ="https://velog.velcdn.com/images/kiteof_park/post/64440277-984c-4238-bc68-a4e3d13906ca/image.png">

<p><code>PDU(Protocol Data Unit)</code> : 네트워크에서 프로토콜 데이터 단위</p>
<table>
<thead>
<tr>
<th><strong>계층</strong></th>
<th><strong>PDU</strong></th>
<th><strong>기능</strong></th>
</tr>
</thead>
<tbody><tr>
<td>응용<br>표현<br>세션</td>
<td><code>Data</code></td>
<td>애플리케이션의 사용자 데이터 전달<br>데이터의 형식 및 암호화 처리<br>세션 설정 및 관리 담당</td>
</tr>
<tr>
<td>전송</td>
<td><code>Segment</code> <strong><em>- TCP</em></strong> <br><code>Datagram</code> <strong><em>- UDP</em></strong></td>
<td>신뢰성 있는 데이터 전송 및 오류 제어 수행<br>신뢰성 없는 데이터 전송 수행</td>
</tr>
<tr>
<td>네트워크</td>
<td><code>Packet</code></td>
<td>논리적 주소(IP) 기반의 데이터 라우팅 처리</td>
</tr>
<tr>
<td>데이터 링크</td>
<td><code>Frame</code></td>
<td>물리적 주소(MAC) 기반의 데이터 전송 처리</td>
</tr>
<tr>
<td>물리</td>
<td><code>Bits</code></td>
<td>0과 1로 구성된 신호로 변환해 전송</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] Section08. AOP]]></title>
            <link>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section08.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A06-AOP</link>
            <guid>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section08.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A06-AOP</guid>
            <pubDate>Tue, 17 Jun 2025 17:27:43 GMT</pubDate>
            <description><![CDATA[<h3 id="🍀-aop-핵심-용어">🍀 AOP 핵심 용어</h3>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Aspect</td>
<td>부가기능(Advice)을 모아놓은 클래스</td>
</tr>
<tr>
<td>Advice</td>
<td>Aspect에서 실제로 수행할 동작을 정의한 메서드(<code>execute()</code>)</td>
</tr>
<tr>
<td>JoinPoint</td>
<td>Advice가 적용될 수 있는 실행 지점<br>메서드 실행, 객체 생성 등 다양한 지점이 될 수 있음</td>
</tr>
<tr>
<td>Pointcut</td>
<td>Advice가 적용될 메서드나 클래스의 범위를 지정하는 표현식<br> <code>&quot;execution(* hello.hello-spring..*(..))&quot;</code> 특정 패키지의 모드 메서드 지정</td>
</tr>
</tbody></table>
<h2 id="26-aop가-필요한-상황">26. AOP가 필요한 상황</h2>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/175bd042-d572-410e-b379-aa70e664531a/image.png">

<h3 id="aopaspect-oriented-programming란">AOP(Aspect Oriented Programming)란?</h3>
<ul>
<li><strong>관점 지향 프로그래밍</strong></li>
<li>어떤 로직을 기준으로 <strong>핵심 관점, 부가 관점을 분리</strong>해 <strong>관점을 기준으로 각각 모듈화</strong><ul>
<li>유지보수성과 재사용성을 높임</li>
</ul>
</li>
<li>핵심 관심 사항(Core concern) : 핵심 비지니스 로직</li>
<li>공통 관심 사항(Cross-cutting concern) : 로깅, 파일 입출력, DB연결 등 부가기능<ul>
<li>로깅</li>
<li>트랜잭션 관리</li>
<li>성능 모니터링</li>
<li>보안처리</li>
</ul>
</li>
</ul>
<p>⏰ 만약 각 메서드의 실행 시간을 측정하고 싶다면? <em>- 시간 측정 로직을 모두 추가</em></p>
<p>📂 <code>MemberService</code>
모든 메서드에 시간 측정 로직을 추가</p>
<pre><code class="language-java">@Transactional
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    // 회원가입 - 동일인의 회원 중복 가입❌
    public Long join(Member member){
        long start = System.currentTimeMillis();
        try {
            validateDuplicatedMember(member);
            memberRepository.save(member);
            return member.getId();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;join() = &quot; + timeMs + &quot;ms&quot;);
        }
    }

    // 중복 회원 확인 
    private void validateDuplicatedMember(Member member) {
        long start = System.currentTimeMillis();
        try {
            memberRepository.findByName(member.getName())
                .ifPresent(m -&gt; {
                    throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
                });
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;validateDuplicatedMember() = &quot; + timeMs + &quot;ms&quot;);
        }
    }

    // 전체 회원 조회
    public List&lt;Member&gt; findMembers(){
        long start = System.currentTimeMillis();
        try {
            return memberRepository.findAll();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;findMembers() = &quot; + timeMs + &quot;ms&quot;);
        }
    }

    // 단일 회원 조회
    public Optional&lt;Member&gt; findMember(Long memberId){
        long start = System.currentTimeMillis();
        try {
            return memberRepository.findById(memberId);
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;findMember() = &quot; + timeMs + &quot;ms&quot;);
        }
    }
}
</code></pre>
<pre><code class="language-plain">validateDuplicatedMember() = 4ms
join() = 7ms
findMembers() = 11ms</code></pre>
<h3 id="💥-문제점">💥 문제점</h3>
<ul>
<li>시간을 측정하는 기능은 핵심 관심 사항❌</li>
<li>시간을 측정하는 로직은 <strong>공통 관심사항</strong> - <em>부가 기능</em></li>
<li>시간 측정 로직과 핵심 비지니스 로직이 섞여 유지보수가 어려움</li>
<li>시간 측정 로직을 별도의 공통 로직으로 만들기 어려움</li>
<li>시간 측정 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 함</li>
</ul>
<hr>
<h2 id="27-aop-적용">27. AOP 적용</h2>
<ul>
<li><strong>핵심 관심사항</strong>(회원가입, 회원조회 등)과 <strong>공통 관심사항</strong>(시간 측정)을 <strong>분리</strong></li>
<li>변경이 필요하면 공통 관심사항만 변경</li>
<li>원하는 적용 대상 선택 가능 <em>- <code>@Around</code></em></li>
</ul>
<p><img src="https://velog.velcdn.com/images/kiteof_park/post/65c8e525-11f0-402f-bf47-4d4c713919b5/image.png" alt=""></p>
<p>📂 <code>TimeTraceAop</code></p>
<pre><code class="language-java">@Aspect
@Component  // 또는 SpringConfig에 @Bean으로 등록(선호하는 방법)
public class TimeTraceAop {

    @Around(&quot;execution(* hello.hello_spring..*(..))&quot;)
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable{
        long start = System.currentTimeMillis();
        // joinPoint.toString() : 어떤 메서드를 호출하는지 얻어옴
        System.out.println(&quot;START: &quot; + joinPoint.toString());
        try {
            Object result = joinPoint.proceed();
            return result;
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;END: &quot; + joinPoint.toString() + &quot; &quot; + timeMs + &quot;ms&quot;);
        }
    }
}</code></pre>
<pre><code class="language-plain">START: execution(String hello.hello_spring.controller.MemberController.list(Model))
START: execution(List hello.hello_spring.service.MemberService.findMembers())
START: execution(List org.springframework.data.repository.ListCrudRepository.findAll())
Hibernate: select m1_0.id,m1_0.name from member m1_0
END: execution(List org.springframework.data.repository.ListCrudRepository.findAll()) 4ms
END: execution(List hello.hello_spring.service.MemberService.findMembers()) 4ms
END: execution(String hello.hello_spring.controller.MemberController.list(Model)) 9ms</code></pre>
<h4 id="핵심-로직">핵심 로직</h4>
<pre><code class="language-java">@Around(&quot;execution(* hello.hello_spring..*(..))&quot;)
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
    ...
}</code></pre>
<ul>
<li><code>@Around</code>: 해당 메서드가 지정된 포인트컷의 전후에 실행될 Advice임을 명시</li>
<li><code>&quot;execution(* hello.hello_spring..*(..))&quot;</code>: <code>hello.hello_spring</code> 패키지 이하의 모든 메서드에 적용하겠다는 의미(join point)</li>
</ul>
<pre><code class="language-java">Object result = joinPoint.proceed();
return result;</code></pre>
<ul>
<li>실제 타겟 메서드 실행</li>
<li><code>proceed()</code>는 <strong>프록시된 대상 메서드를 실행</strong>하며, 예외를 던질 수 있어 throws Throwable이 필요</li>
</ul>
<h3 id="🍀-aop-관련-어노테이션">🍀 AOP 관련 어노테이션</h3>
<table>
<thead>
<tr>
<th>어노테이션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>@Aspect</td>
<td>해당 클래스가 AOP의 관심사(Aspect)를 정의하는 클래스임을 명시</td>
</tr>
<tr>
<td>@Before</td>
<td>핵심 로직 실행 전 실행될 Advice 정의</td>
</tr>
<tr>
<td>@After</td>
<td>핵심 로직 실행 후 실행될 Advice 정의</td>
</tr>
<tr>
<td>@AfterReturning</td>
<td>핵심 로직이 정상적으로 반환된 후 실행될 Advice 정의</td>
</tr>
<tr>
<td>@AfterThrowing</td>
<td>예외가 발생했을 때 실행될 Advice 정의</td>
</tr>
<tr>
<td>@Around</td>
<td>핵심 로직의 전후를 모두 감싸는 Advice (가장 강력한 형태)</td>
</tr>
</tbody></table>
<h3 id="🍀-aop가-동작하기-위한-기타-설정-어노테이션">🍀 AOP가 동작하기 위한 기타 설정 어노테이션</h3>
<table>
<thead>
<tr>
<th>어노테이션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>@EnableAspectJAutoProxy</td>
<td>AOP 프록시를 활성화하는 어노테이션<br>보통 @Configuration 클래스에 선언</td>
</tr>
<tr>
<td>@Component</td>
<td>@Aspect 클래스에 필수<br>스프링 빈으로 등록되기 위해 필요</td>
</tr>
</tbody></table>
<hr>
<h3 id="aop-적용-전-의존-관계">AOP 적용 전 의존 관계</h3>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/5b7edf27-c55c-4a89-9e4b-d5383925875f/image.png">

<h3 id="aop-적용-후-의존-관계">AOP 적용 후 의존 관계</h3>
<img src =" https://velog.velcdn.com/images/kiteof_park/post/96f79c5c-db00-40e7-82d0-958994921713/image.png">

<ul>
<li>AOP가 적용되면 스프링이 올라와서 스프링 컨테이너에 빈을 등록할 때,
진짜 스프링 빈 말고 <strong>가짜 스프링 빈(프록시)을 앞에 세워둠</strong></li>
<li><code>joinPoint.proceed()</code>를 실행하면 내부적으로 진짜 스프링 빈을 호출
처음에 <code>memberController</code>가 호출하는 건 <strong>프록시 <code>memberService</code>를 호출</strong>하는 것</li>
</ul>
<h3 id="aop-적용-전-전체-그림">AOP 적용 전 전체 그림</h3>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/9696bb41-41ec-424d-b99b-a22d6b459c4d/image.png">

<h3 id="aop-적용-후-전체-그림">AOP 적용 후 전체 그림</h3>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/65cb10d6-f4cf-4c05-b3aa-637c1a77e762/image.png">

<h4 id="실제-proxy가-주입되는지-콘솔에-출력해서-확인">실제 Proxy가 주입되는지 콘솔에 출력해서 확인</h4>
<pre><code class="language-java">    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
        System.out.println(&quot;memberService = &quot; + memberService.getClass());
    }</code></pre>
<pre><code class="language-plain">memberService = class hello.hello_spring.service.MemberService$$SpringCGLIB$$0</code></pre>
<h4 id="🍀-cglibcode-generation-library">🍀 CGLIB(Code Generation Library)</h4>
<ul>
<li>자바 <strong>동적 프록시를 생성</strong>하기 위한 바이트코드 생성 라이브러리</li>
<li>인터페이스가 없는 구체 클래스를 프록시 <strong><em>- 클래스 기반 프록시 생성</em></strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] Section07. 스프링 DB 접근 기술[6] - 스프링 데이터 JPA]]></title>
            <link>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section07.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A06-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA</link>
            <guid>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section07.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A06-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA</guid>
            <pubDate>Tue, 17 Jun 2025 12:06:31 GMT</pubDate>
            <description><![CDATA[<h2 id="스프링-데이터-jpa">스프링 데이터 JPA</h2>
<ul>
<li>리포지토리에 구현 클래스 없이 인터페이스만으로 개발이 완료</li>
<li><code>CRUD</code>, <code>findById</code>, <code>findAll</code>, DB에서 PK로 검색하는 기능이<br>모두 만들어져있어 스프링 데이터 JPA가 제공(공통 기능 제공)<ul>
<li>개발자는 그냥 가져다 쓰면 됨!</li>
<li><code>findByName()</code>, <code>findByEmail()</code> 등은 비지니스가 다르므로 공통 정의❌</li>
<li>공통으로 할 수 없는건 직접 정의</li>
<li>그럼 JPA가 이걸 기준으로 쿼리를 작성해줌 ..! <strong><em>- JPQL</em></strong></li>
<li>즉, 인터페이스 이름만으로도 개발 끝 개쩐당</li>
<li><code>Reflection</code> 기술 사용</li>
</ul>
</li>
<li>스프링 데이터 JPA는 JPA를  편리하게 사용하도록 도와주는 기술</li>
</ul>
<p>📂 <code>SpringDataJpaMemberRepository</code></p>
<ul>
<li>스프링 데이터 JPA는 <strong>JpaRepository를 상속받고 있으면<br>구현체를 자동으로 생성</strong>하고, <strong>스프링 빈에 자동으로 등록</strong></li>
<li><strong>프록시 기술</strong>을 이용해 런타임에 구현체를 동적 생성 및 스프링 빈 등록</li>
</ul>
<pre><code class="language-java">public interface SpringDataJpaMemberRepository extends JpaRepository&lt;Member, Long&gt;, MemberRepository {
    // MemberRepository의 findByName() 오버라이딩
    // &#39;select m from Member m where m.name = ?&#39; JPQL -&gt; SQL로 변환돼 실행
    @Override
    Optional&lt;Member&gt; findByName(String name);
}</code></pre>
<p>📂 <code>SpringConfig</code></p>
<pre><code class="language-java">@Configuration
public class SpringConfig {

    private final MemberRepository memberRepository;

    // DI -&gt; Spring Data JPA가 만든 구현체가 등록됨
    // 스프링 컨테이너에서 MemberRepository를 찾음 -&gt; 근데 등록한게 없잖아? 사실 하나 있음 SpringDataJpaMemberRepository
    // SpringDataJpaMemberRepository는 JpaRepository를 상속, 스프링 데이터JPA 인터페이스에 대한 구현체를 만들어서 스프링 빈에 등록
    @Autowired
    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository);
    }
}</code></pre>
<h2 id="스프링-데이터-jpa는-어떻게-인터페이스만으로-레포지토리-역할이-가능할까">스프링 데이터 JPA는 어떻게 인터페이스만으로 레포지토리 역할이 가능할까?</h2>
<h3 id="🍀-스프링과-프록시-객체">🍀 스프링과 프록시 객체</h3>
<p>스프링 데이터 JPA에서 구현체 없이 인터페이스만으로 개발이 가능한 이유는
<strong>스프링이 런타임 시점에 프록시 객체를 자동으로 생성</strong>하고,<br><strong>내부적으로 JPA API를 호출하도록 위임</strong>해주기 때문 </p>
<h3 id="🍀-핵심-이유-요약">🍀 핵심 이유 요약</h3>
<p><strong>1. 프록시 기반 동적 구현</strong></p>
<ul>
<li>스프링이 <code>JpaRepository</code> 인터페이스를 기반으로 <strong>프록시 객체를 만들어 동작을 위임</strong></li>
</ul>
<p><strong>2. Query 메서드 자동 분석</strong></p>
<ul>
<li>메서드 이름을 분석해 <code>JPQL</code> 쿼리를 자동 생성</li>
</ul>
<p><strong>3. 스프링 컨테이너가 구현체 주입</strong></p>
<ul>
<li>개발자가 구현하지 않아도 <code>@Autowired</code>로 주입 가능</li>
</ul>
<h3 id="🍀-스프링의-런타임-시점-수행과정">🍀 스프링의 런타임 시점 수행과정</h3>
<h4 id="1-컴포넌트-스캔">1. 컴포넌트 스캔</h4>
<ul>
<li>애플리케이션 실행 시 <code>@SpringBootApplication</code>을 통해 자동으로 패키지를 스캔</li>
<li><code>@Component</code>, <code>@Controller</code>, <code>@Service</code>, <code>@Repository</code> 등으로 
등록된 클래스 또는 인터페이스를 스캔해 빈으로 등록</li>
</ul>
<h4 id="2-프록시-객체-생성">2. 프록시 객체 생성</h4>
<ul>
<li><strong><code>JpaRepository</code>를 상속받는 인터페이스는 실제 구현체가 없으므로,
Spring이 프록시 객체를 런타임에 생성</strong></li>
<li>프록시 객체는 Spring AOP 기반의 <code>JDK Dynamic Proxy</code> 또는 <code>CGLIB Proxy</code>를 통해 생성</li>
</ul>
<h4 id="3-프록시를-빈으로-등록">3. 프록시를 빈으로 등록</h4>
<ul>
<li>생성된 프록시 객체는 빈으로 등록되며, 이후 DI로 사용 가능</li>
</ul>
<hr>
<img src =" https://velog.velcdn.com/images/kiteof_park/post/149ce635-f3f2-4f05-abce-9246ac71da1c/image.png">]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] Section07. 스프링 DB 접근 기술[5] - JPA]]></title>
            <link>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section07.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A05-JPA</link>
            <guid>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section07.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A05-JPA</guid>
            <pubDate>Tue, 17 Jun 2025 11:04:55 GMT</pubDate>
            <description><![CDATA[<h3 id="jpajava-persistence-api">JPA(Java Persistence API)</h3>
<ul>
<li>JPA는 기존의 반복 코드와, 기본적인 SQL도 JPA가 직접 만들어서 실행</li>
<li>JPA를 사용하면 SQL와 데이터 중심 설계에서 <strong>객체 중심 설계</strong>가 가능</li>
<li>JPA는 인터페이스만 제공, 구현체는 <code>Hibernate</code>(또는 여러 기술 벤더들이 있음</li>
<li><strong>JPA는 ORM 기술 <em>- 객체(Object)와 RDB(Relational) 매핑</em></strong><ul>
<li>어떻게 매핑? 어노테이션(<code>@Entity</code>)으로 매핑</li>
</ul>
</li>
</ul>
<p>📂 <code>build.gradle</code></p>
<ul>
<li>JPA 관련 라이브러리 추가<ul>
<li>JPA 라이브러리는 내부에 <code>JDBC</code> 관련 라이브러리를 추가<pre><code class="language-java">implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;</code></pre>
</li>
</ul>
</li>
</ul>
<p>📂 <code>application.properties</code></p>
<ul>
<li>스프링 부트에 JPA 설정 추가</li>
<li><code>show-sql</code> : JPA가 생성하는 SQL을 출력</li>
<li><code>ddl-auto=none</code> : JPA의 테이블 자동 생성 기능을 끔<ul>
<li><code>create</code> : 애플리케이션 실행 시 기존 테이블 삭제 후 새로 생성</li>
<li><code>create-drop</code> : 애플리케이션 실행 시 테이블 생성, 종료 시 테이블 삭제</li>
<li><code>update</code> : 데이터베이스 스키마를 매핑 정보와 비교해 <strong>필요한 부분만 변경</strong></li>
<li><code>validate</code> : 데이터베이스와 매핑 정보 <strong>검증</strong>만 수행</li>
</ul>
</li>
</ul>
<pre><code class="language-java">spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none</code></pre>
<p>📂 <code>Member</code></p>
<ul>
<li><p>JPA  엔티티 매핑</p>
</li>
<li><p><code>@Entity</code> : 데이터베이스 테이블과 매핑되는 클래스 명시</p>
</li>
<li><p><code>@Id</code> : 기본 키 지정</p>
</li>
<li><p><code>@GeneratedValue(strategy = GenerationType.IDENTITY)</code> : 기본 키 자동 생성 전략</p>
<ul>
<li><p><code>GenerationType.IDENTITY</code> : DB의 <strong>AUTO_INCREMENT</strong> 기능 사용</p>
</li>
<li><p><code>GenerationType.SEQUENCE</code> : DB의 <strong>시퀀스</strong>를 사용(Oracel, PostgreSQL)</p>
</li>
<li><p><code>GenerationType.TABLE</code> : <strong>별도의 키 관리 테이블</strong> 사용</p>
</li>
<li><p><code>GenerationType.AUTO</code> : DB에 맞는 <strong>전략을 자동 선택</strong></p>
<pre><code class="language-java">@Entity
public class Member {

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

public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}</code></pre>
</li>
</ul>
</li>
</ul>
<p>📂 <code>JpaMemberRepository</code></p>
<ul>
<li><strong>JPA를 사용하기 위해 트랜잭션</strong> 필요 <em>- MemberService에 <code>@Transactional</code> 추가</em><ul>
<li>JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행</li>
</ul>
</li>
<li>JPA는 <code>EntityManager</code>로 동작<ul>
<li>build.gradle에서 JPA관련 라이브러리를 받으면 스프링 부트가 <code>EntityManager</code>를 생성</li>
<li>JPA를 사용하려면 <code>EntityManager</code>를 주입받아야 함</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public class JpaMemberRepository implements MemberRepository{

    private final EntityManager em;

    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        // find(조회할 타입, 식별자(PK))
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        // 📌 PK가 아닌 다른 컬럼으로 조회시 JPQL을 사용
        // JPQL은 테이블 대상 쿼리가 아닌 객체(엔티티) 대상 쿼리
        // SELECT 의 대상은 엔티티
        List&lt;Member&gt; member = em.createQuery(&quot;select m from Member m where m.name = :name&quot;, Member.class)
                .setParameter(&quot;name&quot;, name)
                .getResultList();

        return member.stream().findAny();
    }

    @Override
    public List&lt;Member&gt; findAll() {
        return em.createQuery(&quot;select m from Member m&quot;, Member.class).getResultList();
    }
}
</code></pre>
<hr>
<h2 id="entitymanager-pc-entitymanagerfactory">EntityManager, PC, EntityManagerFactory</h2>
<h3 id="🍀-1-entitymanager">🍀 1. EntityManager</h3>
<ul>
<li>JPA에서 제공하는 <strong>엔티티 관리 인터페이스</strong></li>
<li>엔티티의 생성, 조회, 수정, 삭제와 같은 데이터 조작 작업을 수행</li>
<li><strong><code>EntityManager</code>는 내부에 <code>Persistence Context</code>를 둬서 엔티티들을 관리</strong></li>
</ul>
<h4 id="핵심-역할">핵심 역할</h4>
<ul>
<li>영속성 컨텍스트 관리</li>
<li>트랜잭션 관리</li>
<li>쿼리 실행 및 데이터베이스 연결 관리</li>
</ul>
<h4 id="생명-주기">생명 주기</h4>
<ul>
<li>단일 트랜잭션 또는 작업 단위에서만 유효</li>
<li>트랜잭션이 종료되거나 <code>close()</code> 메서드가 호출되면 더 이상 사용❌</li>
</ul>
<h3 id="🍀-2-영속성-컨텍스트persistence-context">🍀 2. 영속성 컨텍스트(Persistence Context)</h3>
<ul>
<li><strong>엔티티 객체를 관리하는 메모리 공간</strong></li>
<li><code>EntityManager</code>가 관리하는 <strong>1차 캐시 역할</strong></li>
<li>객체의 상태를 추적하고 동기화</li>
</ul>
<h4 id="주요-특징">주요 특징</h4>
<ol>
<li><strong>1차 캐시</strong></li>
</ol>
<ul>
<li>동일 트랜잭션 내에서 동일 엔티티 조회 시,</li>
<li><em>데이터베이스 재조회 없이 메모리에서 가져옴*</em></li>
</ul>
<ol start="2">
<li><strong>변경 감지(Dirty Checking)</strong></li>
</ol>
<ul>
<li>엔티티의 상태가 변경되면 자동으로 업데이트 쿼리를 생성하고 반영</li>
</ul>
<ol start="3">
<li><strong>쓰기 지연(Wrire Behind)</strong></li>
</ol>
<ul>
<li>트랜잭션이 커밋될 때까지  SQL 쿼리를 지연 저장하여 성능 최적화</li>
<li>트랜잭션이 커밋 되기 전까지 모든 쿼린문은 영속성 컨텍스트 내부의 
쓰기 지연 SQL 저장소에 저장 <strong><em>- 커밋되는 순간 모든 쿼리를 한 번에 날림</em></strong></li>
</ul>
<ol start="4">
<li><strong>동기화(Flush)</strong></li>
</ol>
<ul>
<li>영속성 컨텍스트와 데이터베이스를 동기화</li>
</ul>
<h4 id="상태-변화">상태 변화</h4>
<pre><code class="language-java">User user = new User();   // 비영속 (Transient)
em.persist(user);         // 영속 (Persistent)
em.detach(user);          // 준영속 (Detached)
em.remove(user);          // 삭제 (Removed)</code></pre>
<ol>
<li><strong>비영속(Transient) :</strong> 아직 영속성 컨텍스트에 포함되지 않은 상태</li>
<li><strong>영속(Persistent) :</strong> <code>persist()</code> 호출 후 관리되는 상태</li>
<li><strong>준영속(Detached) :</strong> <code>detach()</code> 또는 <code>clear()</code> 호출 시 관리가 해제된 상태</li>
<li><strong>삭제(Removed) :</strong> <code>remove()</code> 호출 후 삭제 예약 상태</li>
</ol>
<h3 id="🍀-3-entitymanagerfactory">🍀 3. EntityManagerFactory</h3>
<ul>
<li><code>EntityManager</code> 인스턴스를 생성하는 팩토리 클래스</li>
<li>JPA 설정(데이터베이스 연결, 매핑 정보)를 기반으로 애플리케이션 전체에서 공유</li>
<li>애플리케이션 실행 시 한 번만 생성해야 함</li>
</ul>
<h4 id="주요-특징-1">주요 특징</h4>
<ul>
<li>애플리케이션당 하나의 인스턴스만 사용</li>
<li>Thread-Safe : 멀티스레드 환경에서 안전하게 사용 가능</li>
<li>데이터베이스 연결 정보 및 설정을 포함</li>
<li>성능 최적화를 위해 커넥션 풀링을 지원</li>
</ul>
<pre><code class="language-java">EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;example-unit&quot;);
EntityManager em1 = emf.createEntityManager(); // EntityManager 생성
EntityManager em2 = emf.createEntityManager(); // 또 다른 EntityManager 생성

em1.close();
em2.close();
emf.close(); // 최종 종료</code></pre>
<hr>
<h3 id="🍀-정리">🍀 정리</h3>
<ol>
<li><code>EntityManagerFactory</code> 생성 : 전체 애플리케이션에서 하나만 생성</li>
<li><code>EntityManager</code> 생성 : 각 작업 단위(트랜잭션)에 대해 생성</li>
<li>영속성 컨텍스트 관리 : 데이터베이스와 메모리 간 상태 동기화</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] Section07. 스프링 DB 접근 기술[4] - 스프링 JdbcTemplate]]></title>
            <link>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section07.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A04-%EC%8A%A4%ED%94%84%EB%A7%81-JdbcTemplate</link>
            <guid>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section07.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A04-%EC%8A%A4%ED%94%84%EB%A7%81-JdbcTemplate</guid>
            <pubDate>Tue, 17 Jun 2025 09:50:20 GMT</pubDate>
            <description><![CDATA[<h3 id="jdbc-template">JDBC Template</h3>
<ul>
<li>순수 JDBC와 동일한 환경 설정</li>
<li><code>JdbcTemplate</code>은 JDBC의 <strong>반복 코드를 제거</strong></li>
<li>단,  SQL은 직접 작성해야 함</li>
</ul>
<p>📂 <code>JdbcTemplateMemberRepository</code></p>
<pre><code class="language-java">public class JdbcTemplateMemberRepository implements MemberRepository {
    private final JdbcTemplate jdbcTemplate;

    // 생성자가 1개인 경우 @Autowired 생략가능
    // JdbcTemplateMemberRepository에 DataSource객체를 주입
    public JdbcTemplateMemberRepository(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public Member save(Member member) {
        // SimpleJdbcInsert : JdbcTemplate을 만들어서 넘김
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);

        // 테이블명, PK를 넘기면 insert문 쿼리 작성할 필요❌
        jdbcInsert.withTableName(&quot;member&quot;).usingGeneratedKeyColumns(&quot;id&quot;);

        Map&lt;String, Object&gt; parameters = new HashMap&lt;&gt;();
        parameters.put(&quot;name&quot;, member.getName());

        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
        member.setId(key.longValue());
        return member;
    }

    // 📌 조회는 쿼리 실행의 결과를 RowMappeer로 매핑이 필요
    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        // query() : 반환타입 List, SQL 쿼리 결과를 조회하고 객체로 매핑
        // query(String sql, RowMapper&lt;T&gt; rowMapper)
        List&lt;Member&gt; result = jdbcTemplate.query(
                &quot;select * from member where id = ?&quot;,
                memberRowMapper(),
                id);
        return result.stream().findAny();
    }

    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        List&lt;Member&gt; result = jdbcTemplate.query(
                &quot;select * from member where name = ?&quot;,
                memberRowMapper(),
                name);
        return result.stream().findAny();
    }

    @Override
    public List&lt;Member&gt; findAll() {
        return jdbcTemplate.query(&quot;select * from member&quot;, memberRowMapper());
    }

    // 📌 RowMapper
    private RowMapper&lt;Member&gt; memberRowMapper(){
        return (rs, rowNum) -&gt; {
            Member member = new Member();
            member.setId(rs.getLong(&quot;id&quot;));
            member.setName(rs.getString(&quot;name&quot;));
            return member;
        };
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] Section07. 스프링 DB 접근 기술[3] - 스프링 통합 테스트]]></title>
            <link>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section07.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A03-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%86%B5%ED%95%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section07.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A03-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%86%B5%ED%95%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 17 Jun 2025 03:29:21 GMT</pubDate>
            <description><![CDATA[<h3 id="스프링-통합-테스트">스프링 통합 테스트</h3>
<ul>
<li>스프링 컨테이너와 DB까지 연결한 통합 테스트</li>
<li><code>@SpringBootTest</code> : 스프링 컨테이너와 테스트를 함께 실행</li>
<li><code>@Transactional</code> : 테스트 시작 전에 트랜잭션을 시작, 테스트 완료 후 롤백</li>
</ul>
<h3 id="스프링을-띄우는-테스트가-왜-필요할까">스프링을 띄우는 테스트가 왜 필요할까?</h3>
<ol>
<li><strong>빈(Bean) 관리 검증</strong></li>
</ol>
<ul>
<li>빈이 올바르게 생성되고 의존성이 주입되는지 검증<ul>
<li>Ex. <code>MemberService</code>가 <code>MemberRepository</code>에 의존하는지 확인.</li>
</ul>
</li>
</ul>
<ol start="2">
<li><strong>트랜잭션 테스트</strong></li>
</ol>
<ul>
<li>실제 데이터베이스와 연결하여 쿼리 실행 후 롤백되는지 검증<ul>
<li>Ex. <code>INSERT</code> 이후 데이터가 저장되었는지 확인하고 테스트 후 원상복구</li>
</ul>
</li>
</ul>
<ol start="3">
<li><strong>통합 테스트</strong></li>
</ol>
<ul>
<li>컨트롤러, 서비스, 리포지터리 등 애플리케이션의 여러 계층이<br>올바르게 상호작용하는지 테스트<ul>
<li>Ex. HTTP 요청 → 컨트롤러 → 서비스 → 레포지터리 흐름 검증</li>
</ul>
</li>
</ul>
<ol start="4">
<li><strong>Mock 환경 테스트</strong></li>
</ol>
<ul>
<li>웹 요청을 테스트하거나, 모의 객체(Mock)를 활용하여 특정 부분만 검증<ul>
<li>Ex. <code>MockMvc</code>를 이용한 HTTP 요청 시나리오 검증</li>
</ul>
</li>
</ul>
<p>📂 <code>MemberServiceIntegrationTest</code></p>
<pre><code class="language-java">@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
    @Autowired MemberService memberService;

    // 📌 구현체는 @Configuration한 곳에서 주입받음
    @Autowired MemberRepository memberRepository;

    // 📌 직접 객체 생성 방식❌ -&gt; 스프링 컨테이너에게 주입받는 방식으로 변경
//    @BeforeEach
//    void beforeEach(){
//        memberRepository = new MemoryMemberRepository();
//        memberService = new MemberService(memberRepository);
//    }

    // 📌 테스트의 반복 가능 -&gt; @Transactional로 해결
//    @AfterEach
//    void afterEach(){
//        memberRepository.clearStore();
//    }

    @Test
    // @Commit     // 테스트 데이터가 롤백되지않고 DB에 반영
    void 회원가입() {
        // given
        Member member = new Member();
        member.setName(&quot;zizon의연v&quot;);

        // when
        Long savedId = memberService.join(member);

        // then
        // Service의 findMember()로 테스트
        Member findMember1 = memberService.findMember(savedId).get();
        assertThat(member.getName()).isEqualTo(findMember1.getName());

        // Repository의 findById()로 테스트
        Member findMember2 =  memberRepository.findById(savedId).get();

    }

    @Test
    void 중복_회원_예외(){
        // given
        Member member1 = new Member();
        member1.setName(&quot;zizon의연v&quot;);

        Member member2 = new Member();
        member2.setName(&quot;zizon의연v&quot;);

        // when
        memberService.join(member1);

        // then
        IllegalStateException e = assertThrows(IllegalStateException.class, 
                        () -&gt; memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
    }

    ...

}</code></pre>
<h3 id="🍀통합-테스트에서-transactional을-사용하는-이유">🍀통합 테스트에서 @Transactional을 사용하는 이유</h3>
<p>스프링 부트 통합 테스트(<code>@SpringBootTest</code>)에서 <code>@Transactional</code>을 붙이면,</p>
<ul>
<li><strong>테스트 메서드가 끝난 뒤 자동으로 트랜잭션 롤백</strong></li>
<li><strong>테스트로 인해 DB에 변경된 내용이 테스트 종료 후 남지 않도록</strong></li>
</ul>
<h4 id="transactional-동작-원리">@Transactional 동작 원리</h4>
<ul>
<li>테스트가 실행되면 <code>@Transactional</code>은</li>
<li><strong>트랜잭션을 시작</strong>하고</li>
<li><strong>테스트가 끝나면 <code>rollback()</code>이 실행</strong>됨</li>
</ul>
<h4 id="transactional가-통합-테스트에서-중요한-이유">@Transactional가 통합 테스트에서 중요한 이유</h4>
<ol>
<li>테스트를 실행할 때마다 <strong>DB가 초기 상태로 보장</strong> <em>- 반복 실행 가능</em> </li>
<li>데이터가 섞이지 않아 <strong>테스트 간 간섭이 없음</strong> <em>- 테스트 격리</em></li>
<li>실제 DB 환경에서 서비스/레포지토리 계층을 통합적으로 검증 가능</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] Section07. 스프링 DB 접근 기술[2] - 순수 JDBC]]></title>
            <link>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section07.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A02-%EC%88%9C%EC%88%98-JDBC</link>
            <guid>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section07.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A02-%EC%88%9C%EC%88%98-JDBC</guid>
            <pubDate>Tue, 17 Jun 2025 02:42:24 GMT</pubDate>
            <description><![CDATA[<h3 id="jdbc란">JDBC란?</h3>
<ul>
<li>자바 진영에서 데이터베이스에 접속할 수 있도록하는 API</li>
<li>자바 애플리케이션에서 JDBC API를 사용해 DB에 접근
(자바 언어로 DB 프로그래밍을 하기 위한 라이브러리)</li>
</ul>
<h3 id="환경-설정">환경 설정</h3>
<ul>
<li><p><code>build.gradle</code>파일에 JDBC, h2 데이터베이스 관련 라이브러리 추가</p>
<ul>
<li>자바는 DB랑 붙으려면 JDBC 드라이버가 반드시 필요<pre><code class="language-java">implementation &#39;org.springframework.boot:spring-boot-starter-jdbc&#39;
runtimeOnly &#39;com.h2database:h2&#39;</code></pre>
</li>
</ul>
</li>
<li><p>DB접속 정보는 <code>application.properties</code>에 추가</p>
<pre><code class="language-java">spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=</code></pre>
</li>
<li><p>회원을 저장하는 역할은 <code>MeberRepository</code>가 하지만 
구현은 메모리랑 할 지(<code>MemoryMemberRepository</code>),</p>
</li>
<li><p>DB랑 연동해서 JDBC가 할 지(<code>JdbcMemberRepository</code>)에 대한 차이</p>
</li>
</ul>
<h3 id="jdbc-레포지토리-구현">JDBC 레포지토리 구현</h3>
<ul>
<li><code>DateSource</code>는 데이터베이스 커넥션을 획득할 때 사용하는 객체</li>
<li>스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 <code>DataSource</code>를 생성하고,
스프링 빈을 만들어 둠 → DI를 받을 수 있음</li>
</ul>
<p>📂 <code>JdbcMemberRepository</code></p>
<pre><code class="language-java">public class JdbcMemberRepository implements MemberRepository{
    // 📌 DB에 붙으려면 DataSource가 필요 -&gt; 스프링에게 주입 받아야 함
    // 📌 application.properties의 내용을 스프링부트가 DataSource 생성 및 주입
    private final DataSource dataSource;

    public JdbcMemberRepository(DataSource dataSource){
        this.dataSource = dataSource;
    }

    @Override
    public Member save(Member member) {
        String sql = &quot;insert into member(name) values(?)&quot;;

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try{
            conn = getConnection();
            pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

            // parameterIndex 1은 SQL의 ?와 매칭
            pstmt.setString(1, member.getName());

            pstmt.executeUpdate();            // DB에 쿼리(insert)가 날라감
            rs = pstmt.getGeneratedKeys();    // 방금 생성된 KEY(1, 2, ..)를 반환

            if(rs.next()){
                member.setId(rs.getLong(1));
            } else{
                throw new SQLException(&quot;id 조회 실패&quot;);
            }
            return member;
        } catch (Exception e){
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        String sql = &quot;select * from member where id = ?&quot;;

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try{
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);

            // 조회는 executeUpdate()❌, executeQuery⭕
            rs = pstmt.executeQuery();

            if(rs.next()){
                Member member= new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e){
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        String sql = &quot;select * from member where name = ?&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try{
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);

            rs = pstmt.executeQuery();

            if(rs.next()){
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                return Optional.of(member);
            }
            return Optional.empty();
        } catch (Exception e){
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public List&lt;Member&gt; findAll() {
        String sql = &quot;select * from member&quot;;

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try{
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();

            List&lt;Member&gt; members = new ArrayList&lt;&gt;();
            while(rs.next()){
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                members.add(member);
            }
            return members;
        } catch (Exception e){
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    // 📌 커넥션을 얻
    private Connection getConnection(){
        return DataSourceUtils.getConnection(dataSource);
    }

    // 📌 자원을 역순으로 해제**
    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs){
        try{
            if(rs!= null){
                rs.close();
            }
        } catch (SQLException e){
            e.printStackTrace();
        }
        try{
            if(pstmt != null){
                pstmt.close();
            }
        } catch (SQLException e){
            e.printStackTrace();
        }

        try{
            if(conn != null){
                close(conn);
            }
        } catch (SQLException e){
            e.printStackTrace();
        }
    }

    // 📌 dataSource.getConnection()❌, DataSourceUtils 권장 -&gt; 트랜잭션 유지
    // 📌 데이터베이스와 연결된 커넥션을 얻음
    // 📌 진짜 데이터베이스와 연결되는 열린 소켓을 얻을 수 있음?**
    private void close(Connection conn) throws SQLException{
        DataSourceUtils.releaseConnection(conn, dataSource);
    }
}</code></pre>
<p>📂 <code>SpringConfig</code></p>
<pre><code class="language-java">// 스프링이 application.properties를 보고 DataSource를 빈으로 자동 생성
@Configuration
public class SpringConfig {

    private DataSource dataSource;

    // DI, DataSource주입**

    @Autowired         
    public SpringConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        // return new MemoryMemberRepository();        // 구현체 변경
        return new JdbcMemberRepository(dataSource);
    }
}</code></pre>
<h3 id="jdbc의-정형화된-패턴">JDBC의 정형화된 패턴</h3>
<ul>
<li><code>JdbcMemberRepository</code>는 JDBC를 직접 사용하는 레포지토리 구현의 전형적인 패턴</li>
</ul>
<h4 id="1-sql-준비">1. SQL 준비</h4>
<pre><code class="language-java">String sql = &quot;select * from member where id = ?&quot;;</code></pre>
<ul>
<li>쿼리는 항상 <code>String</code>으로 선언</li>
<li>쿼리 안에 <code>?</code>를 넣고, 나중에 바인딩</li>
</ul>
<h4 id="2-connection-획득">2. Connection 획득</h4>
<pre><code class="language-java">conn = getConnection();</code></pre>
<ul>
<li><code>DataSource</code>로부터 커넥션을 가져옴<ul>
<li>DataSource는 DB와 연결을 관리하는 객체</li>
<li>내부적으로 Connection을 생성하고 풀링하는 역할</li>
<li>DS에서 커넥션을 가져온다는 건</li>
<li><em>DB에 연결된 소켓 하나를 열고, 해당 연결을 Connection 객체로 받는 것*</em></li>
<li><strong>이 커넥션을 이후 SQL 실행, 트랜잭션 처리 등에 사용됨</strong></li>
</ul>
</li>
<li>스프링에서는 반드시 <code>DataSourceUtils.getConnection()</code> 사용해야 함<ul>
<li>트랜잭션 매니저와 연동되지 않으면 트랜잭션 동기화가 깨질 수 있음 (몬말일까?)</li>
</ul>
</li>
</ul>
<h4 id="3-preparedstatement-생성-및-파라미터-바인딩">3. <strong>PreparedStatement 생성 및 파라미터 바인딩</strong></h4>
<pre><code class="language-java">pstmt = conn.prepareStatement(sql);
pstmt.setLong(1, id);</code></pre>
<ul>
<li><code>PreparedStatement</code>를 통해 SQL 실행 준비</li>
<li><code>setXxx()</code>로 파라미터 바인딩 (<code>1</code>은 ?의 인덱스)</li>
</ul>
<h4 id="4-쿼리-실행">4. <strong>쿼리 실행</strong></h4>
<pre><code class="language-java">rs = pstmt.executeQuery();         // 조회
rs =  pstmt.executeUpdate();     // 삽입, 수정, 삭제</code></pre>
<h4 id="5-resultset을-통해-결과-매핑">5. ResultSet을 통해 <strong>결과 매핑</strong></h4>
<pre><code class="language-java">if(rs.next()){
    Member member = new Member();
    member.setId(rs.getLong(&quot;id&quot;));
    member.setName(rs.getString(&quot;name&quot;));
</code></pre>
<ul>
<li><code>ResultSet</code>을 객체로 변환하는 단계 (Manual ORM)</li>
<li>하나씩 수동으로 <code>getter</code> 호출</li>
</ul>
<h4 id="6-예외-처리-및-감싸기">6. <strong>예외 처리 및 감싸기</strong></h4>
<pre><code class="language-java">} catch (Exception e){
    throw new IllegalStateException(e);
}</code></pre>
<ul>
<li>체크 예외인 <code>SQLException</code>을 런타임 예외로 감싸서 던짐</li>
<li>스프링에서는 <code>DataAccessException</code>을 사용하는 경우도 많음</li>
</ul>
<h4 id="7-리소스-해제">7. <strong>리소스 해제</strong></h4>
<pre><code class="language-java">close(conn, pstmt, rs);</code></pre>
<ul>
<li>반드시 <strong>역순으로 닫기</strong>: <code>ResultSet → Statement → Connection</code></li>
</ul>
<h3 id="🍀-정리">🍀 정리</h3>
<ol>
<li><strong><code>JdbcMemberRepository</code> <em>- Repository 계층</em></strong><ul>
<li>데이터베이스에 직접 접근해 SQL쿼리를 실행하고 결과처리</li>
<li>데이터베이스 연결을 위한 <code>DataSource</code>객체가 필요한데,
생성자 주입 방식으로 <code>SpringConfig</code>에서 설정된 <code>DataSource</code>가 전달됨</li>
</ul>
</li>
<li><strong><code>SpringConfig</code> <em>- 설정 및 의존성 주입 관리</em></strong><ul>
<li>스프링 설정 클래스, 애플리케이션의 빈(Bean)을 관리</li>
<li>DI를 통해 객체 생성 및 관리 역할을 수행</li>
<li><code>MemberService</code> → <code>MemberRepository</code> → <code>DataSource</code> 순서로 의존관계 주입</li>
</ul>
</li>
<li><strong><code>application.properties</code> <em>- 설정파일</em></strong><ul>
<li>데이터베이스 설정, 애플리케이션 이름, 정적 리소스 경로 등을 정의</li>
<li>스프링부트는 이 파일을 기반으로 자동 설정(Auto Configuration)을 수행</li>
</ul>
</li>
</ol>
<h3 id="🍀-스프링-전체-동작-과정-정리">🍀 스프링 전체 동작 과정 정리</h3>
<ol>
<li><strong>애플리케이션 실행</strong></li>
</ol>
<ul>
<li><code>application.properties</code>를 기반으로 <code>DataSource</code> 객체가 빈으로 자동 생성</li>
</ul>
<ol start="2">
<li><strong>SpringConfig 로드</strong></li>
</ol>
<ul>
<li><code>JdbcMemberRepository</code>와 <code>MemberService</code>가 빈으로 등록</li>
<li>생성자 주입을 통해 의존 관계가 설정</li>
</ul>
<ol start="3">
<li><strong>Repository 사용</strong></li>
</ol>
<ul>
<li><code>MemberService</code>에서 <code>memberRepository</code>를 호출하면, 
실제 데이터베이스와 연결된 JDBC 코드가 실행</li>
<li>SQL 쿼리를 통해 데이터 삽입, 조회가 이루어짐.</li>
</ul>
<ol start="4">
<li><strong>트랜잭션 및 커넥션 관리</strong></li>
</ol>
<ul>
<li><code>DataSourceUtils</code>를 통해 커넥션 풀을 관리하며,<br>자원 누수 없이 트랜잭션을 유지</li>
</ul>
<h3 id="🍀-스프링과-다형성">🍀 스프링과 다형성</h3>
<p><strong><code>MemoryMemberRepository</code>에서 <code>JdbcMemberRepository</code>로 갈아끼우기</strong></p>
<ul>
<li>인터페이스를 두고 구현체를 바꿔끼울수 있음 <em>- 스프링 사용 이유(스프링 컨테이너가 지원)</em></li>
<li>DI를 통해 편리하게 기존 코드 변경없이 가능</li>
<li>오직 애플리케이션을 설정하는 코드만 변경하면<br>애플리케이션과 관련된 코드는 손댈게 하나도 없음 <strong><em>- OCP(개방 폐쇄 원칙)</em></strong></li>
</ul>
<img src = "https://velog.velcdn.com/images/kiteof_park/post/18e92bf5-34ae-4003-958c-188a53c697dd/image.png">

<img src = "https://velog.velcdn.com/images/kiteof_park/post/ec84e395-53a8-4f96-84fe-6babb2a1f141/image.png">
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] Section06. 회원 관리 예제 - 웹 MVC ]]></title>
            <link>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section06.-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%9B%B9-MVC</link>
            <guid>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section06.-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%9B%B9-MVC</guid>
            <pubDate>Fri, 13 Jun 2025 17:13:37 GMT</pubDate>
            <description><![CDATA[<h3 id="17-회원-웹-기능---홈-화면-추가">17. 회원 웹 기능 - 홈 화면 추가</h3>
<img src ="https://velog.velcdn.com/images/kiteof_park/post/f11bd2c9-701e-47a9-80b4-2ab533e7d8b1/image.png">

<ul>
<li><code>url</code>과 매핑된 컨트롤러가 없으면 <code>static/index.html</code> 실행</li>
<li><code>url</code>과 매핑된 컨트롤러가 있으면 해당 컨트롤러를 호출(우선순위 적용)</li>
</ul>
<p>📂 <code>HomeController.java</code></p>
<pre><code class="language-java">@Controller
public class HomeController{
    @GetMapping(&quot;/&quot;)
    public String home(){
        return &quot;home&quot;;
    }
}</code></pre>
<p>📂 <code>resources/templates/hello.html</code></p>
<pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;body&gt;
&lt;div class=&quot;container&quot;&gt;
    &lt;div&gt;
        &lt;h1&gt;Hello Spring&lt;/h1&gt;
        &lt;p&gt;회원 기능&lt;/p&gt;
        &lt;p&gt;
            &lt;a href=&quot;/members/new&quot;&gt;회원 가입&lt;/a&gt;
            &lt;a href=&quot;/members&quot;&gt;회원 목록&lt;/a&gt;
        &lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
<h3 id="18-회원-웹-기능---등록">18. 회원 웹 기능 - 등록</h3>
<p>📂<code>MemberController.java</code></p>
<pre><code class="language-java">@Controller
public class MemberController {
    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @GetMapping(&quot;/members/new&quot;)
    public String createForm(){

        // View Resolver가 찾아서 선택 -&gt; Thymeleaf가 렌더링
        // templates/members/createMemberForm.html
        return &quot;members/createMemberForm&quot;;
    }

    @PostMapping(&quot;/members/new&quot;)
    // @ModelAttribute 기능을 통해 매핑
    // HTTP 요청의 파라미터 중 키(name)가 MemberForm의 필드 이름과 일치하면 해당 필드에 값이 자동 매핑
    public String create(MemberForm form){  
        Member member = new Member();
        member.setName(form.getName())

        memberService.join(member);

        // 회원가입이 종료되면 홈 화면으로 리다이렉트
        return &quot;redirect:/&quot;;
    }
}
</code></pre>
<p>📂<code>resources/templates/members/createMemberForm.html</code></p>
<pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;body&gt;
&lt;div class=&quot;container&quot;&gt;

&lt;!-- 📌 submit -&gt; /members/new에  post 요청--&gt;
    &lt;form action=&quot;/members/new&quot; method=&quot;post&quot;&gt;
        &lt;div class=&quot;form-group&quot;&gt;
            &lt;label for=&quot;name&quot;&gt;이름&lt;/label&gt;

            &lt;!-- 📌 &quot;name&quot; : 서버로 넘어갈 때 key가 됨--&gt;
            &lt;input type=&quot;text&quot; id=&quot;name&quot; name=&quot;name&quot; placeholder=&quot;이름을 입력하세요&quot;&gt;
        &lt;/div&gt;
        &lt;button type=&quot;submit&quot;&gt;등록&lt;/button&gt;
    &lt;/form&gt;
&lt;/div&gt; &lt;!-- /container --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>📂<code>MemberForm.java</code></p>
<pre><code class="language-java">// 폼 데이터를 매핑하기 위한 DTO역할
// setName()을 통해 name값이 설정
public class MemberForm {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}</code></pre>
<h3 id="회원-등록-플로우">회원 등록 플로우</h3>
<pre><code>[사용자 브라우저]
       |
       | GET /members/new
       v
[MemberController - createForm()]
       |
       | return &quot;members/createMemberForm&quot;
       v
[createMemberForm.html 렌더링됨]
       |
       | &lt;form&gt; 입력 후 등록 버튼 클릭
       v
[POST /members/new 요청]
       |
       | @ModelAttribute: MemberForm에 name 매핑
       v
[MemberController - create()]
       |
       | Member 객체 생성 + 이름 설정
       v
[memberService.join(member)]
       |
       | 회원 저장 로직 실행
       v
[회원 가입 완료 → redirect:/]
       |
       v
[홈 화면으로 리다이렉트]

</code></pre><h3 id="modelattribute">@ModelAttribute</h3>
<ul>
<li>HTTP 요청 파라미터를 객체(빈)에 바인딩해주는 어노테이션</li>
<li>HTML 폼에서 넘어온 데이터를 자바 객체 필드에 자동으로 넣어줌</li>
<li>🌿 객체 타입(<code>MemberForm</code>))인 경우 생략 가능</li>
<li>🌿 기본 타입(<code>String</code>, <code>int</code>)인 경우 <code>@RequestParam</code>으로 처리</li>
</ul>
<hr>
<h3 id="19-회원-웹-기능---조회">19. 회원 웹 기능 - 조회</h3>
<p>📂<code>MemberController.java</code></p>
<pre><code class="language-java">    @GetMapping(&quot;/members&quot;)
    public String list(Model model){
        List&lt;Member&gt; members = memberService.findMembers();
        model.addAttribute(&quot;members&quot;, members);
        return &quot;members/memberList&quot;;
    }</code></pre>
<p>📂<code>memberList.html</code></p>
<pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;body&gt;
&lt;div class=&quot;container&quot;&gt;
    &lt;div&gt;
        &lt;table&gt;
            &lt;thead&gt;
            &lt;tr&gt;
                &lt;th&gt;#&lt;/th&gt;
                &lt;th&gt;이름&lt;/th&gt;
            &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
            &lt;!-- Thymeleaf 템플릿 엔진이 동작하는 부분--&gt;

            &lt;!--  Service의 findMembers() -&gt; Controller가 members로 전달--&gt;
            &lt;!-- ${...} : Model안에 있는 값을 꺼냄--&gt;**
            &lt;tr th:each=&quot;member : ${members}&quot;&gt;
                **&lt;!--  getId(), getName()으로 접근해서 값을 읽음--&gt;**
                &lt;td th:text=&quot;${member.id}&quot;&gt;&lt;/td&gt;
                &lt;td th:text=&quot;${member.name}&quot;&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;/tbody&gt;
        &lt;/table&gt;
    &lt;/div&gt;
&lt;/div&gt; &lt;!-- /container --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h3 id="model-객체">Model 객체</h3>
<ul>
<li>스프링 MVC에서 컨트롤러와 뷰(HTML 템플릿) 사이의 데이터 전달자 역할</li>
<li>내부적으로는 <code>Map&lt;String, Object&gt;</code> 구조<pre><code class="language-java">Map&lt;String, Object&gt; model = new HashMap&lt;&gt;();
model.put(&quot;members&quot;, members);
</code></pre>
</li>
</ul>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] Section05. 스프링 빈과 의존관계]]></title>
            <link>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section05.-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88%EA%B3%BC-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@kiteof_park/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-Section05.-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88%EA%B3%BC-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84</guid>
            <pubDate>Fri, 13 Jun 2025 14:49:18 GMT</pubDate>
            <description><![CDATA[<h3 id="🫛-스프링-빈bean이란">🫛 스프링 빈(Bean)이란?</h3>
<ul>
<li>스프링 프레임워크에서 <strong>스프링 컨테이너가 관리하는 객체, 인스턴스</strong></li>
<li>스프링이 생성하고, 의존성을 주입하고, 생명 주기를 관리하는 객체</li>
<li>일반 자바 객체는 <code>new</code> 키워드로 생성하는 반면,</li>
<li><em>스프링에서는 스프링 컨테이너(<code>Application Context</code>)가 대신 객체 생성 및 관리*</em></li>
<li>어노테이션에 따라 스프링 빈의 대상이 됨 <em>- 해당 클래스의 인스턴스는 빈으로 등록</em></li>
<li><code>@Component</code>, <code>@Service</code>, <code>@Repository</code>, <code>@Controller</code>, <code>@Configuration + @Bean</code></li>
</ul>
<h3 id="스프링-빈을-등록하는-2가지-방법">스프링 빈을 등록하는 2가지 방법</h3>
<ol>
<li>컴포넌트 스캔과 자동 의존관계 설정</li>
<li>자바 코드로 직접 스프링 빈 등록</li>
</ol>
<h2 id="15-컴포넌트-스캔과-자동-의존-관계-설정">15. 컴포넌트 스캔과 자동 의존 관계 설정</h2>
<ul>
<li><p><code>Controller</code>가 <code>Service</code>와 <code>Repository</code>를 사용할 수 있도록 의존 관계를 준비</p>
</li>
<li><p>스프링이 처음에 뜰 때 스프링 컨테이너가 생성되는데,<br><code>MemberController.java</code> 파일에 <code>@Controller</code> 를 붙여두면  </p>
</li>
<li><p><em>컨트롤러 객체를 생성해서 스프링 컨테이너에 넣어두고 관리*</em>한다.</p>
</li>
<li><p>즉, 스프링 컨테이너에서 스프링 빈이 관리된다.</p>
</li>
</ul>
<h3 id="의존-관계의-di---new를-지양한다">의존 관계의 DI <em>- new를 지양한다</em></h3>
<pre><code class="language-java">@Controller
public class MemberController {

    private final MemberService memberService = new MemberService();

    ...
}</code></pre>
<pre><code class="language-java">@Controller
public class MemberController {

    private final MemberService memberService;

    @AutoWired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;

    }
    ...
}</code></pre>
<ul>
<li><code>Controller</code>와 <code>Service</code> 의존관계를 맺는 과정에서
첫 번째 코드처럼 <code>new</code>를 사용하는건 <strong>스프링답지 않다.</strong><ul>
<li>스프링의 의존성 주입이 안됨</li>
<li>AOP, 트랜잭션 등 스프링의 기능이 적용되지 않음</li>
<li>테스트나 유지보수가 불편</li>
</ul>
</li>
<li>스프링답게 사용하기 위해서는 <strong>스프링 컨테이너에 빈으로 등록하고,<br>스프링 컨테이너에서 받아서(주입받아서) 사용할 수 있어야 한다.</strong></li>
<li>🌿 따라서 <code>@Autowired</code>를 사용해 의존관계를 주입한다.</li>
</ul>
<h3 id="컴포넌트-스캔---component-스프링이-관리할-객체빈를-자동-등록-">컴포넌트 스캔 *- @Component, 스프링이 관리할 객체(빈)를 자동 등록 *</h3>
<ul>
<li><code>@Component</code>는 해당 클래스를 <strong>스프링 컨테이너가 관리할 수 있도록 등록</strong>하는 어노테이션</li>
<li>위에서 언급한 대로 <code>@Controller</code>, <code>@Servcie</code>, <code>@Repository</code> 등의 어노테이션은<br>내부적으로 <code>@Component</code>를 포함하고 있어,  </li>
<li><em>스프링이 실행될 때 자동으로 객체를 만들어 관리(스프링 컨테이너에 빈 등록)*</em></li>
<li>⚠️ 단, <code>@ComponentScan</code>이 설정된 패키지 안에 있어야 감지됨!</li>
</ul>
<h4 id="참고---스프링의-기본-빈-생성-방식-singleton">참고 - 스프링의 기본 빈 생성 방식, Singleton</h4>
<ul>
<li>스프링 컨테이너에 빈을 등록할 때 기본적으로 싱글톤으로 등록</li>
<li><strong>오직 하나의 객체(인스턴스)를 생성해 애플리케이션 전체에서 공유하는 방식</strong></li>
<li>모든 빈을 싱글톤으로 생성해 <strong>필요할 때마다 같은 객체를 주입</strong></li>
<li>메모리 효율, 공통된 설정 및 데이터 공유 가능, 애플리케이션 전반에서 동일한 동작 유지 </li>
</ul>
<h3 id="자동-의존관계-설정--autowired-자동으로-필요한-객체를-주입">자동 의존관계 설정 <em>-@Autowired, 자동으로 필요한 객체를 주입</em></h3>
<ul>
<li><p><code>@Autowired</code>는 스프링 컨테이너 안에 있는 필요한 객체(빈)을 자동으로 주입하는 어노테이션</p>
</li>
<li><p><code>Controller</code>가 <code>Service</code>를 사용하고, <code>Service</code>가 <code>Repository</code>를 사용할 수 있도록 
<code>@Autowired</code>는 <strong>연관관계(의존관계)를 자동으로 맵핑(주입)해줌</strong></p>
</li>
<li><p>생성자, 필드, setter() 등에 사용 가능</p>
<ul>
<li>생성자에<code>@Autowired</code>를 사용하면 객체 생성 시점에<br>스프링 컨테이너에서 스프링 빈을 찾아서 주입</li>
</ul>
</li>
<li><p>⚠️ 단, <code>@Autowired</code>를 통한 DI는 <strong>스프링이 관리하는 객체에서만 동작</strong>
(스프링 빈으로 등록하지 않거나, 개발자가 직접 생성한 객체에서는 동작❌)</p>
</li>
</ul>
<h3 id="정리하자면">정리하자면,</h3>
<ul>
<li><code>@Component</code>를 통해 스프링 빈으로 등록하고,</li>
<li><code>@Autowired</code>를 통해 빈들 간의 의존관계를 자동으로 맺는다.</li>
<li>🌿 스프링 컨테이너가 객체를 만들고, 필요한 곳에 자동으로 연결해주는 것이 DI의 핵심</li>
</ul>
<hr>
<h2 id="16-자바-코드로-직접-스프링-빈-등록하기">16. 자바 코드로 직접 스프링 빈 등록하기</h2>
<h3 id="service-repository-autowired를-제거하면">@Service, @Repository, @Autowired를 제거하면?</h3>
<pre><code class="language-java">@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
</code></pre>
<ul>
<li><code>Controller</code>에서 사용하는 <code>Service</code>가 스프링 빈으로 등록되지 않는 경우,</li>
<li><em>Component Scan을 사용하지 않고 직접 등록해서 사용 가능*</em></li>
<li>Config 파일에서 수동으로 Bean으롤 정의한 <code>Service</code>를 <code>Controller</code>에 DI해줌</li>
</ul>
<h3 id="configuration">@Configuration</h3>
<ul>
<li>해당 클래스가 <strong>스프링 설정 클래스</strong>임을 명시</li>
<li>스프링은 이 클래스를 빈 구성정보로 사용하여 애플리케이션의 객체(빈) 관리를 수행</li>
<li><strong><code>@Bean</code> 어노테이션을 사용해 메서드에서 정의한 빈들을 스프링 컨테이너에 등록</strong></li>
<li>스프링은 이 클래스를 읽고, 내부의 빈 정의를 스캔해 스프링 컨테이너에 빈을 등록</li>
</ul>
<h3 id="bean">@Bean</h3>
<ul>
<li>메서드에서 반환하는 객체를 스프링 빈으로 등록</li>
<li>빈으로 등록된 객체는 싱글톤으로 관리, 필요할 때 의존성 주입을 통해 사용</li>
</ul>
<p>📂<code>SpringConfig.java</code></p>
<pre><code class="language-java">@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepsoitory();
    }
}</code></pre>
<h3 id="기타-참고사항">기타 참고사항</h3>
<ul>
<li>DI의 세 가지 방식(생성자 주입, 필드 주입, setter 주입) 중,<br>의존관계가 실행 중에 동적으로 변경되는 경우는 ㄴ거의 없으므로 생성자 주입을 권장</li>
<li>실무에서 정형화된 <code>Controller</code>, <code>Service</code>, <code>Repsotitory</code> 는 컴포넌트 스캔을 사용 </li>
<li><strong>정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 되는 경우,<br>설정을 통해 스프링 빈으로 등록</strong> <em>- @Configuration + @Bean</em></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[친절한 SQL 튜닝] INDEX FULL SCAN과 TABLE FULL SCAN]]></title>
            <link>https://velog.io/@kiteof_park/%EC%B9%9C%EC%A0%88%ED%95%9C-SQL-%ED%8A%9C%EB%8B%9D-INDEX-FULL-SCAN%EA%B3%BC-TABLE-FULL-SCAN</link>
            <guid>https://velog.io/@kiteof_park/%EC%B9%9C%EC%A0%88%ED%95%9C-SQL-%ED%8A%9C%EB%8B%9D-INDEX-FULL-SCAN%EA%B3%BC-TABLE-FULL-SCAN</guid>
            <pubDate>Sun, 08 Jun 2025 19:19:33 GMT</pubDate>
            <description><![CDATA[<h2 id="table-full-scan">Table Full Scan</h2>
<ul>
<li>테이블의 모든 블록을 처음부터 끝까지 읽는 방식</li>
<li>조건과 관계없이 모든 Row를 다 읽고 필요한 조건을 나중에 필터링</li>
</ul>
<h3 id="🍀-사용되는-상황">🍀 사용되는 상황</h3>
<ul>
<li><code>WHERE</code> 조건이 인덱스를 사용할 수 없는 경우</li>
<li>조건이 없거나, 너무 많은 Row를 반환하는 조건(<code>WHERE salary &gt; 100</code>)</li>
<li>인덱스를 사용해도 효율이 낮은 경우 (인덱스 사용 시 I/O 비용 &gt; Table Full Scan)</li>
<li>옵티마이저가 <code>Table Full Scan</code>이 더 빠르다고 판단할 때</li>
<li><code>/*+ FULL */</code> 힌트를 명시했을 때</li>
</ul>
<h3 id="🍀-성능-특징">🍀 성능 특징</h3>
<ul>
<li><code>Multiblock I/O</code> 방식으로 상대적으로 빠르게 읽을 수 있음</li>
<li>큰 테이블에서 많은 데이터를 읽을 때는 오히려 효율적일 수 있음</li>
<li>병렬 처리(<code>PQ</code>)도 용이</li>
</ul>
<hr>
<h2 id="index-full-scan">Index Full Scan</h2>
<ul>
<li>인덱스의 <strong>루트부터 리프까지 전체 노드</strong>를 순차적으로 모두 스캔</li>
<li>테이블은 접근하지 않고, <strong>인덱스만을 통해 필요한 데이터를 모두 가져올 수 있을 때</strong> 사용</li>
</ul>
<h3 id="🍀-사용되는-상황-1">🍀 사용되는 상황</h3>
<ul>
<li>인덱스가 <code>WHERE</code>, <code>SELECT</code>절에 있는 모든 컬럼을 포함할 때 <em>-커버링 인덱스</em><br>(테이블 접근 없이 인덱스만으로 처리 가능한 경우)</li>
<li><code>ORDER BY</code>나 <code>GROUP BY</code>에서 정렬을 위한 인덱스를 사용하는 경우</li>
<li>옵티마이저가 인덱스 전부 읽는 것이 더 낫다고 판단할 경우</li>
<li><code>/*+ INDEX_FFS */</code> 힌트 사용 시</li>
</ul>
<h3 id="🍀-성능-특징-1">🍀 성능 특징</h3>
<ul>
<li>테이블을 접근하지 않음 → Disk I/O 줄어듦</li>
<li>주로 인덱스 컬럼만 필요할 때 유리</li>
<li>읽기 순서가 정렬된 상태이므로 정렬이 불필요한 경우도 있음</li>
<li>인덱스 전체를 읽으므로 레코드 수가 많으면 비용 발생</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[친절한 SQL 튜닝] 커버링 인덱스가 머여요?]]></title>
            <link>https://velog.io/@kiteof_park/%EC%B9%9C%EC%A0%88%ED%95%9C-SQL-%ED%8A%9C%EB%8B%9D-%EC%BB%A4%EB%B2%84%EB%A7%81-%EC%9D%B8%EB%8D%B1%EC%8A%A4%EA%B0%80-%EB%A8%B8%EC%97%AC%EC%9A%94-on6pszl7</link>
            <guid>https://velog.io/@kiteof_park/%EC%B9%9C%EC%A0%88%ED%95%9C-SQL-%ED%8A%9C%EB%8B%9D-%EC%BB%A4%EB%B2%84%EB%A7%81-%EC%9D%B8%EB%8D%B1%EC%8A%A4%EA%B0%80-%EB%A8%B8%EC%97%AC%EC%9A%94-on6pszl7</guid>
            <pubDate>Sun, 08 Jun 2025 19:17:04 GMT</pubDate>
            <description><![CDATA[<h3 id="커버링-인덱스covcering-index">커버링 인덱스(Covcering Index)</h3>
<ul>
<li><strong>쿼리가 필요한 모든 컬럼의 값을 인덱스만 보고 조회할 수 있는 경우</strong>,  
그 인덱스가 커버링 인덱스 </li>
<li>즉, <strong>테이블까지 접근할 필요 없이 인덱스에서만 모든 데이터를 가져올 수 있는 경우</strong></li>
<li><strong>쿼리를 수행할 때 인덱스만 보고도 결과를 낼 수 있는 상황</strong></li>
<li>✨ <strong>테이블 액세스 없이 처리된다는 점이 핵심</strong></li>
</ul>
<pre><code class="language-SQL">SELECT name, age FROM users WHERE email = &#39;abc@example.com&#39;;</code></pre>
<pre><code class="language-SQL">CREATE INDEX idx_users_email_name_age ON users(email, name, age);</code></pre>
<ul>
<li>이 인덱스는 다음 순서로 데이터를 포함한다.<ul>
<li>email</li>
<li>name</li>
<li>age</li>
</ul>
</li>
<li>인덱스 구성요소인 <code>email</code> 은 위의 SQL 쿼리에서 <strong>검색 조건</strong>에 해당하고,<br><code>name</code>과 <code>age</code>는 <strong>조회 컬럼</strong>에 해당하는데<br>SQL 쿼리가 요구하는 바(<code>email</code>로 필터링하고 <code>name</code>, <code>age</code>를 조회)와 상응하기에  </li>
<li><em>인덱스만으로 쿼리 결과를 반환할 수 있어 커버링 인덱스*</em>에 해당된다.</li>
<li>즉, <code>email</code> 을 검색 조건으로 사용하고, <code>name</code>과 <code>age</code>가  
이미 인덱스 안에 포함돼있어, 인덱스만 보고 결과를 만들 수 있다 <em>- 커버링 인덱스</em></li>
</ul>
<h3 id="커버링-인덱스의-성능상-이점">커버링 인덱스의 성능상 이점</h3>
<ol>
<li>테이블 액세스를 생략(인덱스 레벨에서 처리 → 랜덤 I/O 감소)</li>
<li>디스크 I/O 절감</li>
<li>쿼리 속도 향상(🚀빠른 쿼리 수행)</li>
</ol>
<h3 id="커버링-인덱스-주의할-점">커버링 인덱스 주의할 점</h3>
<ol>
<li><code>SELECT *</code> 같은 쿼리는 효과 없음❗
(<strong>테이블 전체 컬럼을 참조하므로 인덱스로 커버 불가능</strong>하다)</li>
<li>인덱스에 너무 많은 컬럼을 포함시키면 <strong>쓰기 성능 저하 및 인덱스 크기 증가</strong> 우류</li>
<li><strong>인덱스 컬럼 순서</strong>도 중요 
(<code>WHERE</code>, <code>SELECT</code> 절을 모두 고려해 설계해야 함)</li>
</ol>
<h3 id="일반-인덱스-vs-커버링-인덱스-차이">일반 인덱스 vs 커버링 인덱스 차이</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>일반 인덱스</th>
<th>커버링 인덱스</th>
</tr>
</thead>
<tbody><tr>
<td>인덱스 역할</td>
<td><code>WHERE</code> 조건 필터링에만 사용</td>
<td><code>WHERE</code> + <code>SELECT</code> 모든 컬럼을 포함</td>
</tr>
<tr>
<td>테이블 접근</td>
<td>추가 접근 필요<br><code>ROWID</code>로 테이블에서 조회 컬럼을 가져옴</td>
<td>인덱스만으로 완료(테이블 접근❌)</td>
</tr>
<tr>
<td>성능</td>
<td>느림(랜덤 I/O 발생)</td>
<td>빠름(I/O 최소화)</td>
</tr>
</tbody></table>
<p>ref. <a href="https://jojoldu.tistory.com/476">https://jojoldu.tistory.com/476</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[친절한 SQL 튜닝] LIKE %KEYWORD%는 인덱스 사용이 아예 불가능할까?]]></title>
            <link>https://velog.io/@kiteof_park/%EC%B9%9C%EC%A0%88%ED%95%9C-SQL-%ED%8A%9C%EB%8B%9D-LIKE-KEYWORD%EB%8A%94-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%82%AC%EC%9A%A9%EC%9D%B4-%EC%95%84%EC%98%88-%EB%B6%88%EA%B0%80%EB%8A%A5%ED%95%A0%EA%B9%8C-26n70j12</link>
            <guid>https://velog.io/@kiteof_park/%EC%B9%9C%EC%A0%88%ED%95%9C-SQL-%ED%8A%9C%EB%8B%9D-LIKE-KEYWORD%EB%8A%94-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%82%AC%EC%9A%A9%EC%9D%B4-%EC%95%84%EC%98%88-%EB%B6%88%EA%B0%80%EB%8A%A5%ED%95%A0%EA%B9%8C-26n70j12</guid>
            <pubDate>Sun, 08 Jun 2025 19:15:25 GMT</pubDate>
            <description><![CDATA[<h3 id="like-연산자와-인덱스-사용-여부">LIKE 연산자와 인덱스 사용 여부</h3>
<pre><code class="language-SQL">WHERE 업체명 LIKE &#39;%대한%&#39;</code></pre>
<ul>
<li>와일드카드 <code>%</code>가 앞에 위치하면 문자열의 시작 위치가 불확실하여,
인덱스를 사용 불가능(<code>Range Scan</code> 불가능)❌</li>
<li>일반적으로 <code>Table Full Scan</code>이 수행된다.</li>
</ul>
<pre><code class="language-SQL">WHERE 업체명 LIKE &#39;대한%&#39;</code></pre>
<ul>
<li>와일드카드가 뒤에만 위치한 경우, 문자열의 시작이 고정되므로
B-Tree 인덱스를 활용한 <code>Range Scan</code>이 가능</li>
</ul>
<h4 id="그런데-like-연산자에서도-사용-가능한-인덱스가-있다--👀">그런데 LIKE 연산자에서도 사용 가능한 인덱스가 있다 ..? 👀</h4>
<p><code>LIKE &#39;%KEYWORD%&#39;</code> 같은 접두어가 없는 패턴 검색은<br>B-Tree 인덱스로는 인덱스 사용이 불가능하지만,
전문 검색(FULL TEXT SEARCH) 기술을 도입하면 인덱스를 통한 효율적인 검색이 가능하다.</p>
<h3 id="1-innodb-full-text-index">1. InnoDB Full Text Index</h3>
<ul>
<li><code>%KEYWORD%</code> 처럼 중간 또는 끝에 있는 단어 검색 가능</li>
<li><code>MATCH(column) AGAINST(&#39;keyword&#39; IN BOOLEAN MODE)</code> 형식으로 사용</li>
<li>내부적으로 <strong>역색인(Inverted Index)</strong>을 활용하므로, 중간 검색도 빠름</li>
</ul>
<p>✅ FULL TEXT INDEX의 역색인 구조는 일반 인덱스의 B-Tree 구조와 다름!</p>
<h3 id="2-n-gram-parser">2. n-gram parser</h3>
<ul>
<li>한글처럼 공백 없는 언어 지원을 위해 도입</li>
<li>FULL TEXT INDEX에 <code>WITH PARSER ngram</code> 설정 필요</li>
<li>한글 포함 문자열에서도 &#39;%KEYWORD%&#39; 검색 최적화 가능</li>
</ul>
<h3 id="3-elasticsearch---외부-전문-검색-엔진">3. ElasticSearch <em>- 외부 전문 검색 엔진</em></h3>
<ul>
<li>외부에 데이터를 연동해서 사용하는 방식.</li>
<li><code>LIKE &#39;%keyword%&#39;</code>가 아닌, <code>match</code>나 <code>wildcard</code> 등 다양한 검색 쿼리 제공</li>
<li>중간 단어, 복합 키워드, 형태소 분석까지 고도화 가능.</li>
<li>대규모 검색 시스템에 적합 (쇼핑몰 검색, 리뷰 검색 등)</li>
</ul>
<h3 id="full-text-index를-활용한-개발-경험-사례-공유">FULL TEXT INDEX를 활용한 개발 경험 사례 공유</h3>
<p><a href="https://www.notion.so/LIKE-FULLTEXT-INDEX-1d4b3ff41d398185bc85e04d6adf4a01#1d4b3ff41d3981d39bcadcc994645a41">FULL TEXT INDEX을 사용한 개발 및 학습 일지</a>
<a href="https://github.com/UIverse-Team/UIverse-BE/pull/276">FULL TEXT INDEX를 도입한 개발 PR</a></p>
]]></description>
        </item>
    </channel>
</rss>