<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>min_gyu.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 01 Nov 2026 18:04:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. min_gyu.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/min_gyu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Mockito]]></title>
            <link>https://velog.io/@min_gyu/Mockito</link>
            <guid>https://velog.io/@min_gyu/Mockito</guid>
            <pubDate>Sun, 01 Nov 2026 18:04:17 GMT</pubDate>
            <description><![CDATA[<h2 id="mockito란">Mockito란?</h2>
<blockquote>
</blockquote>
<p>단위 테스트 진행에 필요한 모의 객체(@Mock)를 관리해주는 자바의 오픈소스 프레임워크이다.
모의 객체는 실제 객체와 비슷하지만, 개발자가 테스트 시나리오에 따라 제어할 수 있는 가짜 객체이다.</p>
<h2 id="mock-stub-spy">Mock, Stub, Spy</h2>
<h3 id="mock">Mock</h3>
<blockquote>
</blockquote>
<p>가짜 객체를 생성하고, 메서드간 상호작용을 검증한다.</p>
<ul>
<li>외부 작업의 호출 여부를 검증할 때</li>
<li>특정 메서드가 원하는 횟수만큼 정확히 호출되었는지</li>
<li>메서드에 전달한 인자가 올바른지</li>
<li>외부 시스템에 영향을 주지 않고 객체 간 상호작용르 테스트할 때<blockquote>
</blockquote>
verify() 를 통해 메서드의 호출횟수를 카운팅 할 수 있다.
예를 들어 email중복되는 상황에서 저장메서드가 실행되지 않음을 테스트할 수 있다.</li>
</ul>
<h3 id="stub">Stub</h3>
<blockquote>
</blockquote>
<p>개발자가 미리 선언한 결과를 반환한다.
DB에 접속하지 않고도 DB와 연계되는 기능을 테스트할 수 있다.</p>
<pre><code class="language-java">Product product = new Product(1L,&quot;키보드&quot;,3000,5);
when(productRepository.findById(1L)).thenReturn(Optional.of(product));</code></pre>
<blockquote>
</blockquote>
<ul>
<li>DB조회 결과가 필요할 때</li>
<li>외부API의 응답이 필요할 때</li>
<li>오류, Null등의 상황을 테스트할 때</li>
</ul>
<h3 id="spy">spy</h3>
<blockquote>
</blockquote>
<p>실제 객체를 감싸, 실제 메서드를 실행하도록 만드는 대역이다.
부분적으로 Stub을 도입할 수 있으며, 이 때는 when().thenReturn()보다 <strong>doReturn().when()</strong>이 권장된다.
spy자주 사용하면 결합도가 상승할 위험이 있어, 꼭 필요한 부분에만 사용을 고려해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[계층별 검증 책임]]></title>
            <link>https://velog.io/@min_gyu/%EA%B3%84%EC%B8%B5%EB%B3%84-%EA%B2%80%EC%A6%9D-%EC%B1%85%EC%9E%84%EA%B3%BC-%EC%A4%91%EB%B3%B5-%EA%B2%80%EC%A6%9D-%ED%94%BC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@min_gyu/%EA%B3%84%EC%B8%B5%EB%B3%84-%EA%B2%80%EC%A6%9D-%EC%B1%85%EC%9E%84%EA%B3%BC-%EC%A4%91%EB%B3%B5-%EA%B2%80%EC%A6%9D-%ED%94%BC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 01 Nov 2026 14:01:51 GMT</pubDate>
            <description><![CDATA[<h2 id="controller">Controller</h2>
<blockquote>
</blockquote>
<p>클라이언트가 API명세에 맞게 요청했는지 검증한다.
주로 @Valid를 통한다.</p>
<pre><code class="language-java">public record UserCreateRequest(
    @NotBlank
    @Size(min = 2, max = 8)
    String username,
&gt;
    @NotBlank
    @Email
    String email
) {}</code></pre>
<h2 id="service">Service</h2>
<blockquote>
</blockquote>
<p>올바른 입력값도 비즈니스 규칙에 어긋난 수 있다.
예를 들어 이메일의 중복검사이다.</p>
<pre><code class="language-java">if (userRepository.existsByEmail(request.email())) {
    throw new DuplicateEmailException();
}</code></pre>
<h2 id="domain">Domain</h2>
<blockquote>
</blockquote>
<p>Email의 경우는 @Email를 통해 검사하는게 보편적이지만 특정 필드의 형식이 지정되어 있다면 정규식 검사를 진행할 수 있다.</p>
<pre><code class="language-java">if (!email.matches(&quot;^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$&quot;)) {
    throw new IllegalArgumentException(&quot;메일 형식이 아님.&quot;);
}</code></pre>
<blockquote>
</blockquote>
<hr>
<p>또는 음수가 될 수 없는 재고 같은 경우도 도메인에서 검사함이 적절하다.</p>
<pre><code class="language-java">    public void addStock(int quantity) {
        if(0 &gt;= quantity) {
            throw new IllegalArgumentException(&quot;추가할 재고는 양수여야 함&quot;);
        }
        this.stock += quantity;
     }</code></pre>
<h2 id="repository">Repository</h2>
<blockquote>
</blockquote>
<p>@EntityGraph, Join fetch이 내가 원하는 테이블을 정확히 가져와서 실제로 N+1을 막았는지 확인한다.</p>
<blockquote>
</blockquote>
<hr>
<p>UNIQUE, NOT NULL같은 테이블 제약조건이 DDL대로 잘 작동하는지 확인한다.
이건 서비스에서 검사한 것 아닌가? 생각할 수 있지만 목적이 다르다</p>
<ul>
<li>서비스는 클라이언트에게 명확한 원인을 알려주거나, 서버에 기록을 남기기 위함이다.</li>
<li>DB의 제약조건은 동시적인 요청에도 무결성을 보장하는 최종방어선이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[영속성 컨텍스트 1차 캐시에 대한 실험]]></title>
            <link>https://velog.io/@min_gyu/%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@min_gyu/%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Thu, 22 Oct 2026 16:51:54 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<blockquote>
</blockquote>
<ul>
<li>외부API로부터 모든(171개) 레코드를 갱신받는 코드를 작성해야 했다.</li>
<li>for를 통해 update를 진행하면 171번의 find sql쿼리가 진행된다.</li>
<li>for문 전에 findAll을 진행하면 171번이 find가 1차 캐시를 검색하고, sql쿼리가 1번으로 감소하리라 예상했다.</li>
</ul>
<h2 id="실험과정하단에-전체-코드-깃허브-있음">실험과정(하단에 전체 코드 깃허브 있음)</h2>
<blockquote>
</blockquote>
<p>실험에 사용할 DB이다.(순서대로 entity_a, entity_b)</p>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th>id</th>
<th>text_a</th>
<th>b_id</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>1A</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>2A</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>3A</td>
<td>3</td>
</tr>
<tr>
<td>4</td>
<td>4A</td>
<td>4</td>
</tr>
<tr>
<td>5</td>
<td>5A</td>
<td>5</td>
</tr>
</tbody></table>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th>id</th>
<th>text_b</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>1B</td>
</tr>
<tr>
<td>2</td>
<td>2B</td>
</tr>
<tr>
<td>3</td>
<td>3B</td>
</tr>
<tr>
<td>4</td>
<td>4B</td>
</tr>
<tr>
<td>5</td>
<td>5B</td>
</tr>
</tbody></table>
<blockquote>
</blockquote>
<hr>
<blockquote>
</blockquote>
<pre><code class="language-java">&gt;
    @Query(&quot;&quot;&quot;
        select a
        from EntityA a
        Join fetch a.entityB
        &quot;&quot;&quot;)
    List&lt;EntityA&gt; findAll();
&gt;//////////////////////////////////////////////////////
&gt;
    @Transactional(readOnly = true)
    public void findAll(){
        entityARepository.findAll();
        for(int i = 1; i &lt; 6; i++){
            find(i);
        }
    }
&gt;//////////////////////////////////////////////////////
&gt;
    @Transactional(readOnly = true)
    public void find(int i){
        EntityA entityA = entityARepository.findById((long) i)
        .orElseThrow(() -&gt; new NoSuchElementException(&quot;없음&quot;));
        String textB= entityA.getEntityB().getTextB();
    }</code></pre>
<p>예상대로 sql 쿼리가 1회 진행되었다. Join fetch로 가져온 entity_b까지 전부 영속성 컨텍스트에서 가져왔다.</p>
<blockquote>
</blockquote>
<hr>
<p>하지만 id가 아닌 값으로 진행하면 어떨까?</p>
<pre><code class="language-java">    @EntityGraph(attributePaths = {&quot;entityB&quot;})
    List&lt;EntityA&gt; findAllByTextAIn(Collection&lt;String&gt; textAS);
&gt;///////////////////////////////////////////////////////
&gt;
    @Transactional(readOnly = true)
    public void findAllByTextAs(List&lt;String&gt; list){
        entityARepository.findAllByTextAIn(list);
        for(String string : list){
            findByTextA(string);
        }
    }
&gt;/////////////////////////////////////////////////////
&gt;
    Optional&lt;EntityA&gt; findByTextA(String textA);
    &gt;
    @Transactional(readOnly = true)
    public void findByTextA(String string){
        EntityA entityA = entityARepository.findByTextA(string)
        .orElseThrow(() -&gt; new NoSuchElementException(&quot;없음&quot;));
        String textB= entityA.getEntityB().getTextB();
    }
&gt;//////////////////////////////////////////
&gt;
    List&lt;String&gt; list = List.of(&quot;1A&quot;,&quot;2A&quot;,&quot;3A&quot;,&quot;4A&quot;,&quot;5A&quot;);
    entityAService.findAllByTextAs(list);</code></pre>
<ul>
<li>6번의 쿼리가 진행되었다. A에 대한 쿼리는 SQL로 나갔지만, getEntityB는 영속성 컨텍스트에서 가져온 것으로 추정된다.</li>
</ul>
<hr>
<ul>
<li>이를 확인하기 위해 @EntityGraph를 주석처리하니, 예상대로 11회의 SQL쿼리가 진행되었다.</li>
<li>getEntityB를 1차 캐시에서 가져올 수 없으니, findAllByIds 1회, findByTextA 5회, getEntityB 5회(LAZY)가 발생한 것이다.</li>
</ul>
<h2 id="결론-및-배운-점">결론 및 배운 점</h2>
<blockquote>
</blockquote>
<ul>
<li>Spring Data JPA의 findById()는 PK를 기준으로 영속성 컨텍스트의 1차 캐시를 먼저 확인하므로, 이미 관리 중인 엔티티라면 SQL 없이 조회할 수 있다. 반면 findByTextA()처럼 PK가 아닌 속성을 조건으로 사용하는 조회 메서드는 일반적으로 SQL을 실행한다. 다만 조회된 엔티티가 이미 영속성 컨텍스트에 존재한다면 기존 인스턴스를 재사용한다.</li>
<li>개요의 상황은 외부API가 제공해 준 name을 파라매터로 사용해야 함으로 적용할 수 없다.</li>
<li>이번 실험을 통해 1차 캐시가 PK를 기준으로 엔티티를 관리한다는 점과, PK 조회와 일반 속성 조회의 차이를 이해했다. 이후 비슷한 상황에서 조회 조건에 따라 적절한 조회 전략을 선택하여 SQL 실행 횟수를 줄일 수 있을 것이다.</li>
</ul>
<h2 id="깃허브-주소">깃허브 주소</h2>
<blockquote>
<p><a href="https://github.com/kmg-git/Persistence_test.git">https://github.com/kmg-git/Persistence_test.git</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[팀 프로젝트 findex 리포트]]></title>
            <link>https://velog.io/@min_gyu/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-findex-%EB%A6%AC%ED%8F%AC%ED%8A%B8</link>
            <guid>https://velog.io/@min_gyu/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-findex-%EB%A6%AC%ED%8F%AC%ED%8A%B8</guid>
            <pubDate>Thu, 22 Oct 2026 16:51:28 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-개요">프로젝트 개요</h2>
<blockquote>
</blockquote>
<p>공공데이터포털의 지수시세정보 api로부터 지수들의 정보를 받아오고, 차트 분석을 보여준다.</p>
<hr>
<h2 id="담당-작업-성과">담당 작업, 성과</h2>
<h3 id="1jpa-엔티티-맵핑">1.JPA 엔티티 맵핑</h3>
<blockquote>
</blockquote>
<p>함께 제작한 ERD를 바탕으로 모든 엔티티에 대한 JPA 어노테이션 작업을 진행하고, 이후 초안에서 DB구조가 변경될 때 DDL 수정도 담당했다.
아래 사진은 ERD초안과 최종안이다.
<img src="https://velog.velcdn.com/images/min_gyu/post/718eacb6-c024-4ba2-8098-59f06340cb60/image.png" alt=""></p>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/min_gyu/post/fd76fb7d-a6e9-46da-9eb2-3718f6c66854/image.png" alt=""></p>
<blockquote>
</blockquote>
<p>category_name은 171개의 지수에서 4개를 돌려 쓰는 중복성이 높은 칼럼이다.
때문에 정합성을 높이기 위해 테이블을 분리했으나, 대부분의 상황에서 추가쿼리가 진행되었다.
이를 통해 왜 다치종속성(4NF), 조인종속성(5NF)를 보통 진행하지 않는지 간접적으로 경험할 수 있었다.</p>
<h3 id="2crud구현지수정보-지수데이터-연동이력-차트작성">2.CRUD구현(지수정보, 지수데이터, 연동이력), 차트작성</h3>
<blockquote>
</blockquote>
<p>엔티티 설명</p>
<ul>
<li>지수정보 : (지수의 이름, 기준지수) 같은 오늘도, 내일도 같은 값들을 저장한다.</li>
<li>지수데이터 : (오늘의 최고가, 오늘의 최저가, 시가총액) 같은 변화하는 값들을 저장한다.</li>
<li><h2 id="연동이력--외부ap에-요청했을-때-요청시간-요청ip-성공여부-같은-값들을-저장한다">연동이력 : 외부AP에 요청했을 때 (요청시간, 요청IP, 성공여부) 같은 값들을 저장한다.</h2>
<hr>
지수데이터를 이용해 1달, 3달, 1년의 차트를 제공한다.
<img src="https://velog.velcdn.com/images/min_gyu/post/b1da970a-71a2-4be8-97bd-0b925115ab4d/image.png" alt=""></li>
</ul>
<h3 id="3-method-flow-설계">3. Method Flow 설계</h3>
<blockquote>
</blockquote>
<p>개발초기에 Method 시그니처를 정하지 못하고, 팀원간 잦은 Dto변동 요청이 있었다.
이에 시그니처를 통일할 필요를 느껴 메서드 흐름도를 만들어 보자고 제안했다.
<img src="https://velog.velcdn.com/images/min_gyu/post/cc400042-be14-4beb-b26f-d646ef5f894b/image.png" alt=""></p>
<blockquote>
</blockquote>
<p>그림을 그리고 싶었지만, 무엇을 정리할지 명확히 하지 못 했으며, 일정 압박이 있어 빠르게 수정이 가능한 글로 작성하였다.
이 작업이후 ERD도 최종형에 가까워지고, Dto변동 요청도 빈도가 대폭 감소했다.</p>
<hr>
<h2 id="문제점-및-해결-과정">문제점 및 해결 과정</h2>
<blockquote>
</blockquote>
<p>API명세서를 보면서 ERD를 협의하는 과정에서 Request와 Response의 body 구성이 다른 경우 무엇을 따를지 혼란이 있었다.
아래는 프로젝트를 진행하면서 느낀점이다.</p>
<ul>
<li>Request는 비즈니스 로직을 진행하면서 다른 Entity의 값을 가져오는데 쓰거나, 외부API의 쿼리조건으로 들어가는 등, 원래와 전혀 다른 필드로 변할 수 있다.</li>
<li>Response는 Entity로부터 만들어지기 때문에 Entity가 Dto를 만들 때 필요한 필드를 반드시 가지고 있어야 한다.<blockquote>
</blockquote>
Request와 Response를 모두 분석하되, 데이터의 저장 요구사항과 엔티티 간 관계를 기준으로 ERD를 설계해야 한다.</li>
</ul>
<hr>
<h2 id="영속성-컨텍스트를-활용한-쿼리-횟수-줄이기-시도">영속성 컨텍스트를 활용한 쿼리 횟수 줄이기 시도</h2>
<blockquote>
<ul>
<li><strong>자세한 실험 과정과 SQL 실행 결과는 별도의 글로 정리했다.</strong>
<a href="https://velog.io/@min_gyu/%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8">https://velog.io/@min_gyu/%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</a></li>
</ul>
</blockquote>
<ul>
<li>외부 API로부터 171개 지수의 정보를 갱신하는 과정에서, 각 지수를 조회하기 위해 최대 171회의 SQL쿼리가 발생할 수 있었다.</li>
<li>이를 개선하기 위해 갱신 전에 findAll()로 엔티티를 영속성 컨텍스트에 올려두면, 이후 개별 조회에서 1차 캐시를 활용하여 SQL 실행 횟수를 줄일 수 있을 것이라 예상했다.</li>
<li>별도의 실험을 진행한 결과, findById()는 1차 캐시에 엔티티가 존재하면 SQL을 실행하지 않았지만, findByName()처럼 PK가 아닌 속성을 조건으로 조회하면 SQL이 실행되었다.</li>
<li>실제 프로젝트에서는 외부 API에서 제공하는 지수명을 기준으로 데이터를 조회해야 했기 때문에, 실험에서 기대했던 방식으로 쿼리 횟수를 줄이기는 어려웠다.</li>
<li>하지만 지연 로딩(LAZY)이 적용된 연관 엔티티를 조회하는 과정에서는 실험을 통해 확인한 최적화 방법을 적용하여 불필요한 쿼리 발생을 줄일 수 있었다.</li>
<li>이번 실험을 통해 1차 캐시가 PK를 기준으로 엔티티를 관리한다는 점과, 조회 조건에 따라 SQL 실행 여부가 달라진다는 점을 이해할 수 있었다.</li>
</ul>
<hr>
<h2 id="협업-과정에서-배운-것">협업 과정에서 배운 것</h2>
<blockquote>
</blockquote>
<p>실제 구현할 파트를 분배하고, 작업을 시작한 날 Controller를 담당한 팀원이 path, params, body 내용을 문서로 정리해 주었다.
혼자 작업할 때는 정리하는 습관이 없었는데, Service를 구현할 때 문서가 많은 도움이 되었다.
위의 경험이 있었기 때문에 다음 날 Method Flow 작성을 제안할 수 있었다.</p>
<hr>
<h2 id="향후-개선">향후 개선</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/min_gyu/post/ad5c9b91-6750-424c-9cc5-cbb6bb2f344b/image.png" alt=""></p>
</blockquote>
<p>프로젝트 마감 시점에 레일웨이 장애가 발생해 배포를 진행하지 못 했다.
레일웨이가 안정화 되면 진행해보면 좋을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[페이징 (page,slice), (오프셋, 커서) 작성중]]></title>
            <link>https://velog.io/@min_gyu/%ED%8E%98%EC%9D%B4%EC%A7%95-pageslice-%EC%98%A4%ED%94%84%EC%85%8B-%EC%BB%A4%EC%84%9C</link>
            <guid>https://velog.io/@min_gyu/%ED%8E%98%EC%9D%B4%EC%A7%95-pageslice-%EC%98%A4%ED%94%84%EC%85%8B-%EC%BB%A4%EC%84%9C</guid>
            <pubDate>Sun, 18 Oct 2026 09:04:12 GMT</pubDate>
            <description><![CDATA[<p>오프셋 페이지</p>
<p>오프셋은 몇개의 데이터를 건너뛸지 정하고, 데이터를 조회한다.
size가 10인 페이지의 n번째 페이지를 조회한다면 limit n offset (10 * (n-1)) 쿼리가 적용된다.
장점 : 특정 페이지를 바로 불러올 수 있다.
단점 : 페이지 검색 사이에 DB상 변경이 발생하면 특정 값이 2번 출력되거나, 스킵될 수 있다.
커서 페이지</p>
<p>커서페이지는 이전 페이지의 마지막 요소를 기억한다.
위치를 기억해 다음값을 찾아간다는 점은 Linked List가 떠올랐으나 자료구조에 직접 위치가 담기는 것과, 커서페이지를 제어할 Dto에 위치가 담기는 것은 큰 차이가 될 것이다.
오프셋에서 DB상 변경으로 중복, 또는 누락이 발생할 수 있다면 커서는 반대로 정확히 커서가 삭제되면 더 직격타를 받는게 아닌가? 의문이었다. 하지만 일정한 정렬 기준에 따라 커서가 없더라도 그보다 크거나 작은 값들을 순서대로 불러온다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트랜잭션의 격리수준]]></title>
            <link>https://velog.io/@min_gyu/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98%EC%9D%98-%EA%B2%A9%EB%A6%AC%EC%88%98%EC%A4%80</link>
            <guid>https://velog.io/@min_gyu/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98%EC%9D%98-%EA%B2%A9%EB%A6%AC%EC%88%98%EC%A4%80</guid>
            <pubDate>Sun, 11 Oct 2026 14:58:23 GMT</pubDate>
            <description><![CDATA[<h2 id="격리수준이란">격리수준이란?</h2>
<blockquote>
<p><strong>Isolation Leve</strong>은 여러 트랜잭션이 동시에 실행될 때, 서로의 변경 사항을 어느 정도까지 허용할 것인지 결정하는 기준이다.
격리 수준이 낮으면 동시성이 높아지고, 격리 수준이 높으면 일관성이 높아지는 Trade-off이다.</p>
</blockquote>
<h2 id="4가지-단계">4가지 단계</h2>
<blockquote>
</blockquote>
<ul>
<li><strong>READ UNCOMMITTED</strong>: 커밋되지 않은 데이터까지 읽을 수 있다.
발생할 문제 : Dirty Read, Non-repeatable Read, Phantom Read</li>
<li><strong>READ COMMITTED</strong>: 커밋된 데이터만 읽지만, 같은 행을 다시 읽으면 값이 달라질 수 있다.
발생할 문제 : Non-repeatable Read, Phantom Read</li>
<li><strong>REPEATABLE READ</strong>: 같은 행을 다시 읽었을 때 값의 일관성을 보장한다. 팬텀 리드의 실제 방지 여부는 구현에 따라 다르다.
발생할 문제 : Phantom Read</li>
<li><strong>SERIALIZABLE</strong>: 트랜잭션을 직렬 실행한 것과 같은 결과를 보장하는 가장 강한 격리 수준이다.
일반적인 격리 이상 현상 방지</li>
</ul>
<h2 id="문제-설명">문제 설명</h2>
<blockquote>
</blockquote>
<ul>
<li><strong>Dirty Read</strong> : 커밋되지 않은 다른 트랜잭션의 변경 데이터를 읽는 현상이다.
A트랜잭션이 재고를 변경한다.
B트랜잭션이 재고를 읽는다.
A트랜잭션이 롤백된다.
B트랜잭션은 잘못된 재고를 읽었다.</li>
</ul>
<hr>
<ul>
<li><strong>Non-repeatable Read</strong> : 같은 트랜잭션에서 조회값이 달라지는 현상이다.
A트랜잭션이 재고를 읽었다.(10개)
B트랜잭션이 재고를 변경했다.(12개)
A트랜잭션이 재고를 다시 읽었다.(12개)</li>
</ul>
<hr>
<ul>
<li><strong>Phantom Read</strong> : 같은 조건으로 조회했는데, 다른 트랜잭션의 INSERT 또는 DELETE 등으로 조회 결과에 포함되는 행의 집합이 달라지는 현상이다.
A트랜잭션이 재고가 5개 이하인 제품을 읽는다.
B트랜잭션이 재고를 리필한다.
A트랜잭션이 재고가 5개 이하인 제품을 다시 읽으면 목록이 바뀐다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA의 N+1 문제]]></title>
            <link>https://velog.io/@min_gyu/JPA-n1</link>
            <guid>https://velog.io/@min_gyu/JPA-n1</guid>
            <pubDate>Fri, 09 Oct 2026 19:32:17 GMT</pubDate>
            <description><![CDATA[<h2 id="n1은-무엇인가">N+1은 무엇인가?</h2>
<blockquote>
<p><strong>처음 실행한 SQL 외에 연관 데이터를 가져오기 위해 추가 SQL이 실행되는 문제이다.</strong></p>
</blockquote>
<ul>
<li>예를 들어 게시글 100개를 조회하고, 각 게시글의 작성자를 확인한다고 가정하자.
게시글 조회: SQL 1회
각 게시글의 작성자 조회: 최대 SQL 100회
총 SQL 실행 횟수: 최대 101회</li>
<li>실제로는 1+N에 더 가까운 느낌이다.</li>
</ul>
<h2 id="발생-원인">발생 원인</h2>
<blockquote>
<p><strong>연관 엔티티를 조회하는 시점 때문이다.</strong></p>
</blockquote>
<pre><code class="language-java">List&lt;Post&gt; posts = postRepository.findAll();
&gt;
for (Post post : posts) {
    System.out.println(post.getMember().getName());
}
// post의 member는 FetchType.LAZY</code></pre>
<ul>
<li>위 코드에서 처음 findAll은 member를 가져오지 않는다.</li>
<li>이후 for문이 getMember를 호출할 때 posts의 크기만큼 sql이 실행된다.
(영속성 컨테이너에 이미 로딩된 member가 있을 경우 sql의 실행 횟수가 줄어들 수 있다)</li>
</ul>
<h2 id="해결법">해결법</h2>
<h3 id="fetch-join">Fetch Join</h3>
<blockquote>
<p><strong>JPQL에서 연관 데이터를 한 번의 SQL로 가져온다.</strong></p>
</blockquote>
<pre><code class="language-java">@Query(&quot;&quot;&quot;
    SELECT p
    FROM Post p
    JOIN FETCH p.member
    &quot;&quot;&quot;)
List&lt;Post&gt; findAllPosts();</code></pre>
<h3 id="entitygraph">@EntityGraph</h3>
<blockquote>
<p><strong>어노테이션을 통해 함께 조회할 연관 엔티티를 선언한다.</strong></p>
</blockquote>
<pre><code class="language-java">@EntityGraph(attributePaths = {&quot;member&quot;})
List&lt;Post&gt; findAll();</code></pre>
<h3 id="dto">DTO</h3>
<blockquote>
<p><strong>DTO를 통해 실제로 필요한 데이터만 조회한다.</strong></p>
</blockquote>
<pre><code class="language-java">@Query(&quot;&quot;&quot;
    SELECT new com.example.dto.PostResponse(
        p.id,
        p.title,
        m.name
    )
    FROM Post p
    JOIN p.member m
    &quot;&quot;&quot;)
List&lt;PostResponse&gt; findPostResponses();</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[정규화]]></title>
            <link>https://velog.io/@min_gyu/%EC%A0%95%EA%B7%9C%ED%99%94</link>
            <guid>https://velog.io/@min_gyu/%EC%A0%95%EA%B7%9C%ED%99%94</guid>
            <pubDate>Mon, 05 Oct 2026 20:10:02 GMT</pubDate>
            <description><![CDATA[<h2 id="정규화란">정규화란?</h2>
<p>*<em>데이터의 중복을 최소화하고, 무결성을 유지하기 위해 테이블을 분해하는 방법이다. *</em>
하지만 정규화 단계가 올라갈수록 JOIN연산이 증가하고 조회가 복잡해져 대부분의 경우 3NF까지만 진행하고 필요할 경우 BCNF를 고려한다.</p>
<h2 id="6가지-단계원부이결다조">6가지 단계(원부이결다조)</h2>
<h3 id="1nf">1NF</h3>
<blockquote>
</blockquote>
<p><strong>원자값</strong>으로 구성 : 모든 칼럼이 더 이상 분해할 수 없는 원자값을 가짐</p>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th align="left">이름</th>
<th align="left">취미</th>
</tr>
</thead>
<tbody><tr>
<td align="left">김민규</td>
<td align="left">음악, 게임</td>
</tr>
</tbody></table>
<blockquote>
</blockquote>
<p>위 예시에서 김민규의 취미에 2가지 값이 들어 있음으로 1NF에 위반된다.</p>
<h3 id="2nf">2NF</h3>
<blockquote>
</blockquote>
<p><strong>부분함수</strong> 종속 제거 : 값은 기본 키 전체에 종속되어야 한다.</p>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th align="left">강의</th>
<th align="left">학번</th>
<th align="left">교수명</th>
<th align="left">학생명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">자바</td>
<td align="left">1234</td>
<td align="left">박명운</td>
<td align="left">김민규</td>
</tr>
</tbody></table>
<blockquote>
</blockquote>
<p>(강의 + 학번) 조합을 pk로 쓰는 수강관리 테이블이 있다고 가정해보자, 위의 구조에서 교수명은 강의에, 학생명은 학번에만 영향을 받아 결정된다.</p>
<h3 id="3nf">3NF</h3>
<blockquote>
</blockquote>
<p><strong>이행함수</strong> 종속 제거 : 값은 기본 키에 종속되어야 한다.</p>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th align="left">사번(PK)</th>
<th align="left">부서</th>
<th align="left">근무지</th>
</tr>
</thead>
<tbody><tr>
<td align="left">1981</td>
<td align="left">DB</td>
<td align="left">2층 DB부서</td>
</tr>
<tr>
<td align="left">위의 예시에서 pk가 아닌 부서를 알면 근무지를 알 수 있다.</td>
<td align="left"></td>
<td align="left"></td>
</tr>
</tbody></table>
<h3 id="bcnf">BCNF</h3>
<blockquote>
</blockquote>
<p><strong>결정함수</strong> 종속 제거 : 모든 결정자가 후보키여야 한다.</p>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th align="left">환자</th>
<th align="left">진료과</th>
<th align="left">담당의</th>
</tr>
</thead>
<tbody><tr>
<td align="left">김민규</td>
<td align="left">외과</td>
<td align="left">유성원</td>
</tr>
<tr>
<td align="left">(환자 + 진료과) 조합을 pk로 쓰는 병원의 테이블이 있다고 가정해보자, 유성원은 외과에 소속된 의사일테니 담당의는 진료과를 결정하는 결정자이다. 하지만 유성원이 담당하는 환자는 김민규 1명이 아닐테니 모든 환자 칼럼을 결정할 수 없다.</td>
<td align="left"></td>
<td align="left"></td>
</tr>
</tbody></table>
<h3 id="4nf">4NF</h3>
<blockquote>
</blockquote>
<p><strong>다치</strong> 종속 제거 : 다치 종속은 독립적인 값을 하나의 테이블에 담으려고 할 경우 발생한다.</p>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th align="left">이름</th>
<th align="left">취미</th>
<th align="left">동아리</th>
</tr>
</thead>
<tbody><tr>
<td align="left">김민규</td>
<td align="left">음악</td>
<td align="left">악기</td>
</tr>
<tr>
<td align="left">김민규</td>
<td align="left">음악</td>
<td align="left">로봇</td>
</tr>
<tr>
<td align="left">김민규</td>
<td align="left">게임</td>
<td align="left">악기</td>
</tr>
<tr>
<td align="left">김민규</td>
<td align="left">게임</td>
<td align="left">로봇</td>
</tr>
</tbody></table>
<blockquote>
</blockquote>
<p>이런 경우 (이름,취미),(이름,동아리) 2개의 테이블로 분리해야 한다.</p>
<h3 id="5nf">5NF</h3>
<blockquote>
</blockquote>
<p><strong>조인</strong> 종속 제거 : 분해한 테이블을 다시 JOIN할 때 무손실을 보장하는 상태이다.</p>
<h2 id="반정규화">반정규화</h2>
<blockquote>
<p><strong>DB의 조회 성능을 높이기 위해 정규화된 모델에 중복을 허용하거나 테이블을 통합, 분리하는 기법이다.</strong></p>
</blockquote>
<p>장점</p>
<ul>
<li>JOIN 을 줄여 조회 속도를 향상시킨다.<blockquote>
<hr>
<p>단점</p>
</blockquote>
</li>
<li>중복 저장으로 인해 무결성이 깨질 위험이 발생.</li>
<li>조회를 제외한 모든 기능(입력,수정,삭제)가 느려진다.</li>
</ul>
<hr>
<p>방법</p>
<ul>
<li>테이블 병합/분할 : 자주 함께 쓰이는 테이블을 합치거나, 큰 테이블을 나누어 관리한다.</li>
<li>칼럼 추가 : 조회를 빠르게 하려고 특정 컬럼을 복사해 놓는다.</li>
<li>관계 추가 : : 멀리 떨어진 테이블 간에 지름길을 만들어 놓는다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL]]></title>
            <link>https://velog.io/@min_gyu/SQL</link>
            <guid>https://velog.io/@min_gyu/SQL</guid>
            <pubDate>Fri, 02 Oct 2026 01:35:11 GMT</pubDate>
            <description><![CDATA[<h2 id="ddldata-definition-language">DDL(Data Definition Language)</h2>
<h3 id="ddl이란">DDL이란?</h3>
<blockquote>
<p><strong>테이블, 뷰같은 데이터를 정의한다.(ex : 제약조건)</strong></p>
</blockquote>
<ul>
<li>CREATE : 새로운 DB 객체를 생성한다.</li>
<li>ALTER : 기존 DB 객체의 구조를 변경한다.</li>
<li>DROP : DB 객체를 삭제한다.</li>
<li>TRUNCATE  : <strong>테이블</strong>의 모든 정보를 삭제하고 초기상태로 되돌린다.
view는 데이터가 없는 가상 테이블이다. view를 비우고 싶다면 원본 테이블에 TRUNCATE를 진행하거나, DML인 DELETE 통해 진행하여야 한다.</li>
</ul>
<hr>
<h2 id="dmldata-manipulation-language">DML(Data Manipulation Language)</h2>
<h3 id="dml이란">DML이란?</h3>
<blockquote>
<p><strong>데이터베이스 속의 데이터에 대해 CRUD를 진행하는 명령어들이다.</strong></p>
</blockquote>
<ul>
<li>INSERT : 새로운 데이터를 저장한다.</li>
<li>SELECT : 데이터를 조회한다.</li>
<li>UPDATE : 기존 데이터를 수정한다.</li>
<li>DELETE : 기존 데이터를 삭제한다.</li>
</ul>
<hr>
<h2 id="dcldata-control-language">DCL(Data Control Language)</h2>
<h3 id="dcl이란">DCL이란?</h3>
<blockquote>
<p><strong>DB에 접속할 사용자들의 정보, 권한을 관리한다</strong>.</p>
</blockquote>
<ul>
<li>GRANT : 사용자에게 권한을 부여한다.</li>
<li>REVOKE : 부여했던 권한을 회수한다.<pre><code class="language-sql">CREATE USER KMG IDENTIFIED BY 1234;
GRANT DBA TO KMG;</code></pre>
위 예시코드에서 CREATE는 관점에 따라 DCL로 분류될 때도 DDL로 분류될 때도 있다.
KMG라는 유저에게 DBA라는 권한을 부여했다.(DBA는 오라클SQL의 최고권한으로 다른 SQL에는 없을 수 있다.)</li>
</ul>
<hr>
<h2 id="tcltransaction-control-language">TCL(Transaction Control Language)</h2>
<h3 id="tcl이란">TCL이란?</h3>
<blockquote>
<p><strong>작업 단위를 트랜잭션으로 묶어서 원자성을 부여한다.</strong>
    원자성은 작업 내용이 모두 적용되거나, 모두 적용되지 않음을 뜻한다.</p>
</blockquote>
<ul>
<li>COMMIT : 작업 내용을 반영한다.</li>
<li>ROLLBACK : 작업을 취소한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[왜 SOAP환경에서 REST로 발전하였을까?]]></title>
            <link>https://velog.io/@min_gyu/%EC%99%9C-SOAP%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-REST%EB%A1%9C-%EB%B0%9C%EC%A0%84%ED%95%98%EC%98%80%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@min_gyu/%EC%99%9C-SOAP%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-REST%EB%A1%9C-%EB%B0%9C%EC%A0%84%ED%95%98%EC%98%80%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Sun, 27 Sep 2026 17:26:34 GMT</pubDate>
            <description><![CDATA[<h2 id="soap">SOAP</h2>
<h3 id="soap란">SOAP란?</h3>
<blockquote>
</blockquote>
<ul>
<li>SOAP은 XML을 기반으로 메시지를 교환하는 프로토콜로, 기업 간 시스템 통합이나 복잡한 분산 시스템에서 널리 사용되었다.</li>
<li>DTD를 지원하긴 하지만 스키마(XSD)의 사용을 강력하게 권장했다.</li>
</ul>
<h3 id="soap의-단점">SOAP의 단점</h3>
<blockquote>
<p><strong>SOAP의 단점 중 많은 부분은 스키마의 단점에서 왔다.</strong></p>
</blockquote>
<ul>
<li>강제된 스키마 : 명확한 스키마 구조를 설계해야 하기에 소규모 프로젝트에선 부담스럽다. </li>
<li>높은 파일 용량 : XML의 여러 태그와 구조는 저장 데이터를 무겁게 만든다.</li>
<li>복잡한 구조 : XSD의 엄격한 구조는 배우기 어렵고, 숙련자도 유지보수가 어렵다.</li>
</ul>
<hr>
<h2 id="rest">REST</h2>
<h3 id="rest란">REST란?</h3>
<blockquote>
</blockquote>
<ul>
<li>REST는 HTTP의 기본 기능을 활용해 리소스를 url로 식별하고, HTTP 메서드를 통해 처리한다.</li>
<li>특정한 형식이나 프로토콜을 강제하지 않으며, JSON의 사용률이 높다.</li>
<li>스마트폰의 등장으로 간결한 통신에 대한 수요가 증가했고, REST가 이해 적합했다.</li>
</ul>
<h3 id="rest의-장점">REST의 장점</h3>
<blockquote>
</blockquote>
<ul>
<li>HTTP 메서드 : (GET,POST,등등)의 메서드를 사용하여 직관적이고, 예측 가능하다.</li>
<li>높은 개발 생산성: 일반적으로 JSON을 사용하며, 복잡한 메시지 규격이 없어 비교적 쉽게 구현할 수 있다.</li>
<li>효율적인 데이터 전송: JSON은 일반적으로 XML보다 데이터가 간결하여 용량을 줄일 수 있다.</li>
<li>무상태성: 서버가 클라이언트의 상태를 기억하지 않아, 수평 확장에 유리.</li>
<li>캐싱 지원: HTTP의 캐싱 기능을 활용하여 반복적인 요청에 대한 속도를 높이고, 서버의 부하를 주일 수 있다.(모든 메서드가 캐싱 되는 것은 아님)</li>
</ul>
<h3 id="soap가-rest보다-유리한-점">SOAP가 REST보다 유리한 점</h3>
<blockquote>
</blockquote>
<ul>
<li>표준화된 통신: 엄격한 규칙을 통해 서로 다른 시스템 간의 통신을 표준화함.</li>
<li>다양한 전송 프로토콜: REST는 HTTP에 의존하지만 SOAP는 다른 전송프로토콜도 사용할 수 있음.</li>
<li>보안 및 신뢰성: 높은 수준의 보안(Web Services Security)과 신뢰성(WS-ReliableMessaging, WS-AtomicTransaction)을 제공.
<img src="https://velog.velcdn.com/images/min_gyu/post/5abbd66d-ff9d-42d9-bfa6-dbbf560a2f9c/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring에서 AOP가 필요한 이유]]></title>
            <link>https://velog.io/@min_gyu/Spring%EC%97%90%EC%84%9C-AOP%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@min_gyu/Spring%EC%97%90%EC%84%9C-AOP%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Sun, 20 Sep 2026 17:22:35 GMT</pubDate>
            <description><![CDATA[<h2 id="aop">AOP</h2>
<h3 id="aop란">AOP란?</h3>
<blockquote>
</blockquote>
<ul>
<li>Aspect Oriented Programming, 관점 지향 프로그래밍</li>
<li>여러 클래스나 메서드가 공통적으로 필요하는 기능을 핵심 비즈니스 로직과 분리하는 방식이다.</li>
</ul>
<h3 id="왜-필요한가">왜 필요한가?</h3>
<blockquote>
</blockquote>
<ul>
<li>AOP를 사용하지 않으면 여러 메서드에 동일한 코드가 반복될 수 있다.</li>
<li>특정 기능을 필요로 하는 클래스 10개에서 각각 구현한다면 정책이 변경될 때 10개의 클래스에서 수정 작업도 10번 진행해야 한다.</li>
<li>AOP의 주요 목적은 코드 중복 제거, 관심사의 분리, 유지보수성 향상이다.</li>
</ul>
<h3 id="spring-aop의-주요-용어">Spring AOP의 주요 용어</h3>
<blockquote>
</blockquote>
<p><strong>스프링에선 @AspectJ 형식의 AOP를 지원한다.</strong>
@AspectJ은 자바5(2004년) 처음 등장했다.</p>
<table>
<thead>
<tr>
<th>용어</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Aspect</strong></td>
<td>공통 기능을 모아 놓은 모듈</td>
</tr>
<tr>
<td><strong>Advice</strong></td>
<td>실제로 수행할 공통 기능</td>
</tr>
<tr>
<td><strong>Join Point</strong></td>
<td>Advice가 적용될 수 있는 지점</td>
</tr>
<tr>
<td><strong>Pointcut</strong></td>
<td>어떤 Join Point에 Advice를 적용할지 지정하는 조건</td>
</tr>
<tr>
<td><strong>Target</strong></td>
<td>AOP가 적용되는 실제 객체</td>
</tr>
<tr>
<td><strong>Proxy</strong></td>
<td>Target을 감싸 AOP 기능을 추가하는 객체</td>
</tr>
</tbody></table>
<h3 id="실제-활용-사례">실제 활용 사례</h3>
<h4 id="1-트랜잭션-처리">1. 트랜잭션 처리</h4>
<blockquote>
</blockquote>
<pre><code class="language-java">// 송금
@Transactional
public void transfer(String fromAccount, String toAccount, int value) {
}</code></pre>
<p>스프링에서는 @Transactional을 통해 트랜잭션을 처리한다.</p>
<h4 id="2-실행시간-로깅">2. 실행시간 로깅</h4>
<blockquote>
</blockquote>
<pre><code class="language-java">@Aspect
@Component
public class PerformanceAspect {
    @Around(&quot;execution(* com.example.service..*(..))&quot;)
    public Object measureTime(ProceedingJoinPoint joinPoint)
            throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        System.out.println(
            joinPoint.getSignature().getName()
            + &quot; 실행 시간: &quot;
            + (end - start) + &quot;ms&quot;
        );
        return result;
    }
}</code></pre>
<ul>
<li>@Aspect : 이 클래스가 AOP Aspect임을 정의</li>
<li>@Around : 어떤 메서드에 어떤 방식으로 공통 기능을 적용할지 정의
예시코드에서는 com.example.service에 속한 모든 클래스에 적용함<pre><code>execution(
  *                         ← 반환 타입: 모든 타입
  com.example.service..*   ← 해당 패키지와 하위 패키지의 모든 클래스
  (..)                      ← 모든 메서드, 모든 파라미터
)</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring MVC, @Controller와 @RestController의 차이]]></title>
            <link>https://velog.io/@min_gyu/Spring-MVC-Controller%EC%99%80-RestController%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@min_gyu/Spring-MVC-Controller%EC%99%80-RestController%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Sun, 20 Sep 2026 16:58:12 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-mvc에서-클라이언트의-요청-처리-흐름">Spring MVC에서 클라이언트의 요청 처리 흐름</h2>
<p><strong>클라이언트 → 디스패처서블릿(DispatcherServlet) → Controller → Service → Repository → Controller → 디스패처서블릿(DispatcherServlet) → 클라이언트</strong></p>
<blockquote>
</blockquote>
<ul>
<li>클라이언트가 HTTP 요청을 보낸다.</li>
<li>DispatcherServlet이 요청을 받는다.</li>
<li>DispatcherServlet은 HandlerMapping을 통해 적절한 Controller를 찾는다.</li>
<li>Controller는 필요한 경우 Service와 Repository를 호출하여 비즈니스 로직과 데이터 처리를 수행한다.</li>
<li>처리 결과를 Controller가 반환한다.</li>
<li>반환 방식에 따라 View나 HTTP를 클라이언트에 반환한다.</li>
</ul>
<h2 id="controller와-restcontroller의-차이">@Controller와 @RestController의 차이</h2>
<h3 id="controller">@Controller</h3>
<p><strong>주로 View를 반환하는 MVC에서 사용한다.</strong></p>
<blockquote>
</blockquote>
<pre><code class="language-java">@Controller 
public class UserController { 
    @GetMapping(&quot;/users&quot;) 
    public String users(Model model) { 
    model.addAttribute(&quot;users&quot;, userService.findAll()); return &quot;users&quot;; 
    } 
}    </code></pre>
<ul>
<li>위 코드처럼 model을 통해 view를 렌더링하여 클라이언트에 응답한다.</li>
<li>클라이언트 요청 → HTTP 요청 → DispatcherServlet → HandlerMapping → Controller 
→ View 이름 반환 → DispatcherServlet → ViewResolver → View 렌더링 → HTML 응답 → 클라이언트</li>
</ul>
<h3 id="restcontroller">@RestController</h3>
<p><strong>주로 HTTP 응답을 반환할 때 사용한다.(Ex : REST API)</strong></p>
<blockquote>
</blockquote>
<pre><code class="language-java">@RestController 
public class UserController { 
    @GetMapping(&quot;/users&quot;) 
    public ResponseEntity&lt;List&lt;User&gt;&gt; users() { 
    List&lt;User&gt; users = userService.findAll(); 
    return ResponseEntity.ok(users); 
    } 
}</code></pre>
<ul>
<li>위 코드처럼 주로 ResponseEntity를 통해 HTTP로 반환한다.</li>
<li>ResponseEntity는 응답 전체를 표현하는 객체로 데이터외에 HTTP 상태 코드, 헤더도 동봉가능하다.</li>
<li>클라이언트 요청 → HTTP 요청 → DispatcherServlet → HandlerMapping → Controller
→ ResponseEntity 반환 → HttpMessageConverter → JSON 변환 →  클라이언트</li>
</ul>
<p> <strong>View 이름 반환, ResponseEntity 반환 전단계의 Controller에서 Controller &gt; Service &gt; Repository &gt; Controller가 될 수 있으나 항상 서비스와 레포지토리를 사용하는 건 아님</strong></p>
<h3 id="controller-restcontroller-두-방식의-차이">@Controller, @RestController 두 방식의 차이</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>@Controller</th>
<th>@RestController</th>
</tr>
</thead>
<tbody><tr>
<td>주요 용도</td>
<td>MVC 웹 페이지</td>
<td>REST API</td>
</tr>
<tr>
<td>반환값의 기본 의미</td>
<td>View 이름</td>
<td>응답 Body 데이터</td>
</tr>
<tr>
<td>ViewResolver</td>
<td>사용 가능</td>
<td>일반적으로 사용하지 않음</td>
</tr>
<tr>
<td>@ResponseBody</td>
<td>별도로 사용 가능</td>
<td>기본적으로 적용됨</td>
</tr>
<tr>
<td>주된 응답 형태</td>
<td>HTML 등 View</td>
<td>JSON, XML 등의 데이터</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[미션4 포스트맨]]></title>
            <link>https://velog.io/@min_gyu/postman%EC%9D%84-%ED%86%B5%ED%95%9C-%EA%B2%80%EC%A6%9D-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@min_gyu/postman%EC%9D%84-%ED%86%B5%ED%95%9C-%EA%B2%80%EC%A6%9D-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Sat, 19 Sep 2026 16:57:08 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>유저 생성
<img src="https://velog.velcdn.com/images/min_gyu/post/93e1e2b6-7487-4eb1-8552-9cad512c26f8/image.png" alt=""></p>
</li>
<li><p>전체 유저 조회
<img src="https://velog.velcdn.com/images/min_gyu/post/024eb3d9-daaa-41fb-a28b-870fd0bf7313/image.png" alt=""></p>
</li>
<li><p>유저 수정
<img src="https://velog.velcdn.com/images/min_gyu/post/4767067a-a8a9-4702-af52-bfe7570144ba/image.png" alt=""></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/min_gyu/post/fdd1107b-c144-415e-ba41-e916b43a98ad/image.png" alt=""></p>
<ul>
<li><p>온라인 상태 업데이트
<img src="https://velog.velcdn.com/images/min_gyu/post/efc3d442-1f9a-48e0-b6ea-3bd89e69cfbb/image.png" alt=""></p>
</li>
<li><p>유저 삭제
<img src="https://velog.velcdn.com/images/min_gyu/post/85179d6e-112c-4650-bf28-c9f63b1ef99b/image.png" alt=""></p>
</li>
<li><p>로그인
<img src="https://velog.velcdn.com/images/min_gyu/post/81105eba-c957-40dd-ad82-8b2168ed0a8b/image.png" alt=""></p>
</li>
<li><p>채널 생성(퍼블릭)
<img src="https://velog.velcdn.com/images/min_gyu/post/09e1ba48-9f4a-4550-8cae-074796c58200/image.png" alt=""></p>
</li>
<li><p>채널 생성(프라이빗)
<img src="https://velog.velcdn.com/images/min_gyu/post/105ca66d-fb54-4c41-b53f-5a97e10033ce/image.png" alt=""></p>
</li>
<li><p>채널 수정
<img src="https://velog.velcdn.com/images/min_gyu/post/4edbe61e-8460-4278-9f33-c0d6e7376eb5/image.png" alt=""></p>
</li>
<li><p>채널 조회(유저id 기준으로 유저가 속한 채널들 조회)
<img src="https://velog.velcdn.com/images/min_gyu/post/c1e5220b-60ee-4231-b010-473bfaf39e96/image.png" alt=""></p>
</li>
<li><p>채널 삭제
<img src="https://velog.velcdn.com/images/min_gyu/post/c88b9bf7-e843-4eb3-89e3-550ac38b8e72/image.png" alt=""></p>
</li>
<li><p>메세지 생성
<img src="https://velog.velcdn.com/images/min_gyu/post/18b932af-d948-4942-ae0b-e7a46894fbb2/image.png" alt=""></p>
</li>
<li><p>메세지 조회(채널별)
<img src="https://velog.velcdn.com/images/min_gyu/post/e03dad05-8039-46ef-8d39-95643f17f2ac/image.png" alt=""></p>
</li>
<li><p>메세지 수정
<img src="https://velog.velcdn.com/images/min_gyu/post/36122167-1072-49a3-bc39-af2575144a26/image.png" alt=""></p>
</li>
<li><p>메세지 삭제
<img src="https://velog.velcdn.com/images/min_gyu/post/eb6f39ad-a799-42ef-b0f1-0c872e227761/image.png" alt=""></p>
</li>
<li><p>리드 스테이터스 생성
<img src="https://velog.velcdn.com/images/min_gyu/post/616cb5bc-b201-4c8d-a46e-dea5e81d1aa0/image.png" alt=""></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/min_gyu/post/1ea0289b-e762-4053-940a-0b147363f0a1/image.png" alt=""></p>
<ul>
<li><p>리드 스테이터스 업데이트
<img src="https://velog.velcdn.com/images/min_gyu/post/508d0484-3c95-419d-96aa-2123c1cac93d/image.png" alt="">
<img src="https://velog.velcdn.com/images/min_gyu/post/aa43b230-be43-4b24-8e8b-ac8b60d179f3/image.png" alt=""></p>
</li>
<li><p>바이너리컨텐츠 조회
<img src="https://velog.velcdn.com/images/min_gyu/post/d3f44574-1ff4-4d1c-8c97-d98fd3d86be7/image.png" alt="">
<img src="https://velog.velcdn.com/images/min_gyu/post/f8d615b1-1870-492f-9ae2-aa3c5280ee80/image.png" alt=""></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Bean]]></title>
            <link>https://velog.io/@min_gyu/Spring-Bean</link>
            <guid>https://velog.io/@min_gyu/Spring-Bean</guid>
            <pubDate>Sun, 13 Sep 2026 17:14:32 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-boot에서-사용되는-다양한-bean-등록-방법들에-대해-설명하고-각각의-장단점을-비교하세요">Spring Boot에서 사용되는 다양한 Bean 등록 방법들에 대해 설명하고, 각각의 장단점을 비교하세요.</h2>
<h3 id="component을-통한-bean-등록">@Component을 통한 Bean 등록</h3>
<pre><code class="language-java">@Component 
public class MyComponent { }</code></pre>
<blockquote>
<p>@Component는 Spring이 해당 클래스를 Bean으로 등록하도록 지정하는 annotation입니다.
Spring Boot의 컴포넌트 스캔 대상 패키지에 포함되어 있다면 애플리케이션 시작 시 Bean으로 등록됩니다.</p>
</blockquote>
<p><strong>@Service, @Repository, @Controller</strong></p>
<pre><code class="language-java">@Service
public class MemberService {
}
@Repository
public class MemberRepository {
}
@RestController
public class MemberController {
}</code></pre>
<blockquote>
<p>Spring에서는 역할에 따라 @Service, @Repository, @Controller와 같은 stereotype annotation을 제공합니다.
@Component 기반의 컴포넌트 스캔을 통해 Bean으로 등록됩니다.</p>
</blockquote>
<h3 id="bean을-이용한-bean-등록">@Bean을 이용한 Bean 등록</h3>
<pre><code class="language-java">@Configuration 
public class AppConfig { 
    @Bean 
    public MyComponent myComponent() { 
        return new MyComponent(); 
    } 
}</code></pre>
<blockquote>
<p>@Bean은 메서드가 반환하는 객체를 Spring Bean으로 등록하는 annotation입니다.
일반적으로 @Configuration 클래스 내부에서 사용합니다.
직접 수정할 수 없는 외부 라이브러리의 객체를 등록할 때 특히 유용합니다.</p>
</blockquote>
<h3 id="componentscan을-이용한-bean-등록">@ComponentScan을 이용한 Bean 등록</h3>
<pre><code class="language-java">@Configuration 
@ComponentScan(&quot;com.example&quot;) 
public class AppConfig { }</code></pre>
<blockquote>
<p>@ComponentScan은 지정된 패키지를 탐색하여 @Component, @Service, @Repository, @Controller 등이 붙은 클래스를 Bean으로 등록하도록 합니다.
일반적인 Spring Boot 프로젝트에서는 별도로 @ComponentScan을 선언하지 않아도 애플리케이션 클래스의 패키지를 기준으로 컴포넌트 스캔이 이루어집니다.</p>
</blockquote>
<h3 id="import를-이용한-bean-등록">@Import를 이용한 Bean 등록</h3>
<pre><code class="language-java">@Component
public class MyComponent {
}
////////////////////////////////////////
@Configuration
@Import(MyComponent.class)
public class AppConfig {
}</code></pre>
<blockquote>
<p>@Import는 특정 설정 클래스나 컴포넌트를 Spring 컨테이너에 등록할 때 사용하는 annotation입니다.</p>
</blockquote>
<h3 id="importselector">ImportSelector</h3>
<pre><code class="language-java">public class MyImportSelector implements ImportSelector { 
    @Override 
    public String[] selectImports( AnnotationMetadata importingClassMetadata ) { 
        return new String[] { &quot;com.example.MyComponent&quot; };
    } 
}</code></pre>
<blockquote>
<p>ImportSelector는 조건이나 설정에 따라 Import할 클래스를 동적으로 선택할 수 있습니다.</p>
</blockquote>
<h3 id="importbeandefinitionregistrar">ImportBeanDefinitionRegistrar</h3>
<pre><code class="language-java">public class MyRegistrar
        implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
    }
}</code></pre>
<blockquote>
<p>ImportBeanDefinitionRegistrar는 BeanDefinition을 직접 등록할 수 있는 확장 기능입니다.
BeanDefinition은 Spring 컨테이너가 Bean을 생성하고 관리하기 위해 사용하는 메타데이터입니다.
이 기능은 Spring Boot 자동 설정이나 프레임워크 수준의 기능을 구현할 때 활용할 수 있습니다.</p>
</blockquote>
<h2 id="장단점-정리표">장단점 정리표</h2>
<h2 id="11-bean-등록-방법-비교">11. Bean 등록 방법 비교</h2>
<table>
<thead>
<tr>
<th>등록 방법</th>
<th>적합한 상황</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><code>@Component</code></td>
<td>직접 작성한 일반 컴포넌트</td>
<td>간결하고 직관적</td>
<td>스캔 범위에 의존</td>
</tr>
<tr>
<td><code>@Service</code> 등</td>
<td>역할이 명확한 애플리케이션 계층</td>
<td>의미가 명확함</td>
<td>외부 클래스에는 적용 어려움</td>
</tr>
<tr>
<td><code>@Bean</code></td>
<td>외부 라이브러리, 커스텀 객체 생성</td>
<td>생성 과정 제어 가능</td>
<td>설정 코드 필요</td>
</tr>
<tr>
<td><code>@ComponentScan</code></td>
<td>패키지 단위 자동 등록</td>
<td>반복 작업 감소</td>
<td>스캔 범위 관리 필요</td>
</tr>
<tr>
<td><code>@Import</code></td>
<td>특정 설정 또는 컴포넌트 명시적 등록</td>
<td>명시적이고 모듈화 가능</td>
<td>Import 구조 복잡 가능</td>
</tr>
<tr>
<td><code>ImportSelector</code></td>
<td>동적 설정 선택</td>
<td>조건부 등록 가능</td>
<td>구현 난도 높음</td>
</tr>
<tr>
<td><code>ImportBeanDefinitionRegistrar</code></td>
<td>프레임워크 수준 BeanDefinition 등록</td>
<td>고급 확장 가능</td>
<td>유지보수 난도 높음</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 서버(Web Server)와 WAS(Web Application Server)의 차이]]></title>
            <link>https://velog.io/@min_gyu/%EC%9B%B9-%EC%84%9C%EB%B2%84Web-Server%EC%99%80-WASWeb-Application-Server%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@min_gyu/%EC%9B%B9-%EC%84%9C%EB%B2%84Web-Server%EC%99%80-WASWeb-Application-Server%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Sun, 13 Sep 2026 16:51:20 GMT</pubDate>
            <description><![CDATA[<h2 id="웹-서버web-server와-wasweb-application-server의-차이">웹 서버(Web Server)와 WAS(Web Application Server)의 차이</h2>
<h3 id="1웹-서버">1.웹 서버</h3>
<blockquote>
<ul>
<li>웹 서버(Web Server)는 클라이언트의 HTTP 요청을 받아 정적 리소스를 제공하고, 필요한 경우 애플리케이션 서버로 요청을 전달하는 역할을 합니다.</li>
</ul>
</blockquote>
<ul>
<li>대표적인 웹 서버는 Nginx, Apache HTTP Server 등이 있습니다.</li>
<li>처리 대상 : HTML, CSS, JavaScript, 이미지 ,etc</li>
</ul>
<h3 id="2-was">2. WAS</h3>
<blockquote>
<ul>
<li>WAS(Web Application Server)는 웹 애플리케이션의 비즈니스 로직을 실행하고 동적인 응답을 생성하는 서버입니다.</li>
</ul>
</blockquote>
<ul>
<li>대표적으로 Jetty, Undertow 등이 있습니다.</li>
<li>처리 대상 : 동적 요청, 비즈니스 로직, DB 세션 및 트랜잭션 , etc</li>
</ul>
<h2 id="spring-boot의-톰캣tomcat은">Spring Boot의 톰캣(Tomcat)은?</h2>
<blockquote>
<p><strong>Tomcat은 Java 기반 웹 애플리케이션을 실행하기 위한 서블릿 컨테이너입니다.</strong></p>
</blockquote>
<ul>
<li>서블릿 컨테이너는 서블릿의 생명주기를 관리하고, HTTP 요청을 서블릿에 전달하며, 서블릿이 생성한 응답을 클라이언트에게 반환합니다.</li>
<li>Tomcat은 HTTP 커넥터를 통해 네트워크 요청을 받고, 서블릿 컨테이너를 통해 웹 애플리케이션을 실행합니다.</li>
<li>따라서 Tomcat은 일반적인 분류에서 WAS로 보는 것이 적절합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Stream API (map, flatMap)]]></title>
            <link>https://velog.io/@min_gyu/Stream-API-map-flatMap</link>
            <guid>https://velog.io/@min_gyu/Stream-API-map-flatMap</guid>
            <pubDate>Sun, 13 Sep 2026 13:57:40 GMT</pubDate>
            <description><![CDATA[<h2 id="🇶-stream-api의-map과-flatmap의-차이점을-설명하고-각각의-활용-사례를-예시-코드와-함께-설명해주세요">🇶 Stream API의 map과 flatMap의 차이점을 설명하고, 각각의 활용 사례를 예시 코드와 함께 설명해주세요.</h2>
<h3 id="1-map">1. map</h3>
<blockquote>
<p>Stream의 요소를 1대1로 변환하는 연산이다.
  아래 코드는 1~10으로 구성된 list 제곱을 구해 list2에 반환하는 코드이다.</p>
</blockquote>
<pre><code class="language-java">  public static void main(String[] args) {
        List&lt;Integer&gt; list = IntStream.rangeClosed(1, 10)
                .boxed()
                .toList();

        List&lt;Integer&gt; list2 =
                list.stream().map(n -&gt;(int) Math.pow(n,2)).collect(Collectors.toList());

    }</code></pre>
<hr>
<h3 id="2-flatmap">2. flatMap</h3>
<blockquote>
<p>JCF의 차원을 한단계 낮추어, 자료구조를 단순화한다.
  아래 코드에서는 map(학과, map&lt;이름,번호&gt;) 구조에서 map&lt;학과,List&lt;이름,번호 번갈아&gt;&gt;
구조로 바꾸었다.</p>
</blockquote>
<pre><code class="language-java">  public class Main {
    public static void main(String[] args) {
        Map&lt;String, Map&lt;String, String&gt;&gt; departments = Map.of(
                &quot;컴퓨터공학과&quot;, Map.of(
                        &quot;김민규&quot;, &quot;010-1111-1111&quot;,
                        &quot;김명운&quot;, &quot;010-2222-2222&quot;,
                        &quot;박도현&quot;, &quot;010-3333-3333&quot;
                ),
                &quot;전자공학과&quot;, Map.of(
                        &quot;신정우&quot;, &quot;010-4444-4444&quot;,
                        &quot;이태호&quot;, &quot;010-5555-5555&quot;
                )
        );

        Map&lt;String, List&lt;String&gt;&gt; result =
                departments.entrySet().stream()
                        .collect(Collectors.toMap(
                                Map.Entry::getKey,
                                entry -&gt; entry.getValue().entrySet().stream()
                                        .flatMap(e -&gt; Stream.of(e.getKey(), e.getValue()))
                                        .toList()
                        ));

    }


}</code></pre>
<p>  <img src="https://velog.velcdn.com/images/min_gyu/post/06d5f1ee-3524-4777-8462-35ab4b6e420d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SRP, OCP]]></title>
            <link>https://velog.io/@min_gyu/SRP-OCP</link>
            <guid>https://velog.io/@min_gyu/SRP-OCP</guid>
            <pubDate>Sun, 13 Sep 2026 13:56:30 GMT</pubDate>
            <description><![CDATA[<h2 id="🇶-객체지향-프로그래밍에서-단일-책임-원칙srp과-개방-폐쇄-원칙ocp에-대해-설명하고-각각의-원칙을-적용한-코드-예시를-들어주세요">🇶 객체지향 프로그래밍에서 &#39;단일 책임 원칙(SRP)&#39;과 &#39;개방-폐쇄 원칙(OCP)&#39;에 대해 설명하고, 각각의 원칙을 적용한 코드 예시를 들어주세요.</h2>
<h3 id="1-단일-책임-원칙">1. 단일 책임 원칙</h3>
<blockquote>
<p> <strong>클래스는 하나의 책임, 하나의 변경 사유만 가져야 한다.</strong></p>
</blockquote>
<p><strong>아래의 장점들이 모여서 테스트 &amp; 검증이 쉬워지며, 유지보수 난이도도 감소함</strong>
-가독성 증가 : 클래스 하나의 행동 범위가 작아지므로 읽고 이해하기 쉬워짐
-결합도 감소 : 하나의 클래스 변경이 다른 코드에 미치는 영향을 줄일 수 있다.
-재사용성 증가 : 코드의 목적성이 명확하여 비슷한 케이스에 쉽게 다시 적용 가능</p>
<hr>
<h3 id="2-개방-폐쇄-원칙">2. 개방-폐쇄 원칙</h3>
<blockquote>
<p><strong>확장에 열려있고, 변경에는 닫혀있어야 한다.</strong></p>
</blockquote>
<p><strong>기능을 확장하는 과정에서 수정을 최소화하여야 한다.</strong>
-설계 단계에서 변경이 일어나도 되는 부분과, 변경하면 안 될 부분을 구분하여 설계한다.
-인터페이스, 다형성 등을 통해 구현한다.</p>
<hr>
<h3 id="3-스프린트2-프로젝트에서-적용-사례">3. 스프린트2 프로젝트에서 적용 사례</h3>
<h4 id="단일-책임-사례-1">단일 책임 사례 1</h4>
<p>메세지를 만들려면</p>
<ol>
<li>메세지를 보낼 채널이 존재하는가?</li>
<li>메세지를 작성한 유저가 해당 채널에 속한 멤버인가?
2가지를 검사해야 한다.
해당 검사는 메시지 자체의 생성 여부가 아니라, 특정 채널에 메시지를 추가할 수 있는지에 대한 검증이므로 채널의 책임으로 분리했다.
만약 추가할 수 없는 메세지일 경우 채널측에서 예외를 throw해주기 때문에 메세지의 JCF에 빈 메세지가 들어가는 일도 막을 수 있다.</li>
</ol>
<details>
<summary>코드</summary>

<pre><code class="language-java">public class JCFMessageService implements MessageService {

    public void create(UUID channelId, UUID userId, String message) {
        Message messageCreate = new Message(userId, message);

        // 채널서비스 측에서
        JCFChannelService.getInstance()
                .addMessage(channelId, userId, messageCreate.getId());

        messageMap.put(messageCreate.getId(), messageCreate);
        reactionMap.put(messageCreate.getId(), new HashMap&lt;&gt;());
    }
}</code></pre>
<pre><code class="language-java">public void addMessage(UUID channelId, UUID userId, UUID messageId) {
    if (!userRoleMap.containsKey(channelId)) {
        throw new IllegalArgumentException(&quot;채널 id가 없습니다.&quot;);
    }

    if (!userRoleMap.get(channelId).containsKey(userId)) {
        throw new IllegalArgumentException(
                &quot;유저 id가 없거나 유저가 채널 소속이 아닙니다.&quot;
        );
    }

    messagesListMap.get(channelId).add(messageId);
}</code></pre>
</details>



<h4 id="단일-책임-사례-2">단일 책임 사례 2</h4>
<p>유저 생성에 대한 책임</p>
<p>1.유저를 등록할 때 Email이 Email형식인가? 는 입력 방식과 상관없이 항상 진행해야 할 검사이다.(도메인 레벨의 책임)
2.열거형 변수 NitroLevel은 print와 scanner를 중심으로 하는 현재 실습 방식에선 검사해야 하지만 콤보박스, 라디오버튼 같은 방식으로 입력받으면 검사할 필요가 없다.(입력 방식의 책임)</p>
<p>그래서 1은 User 엔티티에서 검사하지만 2는 현재 실습에서 scanner입력에 대해 관리해주는 Manager클래스에서 검사했다.</p>
<details>
<summary>코드</summary>

<pre><code class="language-java">    public User(String email, String password, String name, NitroLevel nitroLevel) {
        super();

        if (!email.matches(&quot;^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$&quot;)) {
            throw new IllegalArgumentException(&quot;메일 형식이 아님.&quot;);
        }
        this.email=email;
        this.password=password;
        this.name=name;
        this.nitroLevel=nitroLevel;
    }</code></pre>
<pre><code class="language-java">  //Manager에서 열거형에 대한 scanner 입력을 검사하는 부분
                System.out.println(&quot;니트로 레벨은 아래 목록이 있습니다.&quot;);
                for(NitroLevel level : NitroLevel.values()){
                    System.out.println(level);
                }
                //////////////////////////
                while (true) {

                    System.out.println(&quot;니트로 레벨을 입력하세요.&quot;);

                    nitro = sc.next();

                    try {
                        nitroLevel = NitroLevel.valueOf(nitro);
                        break;
                    } catch (IllegalArgumentException e) {
                        System.out.println(&quot;잘못된 니트로 레벨입니다. 다시 입력해주세요.&quot;);
                    }
                }</code></pre>
</details>



<h4 id="개방-폐쇄-사례">개방-폐쇄 사례</h4>
<p>현재 실습에서 JCFUserService를 만들기 전에 UserService에서 필수적인 메서드들을 정의하는 식으로 진행하고 있다. 이후 File형식으로 기능을 확장할 때 UserService를 불러와 쉽게 틀을 잡을 수 있을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Framework의 탄생, 프레임워크와 라이브러리의 차이]]></title>
            <link>https://velog.io/@min_gyu/%EC%BD%94%EB%93%9C%EC%9E%87-SB-15%EA%B8%B0-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-4%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@min_gyu/%EC%BD%94%EB%93%9C%EC%9E%87-SB-15%EA%B8%B0-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-4%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Fri, 04 Sep 2026 10:01:17 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-framework가-탄생하게-된-배경과-이를-통해-해결하고자-했던-문제점에-대해-설명하세요">Spring Framework가 탄생하게 된 배경과 이를 통해 해결하고자 했던 문제점에 대해 설명하세요.</h2>
<p><img src="https://velog.velcdn.com/images/min_gyu/post/680cfbe2-cf24-4d14-ad8b-6bff207977a2/image.png" alt=""></p>
<center>https://docs.oracle.com/javaee/5/tutorial/doc/bnbyl.html</center>


<p><span style="font-size: 25px;"><strong>Spring이 등장하기 전에는 EJB(Enterprise JavaBeans)를 통해 엔터프라이즈 어플리케이션을 개발하였다.</strong></span></p>
<blockquote>
<p><strong>EJB의 등장 배경은 아래와 같다.</strong></p>
</blockquote>
<ul>
<li>트랜잭션 처리를 일관되게 관리하고 싶다.</li>
<li>분산 시스템 간의 통신을 쉽게 처리하고 싶다.</li>
<li>인증 및 권한 제어를 코드 외부에서 처리하고 싶다.</li>
<li>재사용 가능하고 확장 가능한 비즈니스 컴포넌트가 필요하다.</li>
</ul>
<hr>
<blockquote>
<p><strong>EJB의 한계</strong></p>
</blockquote>
<ul>
<li>단순한 로직에도 여러 파일이 필요한 복잡한 구조</li>
<li>XML 기반의 복잡한 설정은 실수를 유발하기 쉽고, 유지보수가 어렵다.</li>
<li>작은 변경에도 EAR 전체를 다시 배포해야 했다.</li>
<li>컨테이너에 대한 의존도가 높아 단위 테스트에서 불리함.</li>
<li>컨테이너 주도 개발, 직접 의존 객체 생성 등으로 OOP 설계 원칙 훼손<blockquote>
<hr>
</blockquote>
</li>
<li><em>Spring Framework의 등장배경*</em></li>
<li>POJO 기반 개발 : EJB와 달리, 컨테이너에 종속되지 않는 순수한 자바 객체 사용</li>
<li>IoC(Inversion of Control) : 객체의 생성과 의존성 주입을 개발자가 아닌 컨테이너가 담당</li>
<li>AOP(Aspect-Oriented Programming) : 공통 관심사를 비즈니스 로직과 분리</li>
<li>경량 컨테이너: EJB 컨테이너처럼 무거운 애플리케이션 서버에 의존하지 않고 필요한 객체의 생성과 의존성 관리를 담당하는 컨테이너<blockquote>
</blockquote>
위의 내용은 Spring 제시한 새로운 방향성이다.
이러한 특징을 통해 복잡성을 제거하면서 기능성을 유지하는 절묘한 균형을 제공했다. 특히, 단위 테스트가 용이하고, 개발 속도가 빠르며, 기존 기술과의 통합이 유연하다는 점에서 많은 개발자들의 지지를 받게 된다.</li>
</ul>
<h2 id="프레임워크와-라이브러리의-차이점을-제어-흐름의-주체와-사용-방식을-중심으로-설명하고-spring-framework와-일반-java-라이브러리를-예시로-들어-설명하세요">프레임워크와 라이브러리의 차이점을 제어 흐름의 주체와 사용 방식을 중심으로 설명하고, Spring Framework와 일반 Java 라이브러리를 예시로 들어 설명하세요.</h2>
<h3 id="1-프레임워크">1. 프레임워크</h3>
<blockquote>
<p>애플리케이션의 전체적인 뼈대, 구조를 제공하여 정해진 규칙에 따라 코드를 직접 실행하고 제어 주도권을 프레임워크가 갖는다.</p>
</blockquote>
<ul>
<li>스프링 : IoC 컨테이너가 객체의 생성, 의존성 주입, 생명주기 등을 관리하고 애플리케이션의 구성 요소를 연결한다.</li>
<li>안드로이드 : OS가 앱의 생명주기에 따라 콜백을 호출한다.</li>
</ul>
<h3 id="2-라이브러리">2. 라이브러리</h3>
<blockquote>
<p>연관된 기능이나 로직을 모아놓은 코드의 집합이다.
개발자가 전체적인 흐름을 제어하며 필요할 때 불러와 사용한다.</p>
</blockquote>
<p><strong>자바 표준 라이브러리</strong></p>
<ul>
<li>java.lang: 문자열, 수학, 스레드 등 핵심 클래스를 포함하며 자동으로 임포트된다.</li>
<li>java.util: JCF, 난수생성기 등을 제공한다.<blockquote>
</blockquote>
</li>
<li><em>오픈소스 외부 라이브러리*</em></li>
<li>Google Guava: 구글에서 만들 컬렉션 확장, 캐시, 문자열 처리 등에 사용되는 라이브러리이다.</li>
<li>Jackson: 자바 객체와 JSON을 서로 변환할 때 사용한다.</li>
</ul>
<h3 id="3-jcf는-무엇인가">3. JCF는 무엇인가?</h3>
<blockquote>
</blockquote>
<ul>
<li>Spring Framework에서 Framework는 애플리케이션의 실행 흐름과 객체 생명주기 등을 관리하는 프레임워크라는 의미가 강하다.</li>
<li>반면 JCF에서 Framework는 컬렉션을 다루기 위한 인터페이스, 구현체, 알고리즘 등이 일관된 규칙과 구조로 구성된 체계라는 의미에 가깝다.</li>
<li>따라서 JCF는 개발자가 필요할 때 직접 호출하여 사용하는 라이브러리의 성격을 가지면서도, 컬렉션 자료구조에 대한 표준화된 설계 체계를 제공한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[HashSet이 빠른 이유, O(n)과 O(log n)차이는 무엇인가?]]></title>
            <link>https://velog.io/@min_gyu/%EC%BD%94%EB%93%9C%EC%9E%87-SB-15%EA%B8%B0-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-2%EC%A3%BC%EC%B0%A8-b539enjt</link>
            <guid>https://velog.io/@min_gyu/%EC%BD%94%EB%93%9C%EC%9E%87-SB-15%EA%B8%B0-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-2%EC%A3%BC%EC%B0%A8-b539enjt</guid>
            <pubDate>Sun, 30 Aug 2026 15:46:15 GMT</pubDate>
            <description><![CDATA[<h2 id="hashset의-내부-동작-방식과-중복-제거-메커니즘을-설명하고-hashset이-효율적인-중복-체크를-할-수-있는-이유를-설명해주세요">HashSet의 내부 동작 방식과 중복 제거 메커니즘을 설명하고, HashSet이 효율적인 중복 체크를 할 수 있는 이유를 설명해주세요.</h2>
<h3 id="1-hashset은-어떻게-중복을-제거할까">1. HashSet은 어떻게 중복을 제거할까?</h3>
<blockquote>
<p> <strong>해시함수를 사용해 저장할 위치를 정한다</strong>
새로 들어온 값의 해시값을 계산하고, 그 해시값을 바탕으로 해당 버킷을 찾아 기존 데이터와 비교한다.
그렇기 때문에 List처럼 모든 index를 순회하며 비교할 필요가 없다.</p>
</blockquote>
<p><strong>해시값이 같으면 같은 데이터인가?</strong>
항상 같은 데이터가 아닐 수도 있다.
이런 상황을 해시 충돌이라고 부르며, 잦은 해시충돌은 최악의 경우 O(n)으로 시간복잡도를 악화시킨다.
값을 고르게 분포시켜 해시충돌을 최소화하는게 좋은 해시함수이다.</p>
<blockquote>
<p><strong>자바에서</strong>
hashCode() = 정수형태의 해시값이 반환되며, 포인터는 아니다.
equals() = 실제로 같은 데이터인지 확인한다. equals()가 true면 hashCode()도 반드시 같다.</p>
</blockquote>
<p><strong>해시 함수를 미분이라고 가정해 보자.</strong>
데이터 x와 2x를 각각 미분하면 해시값은 1, 2가 된다.
그런데 x+1이라는 데이터가 추가되면 이를 미분한 값 역시 1이므로 x와 x+1 사이에 해시 충돌이 발생한다.
하지만 새로운 데이터 3x가 들어왔을 때 3x를 미분해서 해시값 3을 구하는 연산 자체가 충돌 때문에 느려지는 것은 아니다.
즉, 해시 함수가 값을 계산하는 과정은 여전히 O(1)이다.</p>
<hr>
<hr>
<hr>
<h2 id="on과-olog-n의-성능-차이를-실생활-예시를-들어-설명하고-데이터의-크기가-1백만-개일-때-각각-대략-몇-번의-연산이-필요한지-비교해주세요">O(n)과 O(log n)의 성능 차이를 실생활 예시를 들어 설명하고, 데이터의 크기가 1백만 개일 때 각각 대략 몇 번의 연산이 필요한지 비교해주세요.</h2>
<h3 id="on--100만번">O(n) : 100만번</h3>
<blockquote>
<p>소설을 읽는다고 했을 때, 소요 시간은 권 수, 또는 권당 페이지 수에 비례할 것이다.</p>
</blockquote>
<hr>
<h3 id="olog-n--20번">O(log n) : 20번</h3>
<blockquote>
<p>20권으로 구성된 어떤 소설에서 다시 보고 싶은 부분이 생겼다.
10권을 열어봤더니 이것보단 뒤인 것 같다
15권을 열어보니 이것보단 앞인 것 같다.
13권을 열어보니 찾던 내용이었다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git & Git Hub]]></title>
            <link>https://velog.io/@min_gyu/%EC%BD%94%EB%93%9C%EC%9E%87-SB-15%EA%B8%B0-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@min_gyu/%EC%BD%94%EB%93%9C%EC%9E%87-SB-15%EA%B8%B0-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Mon, 17 Aug 2026 13:50:33 GMT</pubDate>
            <description><![CDATA[<h2 id="🇶-git-rebase와-git-merge의-차이점을-설명하고-각각-어떤-상황에서-사용하는-것이-적절한지-설명해주세요">🇶 git rebase와 git merge의 차이점을 설명하고, 각각 어떤 상황에서 사용하는 것이 적절한지 설명해주세요.</h2>
<h3 id="1-git-rebase">1. git rebase</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/min_gyu/post/6db7a550-b2e5-4565-868e-d00abc7cc6e7/image.png" alt="">
브랜치의 base를 옮긴다.
커밋 히스토리를 선형화하여 그래프를 단순화할 수 있지만, 기존의 브랜치 분기 및 병합 과정을 파악하기 어려울 수 있다.
개인이 작업중인 브랜치에 업데이트된 프로젝트 내용물을 반영할 때 사용한다.</p>
</blockquote>
<hr>
<h3 id="2-git-merge">2. git merge</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/min_gyu/post/9258d851-f40d-41ed-a798-5b287fc38f04/image.png" alt="">
2개 이상의 브랜치를 병합한다.
기존 커밋의 ID가 유지되며, merge커밋에 새로운 ID가 생긴다.(항상 merge커밋이 생기진 않고 필요한 경우 생성함)
브랜치의 분기와 병합 과정을 포함한 이력을 유지하고 싶을 때 사용할 수 있다.</p>
</blockquote>
<hr>
<h3 id="3-pull-request-merge">3. Pull Request merge</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/min_gyu/post/6046abfc-177a-453f-a205-8e9f5985bb5c/image.png" alt="">
로컬에서 브랜치를 merge한 경우와 달리 PR merge는 원격 저장소에서 대상 브랜치에 변경사항을 병합한다.
main에 반영된 내용을 로컬 main에 반영하려면 로컬 main을 업데이트해야 한다.
PR에 충돌이 발생한 경우에는 충돌 해결을 위해 대상 브랜치의 변경사항을 소스 브랜치에 먼저 반영한 후 다시 PR을 병합할 수 있다.
이를 통해 main에 병합하기 전에 코드 리뷰와 테스트 등의 협업 과정을 진행할 수 있다.</p>
</blockquote>
<hr>
<hr>
<hr>
<h2 id="git-fetch와-git-pull의-차이점을-설명하고-각각을-사용하는-것이-적절한-상황을-설명해주세요">git fetch와 git pull의 차이점을 설명하고, 각각을 사용하는 것이 적절한 상황을 설명해주세요.</h2>
<h3 id="1-git-fetch">1. git fetch</h3>
<blockquote>
<p>원격 저장소의 최신 변경사항을 로컬의 원격 추적 브랜치(Remote Tracking Branch)에 반영한다.
로컬 브랜치에 영향을 주지 않고 내용물을 확인하고 싶을 때 사용한다.</p>
</blockquote>
<hr>
<h3 id="2-git-pull">2. git pull</h3>
<blockquote>
<p>원격 저장소의 최신업데이트를 바로 로컬 브랜치에 반영한다.
fetch수행 후 merge를 자동으로 수행한다.
pull의 기본 설정은 merge이지만 필요하다면 rebase도 사용할 수 있다</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>