<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jaeseok-go.log</title>
        <link>https://velog.io/</link>
        <description>명확하게 말하고, 꼼꼼하게 개발하자</description>
        <lastBuildDate>Tue, 21 Jun 2022 13:51:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jaeseok-go.log</title>
            <url>https://images.velog.io/images/jaeseok-go/profile/4ddf3b50-a71c-431b-812f-42b6d9390c45/고재석.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jaeseok-go.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jaeseok-go" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[쿼리 튜닝 개요]]></title>
            <link>https://velog.io/@jaeseok-go/%EC%BF%BC%EB%A6%AC-%ED%8A%9C%EB%8B%9D-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@jaeseok-go/%EC%BF%BC%EB%A6%AC-%ED%8A%9C%EB%8B%9D-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Tue, 21 Jun 2022 13:51:30 GMT</pubDate>
            <description><![CDATA[<h2 id="쿼리-튜닝과-필요성">쿼리 튜닝과 필요성</h2>
<p>DB 기반 시스템에서의 DB관련 성능저하의 비율은 70% 이상이다. 그만큼 DB 설계 및 쿼리 단계에서 성능을 고려하지 못하는 경우가 발생하기 쉽고, 이를 기술적으로 해결해나가는 것이 중요하다.</p>
<p>이처럼 DB 쿼리의 성능저하 이슈를 해결하는 작업을 <code>쿼리 튜닝</code>이라고 한다.</p>
<br>

<h2 id="데이터베이스-메모리-구조">데이터베이스 메모리 구조</h2>
<p><img src="https://velog.velcdn.com/images/jaeseok-go/post/c41c34dd-1e3c-438b-9c6f-53cd762311eb/image.png" alt="">출처 : <a href="http://dbcafe.co.kr/wiki/index.php/SGA_%EC%99%80_PGA">http://dbcafe.co.kr/wiki/index.php/SGA_%EC%99%80_PGA</a></p>
<p>위의 그림은 오라클 기반의 DB 메모리 구조를 표현한 그림이다.</p>
<p>메모리는 크게 PGA와 SGA로 구분되는데, <code>PGA</code>는 Client Session, SQL 처리 메모리, Sorting 메모리 등을 포함하며 관리한다. 이러한 PGA는 Client Connection마다 하나씩 할당되는 영역이다.</p>
<p><code>SGA</code>는 Shared Pool, DB Buffer Cache, Redo Log Buffer 등을 가지며 모든 사용자가 공유하는 영역이다.</p>
<p>메모리 리소스들의 자세한 역할은 아래의 SQL문 처리 과정을 보며 이해해보자.</p>
<br>

<h2 id="sql문-처리-과정">SQL문 처리 과정</h2>
<p>Client side에서 SQL문을 실행하는 경우 아래와 같은 과정을 통해 SQL문이 실행된다.</p>
<blockquote>
<p>parse (구문분석) - bind (바인드) - execute (실행) - fetch (인출)</p>
</blockquote>
<h4 id="1-parse">1. parse</h4>
<p>구문분석 과정에서는 전달받은 SQL로 <code>컴파일 코드</code>와 <code>실행 계획</code>을 만들어서 Shared Pool에 저장하는 과정이다. parse과정은 아래의 순서로 진행된다.</p>
<blockquote>
<p>1) SQL문의 구문을 체크한다. (select, from, where 등)
2) table/column의 권한/보안정책을 체크한다.
3) parse tree (컴파일 코드)를 생성하고 Shared Pool에 저장한다.
4) Optimizer가 어떤 수행방법이 빠른 방법인지 검토한다.
5) 실행 계획을 만들어서 Shared Pool에 저장한다.</p>
</blockquote>
<p>사실 위의 과정에서 한 가지 빠진 과정이 있는데, 이는 Shared Pool에 현재 전달받은 쿼리가 존재하는지 검증하는 과정이다.</p>
<p>이 과정에서는 입력받은 쿼리를 ascii code로 바꾸어서 모든 값의 합계를 구한다. Shared Pool에 존재하는 쿼리와 입력받은 쿼리의 합계가 다른 경우는 다른 쿼리로 판별하고, 같은 경우의 쿼리만 비교해서 직접 검증한다.</p>
<p>만약 입력받은 쿼리가 이미 직전에 입력받은 쿼리여서 Shared Pool에 존재하는 쿼리라면 3~5번 과정은 생략한다.
이렇게 생략하는 것을 <code>soft parse</code>라고 하며, 반대로 모든 과정을 수행하는 과정을 <code>hard parse</code>라고 한다.</p>
<p>hard parse보다 soft parse를 지향하는 쿼리가 더 성능이 좋은 쿼리이므로, 정해진 작성룰에 맞춰 쿼리를 작성하는 것이 좋다.
(빈 칸, 공백 등의 사소한 차이도 다른 쿼리로 인식)</p>
<h4 id="2-bind">2. bind</h4>
<p>Shared Pool에 작성된 parse tree에 변수가 존재한다면 그 값을 바인딩하는 과정이다. 변수가 없는 경우에는 다음 단계로 넘어간다.</p>
<h4 id="3-execute">3. execute</h4>
<p>실행과정은 실행계획을 적용하는 과정이다.</p>
<p>select문의 경우 아래와 같은 과정으로 실행과정이 실행된다.</p>
<blockquote>
<ol>
<li>DB Buffer Cache에 데이터가 있는지 확인한다.</li>
<li>데이터가 없다면, data file에서 필요한 data block을 Cache로 가져온다.</li>
</ol>
</blockquote>
<p>update문의 경우 아래와 같은 과정으로 실행과정이 실행된다.</p>
<blockquote>
<ol>
<li>DB Buffer Cache에 데이터가 있는지 확인한다.</li>
<li>데이터가 없다면, data file에서 필요한 data block을 Cache로 가져온다.</li>
<li>데이터가 없다면, Undo Block을 할당받아서 Cache로 Copy한다.</li>
<li>새로운 데이터와 예전 데이터를 Redo Log Buffer에 저장한다.</li>
<li>Undo Segment의 Undo Block에 예전 데이터를 저장한다.</li>
<li>Cache의 Data Block에 새로운 데이터를 update한다.</li>
</ol>
</blockquote>
<h4 id="4-fetch">4. fetch</h4>
<p>인출과정은 select문의 경우에만 수행되는 과정이다. DB Buffer Cache에 있는 요청 데이터를 Array Size 만큼 반복하여 인출하는 과정이다. 받은 데이터를 PGA에서 Sorting작업을 수행한다.</p>
<h2 id="옵티마이저">옵티마이저</h2>
<p>옵티마이저란 SQL문 parse 과정에서 실행 계획을 만들어 내는 최적화 리소스이다. 이 때 만들어진 실행계획을 가지고 얼마나 최적화된 경로로 데이터를 조회할지 결정하기 때문에, <code>성능을 결정하는 매우 중요한 요소</code>이다. 옵티마이저가 보다 최적화된 실행계획을 만들어내게 유도함으로써 쿼리 튜닝을 수행할 수  있다.</p>
<p>이와 같은 옵티마이저가 만들어낸 실행계획을 아래와 같이 확인해보자.
<img src="https://velog.velcdn.com/images/jaeseok-go/post/4dc9cf3f-0eab-4117-b549-c44025ab1f41/image.png" alt="">개인환경에서는 oracle을 준비하지 못해 mysql로 실행계획을 확인해보았다.
실행계획에서는 해당 쿼리가 어떤 타입인지, 어떤 인덱스를 사용하는지, 몇 줄의 데이터를 검색하는지 등의 데이터를 가지고 있다. 이와 같은 정보들을 활용해 현재 쿼리의 성능이 나쁜 원인을 분석해서 더 나은 쿼리를 만들어 내거나, 인덱스와 같은 자료구조를 수정해서 성능을 개선하는 작업을 수행할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kafka 시작하기]]></title>
            <link>https://velog.io/@jaeseok-go/Kafka-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jaeseok-go/Kafka-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 03 May 2022 13:46:28 GMT</pubDate>
            <description><![CDATA[<h3 id="카프카란">카프카란?</h3>
<p>카프카는 이벤트 스트리밍 플랫폼으로 발생하는 이벤트에 대해 실시간으로 처리하고 이를 보장한다.
카프카는 producer와 consumer가 하나의 topic을 publish/subscribe함으로써 이벤트를 발생시키고/처리하는 방식으로 구성된다.</p>
<p><img src="https://velog.velcdn.com/images/jaeseok-go/post/6ec8a28d-bb97-414e-9d2a-1e6c7254e008/image.png" alt=""> 출처 : <a href="https://aws.amazon.com/ko/blogs/korea/introducing-amazon-sns-fifo-first-in-first-out-pub-sub-messaging">https://aws.amazon.com/ko/blogs/korea/introducing-amazon-sns-fifo-first-in-first-out-pub-sub-messaging</a></p>
<p>예를 들어 A가 B에게 송금을 하고자한다.
이 때 A는 이벤트를 발생시켜서 이를 &#39;payments&#39;라는 토픽에 게시한다. 토픽이란 이벤트를 저장할 하나의 주제인데, 이를 카프카 브로커에서 메세지 큐 형태로 관리한다. 이 때 이벤트의 내용은 아래와 같다.</p>
<ol>
<li>이벤트 키 : A</li>
<li>이벤트 값 : A가 B에게 200만원 송금</li>
<li>이벤트 타임스탬프 : 2022-05-03 22:03:20
이 이벤트는 토픽에 쌓이게 되고, 이 토픽을 게시하고 있는 consumer가 해당 이벤트를 처리하게 된다.</li>
</ol>
<h3 id="카프카-시작하기">카프카 시작하기</h3>
<h4 id="1-카프카-다운로드">1. 카프카 다운로드</h4>
<p><a href="https://www.apache.org/dyn/closer.cgi?path=/kafka/3.1.0/kafka_2.13-3.1.0.tgz">https://www.apache.org/dyn/closer.cgi?path=/kafka/3.1.0/kafka_2.13-3.1.0.tgz</a>
위의 카프카 공식 홈페이지에서 tgz 파일을 다운로드 받는다.
<img src="https://velog.velcdn.com/images/jaeseok-go/post/aaf5208a-bc5d-4c9d-861b-2829cc69fc0f/image.png" alt=""></p>
<h4 id="2-카프카-환경-세팅">2. 카프카 환경 세팅</h4>
<p><img src="https://velog.velcdn.com/images/jaeseok-go/post/ae10494b-811b-4981-b274-5ea8ed3a0e3f/image.png" alt="">위와 같은 명령어로 zookeeper 서버를 실행시킨다. 
zookeepers는 분산환경 코디네이터 시스템으로 카프카의 메타데이터와 상태관리등의 목적으로 실행한다.</p>
<p><img src="https://velog.velcdn.com/images/jaeseok-go/post/20d3899d-755b-4955-8595-aa0bff18dff7/image.png" alt=""> 새로운 커맨드 창을 띄워서 위와 같은 명령어를 입력하면 카프카 서버가 실행된다.
이 카프카 서버에 토픽을 등록하고 이 토픽에 대한 이벤트 관리등을 수행할  것이다.</p>
<h4 id="3-토픽-생성하기">3. 토픽 생성하기</h4>
<p><img src="https://velog.velcdn.com/images/jaeseok-go/post/12d698e6-82aa-4628-94ae-e6435652de32/image.png" alt=""> 위와 같은 명령어로 example-events라는 토픽을 생성해보았다. 
<img src="https://velog.velcdn.com/images/jaeseok-go/post/2451d1dd-66a5-476a-80e7-d4b71ee25e45/image.png" alt="">이처럼 카프카서버 로그에 토픽 생성 로그가 남았다.</p>
<h4 id="4-카프카-클라이언트-생성해서-이벤트-주고받기">4. 카프카 클라이언트 생성해서 이벤트 주고받기</h4>
<p><img src="https://velog.velcdn.com/images/jaeseok-go/post/96c27b61-0378-4f8c-b2a7-cabfb65d8f88/image.png" alt=""> 위와 같이 kafka producer를 생성해서 example-events라는 토픽에 등록한다. 그리고 임의의 3개의 이벤트를 write했다.</p>
<p><img src="https://velog.velcdn.com/images/jaeseok-go/post/39171b54-c77b-4368-a7ae-f337923d93c8/image.png" alt=""> 그리고 위와 같이 kafka consumer를 생성해서 example-events의 토픽에 등록하고 --from-beginning 옵션을 주었더니 해당 토픽의 모든 데이터를 받아왔다.
이 경우 producer가 생성했던 3개의 이벤트를 모두 받아왔다.</p>
<p>이와 같은 카프카의 동작을 애플리케이션 서버와 연동해서 운영할 수 있지만 추후에 새로운 게시물에 해당 내용을 자세히 공부하며 정리할 예정이다.</p>
<p><img src="https://velog.velcdn.com/images/jaeseok-go/post/37b1a387-7d4c-4027-9795-7603da12169b/image.png" alt=""> 마지막으로 해당 명령어를 입력해서 카프카 환경 등을 삭제한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DI 생성해보기]]></title>
            <link>https://velog.io/@jaeseok-go/DI-%EC%83%9D%EC%84%B1%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@jaeseok-go/DI-%EC%83%9D%EC%84%B1%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 05 Dec 2021 07:35:40 GMT</pubDate>
            <description><![CDATA[<h2 id="di">DI?</h2>
<p>Spring에서 지원하는 주요 개념인 DI(Dependency Injection)는 의존성 주입의 약어로 인스턴스를 만들지 않더라도 스프링 컨텍스트에 등록된 빈의 인스턴스를 생성해주는 것으로 잘 알려져있다.</p>
<p>실제 DI가 어떤 식으로 구현되어 있고 동작하는지 백기선님의 Java강의를 참고하여 DI를 만들어보며 이해해보자.</p>
<h2 id="di-동작-순서">DI 동작 순서</h2>
<p>우선 Java의 리플렉션 API를 활용하여 개발을 해보고자 한다. 우선 특정 클래스의 인스턴스를 만들고자 한다. 이 인스턴스를 생성할 때 해당 클래스의 필드 중 특정 어노테이션이 달려 있는 경우에 해당 필드의 인스턴스까지 함께 생성하고자 한다.</p>
<h3 id="annotation-생성">Annotation 생성</h3>
<p>스프링에서는 @Autowired 어노테이션으로 필드에 의존성을 주입한다. 나는 @Inject라는 어노테이션을 정의하여 이 어노테이션으로 의존성을 주입하고자 한다. 우선 Inject 어노테이션 파일을 만든다.</p>
<pre><code class="language-java">package study.jaeseok;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
}</code></pre>
<h3 id="getobject-메소드-생성">getObject 메소드 생성</h3>
<p>컨테이너 서비스에서는 인스턴스를 생성해서 반환하는 createInstance() 메소드를 가진다. 이 때 리플렉션 api를 활용하여 기본 생성자를 활용하여 생성한다. </p>
<p>그리고 생성한 인스턴스에 Inject 어노테이션이 붙어있는 필드가 있는지 검사해서 해당 필드의 인스턴스도 생성하는 로직을 구성한다.</p>
<pre><code class="language-java">package study.jaeseok;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;

public class ContainerService {

    public static &lt;T&gt; T getObject(Class&lt;T&gt; classType) {
        T instance = createInstance(classType);

        Arrays.stream(classType.getDeclaredFields())
                .forEach(field -&gt; {
                    Annotation annotation = field.getAnnotation(Inject.class);
                    if (annotation != null) {
                        try {
                            field.setAccessible(true);
                            field.set(instance, createInstance(field.getType()));
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                });

        return instance;
    }

    private static &lt;T&gt; T createInstance(Class&lt;T&gt; classType) {
        try {
            return classType.getConstructor(null).newInstance();
        } catch (InstantiationException | IllegalAccessException | 
                InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }
}</code></pre>
<h3 id="테스트">테스트</h3>
<p>그렇다면 의존성 주입이 제대로 이루어졌는지 확인하는지 테스트 코드로 확인해보자. 아래와 같이 코드를 작성하고 BookService에 BookRepository 타입의 필드에 @Inject 어노테이션을 달아놓은 상태이다.</p>
<pre><code class="language-java">
package study.jaeseok;

import org.junit.Assert;
import org.junit.Test;

public class ContainerServiceTest {

    @Test
    public void getBookService() {
        BookService bookService = ContainerService.getObject(BookService.class);
        Assert.assertNotNull(bookService);
        Assert.assertNotNull(bookService.bookRepository);
    }

    @Test
    public void getBookRepository() {
        BookRepository bookRepository = ContainerService.getObject(BookRepository.class);
        Assert.assertNotNull(bookRepository);
    }
}</code></pre>
<p>테스트 코드 실행 결과 아래와 같이 Tests passed 결과를 확인할 수 있다.
<img src="https://images.velog.io/images/jaeseok-go/post/d58d5c8e-148d-4131-8245-ca78995f870b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[좋은 코드를 작성하기 전에]]></title>
            <link>https://velog.io/@jaeseok-go/%EC%A2%8B%EC%9D%80-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jaeseok-go/%EC%A2%8B%EC%9D%80-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 05 Dec 2021 05:07:50 GMT</pubDate>
            <description><![CDATA[<h2 id="좋은-코드란">좋은 코드란?</h2>
<p>좋은 코드를 작성하기 전에 좋은 코드가 무엇인지 알고 넘어갈 필요가 있을 것 같다.</p>
<p>나는 <code>클린코드</code>라는 책을 읽으며 좋은 코드를 작성하는 법을 배우고 있다. 해당 책에서는 소프트웨어 개발 분야의 몇몇 OG들의 코멘트를 인용하여 좋은 코드를 설명하고 있다. 이를 내 나름대로 요약하자면 좋은 코드란 아래와 같다.</p>
<blockquote>
<ol>
<li>명확하게 표현되어 읽기 쉬운 코드</li>
<li>테스트 코드가 잘 작성된 코드</li>
<li>고치기 쉬운 코드</li>
</ol>
</blockquote>
<p>이와 같은 요건을 만족하여 좋은 코드를 작성하기 위해서는 조금 더 세부적으로 공부하고 이해할 필요가 있다.</p>
<p>명확하게 코드를 작성해야겠다라고 생각만하고 코드를 작성한다고 명확한 코드가 나오는 것은 아니다. 예를 들어 메소드는 하나의 기능만을 수행한다던지, 변수명을 명확하게 작성한다던지 더 디테일한 기법을 적용하여 좋은 코드를 작성할 수 있을 것이다. 아마도 이 책을 계속 읽어 나가고 정리하다보면 이러한 것들을 이해하고 체득할 수 있을 것이라고 예상하고 기대한다.</p>
<h2 id="좋은-코드를-작성해야하는-이유">좋은 코드를 작성해야하는 이유</h2>
<p>좋은 코드는 &#39;좋은&#39; 코드이니깐! 과 같은 무책임한 이유 말고 정말 현실적인 이유는 무엇일까?</p>
<p><strong>내가 생각하기에 가장 큰 부분은 <code>유지보수성</code></strong>이다. 소프트웨어 서비스는 소모품이 아니다. 한번 쓰고 버리는 것이 아니라 계속 쓸 것이고, 새로운 요건이 생기면 수정해서 사용할 것이다. 정말 서비스의 의도 자체가 쓸모 없어지지 않는 한 소프트웨어는 지속적이다.</p>
<p>법이 바뀌고 세상이 바뀐다. 그에 따라 비즈니스 요구사항도 역시 계속 변하기 마련이다. 내가 말하는 유지보수성은 기존 비즈니스 요구사항을 만족하며 새로운 요구사항에도 만족하는 코드를 작성하는 것이다.</p>
<p>그렇다면 변한 요구사항에 따라 소프트웨어의 특정 모듈을 수정해야하는 상황이라고 가정해보자. 나쁜 코드는 어떨까? 특정 모듈을 고치면 된다고 생각했는데 특정 모듈이 다른 모듈에서 의존성을 가진 모듈이다. 그럼 다른 모듈도 싹 다 고쳐야 한다. 하루 걸릴 일이라고 생각했는데 일주일이 걸리게 된다. 혹은 로직이 변함에 따라 버그가 초래되기도 한다.</p>
<p>따라서 애초에 좋은 코드를 작성해서 이러한 일들이 발생하지 않게끔 주의를 기울여야한다. 처음에는 돌아가는 길이라고 생각이 들지 모르겠지만 결국에 뒤돌아보면 지름길인 것이다.</p>
<h2 id="좋은-코드를-작성할-수-있는-환경">좋은 코드를 작성할 수 있는 환경</h2>
<p><strong>좋은 코드를 작성하기 위해 기한, 공수 등의 조건에 전문가로써 적극적인 의견 전달</strong>이 필요하다. 즉 좋은 코드를 작성하는데에 더 많은 시간이 든다면, 관리자에게 적극적으로 어필하고 시간 투자가 되지 않았을 경우 어떤 사태를 초래할 수 있는지 말해야 한다는 것이다. 우리는 업무에서 전문가이자 프로이다. 나쁜 코드를 짜게 되는데에 변명할 것이 아니라 좋은 코드를 짜게끔 스스로 환경을 만들어 나가야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스트림]]></title>
            <link>https://velog.io/@jaeseok-go/%EC%8A%A4%ED%8A%B8%EB%A6%BC</link>
            <guid>https://velog.io/@jaeseok-go/%EC%8A%A4%ED%8A%B8%EB%A6%BC</guid>
            <pubDate>Tue, 29 Jun 2021 06:58:22 GMT</pubDate>
            <description><![CDATA[<h3 id="스트림이란">스트림이란?</h3>
<p>스트림이란 컬렉션의 저장 요소를 하나씩 참조하여 람다식으로 처리할 수 있도록 해주는 반복자이다. </p>
<p>iterator와 비슷하다고 생각이 들 수도 있다. 하지만 iterator는 자체적으로 반복하지 않고 외부의 반복자가 필요하다. 반면에 스트림은 <strong>내부 반복자</strong>로 <strong>람다식</strong>으로 처리하기 때문에 자체적으로 멀티 스레드로 병렬 처리하여 더 좋은 성능으로 작업을 수행할 수 있다.</p>
<br>

<h3 id="스트림-파이프라인">스트림 파이프라인</h3>
<p><img src="https://images.velog.io/images/jaeseok-go/post/80ed1739-0991-44ff-b2de-00efd4e1a3c0/image.png" alt=""></p>
<p>스트림은 위의 그림과 같은 파이프라인을 구성하여 원하는 결과를 도출할 수 있다. 초기 스트림에서 필터링 과정을 거치면 특정 데이터만 필터링한 중간 스트림으로 반환된다. 이 중간 스트림이 매핑 과정을 거치면 각 요소를 특정 타입의 특정 데이터로 새롭게 매핑한 스트림으로 반환된다. 최종적으로는 집계 처리 과정을 거침으로써 최종 스트림의 결과로 원하는 타입의 데이터를 반환받을 수 있다.</p>
<p>스트림 파이프라인에서는 중간 처리 과정과 최종 처리 과정이 구분된다. 최종 처리 과정이 없으면 중간 처리 과정의 모든 작업들이 수행되지 않고 지연(LAZY)된다. 따라서 원하는 결과를 얻기 위해서는 꼭 최종 처리 과정이 필요하다.</p>
<p>간단한 스트림 파이프라인 예제를 살펴보자.
<img src="https://images.velog.io/images/jaeseok-go/post/61d0bbc4-8ea1-4812-9825-b06852bce6aa/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/052590bc-c662-4550-8b00-20387cbba7e7/image.png" alt=""></p>
<br>

<h3 id="필터링-기법">필터링 기법</h3>
<p>스트림에서 원하는 데이터만 필터링하는 필터링 과정에서 사용하는 몇 가지 메소드가 있다. 그 중에서도 가장 대표적인 filter(Predicate) 메소드로 한번 알아보자.</p>
<p>filter 메소드의 매개 변수는 Predicate 타입으로 해당 함수형 인터페이스의 구현 객체가 true를 반환하는 요소만 남긴 중간 스트림을 반환한다.</p>
<p>위에서 실습해본 내용으로 확인해보자면 filter 메소드 안의 람다식은 <code>n -&gt; n.contains(&quot;재석&quot;)</code>이였다. 그렇다면 기존 스트림에서 가지고 있던 문자열 중에 &quot;재석&quot;이라는 문자열을 포함한 문자열만 남긴 중간 스트림을 반환한다. 위의 실습에서는 모든 요소가 재석이라는 문자열을 포함하기 때문에 그대로 반환한다.</p>
<br>

<h3 id="매핑-기법">매핑 기법</h3>
<p>스트림에서 컬렉션의 각 요소를 원하는 포맷이나 타입으로 바꾸는 과정으로 flatMapXXX(), mapXXX(), asXXXStream(), boxed() 등의 메소드를 활용할 수 있다.</p>
<p>flatMapXXX() 메소드는 하나의 요소를 여러 개의 요소로 바꾼 중간 스트림을 반환하는 메소드이다. </p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/f2542e83-ebd1-494d-81b2-91b67d39d7b8/image.png" alt=""> <img src="https://images.velog.io/images/jaeseok-go/post/d78c7a2f-7b4a-4e78-8619-4f07c67a6290/image.png" alt=""></p>
<p>위 코드를 보면 flatMap 메소드의 매개변수는 Function 인스턴스를 람다식으로 구현하여 전달된다. 이 람다식은 String 타입의 데이터를 Stream<String> 타입의 데이터로 반환하는 람다식이다. 이 Stream<String> 타입으로 매핑된 각 데이터는 flatMap 메소드가 하나의 중간 스트림으로 반환한다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/4a8e778d-d385-4815-8458-83d2c6e568f1/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/6fd1588e-fe04-4679-be07-46057f7596ec/image.png" alt=""></p>
<p>위의 코드를 보면 mapToInt 메소드로 기존의 Student 타입이던 각 요소를 int타입으로 매핑하여 다시 반환한다.</p>
<br>

<h3 id="정렬-기법">정렬 기법</h3>
<p>스트림의 각 요소를 특정한 기준으로 정렬하기 위해서는 Comparable 인터페이스를 구현해야한다. 이렇게 구현한 내용을 정렬하기 위해서는 아래와 같은 코드를 사용하면 된다.</p>
<pre><code class="language-java">.sorted();
.sorted((a,b) -&gt; a.compareTo(b));
.sorted(Comparator.naturalOrder());</code></pre>
<p>그리고 반대로 정렬하고 싶으면 아래와 같은 코드를 사용하면 된다.</p>
<pre><code class="language-java">.sorted((a,b) -&gt; b.compareTo(a));
.sorted(Comparator.reverseOrder());</code></pre>
<br>

<h3 id="집계-기법">집계 기법</h3>
<p>스트림에서 필터링하고 매핑과정을 거친 스트림은 최종 처리 단계에서 집계되어 최종 데이터를 반환할 수 있다. count(), sum() 메소드를 제외한 다른 집계 메소드는 Optional 타입으로 결과를 반환한다. 따라서 .orElse(), ifPresent() 메소드 등을 활용하여 NoSuchElementException을 피하는 처리를 해주어야한다. (스트림에 아무 요소 없이 집계되는 것을 생각하면 예외처리가 필요함을 느낄 수 있다.)</p>
<p>집계 기법 중에 커스텀 집계를 활용할 수 있는 reduce() 메소드로 한번 알아보자.
<img src="https://images.velog.io/images/jaeseok-go/post/e3555e8a-255d-4e3d-b5da-9a583cce56bd/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/a4be6e5e-8726-4eaf-9ff2-2bcf587a6dd6/image.png" alt=""></p>
<p>위의 코드를 보면 reduce() 메소드 안에 각 요소를 더하는 람다식을 매개변수로 주었다. 이 경우에는 반환값이 Optional 타입으로 반환되어 .get() 메소드를 추가로 사용했다. reduce 메소드에 두 개의 파라미터를 전달하는 경우에는 첫 파라미터가 identity 값으로 null인 경우를 명시하게끔 되어있다. 이 경우에는 반환 값 타입이 int 타입이다.</p>
<br>

<h3 id="수집-기법">수집 기법</h3>
<p>collect() 메소드는 필터링과 매핑이 모두 끝난 중간 스트림을 최종 처리하여 다시 컬렉션으로 수집하여 반환하는 메소드이다. 집계는 중간 스트림으로 기본 타입의 집계 결과를 반환하는 것에 비해 수집은 다시 컬렉션이 반환된다.</p>
<p>collect(Collector&lt;T,A,R&gt; collector) 메소드는 매개변수로 Collector 타입의 인스턴스를 전달받는데, 이 매개변수는 T요소를 R에 A가 저장한다는 의미의 제네릭을 가진다. Collectors 클래스에서는 이와 같은 Collector 타입의 반환값을 가지는 정적 메소드를 제공한다. toList(), toSet(), toMap(keyMapper, valueMapper)과 같은 메소드를 제공하여 수집된 컬렉션을 반환하게끔 할 수 있고, <code>toCollection(Supplier&lt;Collection&lt;T&gt;&gt;)</code>으로 Supplier가 제공하는 Collection에 저장할 수도 있다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/6c7a919a-8163-41ea-b4a5-05e271182730/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/3caebf71-c9af-4e7b-94de-267eadeb6f02/image.png" alt=""></p>
<p>위의 코드를 보자. 90점 이상의 학생들만 필터링한 스트림을 List로 collect했다. 이 과정에서 Collectors 클래스의 정적 메소드인 toList() 메소드를 활용했다. </p>
<p>그리고 90점 이하의 학생들만 필터링한 스트림을 HashSet으로 collect했다. 이 과정에서는 toCollection() 메소드를 활용했으며 이 때 매개변수로는 HashSet 생성자 참조로 Supplier를 구현했다.</p>
<h4 id="그룹핑해서-수집">그룹핑해서 수집</h4>
<p> 그룹핑해서 수집하는 경우에는 무조건 Map 컬렉션에 저장된다. 이 때 반환되는 Map의 Key에는 그룹핑의 기준이 되는 데이터가 저장되고, Value에는 기준에 따라 분류된 데이터들이 컬렉션에 담겨 저장된다.</p>
<p>  <img src="https://images.velog.io/images/jaeseok-go/post/e192418d-df50-48bd-8a8f-311daa0bb797/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/643a27c5-f43f-41a1-b0b3-a1fbf19fd1dd/image.png" alt=""></p>
<p>위의 코드를 보면 collect 메소드의 매개변수로 Collectors의 정적 메소드인 groupingBy 메소드의 반환값이 주어진다. 이 groupingBy 메소드의 첫번째 매개변수는 그룹핑 기준이 되는 데이터를 매핑하는 메소드를 참조하여 주어졌다. 그리고 두 번째 파라미터는 다시 Collections 클래스의 정적 메소드인 mapping 메소드가 주어졌다. 이 메소드는 groupingBy되어 Value에 저장될 데이터를 매핑하는 메소드이다. 여기서는 Student 객체를 다시 String으로 매핑하여 리스트로 저장함을 명시했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컬렉션 프레임워크]]></title>
            <link>https://velog.io/@jaeseok-go/%EC%BB%AC%EB%A0%89%EC%85%98-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@jaeseok-go/%EC%BB%AC%EB%A0%89%EC%85%98-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Thu, 24 Jun 2021 06:56:46 GMT</pubDate>
            <description><![CDATA[<h3 id="컬렉션-프레임워크">컬렉션 프레임워크?</h3>
<p><img src="https://images.velog.io/images/jaeseok-go/post/358b439b-f08d-454d-b7af-98641d023174/image.png" alt=""></p>
<p>배열은 기본적으로 크기와 타입이 고정적이기 때문에 동적인 작업을 위한 자료구조로는 부적합하다. 자바에서는 이러한 배열을 문제점을 해소하기 위해 기본적인 자료구조 형태의 컬렉션 프레임워크를 지원하고 있다.</p>
<p>컬렉션 프레임워크는 특정 객체들을 저장, 검색, 삭제 등의 작업을 수행할 수 있는 역할을 한다. 컬렉션 프레임워크에서 제공하는 기본적인 인터페이스로는 List, Set, Map 등이 있다. 이 인터페이스들을 기준으로 각 컬렉션 클래스를 알아보자.</p>
<br>

<h3 id="list">List</h3>
<p>리스트는 저장된 객체들을 순서와 중복을 허용하는 컬렉션 인터페이스이다. 리스트 인터페이스를 구현하는 클래스로는 ArrayList, LinkedList, Vector가 있다. </p>
<h4 id="arraylist">ArrayList</h4>
<p><img src="https://images.velog.io/images/jaeseok-go/post/1c1734f5-6d27-418f-8324-2a89892bd63f/image.png" alt=""></p>
<p>ArrayList는 객체의 참조 값을 순서대로 저장하는 컬렉션 클래스이다. 기본 생성자로 ArrayList를 생성하면 기본적으로 10개의 용량을 가진채로 생성된다. 여기서 추가적으로 데이터가 add된다면 동적으로 용량이 변한다. </p>
<p>특정 인덱스의 데이터를 삭제하는 경우에는 인덱스 관리를 위해 삭제된 데이터 이후의 인덱스를 가진 모든 데이터의 인덱스를 앞으로 당겨줘야한다. 데이터를 중간에 삽입하는 경우도 마찬가지이다.</p>
<p>이와 같이 인덱스로 관리된다는 특징 때문에 ArrayList는 <strong>검색 작업에는 강점을 보이나, 삭제, 삽입 작업에는 비효율적</strong>이다.</p>
<h4 id="vector">Vector</h4>
<p>Vector는 ArrayList와 동일한 내부 구조를 가지고 있다. 하지만 ArrayList와 다른 점은 Vector는 메소드들이 동기화되어 있기 때문에 멀티 스레드 작업 시 Vector 인스턴스는 Thread Safe하게 사용할 수 있다. </p>
<h4 id="linkedlist">LinkedList</h4>
<p>LinkedList는 ArrayList와 사용 방법은 비슷하지만 내부 구조는 큰 차이를 가지고 있다. ArrayList는 내부적으로 인덱스를 가지고 객체를 관리하지만, LinkedList는 인접 참조를 활용해서 객체를 관리한다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/d75c16cd-b5f4-43df-800a-7c5ff0a919d5/image.png" alt=""></p>
<p>위의 그림을 보면 쉽게 이해할 수 있다. 각 노드는 객체의 참조값(데이터) 뿐만 아니라 다음 노드를 가르키는 참조값도 가지고 있다. 따라서 각 노드의 순서는 위와 같은 링크로 관리된다.</p>
<p>LinkedList는 검색 시 효율이 많이 떨어진다. 인덱스로 관리하는 ArrayList는 주어진 인덱스로 바로 접근할 수 있지만, LinkedList는 맨 처음 노드부터 순서대로 모든 노드를 방문해서야 원하는 노드까지 접근할 수 있다. 하지만 새로운 노드를 삽입하거나 삭제하는 경우에는 중간에 노드를 삽입 또는 삭제하고 양 옆의 노드의 인접 참조값만 수정해주면 손쉽게 작업을 수행할 수 있다.</p>
<p>이와 같이 인접 참조값으로 관리된다는 특징 때문에 <strong>LinkedList는 검색에는 비효율적이지만 삽입과 삭제에는 효율적으로 작업을 수행할 수 있다.</strong></p>
<br>

<h3 id="set">Set</h3>
<p>Set은 List와 달리 순서와 중복을 허용하지 않는 컬렉션 인터페이스이다. 순서가 없기 때문에 인덱스를 매개변수로 가지는 메소드는 존재하지 않는다. 따라서 전체 객체를 검색하는 경우 Iterator를 활용해서 검색 작업을 수행한다. Set 인터페이스를 구현하는 컬렉션 클래스는 HashSet, LinkedHashSet, TreeSet이 있다.</p>
<h4 id="hashset">HashSet</h4>
<p>HashSet은 객체들을 순서없이 저장하고, 동일한 객체는 중복 저장하지 않는다. 여기서 중복 체크를 할 때 객체 사이의 동등 비교작업을 수행한다. 동등 비교 작업은 hashCode() 반환값 비교, equals() 반환값 비교를 순서대로 수행하고 동등한지 아닌지 결과를 도출한다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/52089973-f980-42b7-aba4-1aa6e5446a3f/image.png" alt=""></p>
<p>인텔리제이에서 HashSet 클래스에 접근해보았는데 내부적으로 HashMap 인스턴스를 하나 가지고 있고 Key에 데이터, Value에 더미 오브젝트를 넣어서 사용하고 있었다. iterator를 반환하는 경우에도 이 HashMap 인스턴스의 KeySet을 반환하는 방식으로 메소드가 구현되어 있었다.</p>
<h4 id="linkedhashset">LinkedHashSet</h4>
<p>LinkedHashSet은 객체들의 저장 순서대로 반환하는 컬렉션 클래스이다. 클래스 내부 구조를 살펴보았더니 HashSet 클래스를 상속받고 있었고 모든 생성자도 super()메소드로 HashSet의 생성자를 가져다가 썼다. 그렇다고 다른 메소드가 오버라이딩 되어있지도 않아서.. 객체 저장 순서를 어떤 식으로 보존하는지 확인하기가 어려웠다. 결국 구글링으로 구조를 찾아보았는데 아래와 같은 그림을 찾을 수 있었다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/07335a43-272e-4cf2-86ad-b210ff889b0d/image.png" alt=""></p>
<p>이 그림에 따르면 결국 Linked라는 접두어가 붙은 대로 각 노드가 인접 참조값을 가지고 순서를 유지하는 방식이었다.</p>
<h4 id="treeset">TreeSet</h4>
<p>TreeSet은 이진 트리를 기반으로 한 Set 컬렉션이다. TreeSet에서도 각 노드는 앞 뒤 노드의 인접 참조값을 가진다. 여기서 객체를 저장할 때 자동으로 정렬이 되는데 root를 기준으로 부모 노드보다 큰 경우 오른쪽, 작은 경우 왼쪽 노드로 나아가면서 정렬된다.</p>
<br>

<h3 id="map">Map</h3>
<p>Map 컬렉션 인터페이스는 Key와 Value로 이루어진 Entry를 저장하는 구조를 가지고 있다. Key, Value는 객체로 저장되며 키는 중복이 허용되지 않는다. Map 컬렉션에는 HashMap, HashTable, LinkedHashMap, Properties, TreeMap 등이 있다.</p>
<h4 id="hashmap">HashMap</h4>
<p>HashMap은 저장된 Entry의 Key 중복을 허용하지 않는다. 따라서 Entry를 저장할 때 Key의 동등 비교 작업이 선행된다. 이 동등 비교 작업은 HashSet과 같다. 먼저 hashCode() 반환값을 비교하고 equals() 반환값을 비교한다.</p>
<h4 id="hashtable">HashTable</h4>
<p>HashTable은 HashMap과 동일한 내부 구조를 가지고 있다. 유일한 차이점은 HashTable의 메소드들은 동기화되어 있다는 것이다. 이는 ArrayList와 Vector의 관계와 비슷하다. 따라서 HashTable은 Thread Safe한 작업이 필요한 경우에 사용된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[람다식]]></title>
            <link>https://velog.io/@jaeseok-go/%EB%9E%8C%EB%8B%A4%EC%8B%9D</link>
            <guid>https://velog.io/@jaeseok-go/%EB%9E%8C%EB%8B%A4%EC%8B%9D</guid>
            <pubDate>Tue, 22 Jun 2021 05:24:47 GMT</pubDate>
            <description><![CDATA[<h3 id="람다식과-함수형-프로그래밍">람다식과 함수형 프로그래밍</h3>
<p><img src="https://images.velog.io/images/jaeseok-go/post/79dafd19-a226-436f-8699-b0ac24d2713a/image.png" alt=""></p>
<p>함수평 프로그래밍은 프로그래밍 패러다임의 하나로 자료를 특정 상태를 바꾸는 것이 아닌 함수를 구현하여 실행하고 결과를 얻는 방식으로 처리하는 프로그래밍 기법이다. 함수형 프로그래밍은 병렬 처리와 이벤트 지향 프로그래밍에 적합하기에 자바 8부터 지원하기 시작했다.</p>
<p>함수형 프로그래밍을 지원하는 대표적인 기능이 람다식이다. 람다식은 익명 함수를 생성하기 위한 기능으로 자바 코드가 매우 간결해지고, 컬렉션의 요소를 쉽게 집계할 수 있도록 한다. </p>
<br>

<h3 id="람다식-기본-사용-문법">람다식 기본 사용 문법</h3>
<p>그렇다면 기본적인 람다식 사용 문법을 알아보자.</p>
<p>기본적으로 람다식은 <code>(매개변수) -&gt; {실행문}</code>의 문법을 가진다. 아래의 코드를 보자.</p>
<pre><code class="language-java">@FunctionalInterface
public interface MyFunctionalInterface {
    public void method(int x, int y);
}


MyFunctionalInterface mfi = (x, y) -&gt; { return x + y; };
int result = mfi.method(1, 2); // 3</code></pre>
<p>MyFunctionalInterface라는 인터페이스를 만들고 추상 메소드를 하나 만들었다. 그리고 이 인터페이스를 FunctionalInterface Annotation으로 함수형 인터페이스로 선언했다. (추상 메소드가 하나인 경우는 따로 명시할 필요가 없긴 하다.) 이 인터페이스의 익명 구현 객체를 만들어서 사용하고자 한다. method() 메소드를 오버라이딩해서 구현해도 되지만 코드가 많이 복잡해진다. 이 경우에는 method() 메소드에서 받고자 하는 파라미터로 어떤 자료 처리를 할 것인지 람다식으로 표현하면 위와 같이 깔끔하게 구현할 수 있다.</p>
<br>


<h3 id="함수적-인터페이스-api">함수적 인터페이스 API</h3>
<p>자바에서 한 개의 추상 메소드를 가지는 인터페이스들은 모두 람다식으로 익명 객체를 구현할 수 있다. 그런데 타입이나 파라미터의 갯수 등 여러 메소드를 함수형 프로그래밍으로 구현하기 위해 각각 인터페이스 코드를 작성해줘야 한다. 자바 8 이후로는 함수형 인터페이스 API를 제공함으로써 이러한 번거로움을 해결하기 위해 지원한다. java.util.function 표준 API 패키지로 지원하고 있으며 그 종류는 크게 Consumer, Supplier, Function, Operator, Predicate로 구분된다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/7bee8c95-11d9-44dd-bbd1-0307d31a48ea/image.png" alt=""></p>
<h4 id="consumer로-이해하기">Consumer로 이해하기</h4>
<p>Consumer는 매개값은 있고 리턴값은 없는 함수형 인터페이스이다. 파라미터의 타입과 수에 따라서 여러 Consumer들이 있다. </p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/f5121aa9-3c00-4e91-b37f-88b5d8330a66/image.png" alt=""></p>
<p>아래의 코드를 보고 Consumer를 이해해보자.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/563b330b-5c45-4e1c-9f32-173f0e3aedfd/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/c47e10ad-7193-4c8f-965f-9eb9e4fc8220/image.png" alt=""></p>
<p>람다식으로 구현할 객체의 로직(함수)는 매개 변수를 받아서 작업을 하지만 리턴값은 존재하지 않는다. 따라서 Consumer를 사용하는 것이고, 매개변수의 타입마다 각기 다른 Consumer를 사용해서 구현한다.</p>
<br>

<h3 id="함수형-인터페이스의-디폴트-메소드">함수형 인터페이스의 디폴트 메소드</h3>
<p>지금까지는 하나의 함수형 인터페이스로 로직을 구성하는 방법을 알아봤다. 하지만 andThen()과 compose()메소드를 활용하면 두 개 이상의 함수형 인터페이스로 하나의 결과 값이나 매개변수를 다시 매개 변수로 넘겨주는 기능을 사용할 수 있다. 아래의 코드를 참고해보자.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/ed8394d2-db62-4ef8-8ef0-ad42c5a9d151/image.png" alt=""></p>
<p>andThen()과 compose() 메소드는 순서가 바뀌는 것이라고 생각하면 된다. andThen()메소드를 활용한 첫번째 람다식의 경우에는 functionA가 먼저 실행되고 Address 인스턴스가 반환되어 functionB의 매개변수로 전달된다. 그래서 최종적으로 functionB가 실행되어 city 필드의 값을 반환하게 된다.</p>
<br>

<p>Predict 종류의 함수적 인터페이스는 and(), or(), negate() 디폴트 메소드를 가지고 있다. 이 메소드들은 andThen()과 compose()와 같이 두 개 이상의 함수형 인터페이스를 활용하는 기능을 제공한다. 각각 &amp;&amp;, ||, ! 연산자와 대응된다고 생각하면 된다. <img src="https://images.velog.io/images/jaeseok-go/post/afc613d4-23a8-4a96-9ac8-03c179976c9d/image.png" alt=""></p>
<p>위의 코드와 같이 두 개의 논리값을 연산하여 최종 논리값을 반환하게 된다.</p>
<br>

<h3 id="메소드-생성자-참조">메소드, 생성자 참조</h3>
<p>메소드 참조, 생성자 참조는 말 그대로 특정 클래스의 메소드나 생성자만을 참조하여 불필요한 매개 변수를 제거하는 기능이다. 아래의 코드와 같이 메소드를 참조할 수 있다.</p>
<pre><code class="language-java">IntBinaryOperator operator = Math::max;</code></pre>
<p>이렇게 선언하면 operator로 선언된 익명 객체는 두 매개 변수를 입력받아 최대값을 반환하는 로직을 가진 함수로 구현된다. 이렇게 메서드나 생성자를 참조하게 되면 기존의 코드를 재사용해서 함수형 프로그래밍을 활용할 수 있다는 장점이 있고, 코드도 훨신 깔끔해진다. 생성자를 참조하는 코드는 아래와 같다.</p>
<pre><code class="language-java">// new Member(String id)
Function&lt;String, Member&gt; function = Member::new;
Member member = function.apply(&quot;jae-seok&quot;);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭]]></title>
            <link>https://velog.io/@jaeseok-go/%EC%A0%9C%EB%84%A4%EB%A6%AD</link>
            <guid>https://velog.io/@jaeseok-go/%EC%A0%9C%EB%84%A4%EB%A6%AD</guid>
            <pubDate>Wed, 16 Jun 2021 01:06:44 GMT</pubDate>
            <description><![CDATA[<h3 id="제네릭을-사용하는-이유">제네릭을 사용하는 이유?</h3>
<p><img src="https://images.velog.io/images/jaeseok-go/post/fad94f49-21cb-4878-9584-786a8cb59638/image.png" alt=""></p>
<p>자바에서 사용되는 다양한 컬렉션 클래스나 함수형 프로그래밍 관련 클래스들에서는 제네릭 타입으로 많은 파라미터들이 선언되어 있다. 따라서 자바를 제대로 이해하려면 제네릭에 대해서 확실히 알고 넘어가야 한다.</p>
<p>그렇다면 제네릭을 사용하는 이유는 무엇일까? 첫 번째로 제네릭을 사용하면 <code>더 강한 데이터 타입 체크</code>를 할 수 있다. 제네릭을 활용한 코드에서 타입 에러가 발생하는 경우 checked exception이 발생하여 컴파일 단계에서 개발자가 타입을 체크할 수 있다. 두 번째로는 <code>캐스팅을 줄여 성능상 이점</code>을 가질 수 있다. 컬렉션 클래스를 사용하는 경우 제네릭으로 선언하여 타입 변환을 줄일 수 있다. 이 부분은 조금 넘어가서 다시 알아보자.</p>
<br>

<h3 id="제네릭-타입을-사용하는-방법">제네릭 타입을 사용하는 방법</h3>
<p>그래서 결국 제네릭 타입은 무엇인가? 제네릭 타입은 타입을 파라미터로 전달받은 클래스와 인터페이스를 말한다. 예를 들어 내가 생성한 Student라는 클래스가 있다고 하자. 이 클래스의 리스트를 만들고자 한다면 <code>List&lt;Student&gt;</code>와 같이 <code>&lt;&gt;</code>안에 리스트에 담고 싶은 타입을 파라미터로 넘겨주어 리스트를 생성한다.
<img src="https://images.velog.io/images/jaeseok-go/post/0eb1698b-0a66-4f36-bf5f-468ea3bee5c2/image.png" alt=""></p>
<p>List 인터페이스가 선언된 코드를 보면 <code>List&lt;E&gt;</code>로 어떤 타입을 리스트에 담을지는 파라미터로 넘겨 받도록 선언된 것을 확인할 수 있다. </p>
<br>


<h3 id="제네릭-타입이-캐스팅을-줄이는-이유">제네릭 타입이 캐스팅을 줄이는 이유</h3>
<p>이 부분은 코드로 보는게 훨씬 이해가 쉬울 것 같아서 먼저 코드를 보자.</p>
<pre><code class="language-java">public class FirstDeveloper {
    private Object object;
    public void set(Object object) { this.object = object; }
    public Object get() { return this.obj; } 
}

public class SecondDeveloper&lt;T&gt; {
    private T t;
    public void set(T t) { this.t = t; }
    public T get() { return this.t; }
}

public class Example {
    public static void main(String[] args){
        FirstDeveloper developer1 = new FirstDeveloper();
        developer1.set(&quot;고재석&quot;);                  // 자동 타입 변환 (String -&gt; Object)
        String name = (String) developer1.get(); // 강제 타입 변환 (Object -&gt; String)

        SecondDeveloper&lt;String&gt; developer2 = new SecondDeveloper&lt;&gt;();
        developer2.set(&quot;고재석&quot;);
        String name = (String) developer2.get();
    }
}</code></pre>
<p>위의 코드를 보면 일반 타입인 FirstDeveloper와 제네릭 타입인 SecondDeveloper라는 두 클래스를 선언했다. 그리고 각 클래스의 필드 변수를 set, get하며 타입 변환이 일어나는지 보았다. 일반 타입의 클래스에는 set하는 경우 String 타입의 변수가 Object 타입으로 자동 타입 변환이 일어났고, get하는 경우 Object 타입의 변수가 String 타입으로 강제 타입 변환이 발생했다. 하지만 제네릭 타입의 클래스에서는 어떠한 타입 변환이 발생하지 않는다.</p>
<p>어차피 한 타입으로만 특정 변수를 사용하는 경우라면 위와 같이 선언 시에 제네릭 타입으로 줄이는 것이 훨씬 효율적이다. 그렇다면 한 타입으로만 사용하는 것이 아니라면 어떨까? 이러한 경우 타입을 제한하는 방법을 사용할 수 있다.</p>
<br>

<h3 id="타입-파라미터-제한하기">타입 파라미터 제한하기</h3>
<p>타입 파라미터를 제한하는 방법으로 상속관계를 표현해서 명시한 클래스의 하위 클래스만 파라미터로 받을 수 있게 제한하는 방법이 있다.</p>
<pre><code class="language-java">public &lt;T extends Number&gt; int compare(T t1, T t2) { ... }</code></pre>
<p>위의 코드와 같이 T라는 타입 파라미터 변수가 Number의 하위 클래스만 오도록 제한할 수 있다. 따라서 compare 메소드의 파라미터는 Integer, Double등의 Number 클래스의 하위 클래스의 타입 객체만 가능하다.</p>
<p>타입 파라미너를 조금 더 세세하게 제한하는 방법이 있는데 와일드카드 타입으로 선언하는 것이다. 코드에서 ?를 와일드카드라고 부른다. 이 와일드카드를 활용하여 제네릭 타입을 선언하는 방법은 크게 세 가지가 있다. </p>
<ol>
<li>클래스명&lt;?&gt;</li>
<li>클래스명&lt;? extends 상위타입&gt;</li>
<li>클래스명&lt;? super 하위타입&gt;</li>
</ol>
<p>이와 같이 와일드카드 타입을 사용하면 super로 특정 클래스의 상위 타입으로 제네릭 타입을 제한할 수도 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커 컴포즈 사용하기]]></title>
            <link>https://velog.io/@jaeseok-go/%EB%8F%84%EC%BB%A4-%EC%BB%B4%ED%8F%AC%EC%A6%88-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jaeseok-go/%EB%8F%84%EC%BB%A4-%EC%BB%B4%ED%8F%AC%EC%A6%88-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 31 May 2021 16:03:02 GMT</pubDate>
            <description><![CDATA[<p>출처 : <a href="https://docs.docker.com/get-started">https://docs.docker.com/get-started</a></p>
<h3 id="도커-컴포즈를-사용하면-좋은-점">도커 컴포즈를 사용하면 좋은 점</h3>
<p><img src="https://images.velog.io/images/jaeseok-go/post/4eb2d69d-e663-4ffe-ab0d-2a7e0c9a0598/image.png" alt=""></p>
<p>도커 컴포즈는 다중 컨테이너 어플리케이션을 정의하고 공유하기 위해 개발된 기능이다.</p>
<p>이전에 node.js 애플리케이션과 MySQL을 각 컨테이너에 구동하기 위해서 장문의 명령어를 입력하고 네트워크를 따로 생성해주기도 하였다. 물론 컨테이너를 실행에 필요한 인자들은 입력해야겠지만 실행마다 입력하기에는 번거롭고 실수의 우려도 있다. 또한 내 프로젝트를 타인과 공유하는 경우 명령어나 세팅을 공유하여야 제대로 컨테이너가 동작할 것이다.</p>
<p>하지만 위의 그림과 같이 도커 컴포즈를 사용한다면 docker-compose.yml 파일만 제대로 작성해둔다면 다중 컨테이너를 한번에 실행할 수 있고, 타인과 내 프로젝트를 공유하는데에도 훨씬 편할 수 있다.</p>
<p>그렇다면 이렇게 좋은 도커 컴포즈를 한번 사용해보자.</p>
<br>

<h3 id="yaml-파일-만들기">YAML 파일 만들기</h3>
<p>우선 docker-compose.yml 파일을 만들어 내용을 다 입력하고 한 번 살펴보자.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/c3f345dd-d762-4ebb-b470-230f1db38f08/image.png" alt=""></p>
<p>우선 맨 위에는 도커 컴포즈 스키마 버전을 명시한다. 최신 버전은 3.8 버전이다.</p>
<p>그리고 그 아래에 각 서비스를 run 하기 위한 명령어를 명세로 나타낸다. 여기서 각 서비스는 app과 mysql이다.</p>
<p>app 서비스의 이미지, 실행 시 명령어, 포트, 작업 디렉토리, 바인드 마운트, 환경 변수 등을 명시해준다. 그리고 이어서 mysql의 이미지, 볼륨, 환경 변수를 명시해준다.</p>
<p>여기서 주의깊게 봐야할 부분은 services와 같은 layer에 명시된 volumes이다. 명령어로 어플리케이션은 구동하는 경우에는 볼륨이 따로 생성되어 있지 않으면 자동으로 생성해주었지만, 도커 컴포즈 파일에서는 만들어진  volume과 마운팅할 수 있기 때문에 mysql에서 사용하는 볼륨인 <code>todo-mysql-data</code>라는 볼륨을 하나 생성해주는 것이다.</p>
<p>그리고 명령어에서 명시했던 네트워크를 따로 만들지 않았다. 도커 컴포즈는 애초에 다중 컨테이너 어플리케이션을 위한 기능이기 때문에 자동으로 하나의 네트워크에 묶어준다. 따라서 따로 명시할 필요가 없다.</p>
<br>

<h3 id="다중-컨테이너-어플리케이션-실행">다중 컨테이너 어플리케이션 실행</h3>
<p><code>docker-compose up -d</code> 명령어로 다중 컨테이너를 실행해보자.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/7dea5ed4-bac9-4f50-bbe5-3a3ccc577a51/image.png" alt=""></p>
<p>위 사진을 참고하여 실행 로그를 보면, 맨 처음 네트워크와 볼륨이 먼저 생성된다. 그리고 각 서비스들이 구동된다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/464de3da-da88-43d0-a4bf-0daed990dcce/image.png" alt="">
그리고 <code>docker-compose logs -f</code> 명령어로 실행 로그를 확인해보면, <code>Waiting for mysql:3306...</code>이라는 로그를 볼 수 있다. 도커에서는 다른 컨테이너와 connect될 수 있는 상태까지 기다리는 기능은 없다. 하지만 node 기반의 프로젝트에서는 wait-port dependency를 사용해서 이러한 기능을 지원해준다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/8d637c28-4bc0-45f1-b843-16e195af279c/image.png" alt=""></p>
<p>어쨌든 두 개의 컨테이너가 정상적으로 동작하고 각 서비스가 <code>&lt;project-name&gt;_&lt;service-name&gt;_&lt;replica-number&gt;</code> 형식으로 네이밍 된 것을 볼 수 있다.</p>
<br>

<h3 id="다중-컨테이너-어플리케이션-종료">다중 컨테이너 어플리케이션 종료</h3>
<p>간단하다. <code>docker-compose down</code> 명령어면 어플리케이션이 종료된다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/214f3a27-4624-4157-87e7-044146fd3074/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컨테이너간 통신]]></title>
            <link>https://velog.io/@jaeseok-go/%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EA%B0%84-%ED%86%B5%EC%8B%A0</link>
            <guid>https://velog.io/@jaeseok-go/%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EA%B0%84-%ED%86%B5%EC%8B%A0</guid>
            <pubDate>Mon, 31 May 2021 07:57:42 GMT</pubDate>
            <description><![CDATA[<p>출처 : <a href="https://docs.docker.com/get-started">https://docs.docker.com/get-started</a></p>
<h3 id="여러-개의-컨테이너를-사용하는-이유">여러 개의 컨테이너를 사용하는 이유</h3>
<p><img src="https://images.velog.io/images/jaeseok-go/post/0ae84f28-8839-4c50-a9b1-3b0cdf538653/image.png" alt="">
웹 아키텍쳐에서는 여러 개의 애플리케이션이 구동되기도 한다. 특히 데이터베이스를 따로 구동하는 경우가 많다. 그렇다면 도커에서는 데이터베이스를 어디에서 구동할까? 하나의 컨테이너안에 node.js 애플리케이션과 같이 구동할까? </p>
<p>정답은 새로운 컨테이너에서 구동한다 이다. 보통 <strong>하나의 컨테이너에는 하나의 애플리케이션</strong>이 구동 되는 것이 좋다. 그 이유는 아래와 같다.</p>
<ol>
<li>데이터베이스와는 달리 프론트 엔드나 API 서버는 변동사항이 발생하는 경우가 많다.</li>
<li>분리된 컨테이너는 분리된 버전 관리가 가능하다.</li>
<li>데이터베이스가 실행되고 있는 컨테이너에는 따로 데이터베이스 관리 서비스를 실행할 수 있다.</li>
<li>한 컨테이너에는 한 프로세스만 동작하는데, 여러 프로세스가 동작하면 복잡도가 증가한다.</li>
</ol>
<p>그렇다면 여러 개의 컨테이너를 사용하는 경우에 컨테이너간 통신은 어떻게 가능할까? 그것은 컨테이너 네트워킹으로 해결할 수 있다. MySQL을 활용하여 컨테이너간 통신을 해보자.</p>
<br>

<h3 id="컨테이너-통신-해보기">컨테이너 통신 해보기</h3>
<h4 id="네트워크-생성">네트워크 생성</h4>
<p>이전에 앞서 도커 볼륨을 생성했었다. 하지만 볼륨 이외에도 <strong>네트워크</strong>라는 도커 객체가 존재한다. 네트워크를 만들어서 각 컨테이너들을 하나의 네트워크로 묶고 컨테이너 간 통신을 할 수 있다.</p>
<p>우선 <code>docker network create todo-app</code> 명령어로 네트워크를 하나 생성해보자.
<img src="https://images.velog.io/images/jaeseok-go/post/d6d1c022-aaf7-475d-8f3b-cef9f8d0edd2/image.png" alt=""></p>
<h4 id="mysql-컨테이너-실행">MySQL 컨테이너 실행</h4>
<p>MySQL 컨테이너를 실행해보자. 이미지가 없더라도 자동으로 도커 엔진이 다운받아온다. 명령어는 <code>docker run -d 
     --network todo-app --network-alias mysql 
     -v todo-mysql-data:/var/lib/mysql 
     -e MYSQL_ROOT_PASSWORD=secret 
     -e MYSQL_DATABASE=todos 
     mysql:5.7</code>
이다.<img src="https://images.velog.io/images/jaeseok-go/post/c8eb89a3-c815-4e9e-a705-62701757ce7e/image.png" alt=""></p>
<p>여기서 -e 플래그는 Enviromnemt 정보를 입력하는데 MySQL의 경우에는 MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD 등이 있다.</p>
<p>이렇게 실행된 MySQL 컨테이너에 접속해서 간단한 조회를 해보자. 명령어는 <code>docker exec -it &lt;mysql-container-id&gt; mysql -u root -p</code> 이다. 비밀번호는 컨테이너 생성시 초기에 설정해두었던 secret으로 입력하면 된다.
<img src="https://images.velog.io/images/jaeseok-go/post/46273cc5-3244-4f81-8344-b337ed9cea22/image.png" alt=""></p>
<p>이와 같이 데이터베이스를 todo-app 이라는 네트워크에 잘 실행했다.</p>
<h4 id="nodejs-앱-실행">Node.js 앱 실행</h4>
<p>예제로 사용하고 있는 node.js 애플리케이션을 실행해보자. 당연히 MySQL과 다른 컨테이너, 같은 네트워크로 실행할 것이다. 명령어는 <code>docker run -dp 3000:3000 
   -w /app -v &quot;$(pwd):/app&quot; 
   --network todo-app 
   -e MYSQL_HOST=mysql 
   -e MYSQL_USER=root 
   -e MYSQL_PASSWORD=secret 
   -e MYSQL_DB=todos 
   node:12-alpine 
   sh -c &quot;yarn install &amp;&amp; yarn run dev&quot;</code> 이다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/682fa94b-d731-456f-aeb8-9c1dd641ab7a/image.png" alt=""></p>
<p>이렇게 잘 실행된 컨테이너에서 구동되는 앱으로 접속한다. 그리고 아래의 그림과 같이 Mouse라는 아이템을 Add 했다.
<img src="https://images.velog.io/images/jaeseok-go/post/dd390439-dfa4-4252-bf0b-33663c09713e/image.png" alt=""></p>
<p>그리고 나서 MySQL로 다시 접속해보자. 명령어는 위와 같다.
<img src="https://images.velog.io/images/jaeseok-go/post/a2497831-0d43-45f5-8d14-0f3ff81c1f0e/image.png" alt=""></p>
<p>아래와 같이 name이 Mouse인 아이템이 잘 저장된 것을 확인할 수 있다.
<img src="https://images.velog.io/images/jaeseok-go/post/5c45a7a2-4e8e-4d36-849e-254a4a376181/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[볼륨과 바인드 마운트]]></title>
            <link>https://velog.io/@jaeseok-go/%EB%B3%BC%EB%A5%A8%EA%B3%BC-%EB%B0%94%EC%9D%B8%EB%93%9C-%EB%A7%88%EC%9A%B4%ED%8A%B8</link>
            <guid>https://velog.io/@jaeseok-go/%EB%B3%BC%EB%A5%A8%EA%B3%BC-%EB%B0%94%EC%9D%B8%EB%93%9C-%EB%A7%88%EC%9A%B4%ED%8A%B8</guid>
            <pubDate>Fri, 28 May 2021 06:37:49 GMT</pubDate>
            <description><![CDATA[<p>출처 : <a href="https://docs.docker.com/get-started">https://docs.docker.com/get-started</a></p>
<h3 id="컨테이너의-파일-시스템">컨테이너의 파일 시스템</h3>
<p>도커의 컨테이너는 각각 독립적인 파일 시스템을 가지고 있다. 따라서 같은 이미지로 만든 두 개의 컨테이너는 한 쪽 컨테이너에서 작업한 내용이 다른 컨테이너에 적용되지는 않는다. 도커 공식 문서에서 제공되고 있는 예제를 한번 따라해보자.</p>
<p>ubuntu 이미지를 가지고 두 개의 컨테이너를 실행할 것이다. 하나의 컨테이너에는 data.txt라는 파일을 넣을 것이고 다른 컨테이너에는 아무 파일도 넣지 않을 것이다.</p>
<p>우선 <code>docker run -d ubuntu bash -c &quot;shuf -i 1-10000 -n 1 -o /data.txt &amp;&amp; tail -f /dev/null&quot;</code> 명령어로 data.txt 파일을 하나 생성하면서 컨테이너를 실행한다. 이 때 ubuntu이미지가 없다면 도커 엔진이 알아서 다운로드 받아서 컨테이너를 실행해준다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/c177dd51-c725-4c26-b5e3-4ef46c8190e4/image.png" alt=""></p>
<p>실행한 도커 컨테이너에 <code>docker exec [container_id] cat /data.txt</code> 명령어로 data.txt 파일의 내용을 확인해보니 1978이라는 1~10000 사이의 랜덤 숫자가 출력된다.</p>
<p>그러면 다른 ubuntu 컨테이너를 실행하며 ls 명령어를 날려보자.<img src="https://images.velog.io/images/jaeseok-go/post/d925a318-4a82-4d7d-a656-73579782e64e/image.png" alt=""></p>
<p>이 컨테이너는 같은 ubuntu 컨테이너이지만 data.txt 파일이 없음을 확인할 수 있다.</p>
<br>

<h3 id="볼륨과-바인드-마운트">볼륨과 바인드 마운트</h3>
<p>위와 같이 도커 컨테이너 내의 파일 시스템은 철저하게 독립적으로 운영된다. 만약 컨테이너 안에 MySQL와 같은 DBMS가 실행되고 있다가 컨테이너를 종료하거나 삭제하는 경우 데이터가 보존되지 않을 것이다. 이러한 문제를 해결하기 위해서 도커에서는 <code>볼륨</code>과 <code>바인드 마운트</code>이라는 두 가지 기능을 제공 하고 있다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/45f9df5b-ad49-4c9c-8cee-68d358c864fe/image.png" alt=""></p>
<h4 id="볼륨을-활용한-데이터-보존">볼륨을 활용한 데이터 보존</h4>
<p>볼륨은 컨테이너 내 특정 경로와 호스트 머신과의 연결 기능을 제공한다. 연결된 컨테이너 경로에 어떤 변동사항이 있더라도 호스트 머신에 갱신되기 때문에 컨테이너가 재시작되더라도 파일이 그대로 존재하는 것을 확인할 수 있다.</p>
<p>우선 <code>docker volume create todo-db</code> 명령어로 todo-db라는 볼륨을 하나 만들어 주자.<img src="https://images.velog.io/images/jaeseok-go/post/bd93cf68-2447-43e1-b16f-12c47c10cea7/image.png" alt=""></p>
<p>컨테이너 안에서 데이터가 쌓이는 특정 경로와 이 볼륨을 마운팅 해주면 된다. <code>docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started</code> 명령어를 입력해보자. -v 플래그에 [볼륨 이름]:[컨테이너 특정 경로]를 argument로 주면 된다. 컨테이너 내부에 들어가서 db파일을 확인해보자.<img src="https://images.velog.io/images/jaeseok-go/post/3a95e6c5-e401-4f88-ba81-af094aa84daf/image.png" alt=""> 이와 같이 앱을 마운팅 했다. 그렇다면 Mouse라는 데이터를 하나 입력해보자.<img src="https://images.velog.io/images/jaeseok-go/post/de399af7-c808-41ce-8cf4-6952ad0738ba/image.png" alt=""> 그리고 컨테이너를 껐다 키더라도 Mouse라는 데이터가 담기는 todo.db라는 파일이 호스트 머신과 마운트되어 있기 때문에 데이터가 유지된다.
<img src="https://images.velog.io/images/jaeseok-go/post/041339ea-f721-427e-9077-3f98c851e939/image.png" alt=""></p>
<p>볼륨을 활용한 마운팅은 호스트 머신에 저장되는 경로를 도커 엔진이 알아서 고른다. <code>docker volume inspect todo-db</code> 명령어로 경로를 확인해볼 수 있다.
<img src="https://images.velog.io/images/jaeseok-go/post/a0835e91-35d0-4cca-b7d8-dcd272283b14/image.png" alt=""> Mountpoint가 /var/lib/docker/volumes/todo-db/_data라고 되어있는데, 도커 엔진 위에서 가상화된 VM의 경로이다. 따라서 직접 확인하기 위해서는 VM에 먼저 접속해야 한다.</p>
<h4 id="바인드-마운트을-활용한-변동사항-반영">바인드 마운트을 활용한 변동사항 반영</h4>
<p>바인드 마운트 역시 컨테이너의 특정 경로와 호스트 머신의 특정 경로를 마운팅 하는 기능이다. 하지만 호스트 머신의 특정 경로를 도커 엔진이 선택하게 하는 볼륨과 달리 개발자가 직접 호스트 머신의 경로를 설정할 수 있다. 도커 엔진 위에 가상화된 VM의 경로가 아니라 호스트의 파일 시스템 경로와 마운트 할 수 있기 때문에 변동사항을 즉시 반영하기 위해 많이 사용한다.</p>
<p>실행되고 있는 컨테이너를 다 삭제하고 node.js를 활용한 예제 어플리케이션을 가지고 실습해보자. </p>
<p>우선 <code>docker run -dp 3000:3000 
     -w /app -v &quot;$(pwd):/app&quot; 
     node:12-alpine 
     sh -c &quot;yarn install &amp;&amp; yarn run dev&quot;</code> 명령어를 입력해보자. 
 이 명령어에 대해서 설명을 하자면 -w 플래그는 워크스페이스를 지정하는 플래그이고, -v는 마운팅 플래그인데 호스트의 현재 디렉토리와 컨테이너의 /app 디렉토리를 마운팅한다. node:12-alpine은 사용되는 기본 이미지이다. 그리고 sh를 사용하여 셸을 시작하고 yarn install을 실행하여 모든 종속성을 설치 한 다음 yarn run dev를 실행한다.</p>
<p> 이 때 package.json 파일 안에는 nodemon이 설치된 것을 확인할 수 있는데, nodemon은 파일 변경 사항을 감시 한 다음 응용 프로그램을 다시 시작하는 데 유용한 도구이다.</p>
<p> <img src="https://images.velog.io/images/jaeseok-go/post/e097a094-ea35-464b-b40a-a1b1881d21bb/image.png" alt=""></p>
<p> 이렇게 실행된 어플리케이션을 확인해보면
 <img src="https://images.velog.io/images/jaeseok-go/post/7868c268-371f-440e-9860-09b39cd8c493/image.png" alt="">
 이와 같이 실행되었다. /app 경로를 마운팅했기 때문에 코드를 바꾸면 바로 변동사항이 적용될 것이다. Add Items 라는 문구를 Add로 바꿔보자.
 <img src="https://images.velog.io/images/jaeseok-go/post/6980289f-ecf8-4557-bb89-3f2ecb8f53fc/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/20c77004-51d3-4ac5-b134-b6c6396f51b3/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/658f14e3-89a9-42a9-bd09-d87854031585/image.png" alt=""></p>
<p> 이와 같이 코드를 바꾸었더니 변동사항이 바로 적용된 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멀티 스레드]]></title>
            <link>https://velog.io/@jaeseok-go/%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C</link>
            <guid>https://velog.io/@jaeseok-go/%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C</guid>
            <pubDate>Wed, 26 May 2021 14:07:48 GMT</pubDate>
            <description><![CDATA[<h3 id="프로세스와-스레드">프로세스와 스레드</h3>
<p>자바에서 사용하는 멀티 스레드를 공부하기 이전에 스레드와 프로세스가 무엇인지 한 번 짚고 넘어가자.</p>
<p><code>프로세스</code>는 운영체제 관점에서 실행 중인 프로그램이라고 볼 수 있다. 이 하나하나의 프로그램들은 CPU가 실행한다. 즉 프로세스는 CPU를 할당받아서 동작하는 작업의 단위이다. 따라서 CPU를 하나의 프로세스에 할당해서 작업을 수행한다.</p>
<p>그렇다면 <code>스레드</code>는 무엇일까? 스레드는 직역하면 <strong>실</strong>이라는 뜻으로 하나의 실행 흐름이라고 볼 수 있다. 하나의 프로세스는 최소 한 개 이상의 스레드를 가지며, 이 실행 흐름을 따라 프로세스가 동작한다. 하나의 프로세스에 두 개 이상의 스레드가 있다면, 이것이 멀티 스레드 프로세스이다. 
<img src="https://images.velog.io/images/jaeseok-go/post/ff2497b9-71fa-474a-b93d-ba5fe11869b5/image.png" alt=""></p>
<p>위의 그림을 보면 각 스레드들은 코드, 데이터, 파일과 같은 메모리 영역을 공유한다. 따라서 한 프로세스 내의 다른 메소드를 여러 스레드에서 호출하거나 스레드간 통신이 가능하다. 하지만 스택 영역은 독립적으로 할당하는 것을 볼 수 있는데 이 스택은 각 스레드 영역에서 선언하는 변수, 돌아갈 주소 등을 독립적으로 관리해야하기 때문이다.</p>
<p>그렇다면 자바에서 스레드는 어떤 개념이라고 할 수 있을까? JVM이 하나의 프로세스이고, main 스레드를 포함한 여러 실행 흐름이 스레드라고 볼 수 있다. 멀티 스레드 기능을 활용하지 않고 개발한다면 프로세스는 당연히 main 스레드만으로 동작하는 단일 스레드 프로세스로 동작하겠지만 멀티 스레드 기능을 지원하기 때문에 이를 활용할 수도 있다.</p>
<br>

<h3 id="스레드-실행">스레드 실행</h3>
<p>우선 스레드를 하나 만들어서 실행해보자.</p>
<p>스레드는 Thread라는 클래스로 생성할 수 있다. Thread 클래스는 Runnable 인터페이스를 구현하고 있는데, Runnable 인터페이스는 run()이라는 추상 메소드만을 가지고 있는 함수형 인터페이스이다.</p>
<p>주로 사용하는 Thread의 생성자는 <code>Thread(Runnable target)</code>이다. 아래의 코드와 같이 스레드 인스턴스를 생성하고 start() 메소드를 입력하면 run() 메소드의 내용을 수행하며 스레드가 시작된다.</p>
<pre><code class="language-java">
Thread thread = new Thread(()-&gt;{
    for(int i = 0; i &lt; 10; i++) {
    System.out.println(i);
    }
});
thread.start();</code></pre>
<p>그럼 두 개 이상의 스레드 인스턴스를 생성해서 실행하면 어떨까? 아래의 코드를 참고해보자.</p>
<pre><code class="language-java">public static void main(String[] args) {

    // main thread
    System.out.println(Thread.currentThread().getName() + &quot; Start ==================&quot;);


    // beep thread
    Thread beepThread = new Thread(()-&gt;{
        Toolkit toolkit = Toolkit.getDefaultToolkit();

        for(int i = 0; i &lt; 5; i++){
            toolkit.beep();
            try {Thread.sleep(500); } catch (Exception e) {e.printStackTrace();}
        }
    });
    beepThread.setName(&quot;beepThread&quot;);
    System.out.println(beepThread.getName() + &quot; Start ==================&quot;);
    beepThread.start();


    // print thread
    Thread printThread = new Thread() {
        @Override
        public void run() {
            for (int i = 0; i &lt; 5; i++){
                System.out.println(&quot;땡&quot;);
                try {Thread.sleep(500); } catch (Exception e) {e.printStackTrace();}
            }
        }
    };
    System.out.println(printThread.getName() + &quot; Start ==================&quot;);
    printThread.start();
}</code></pre>
<p>우선 Thread의 전역 메소드인 currentThread()를 사용해서 현재 스레드의 이름을 get 했다. 이 때는 main이라고 출력이 되었을 것이다.</p>
<p>그리고 0.5초 단위로 출력음이 들리는 스레드와, &quot;땡&quot;이라는 글자가 출력되는 스레드를 동시에 실행시켜서 소리가 들리며 콘솔에 출력되게끔 구현했다.</p>
<br>


<h3 id="동기화">동기화</h3>
<p>위의 설명에서 스레드는 메모리를 공유하는 것이라고 말했었다. 그렇다면 하나의 변수나 객체에 두 개 이상의 스레드가 작업을 수행하면 어떻게 될까? 당연히 무결성이 깨질 것이다.</p>
<p>예를 들어 A 스레드가 object라고 하는 객체에 접근해서 데이터를 10으로 바꾸고 출력한다고 하자. 그리고 B 스레드가 object 객체의 데이터를 20으로 바꾸고 출력한다. 이 두 스레드가 동시에 object 객체에 접근해서 A가 10으로 바꾸고 출력하기 직전 B가 20으로 바꾼다면? A는 본인의 작업을 성실히 수행했지만 출력값은 20으로 받을 것이다.</p>
<p>이와 같이 공유 객체를 사용하는 경우에는 동기화가 필수적이다. 방법은 간단하다. 공유 객체에 접근하는 method에 synchronized라고 명시해주거나 따로 블록을 만들면 된다. 아래의 코드를 참고해보자.</p>
<pre><code class="language-java">public static void main(String[] args) {
    Calculator calculator = new Calculator();

    Thread user1 = new Thread(()-&gt;{
        calculator.setMemory(100);
    });
    user1.setName(&quot;User1&quot;);
    user1.start();


    Thread user2 = new Thread(()-&gt;{
        calculator.setMemory(50);
    });
    user2.setName(&quot;User2&quot;);
    user2.start();
}


public static class Calculator {
    private int memory;

    public synchronized void setMemory(int memory) {
        this.memory = memory;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {}
        System.out.println(Thread.currentThread().getName() + &quot;: &quot; + this.memory);
    }
}</code></pre>
<p>연산을 수행하는 객체인 Calculator 객체의 setMemory 메소드에 <code>synchronized</code>라고 명시해준 것을 볼 수 있다. 이 경우 user1 스레드가 먼저 작업을 수행하고 모든 작업이 끝날 때 까지 user2 스레드는 기다리게 된다. 따라서 정상적으로 출력이 끝난 후에 user2의 작업이 실행된다.</p>
<br>


<h3 id="스레드-상태">스레드 상태</h3>
<p>스레드는 실행되는 상태만 존재하는 것이 아니다. 다른 스레드가 작업 중이면 기다리기도 해야하고 임의로 스레드를 종료할 수도 있다. 멀티 스레드를 활용한 프로그래밍을 위해서는 스레드의 라이프사이클을 이해하고 각 상태에 대한 지식이 있어야 한다. 아래의 그림을 참고하여 스레드의 다양한 상태를 알아보자.
<img src="https://images.velog.io/images/jaeseok-go/post/cdbd9700-7a24-4adb-9d88-a97fe346eff2/image.png" alt=""></p>
<ol>
<li>NEW : 객체 생성 상태, 스레드 객체가 생성되고 아직 start() 되지 않은 상태</li>
<li>Runnable : 실행 상태로 언제든지 갈 수 있는 상태</li>
<li>Blocked : 사용하고자 하는 객체의 락이 풀릴 때까지 기다리는 상태</li>
<li>Waiting : 다른 스레드가 통지할 때까지 기다리는 상태</li>
<li>Timed_Waiting : 주어진 시간동안 기다리는 상태</li>
<li>Terminated : 실행이 끝난 상태</li>
</ol>
<p><img src="https://images.velog.io/images/jaeseok-go/post/b1f0df5f-8fae-4dfa-a8df-a00f59ae792b/image.png" alt=""></p>
<p>위와 같이 Thread 클래스의 열거 상수로 각 상태가 명시되어 있다. 그렇다면 각 상태에서 스레드를 어떤 식으로 제어하면 될까?</p>
<h4 id="sleep">sleep()</h4>
<p>스레드의 정적 메소드인 sleep() 메소드로 스레드를 주어진 시간동안 일시정지 할 수 있다. </p>
<p><code>Thread.sleep(100);</code>과 같은 코드로 실행 상태에서 timed_waiting 상태로 전이시킨다.</p>
<h4 id="yield">yield()</h4>
<p>스레드의 정적 메소드인 yield() 메소드로 다른 스레드에게 실행을 양보할 수 있다.
<code>Thread.yield();</code>로 runnable 상태의 다른 스레드가 running 되게 한다. 보통 무의미한 작업을 반복하며 기다리는 경우 다른 스레드에게 실행을 넘겨 효율적으로 작업 시간을 분배하기 위해 사용한다.</p>
<h4 id="join">join()</h4>
<p>스레드의 클래스의 join() 메소드로 다른 스레드의 종료를 기다릴 수 있다. 다른 스레드의 작업이 선행되어야 하는 경우 <code>otherThread.join();</code>로 다른 스레드가 terminated될 때까지 현재 스레드는 기다린다.</p>
<h4 id="wait-notify-notifyall">wait(), notify(), notifyAll()</h4>
<p>두 개 이상의 스레드를 번갈아가며 실행하는 경우, 자신을 일시 정지시키는 wait() 메소드와 waiting 중인 스레드 하나를 runnable로 만드는 notify()나 waiting 중인 모든 스레드를 runnable로 만드는 notifyAll() 메소드를 사용할 수 있다.</p>
<h4 id="interrupt">interrupt()</h4>
<p>스레드를 종료하기 위해서 interrupt() 메소드를 사용하면 된다. 스레드가 일시 정시 상태일 때 interrupt() 메소드를 사용하면 InterruptedException이 발생해서 예외 처리 블록으로 이동하게 된다. 하지만 스레드가 일시 정지 상태가 아니라면 추후에 일시 정지 상태가 되는 경우에 예외가 발생한다. 따라서 실행하다가 스레드가 바로 종료되버리면 interrupt() 메소드는 의미가 없다.
<br></p>
<h3 id="스레드-풀">스레드 풀</h3>
<p><img src="https://images.velog.io/images/jaeseok-go/post/ac8d2a88-264e-4aa9-9fa1-86e4096e0969/image.png" alt=""></p>
<p>여러 개의 작업이 주어질 때 모든 작업에 스레드가 생기면 스레드 생성 및 스케줄링으로 CPU에 오버헤드가 발생하고 성능이 저하될 수 있다. 이러한 경우에 사용하면 좋은 것이 스레드 풀이다.</p>
<p>스레드 풀은 스레드의 갯수를 제한해두고 그 이상의 스레드가 생성되지 않도록 한다. 따라서 작업 요청이 폭증하더라도 급격한 성능 저하를 방지할 수 있다.</p>
<p>자바에서는 ExecutorService 인터페이스와 Executors 클래스를 제공함으로써 스레드풀을 생성할 수 있도록 한다. ExecutorService 구현 객체는 Executors 클래스의 메소드로 생성할 수 있다. </p>
<pre><code class="language-java">        ExecutorService executorService = Executors.newFixedThreadPool(
                Runtime.getRuntime().availableProcessors()
        );

        executorService.shutdown();</code></pre>
<p>newFixedThreadPool() 메소드는 최대 스레스 수를 파라미터로 받아 스레드 풀을 생성한다. 그리고 shutdown() 메소드로 남아있는 작업을 마무리하고 스레드 풀을 종료한다. shutdownNow() 메소드는 지금 당장 강제 종료하는 메소드이다.</p>
<h4 id="작업-처리">작업 처리</h4>
<p>각 스레드가 처리할 작업은 Runnable / Callable 인터페이스를 구현해서 만들어준다. 리턴 값이 없는 경우에는 Runnable, 있는 경우는 Callable 인터페이스를 사용한다. Callable 타입의 제네릭은 리턴 값의 타입으로 명시해주면 된다.</p>
<p>이렇게 생성한 작업을 스레드 풀에 넣어주는 메소드가 두 가지 있다. execute(), submit()이다. 이 두 메소드의 차이점은 execute()는 리턴 값이 없는 메소드이고, submit()은 Future 타입의 리턴 값을 반환하는 메소드이다. </p>
<p>그리고 작업 처리 도중 예외가 발생하는 경우 execute() 타입은 스레드가 종료되고 스레드가 스레드 풀에서 삭제된다. 그리고 다른 작업 처리를 위해 스레드가 생성된다. 하지만 submit() 메소드는 스레드가 종료되지 않고 다른 작업을 위해 재사용된다. 따라서 submit()을 사용하는 것이 스레드 생성 오버헤드를 줄이는데에 효과적이다.</p>
<pre><code class="language-java">public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(2);

    for(int i = 0; i &lt; 10; i++){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;

                int poolSize = threadPoolExecutor.getPoolSize();
                String threadName = Thread.currentThread().getName();
                System.out.println(&quot;[총 스레드 개수 : &quot; + poolSize + &quot; ] 작업 스레드 이름 : &quot; + threadName);

                int value = Integer.parseInt(&quot;삼&quot;);
            }
        };

        Future future = executorService.submit(runnable);
        Thread.sleep(10);
    }
    executorService.shutdown();
}</code></pre>
<p><img src="https://images.velog.io/images/jaeseok-go/post/fffab455-6e1e-45dd-9784-56bfcb44f02d/image.png" alt=""></p>
<p>스레드가 종료되지 않고 같은 스레드를 반복해서 사용하는 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커 시작하기]]></title>
            <link>https://velog.io/@jaeseok-go/%EB%8F%84%EC%BB%A4-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jaeseok-go/%EB%8F%84%EC%BB%A4-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 24 May 2021 05:40:49 GMT</pubDate>
            <description><![CDATA[<p>출처 : <a href="https://docs.docker.com/get-started/02_our_app/">https://docs.docker.com/get-started/02_our_app/</a></p>
<h3 id="컨테이너-실행하기">컨테이너 실행하기</h3>
<p>Node.js 예제를 활용하여 도커를 시작해보자. JavaScript에 대한 사전 지식이 없더라도 크게 문제가 없을 것이다. 우선 아래의 링크에서 도커 공식문서가 제공하는 github repository를 clone하고 인텔리제이로 열어보자.</p>
<blockquote>
<p><strong>docker / getting-started</strong>
<a href="https://github.com/docker/getting-started">https://github.com/docker/getting-started</a></p>
</blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/690d3ca8-972f-46d3-b911-d01b8482486c/image.png" alt=""></p>
<p>이 예제를 컨테이너 안에서 실행하기 위해서는 우선 도커 이미지가 필요하다. 도커 이미지를 빌드하기 위해서는 Dockerfile이라는 파일이 필요하다. Docker 파일은 텍스트 기반의 이미지 빌드 스크립트이다.</p>
<p>그렇다면 package.json 파일과 같은 경로에 Dockerfile이라는 파일을 하나 만들고 아래와 같이 입력해보자.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/d166c168-ec6c-4daa-b470-52a9d58d640d/image.png" alt=""></p>
<p>이와 같이 Dockerfile을 만들었다면 이미지를 만들기 위해 PowerShell을 키고 Dockerfile이 있는 경로로 이동해보자.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/a44b8ec5-5d08-45e5-9cd1-61dc4199fede/image.png" alt=""></p>
<p><code>docker build -t getting-started .</code> 명령어를 입력하면 도커 이미지가 빌드된다.</p>
<p>도커 이미지가 빌드되면서 많은 레이어가 다운로드된다. 이는 <code>FROM node:12-alpine</code> 스크립트를 명시했기 때문에 해당 이미지를 다운로드 받았기 때문이다. 그리고 <code>CMD [&quot;node&quot;, &quot;src/index.js&quot;]</code> 스크립트는 컨테이너에서 이미지를 실행할 때 실행할 기본 명령어를 명시한 부분이다.</p>
<p>빌드 명령어에서 -t 는 태그 플래그이며 이미지를 찾을 태그를 명시하는 플래그이다. 명령의 끝의 <code>.</code>는 현재 위치에서 Dockerfile을 찾으면 된다는 의미이다.</p>
<br>

<p>그럼 이제는 컨테이너를 실행시켜볼 차례이다. <code>docker run -dp 3000:3000 getting-started</code> 명령어를 입력해보자. 참고로 -d 플래그는 백그라운드에서 실행시키는 플래그이고, -p 플래그는 포트번호를 명시하는 플래그이다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/e78845df-2227-49d6-92e3-c81e3022635b/image.png" alt=""></p>
<p>위와 같이 컨테이너가 실행된 것을 확인했다면, <a href="http://localhost:3000">http://localhost:3000</a> 으로 이동하면 실행된 어플리케이션을 확인할 수도 있다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/9d67fe09-aa31-4888-9ea6-9564e746466f/image.png" alt=""></p>
<br>

<h3 id="변경사항-적용하기">변경사항 적용하기</h3>
<p>이렇게 컨테이너에서 동작하는 어플리케이션에 대해 변경사항이 발생했다고 생각해보자. 그렇다면 어떻게 해야할까? </p>
<p>우선 변경사항을 코드에 그대로 적용하고 바로 이미지를 빌드하면 된다. <code>docker build -t getting-started .</code> 명령어를 활용하자.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/ed3cb682-6370-4685-a4c1-9266674f63a0/image.png" alt="">
이미지를 빌드하고 나니 getting-started라는 이미지를 그대로 생성했고, 원래 빌드되어있던 이미지의 이름이 없어졌다.</p>
<p>그리고 <code>docker run -dp 3000:3000 getting-started</code> 명령어로 바로 컨테이너를 실행시키면 에러가 발생한다. 
<img src="https://images.velog.io/images/jaeseok-go/post/b4aae33f-12f1-45bd-bd24-497cabf1717c/image.png" alt=""></p>
<p>내용을 간략하게 해석해보자면 3000번 포트가 이미 할당되어 있기에 바인딩 에러가 발생했다는 뜻이다. 그렇다면 두 가지 방법이 있을 것 같다. 첫 번째는 다른 포트를 할당받아서 어플리케이션을 구동하는 방법이고 두 번째는 기존의 컨테이너를 삭제하는 방법이다.</p>
<h4 id="다른-포트-할당받기">다른 포트 할당받기</h4>
<p><code>docker run -dp 4000:3000 getting-started</code> 명령어를 입력한다. 여기서 -p 플래그 뒤의 포트 바인딩  argument는 [호스트 포트] : [컨테이너 내부 포트]이다. 즉, 4000:3000은 호스트 4000포트에 컨테이너 내부의 3000포트를 바인딩한다는 뜻이다.
<img src="https://images.velog.io/images/jaeseok-go/post/13bad4dc-4f08-4732-a43d-e3932318d8b9/image.png" alt=""></p>
<p>이렇게 어플리케이션을 실행시키면 3000포트, 4000포트 둘 다 어플리케이션이 떠있는 것을 확인할 수 있다.<img src="https://images.velog.io/images/jaeseok-go/post/096aebc7-056a-4272-a882-e07088a88619/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/f6a4926f-acc2-4fd9-87b5-317125a35ce0/image.png" alt=""></p>
<p>4000포트에는 변경사항이 적용된 것도 확인할 수 있다.</p>
<h4 id="컨테이너-삭제하기">컨테이너 삭제하기</h4>
<p>우선 구동 중인 컨테이너들을 확인하고 삭제할 컨테이너를 찾아야 할 것이다. <code>docker ps</code> 명령어로 구동 중인 컨테이너 리스트를 조회해보자.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/c6fe784d-8bf4-4ef6-8d50-746f8d27c72e/image.png" alt=""></p>
<p>각 컨테이너마다 가지고 있는 아이디로 컨테이너를 삭제할 수 있다. 우선 컨테이너 실행을 멈추어야 삭제가 가능하다. <code>docker stop [container_id]</code> 명령어로 실행을 멈출 수 있다. 이 때 컨테이너 아이디는 모두 다 입력할 필요는 없고 앞자리만 일부 입력해도 알아서 컨테이너를 찾아준다.
<img src="https://images.velog.io/images/jaeseok-go/post/f4c04c92-3db0-4fa8-91fe-96b2534d4dfe/image.png" alt=""></p>
<p>그리고 멈춘 컨테이너를 <code>docker rm [container_id]</code> 명령어로 삭제한다.</p>
<p>그렇다면 이제 변경 사항이 적용된 어플리케이션을 다시 run 해보자. 명령어는 <code>docker run -dp 3000:3000 getting-started</code>이다.<img src="https://images.velog.io/images/jaeseok-go/post/aa944aa4-0a1d-4717-97e6-82bababbdd4e/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/de91a070-99fc-4a63-bead-3f603d6d189a/image.png" alt=""></p>
<p>내가 원하는 3000번 포트에 변경사항이 적용된 어플리케이션이 제대로 동작하는 것을 확인할 수 있다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커 개요]]></title>
            <link>https://velog.io/@jaeseok-go/%EB%8F%84%EC%BB%A4-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@jaeseok-go/%EB%8F%84%EC%BB%A4-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Sun, 23 May 2021 15:04:16 GMT</pubDate>
            <description><![CDATA[<p>출처 : <a href="https://docs.docker.com/">https://docs.docker.com/</a></p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/05a8a97e-a493-47cd-9db1-0401760a7fcb/image.png" alt=""></p>
<h3 id="도커란">도커란?</h3>
<p>도커란 어플리케이션의 개발, 실행, 배포를 위한 개방형 플랫폼이다. 도커는 나의 어플리케이션에 독립적인 환경을 제공하여 플랫폼 독립적으로 어플리케이션을 개발할 수 있게 도와준다.</p>
<h3 id="도커를-사용하는-이유">도커를 사용하는 이유</h3>
<p>그렇다면 도커를 사용해야하는 이유는 무엇일까? 우선 가상 머신과 비교해보자.</p>
<p>가상 머신은 도커에 비해 더 강력하게 격리된다. 가상화된 하드웨어 위에 각 머신마다 OS가 독립적으로 올라간다.</p>
<p>그리고 도커는 하드웨어 위에 올라가 있는 하나의 OS 위에서 도커 엔진이 가상화된 컨테이너를 구분하여 격리한다.</p>
<p>따라서 컨테이너가 가상머신보다 더 공간을 적게 차지하고, 더 빠르다. 왜냐하면 OS 자체를 가상화하는데 사용되는 저장공간이 굉장히 크기 때문이고, 그만큼 커널에서 처리해야하는 부분이 줄기 때문이다.</p>
<p>하지만 도커를 사용하는 가장 큰 이유는 지속적 통합/배포에 용이하기 때문이다. 개발자는 로컬에서 작업한 내용을 다른 동료와 환경에 구애받지 않고 작업물을 쉽게 공유할 수 있다. 공유한 내용을 바로 테스트하고 push만 한다면 간단하게 수정사항을 배포할 수 있다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/cb3d7c23-23cc-4cb2-97e0-739d315c684c/image.png" alt=""></p>
<h3 id="이미지와-컨테이너">이미지와 컨테이너</h3>
<h4 id="이미지">이미지</h4>
<p>도커 이미지는 어플리케이션과 실행 환경을 묶어놓은 도커 오브젝트이다. 이미지는 내가 만들 수도, 다른 사람이 잘 만들어 놓은 것을 가져다 쓸 수도 있다. 내가 이미지를 만들기 위해서는 Dockerfile이라고 하는 명세 파일을 만들어야 한다. 이 파일이 있는 경로에서 build한다면 내가 원하는 어플리케이션과 실행 환경을 가지고 있는 이미지를 만들어 낼 수 있다. </p>
<h4 id="컨테이너">컨테이너</h4>
<p>이렇게 만든 이미지를 실행하는 환경은 컨테이너이다. 컨테이너는 도커 엔진이 가상으로 격리해놓은 환경으로 개발자가 컨테이너의 라이프 사이클을 관리할 수 있다. 호스트 환경과 격리되어 있기 때문에 호스트 환경 대신 컨테이너 환경에서 필요한 라이브러리나 OS를 pull 받아서 독립적으로 실행된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[예외]]></title>
            <link>https://velog.io/@jaeseok-go/%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-%EA%B8%B0%EB%B2%95</link>
            <guid>https://velog.io/@jaeseok-go/%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-%EA%B8%B0%EB%B2%95</guid>
            <pubDate>Thu, 20 May 2021 14:59:56 GMT</pubDate>
            <description><![CDATA[<h3 id="예외란">예외란?</h3>
<p>예외(Exception)란 무엇일까? 사용자의 잘못된 조작이나 개발자의 잘못된 코딩으로 발생하는 프로그램 오류를 말한다. 예외가 발생하면 프로그램은 바로 종료된다. 하지만 예외 처리를 해줌으로써 프로그램이 종료되지 않고 다른 동작을 하게끔 할 수 있다.</p>
<h3 id="exception의-종류">Exception의 종류</h3>
<h4 id="checked-exception">checked exception<img src="https://images.velog.io/images/jaeseok-go/post/d8425688-c590-4b50-9d04-dd0f43dbd049/image.png" alt=""></h4>
<p>공식문서에 따르면 Exception과 그 하위 클래스들은 Throwable의 하위 클래스이며, 합리적인 애플리케이션이라면 catch하고 싶어하는 조건들을 명시한다고 한다.
즉, 예외적인 상황이 발생해서 프로그램 오류가 발생하는 경우 Exception이 발생하게끔 특정 조건들이 명시되어있다는 뜻이다.</p>
<p>그리고 RuntimeException을 제외한 모든 하위 클래스와 Exception 클래스는 checked exceptions로 표현한다. 만약 메소드나 생성자의 실행으로 checked exception이 throw 될 수 있거나 그 범위 밖으로 전달될 수 있는 경우에, checked exception은 메소드나 생성자의 throws 절에 선언되어야한다고 한다.
즉, 예외가 발생되는 경우 throw 절에 exception class를 명시해서 예외를 날려주어야한다는 뜻이다.</p>
<h4 id="unchecked-exception">unchecked exception<img src="https://images.velog.io/images/jaeseok-go/post/9aee8223-e939-47f8-b06d-3226bfe672cb/image.png" alt=""></h4>
<p>RuntimeException은 JVM의 실행 도중에 발생될 수 있다고 한다.</p>
<p>그리고 RuntimeException과 그 하위 클래스들을 unchecked exceptions로 표현한다. checked exception과 반대로 예외가 발생되는 경우 예외를 날려주지 않아도 된다고 한다.</p>
<br>

<h3 id="자주-발생하는-unchecked-exception의-종류">자주 발생하는 unchecked exception의 종류</h3>
<h4 id="nullpointerexception">NullPointerException</h4>
<p>인스턴스의 참조 값에 null이 들어가는 경우 .를 사용한 경우에 발생한다.</p>
<h4 id="indexoutofboundsexception">IndexOutOfBoundsException</h4>
<p>배열에서 인덱스 범위를 초과하여 사용한 경우에 발생한다.</p>
<h4 id="numberformatexception">NumberFormatException</h4>
<p>문자열을 숫자도 변환하는 과정에서 숫자로 변환이 안되는 문자열이 포함된 경우에 발생한다.</p>
<h4 id="classcastexception">ClassCastException</h4>
<p>상속/구현 관계의 클래스가 아닌 클래스끼리 타입을 변환하고자하는 경우에 발생한다.</p>
<br>

<h3 id="예외-처리-코드">예외 처리 코드</h3>
<p>이와 같이 예외가 발생한 경우에 어떤 식으로 처리해주어야 할까? 보통은 try-catch-finally 블록을 이용한다.<img src="https://images.velog.io/images/jaeseok-go/post/6f0826a0-595b-454c-a901-a712ff005460/image.png" alt="">위의 그림과 같이 catch문 안에 예외가 발생할 경우 실행할 코드를 입력하면 된다.</p>
<pre><code class="language-java">try {
     StringBuilder sb = null;
     System.out.println(sb.toString());
}

catch(NullPointerException e){
     System.out.println(&quot;NullPointerException 발생!!&quot;);
}

finally{
     System.out.println(&quot;---end---&quot;);
}</code></pre>
<p>따라서 위와 같은 코드를 작성하면 </p>
<pre><code>NullPointerException 발생!!
---end---</code></pre><p>이와 같은 결과 값이 출력될 것이다.</p>
<br>

<h3 id="catch문-활용">catch문 활용</h3>
<p>try-catch-finally 블록에서는 catch문을 여러 개 선언하여 다양한 예외에 대해 각각 알맞은 처리를 명시할 수 있다. 하지만 여기서 주의해야할 부분이 있다.</p>
<p>catch문은 위에서부터 순서대로 실행된다. 상위 클래스의 Exception이 제일 위에 있다면 하위 클래스의 Exception이 발생했을 때에도 위에 있는 catch문에 걸려 아래의 catch문까지 도달하지 못한다. 따라서 아래의 코드와 같이 Exception의 순서를 하위 -&gt; 상위 순으로 나열해야한다.</p>
<p>또한 하나의 catch문 안에 여러 개의 예외를 처리할 수도 있다. 처리할 예외들을 | 로 묶어서 명시하면 된다.</p>
<pre><code class="language-java">try {
     StringBuilder sb = null;
     System.out.println(sb.toString());
}

catch(NullPointerException | IndexOutOfBoundsException e){
     System.out.println(&quot;NullPointerException or IndexOutOfBoundsException 발생!!&quot;);
}

catch(Exception e){
     System.out.println(&quot;Exception 발생!!&quot;);
}

finally{
     System.out.println(&quot;---end---&quot;);
}</code></pre>
<br>

<h3 id="자동-리소스-닫기">자동 리소스 닫기</h3>
<p>try-with-resources를 사용하면 사용했던 resource 객체를 자동으로 닫아준다.</p>
<p>이게 무슨 말이냐면 기존의 try-catch-fianlly 문법에서 try 블록에서 사용하던 리소스 객체(스트림, 소켓 등)를 finally 블록에서 close() 해주어 리소스 관리를 하게끔 했었다. 이 때 close 메소드도 예외처리가 되어야 하기 때문에 finally 블록 안에 다시 try-cath-finally 문이 같이 입력되곤 했었다.</p>
<p>하지만 try-with-resources는 이와 같이 번거롭고 코드가 지저분해지는 작업을 줄여준다.</p>
<pre><code class="language-java">try(FileInputStream fis = new FileInputStream(&quot;file.txt&quot;)) {
...
}

catch(IOException e){
...
}</code></pre>
<p>이와 같이 오히려 finally 블록을 없애버릴 수도 있다. try 블록이 실행이 완료되거나 예외가 발생하면 자동으로 close 메소드가 호출되고 그 이후에 catch나 finally블록이 실행된다.</p>
<br>

<h3 id="throw-절-활용">throw 절 활용</h3>
<p>throw는 예외를 떠넘기기 위한 문법이다. try-catch-finally처럼 예외를 직접 처리해주는 것이 아니라 메소드나 생성자를 호출한 곳에서 처리하게끔 하는 것이다. </p>
<p>예를 들어 main 메소드가 A 메소드를 호출해서 예외가 발생했다. 이 때 A 메소드는 이 예외를 throw했다. 그렇다면 이 예외는 main 메소드에서 처리해주어야 하는 것이다. 그렇다면 main 메소드에서 throw하면 어떻게 될까? 그렇다면 결국 JVM이 예외를 처리해주어 콘솔에 예외 정보를 찍어준다.</p>
<pre><code class="language-java">
public static void main(String[] args) throws ClassNotFoundException {
    findClass();
}

public static void findClass() throws ClassNotFoundException {
    Class cls = Class.forName(&quot;java.lang.String2&quot;);
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[연관관계 매핑]]></title>
            <link>https://velog.io/@jaeseok-go/%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91</link>
            <guid>https://velog.io/@jaeseok-go/%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91</guid>
            <pubDate>Tue, 27 Apr 2021 16:15:20 GMT</pubDate>
            <description><![CDATA[<h3 id="단방향-매핑">단방향 매핑</h3>
<p>데이터베이스에서 두 개의 테이블은 서로 연관 관계를 가지기도 한다. 예를 들어 Member table에는 Job table에서 member의 job을 참조할 job_id를 외래키로 가지게 된다. 이러한 경우에 두 테이블은 서로 연관 관계를 가진다고 한다.</p>
<p>데이터베이스에서는 job_id를 가지고 두 개의 테이블에서 원하는 결과를 가져올 수 있다. 하지만 객체에서 하나의 필드로 원하는 결과를 가져오기 위해서는 상당히 지저분하거나 복잡한 로직이 필요하다. 대신 다른 엔티티의 참조 값을 가지고 있음으로써 간단하게 나와 연관관계가 있는 엔티티의 정보를 가져올 수 있다. 엔티티에서의 연관관계 매핑은 이런식으로 이루어진다.</p>
<p>기본적으로 아래와 같은 테이블이 있다고 생각해보자</p>
<pre><code class="language-sql">CREATE TABLE MEMBER (
    ID INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(50) NOT NULL,
    JOB_ID INT,
    FOREIGN KEY (&#39;JOB_ID&#39;) REFERENCES JOB(&#39;ID&#39;);
)


CREATE TABLE JOB (
    ID INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(50) NOT NULL,
    AVG_SALARY INT
)</code></pre>
<p>데이터베이스에서 id가 1인 멤버의 job name을 알고 싶다고 하자. 그렇다면 아래와 같은 쿼리를 작성해야 할 것이다.</p>
<pre><code class="language-sql">SELECT NAME
FROM JOB
WHERE ID = (SELECT JOB_ID
        FROM MEMBER
        WHERE ID = 1);

OR

SELECT J.NAME
FROM MEMBER M LEFT OUTER JOIN JOB J
ON M.JOB_ID = J.ID
WHERE M.ID = 1;</code></pre>
<p>데이터베이스에서는 job_id라는 외래키 컬럼을 가지고 이렇게 조회할 수 있다. 그렇다면 JPA에서는 어떨까?</p>
<pre><code class="language-java">Member member = em.find(Member.class, 1);
String jobName =  member.getJob().getName();</code></pre>
<p>JPA에서도 생각보다 간단하게 조회가 가능하다. 이것이 다 연관관계를 매핑해놓았기 때문이다.</p>
<pre><code class="language-java">@Entity
public class Member{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;)
    private int id;

    @Column(name = &quot;name&quot;)
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;job_id&quot;)
    private Job job;
}</code></pre>
<p>위의 Member 엔티티 코드에서는 Job을 인스턴스 참조 값으로 그대로 가진다. 따라서 위의 코드 처럼 객체를 탐색하여 손쉽게 job name을 찾을 수 있었던 것이다.</p>
<p>이 경우에는 Member -&gt; Job으로의 한 방향의 연관관계만 정해져있다. 이러한 연관관계를 단방향 연관관계라고 한다.</p>
<br>

<h3 id="매핑-어노테이션">매핑 어노테이션</h3>
<h4 id="manytoone">@ManyToOne</h4>
<p>테이블 간의 연관관계는 일대일, 일대다, 다대다로 구분지을 수 있다. 한쪽 테이블에서 일대다라면 반대쪽에서는 다대일 관계이다.</p>
<p>위의 코드에서 @ManyToOne이라는 어노테이션을 보았을 것이다. 이 어노테이션을 그대로 해석하자면 다대일 관계라는 뜻이다. 어떤 하나의 job을 가진 member는 여러 명이 될 수 있다. 반면에 하나의 member는 하나의 job만을 가진다. 따라서 member 입장에서는 다가 되고 job 입장에서는 일이 된다.</p>
<p>비슷한 어노테이션으로는 @OneToOne, @OneToMany, @ManyToMany가 있으면 연관관계를 적절하게 판단하여 사용하면 된다.</p>
<h4 id="joincolumn">@JoinColumn</h4>
<p>위 코드의 어노테이션 중에 @JoinColumn이라는 어노테이션을 보았을 것이다. 이 어노테이션은 엔티티에서 참조 값으로 가지고 있는 이 연관관계를 데이터베이스의 컬럼과 매핑하는 어노테이션이다. 즉, 자바의 Job 과 데이터베이스의 job_id를 매핑해준다.</p>
<p>이 어노테이션을 생략하면 [필드명]_[컬럼명]을 사용해서 자동으로 매핑해준다. 즉 job_id로 자동으로 매핑해준다.
<br></p>
<h3 id="양방향-매핑">양방향 매핑</h3>
<p>두 개의 엔티티의 매핑은 단방향 매핑만 존재하는 것이 아니다. 두 테이블이 서로 연관관계를 가지는 양방향 매핑이 존재한다.</p>
<p>단방향 매핑에서는 외래키를 가지는 테이블과 매핑되는 엔티티에서 연관관계를 가지고 있었다. 그래서 member.getJob()이 가능했다. 하지만 member join job, job join member가 모두 가능한 데이터베이스 테이블에 비해서는 서로 참조가 자유롭지 못했다. 양방향 매핑에서는 job에서도 member에 접근할 수 있는 연관관계를 관리한다.</p>
<p>member : job은 다 대 일 관계였다. 따라서 Member 엔티티에서는 @ManyToOne으로 연관관계를 매핑했었다. 그렇다면 Job에서는 반대로 @OneToMany로 매핑하게 된다. 그리고 하나의 Job이 여러 개의 member를 가질 수 있기 때문에 Member를 List, Map, Set등의 컬렉션으로 저장하게 된다. 따라서 Job에서는 아래와 같이 연관관계를 관리한다.</p>
<pre><code class="language-java">@OneToMany
private List&lt;Member&gt; members = new ArrayList&lt;Member&gt;();</code></pre>
<br>

<h3 id="연관관계의-주인">연관관계의 주인</h3>
<p>양방향 매핑은 사실상 하나의 연관관계가 양쪽을 이어준다고 볼 수는 없다. 두 개의 단방향 연관관계가 하나의 양방향 연관관계처럼 보일 뿐이다. 그렇다면 이 두 개의 단방향 연관관계를 데이터베이스와 어떻게 매핑 시킬까?</p>
<p>방법은 연관관계의 주인을 설정함으로써 그 주인을 기준으로 매핑한다. 보통은 ManyToOne 어노테이션이 붙은 필드가 연관관계의 주인이자 외래키와 매핑되는 필드가 된다. 그리고 실제 컬럼이 존재하지 않는 반대쪽의 필드에는 연관관계의 주인이 아니라는 뜻으로 mappedBy라는 속성을 연관관계 어노테이션에 붙인다.</p>
<pre><code class="language-java">
@ManyToOne
private Job job;

-------------------

@OneToMay(mappedBy = &quot;job&quot;)
private List&lt;Member&gt; members = new ArrayList&lt;Member&gt;();
</code></pre>
<p>이와 같이 매핑하게 되면 연관관계의 주인은 job이라는 필드가 되고, 데이터베이스의 job_id와 매핑된다. 그리고 members 필드에는 mappedBy 속성을 붙여 job이라는 연관관계의 주인과 매핑해준다.</p>
<br>

<h3 id="양방향-매핑-시-주의할-점">양방향 매핑 시 주의할 점</h3>
<p>이와 같이 양방향 매핑을 함에 있어서 주의할 점이 있다. 우리는 데이터베이스와의 매핑은 신경쓰지 않고 JPA가 알아서 처리해준다. 하지만 데이터베이스의 데이터를 객체로 핸들링하기 때문에 객체의 데이터 무결성에 대해서는 주의하여야 한다. 특히 양방향 매핑에서 흔히 발생하는 실수들이 있다.</p>
<p>name = developer인 job과 name = engineer인 job이 있다고 하자. name = jaeseok-go인 member의 job이 developer라고 하자.</p>
<pre><code class="language-java">Job job1 = new Job(&quot;developer&quot;, 1000);
Job job2 = new Job(&quot;engineer&quot;, 900);

Member member = new Member(&quot;jaeseok-go&quot;, job1);
job1.members.add(member);

em.persist(job1);
em.persist(job2);
em.persist(member);
</code></pre>
<p>위와 같이 developer와 jaeseok-go는 양방향 매핑이 되었다. 여기서 job이 engineer로 바뀐다고 생각해보자. 연관관계의 주인은 member.job이기 때문에 member.job의 객체를 바꿔주면 양방향 매핑이 바뀐다.</p>
<pre><code class="language-java">member.job = job2;
job2.members.add(member);

em.persist(member);
em.persist(job2);</code></pre>
<p>하지만 위의 코드를 보면 job2의 members에도 member 객체를 추가해주었다. 추가를 안한다고 하더라도 양방향 매핑이 안되는 것은 아니다. JPA는 연관관계의 주인을 기준으로 매핑을하고 데이터 무결성을 보장한다. 하지만 연관관계의 주인이 아닌 곳을 신경쓰지 않는다면 객체 상태의 연산 중에 에러가 발생할 수 있다. 객체의 무결성은 개발자가 직접 신경써야하는 부분이다.</p>
<p>하지만 위의 코드로 객체가 무결하다고 볼 수 없다. 왜냐하면 아직 job1은 member를 가지고 있기 때문이다. 따라서 job1의 members의 객체를 삭제해줘야 한다.</p>
<pre><code class="language-java">
job1.members.remove(member);</code></pre>
<p>이러한 작업들이 job2를 member에 set하는 과정이다. 이를 하나의 setter method로 구현할 수 있다. 이를 연관관계 편의 메소드라고 한다.</p>
<pre><code class="language-java">
public void setJob(Job job){

    if (this.job != null){
        this.job.getMembers().remove(this);
    }
    this.job = job;
    job.getMembers().add(this);

}

---------------------

member.setJob(job2);
em.persist(member);
em.persist(job1);
em.persist(job2);</code></pre>
<p>위와 같은 연관관계 편의 메소드를 작성해서  코드가 훨씬 깔끔해졌고 실수를 줄일 수 있다.</p>
<br>

<h3 id="다양한-매핑-기법">다양한 매핑 기법</h3>
<p>지금까지는 다대일의 단방향/양방향 매핑을 알아보았다. 그렇다면 일대일, 일대다, 다대다에서 단방향/양방향 매핑을 불가능한 것일까? 차례대로 한번 짚어보자.</p>
<h4 id="일대일-단방향양방향-매핑">일대일 단방향/양방향 매핑</h4>
<p>일대일 관계에서는 두 테이블 어느 곳이든 외래키를 가질 수 있다. 두 개의 테이블을 주 테이블과 대상 테이블로 구분한다.</p>
<p>객체 지향 관점의 개발자는 주 테이블에 외래키를 두어 객체 그래프 탐색으로 주 테이블에서 대상 테이블을 쉽게 참조할 수 있게 설계하는 것을 선호한다.</p>
<p>반대로 데이터베이스 관점의 개발자는 대상 테이블에 외래키를 두어 명확하게 테이블을 구성한다. 즉 일대다 관계가 되더라도 수정할 필요가 없다.</p>
<p>어느 곳에서 외래키를 관리하던 외래키를 관리하는 곳에서 연관관계 매핑을 수행하면 된다. 단방향 매핑의 경우에는 외래키가 없는 곳에서 연관관계의 주인이 되어 반대편의 외래키를 관리하고자 한다고 하더라도 이를 지원하는 방법이 없다. </p>
<h4 id="일대다-단방향-매핑">일대다 단방향 매핑</h4>
<p>일대다 매핑은 다쪽의 외래키를 일쪽의 필드에서 컬렉션으로 매핑한다.</p>
<pre><code class="language-java">@OnetoMany
@JoinColumn(name = &quot;job_id&quot;)
private List&lt;Member&gt; members = new ArrayList&lt;&gt;();</code></pre>
<p>이와 같이 member테이블의 job_id라는 외래키를 member 엔티티에서 매핑한다.</p>
<p>이와 같은 경우에는 외래키가 다른 테이블에 있기  때문에 쿼리의 성능이 떨어진다. 아래의 코드를 보고 쿼리의 성능을 가늠해보자.</p>
<pre><code class="language-java">Member member1 = new Member(&quot;jaeseok-go&quot;);
Member member2 = new Member(&quot;minsu-kim&quot;);

Job job = new Job(&quot;developer&quot;);
job.members.add(member1);
job.members.add(member2);

em.persist(member1);
em.persist(member2);
em.persist(job);

transaction.commit();
</code></pre>
<pre><code class="language-sql">-- ID : 1
INSERT INTO MEMBER(NAME)
VALUES (&quot;jaeseok-go&quot;); 

-- ID : 2
INSERT INTO MEMBER(NAME)
VALUES (&quot;minsu-kim&quot;);

-- ID :1
INSERT INTO JOB(NAME)
VALUES (&quot;developer&quot;);

UPDATE MEMBER
SET JOB_ID = 1
WHERE ID = 1;

UPDATE MEMBER
SET JOB_ID = 1
WHERE ID = 2;</code></pre>
<p>다대일 매핑이었다면 3개의 쿼리로 위의 작업이 모두 수행되었을 것이다. 하지만 JOB_ID 외래키를 관리하는 게 members라는 반대쪽 엔티티의 필드이기 때문에 두 개의 update문이 더 발생했다.</p>
<p>따라서 일대다 단방향 매핑은 왠만하면 지양하고,컬렉션 필드를 꼭 가지고 싶다면 다대일 양방향 매핑을 사용하자.</p>
<h4 id="일대다-양방향-매핑">일대다 양방향 매핑</h4>
<p>일대다 단방향 매핑 역시 깔끔하게 사용하기는 힘들다. 일단 @ManyToOne에 mappedBy 속성이 아예 없다. 굳이 이 매핑을 사용하는 방법은 @ManyToOne에 읽기전용 필드를 하나 만들어서 이를 매핑해주는 방법이다. 하지만 이 역시 일대다 단방향 매핑과 마찬가지로 성능저하를 불러일으킨다.</p>
<p>왠만하면.. 일대다 매핑보다는 <strong>다대일 양방향 매핑을 사용하자!!</strong></p>
<h4 id="다대다-단방향양방향-매핑">다대다 단방향/양방향 매핑</h4>
<p>우선 다대다 매핑은 관계형 데이터베이스에서 그대로 구현할 수 없다. 따라서 보통은 관계 테이블을 하나 추가해서 이를 활용한다.</p>
<p>예를 들어서 회원 테이블과 상품 테이블이 있다고 생각해보자. 회원은 여러 개의 상품을 구매하고 각 상품은 여러 명의 회원들이 구매한다. 그럼 테이블을 어떻게 구성할 수 있을까?</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/a728b127-e439-44dc-8aed-ff30870cf69c/image.png" alt=""></p>
<p>위와 같이 member에 product_id를 넣으면 될까? 이러면 하나의 상품을 여러 명의 회원이 구매하는 것으로 보일 수 있겠지만 여러 상품을 한 명의 회원이 구매하는 것은 구현하기 어려워보인다.</p>
<p>그렇다면 반대도 마찬가지일 것이다. 그래서 보통은 아래와 같이 이 두 개의 테이블 사이에 관계 테이블을 추가한다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/9665e747-5fe7-4e0b-886f-74c0c177215d/image.png" alt=""></p>
<p>그렇다면 엔티티와 연관관계는 어떻게 매핑할까?</p>
<p>크게 두 가지로 구분된다. 첫 번째는 3개의 엔티티를 만들어서 다대일 연관관계 두 개를 주문 테이블에 매핑하는 것이다. 이 방법이 가장 권장되는 방법이다.</p>
<p>두 번재로는 JPA에서 지원하는 다대다 연관관계 매핑을 하는 것이다. 이 방법은 관계 테이블을 생략하고 두 개의 테이블로 연관관계를 매핑할 수 있다.</p>
<pre><code class="language-java">@Entity
public class Member {

     @Id
     @Column(name = &quot;id&quot;)
     private Long id;

     private String email;
     private String password;
     private String name;

     @Column(nullable = true)
     private String address;

     @ManyToMany
     @JoinTable(name = &quot;orders&quot;, 
             joinColumns = @JoinColumn(name = &quot;member_id&quot;),
             inverseJoinDolumns = @JoinColumn(name = &quot;product_id&quot;))
     private List&lt;Product&gt; products = new ArrayList&lt;Product&gt;();

}</code></pre>
<p>위와 같은 코드로 단방향 매핑을 할 수 있다. 양방향 매핑을 하고 싶다면 반대쪽 엔티티에 <code>@ManyToMany(mappedBy = &quot;products&quot;)</code>를 붙여주기만 하면 된다.</p>
<p>이렇게 편리한 다대다 매핑보다 왜 다대일 매핑을 여러 개 만드는 것이 더 권장되는 방법일까? 그것은 관계 테이블에 단순히 매핑 정보만 담기지 않기 때문이다.</p>
<p>위와 같은 경우에만 해도 주문 테이블에 상품 주문량이나 주문 일자 등 다양한 컬럼이 추가되어서 관리될 수 있다. 하지만 다대다 매핑은 관계 테이블자체를 생략하고 매핑을 하기 때문에 권장되지 않는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[엔티티 매핑]]></title>
            <link>https://velog.io/@jaeseok-go/%EC%97%94%ED%8B%B0%ED%8B%B0-%EB%A7%A4%ED%95%91</link>
            <guid>https://velog.io/@jaeseok-go/%EC%97%94%ED%8B%B0%ED%8B%B0-%EB%A7%A4%ED%95%91</guid>
            <pubDate>Sun, 25 Apr 2021 15:47:28 GMT</pubDate>
            <description><![CDATA[<h3 id="객체와-테이블-매핑">객체와 테이블 매핑</h3>
<p>엔티티는 데이터베이스의 테이블과 매핑되는 자바 객체이다. 따라서 자바에서 테이블의 데이터를 핸들링하기 위해서는 엔티티를 잘 매핑해야한다.</p>
<p>엔티티를 테이블과 매핑하기 위한 기본적인 문법을 살펴보자.</p>
<h4 id="entity">@Entity</h4>
<p>JPA를 사용해서 테이블과 매핑할 자바 객체를 엔티티라고 한다. 이 엔티티에는 Entity 어노테이션을 사용해서 테이블과 매핑할 것이라고 알려줘야한다. </p>
<p>name attribute를 사용해서 엔티티의 이름을 지정할 수 있다. 생략할 수 있으며 생략하는 경우에는 클래스의 이름을 엔티티의 이름으로 사용한다.</p>
<h4 id="table">@Table</h4>
<p>엔티티와 매핑할 데이터베이스 테이블을 지정하는 어노테이션이다. 생략할 수 있으며 생략하게 되면 엔티티의 이름과 동일한 테이블을 찾아서 자동으로 매핑한다.</p>
<p>name attribute로 매핑할 테이블 이름을 지정해야한다.</p>
<pre><code class="language-java">@Entity
@Table(name = &quot;MEMBER&quot;)
public class Member {

   @Id
   @Column(name = &quot;id&quot;)
   private Long id;

   @Column(name = &quot;name&quot;)
   private String name;

}
</code></pre>
<br>

<h3 id="기본-키-매핑">기본 키 매핑</h3>
<p>JPA에서 어떤 테이블을 조회할 때 보통 기본키 식별자를 가지고 조회한다. 따라서 데이터베이스의 기본키를 엔티티에도 매핑을 해주어야만 한다.</p>
<h4 id="id">@Id</h4>
<p>Id 어노테이션을 기본 키로 사용할 엔티티의 필드위에 명시한다.</p>
<h4 id="기본-키-생성-전략">기본 키 생성 전략</h4>
<p>데이터베이스의 기본 키는 bigint 타입의 정수형으로 선언되며 auto_increment 제약 조건을 가지는 경우가 많다. 이러한 경우에 엔티티에서는 어떤 방식으로 기본 키를 생성할까?</p>
<ol>
<li>직접 할당</li>
<li>IDENTITY</li>
<li>SEQUENCE</li>
<li>TABLE</li>
</ol>
<p>엔티티에서는 위와 같은 네 가지 방식으로 기본 키를 생성한다.</p>
<h4 id="직접-할당">직접 할당</h4>
<p>직접 할당 방식은 데이터베이스에서 auto_increment 제약 조건을 사용하지 않는 경우이다. 이 경우에는 엔티티에서 따로 기본 키 생성 전략을 명시하지 않고, 데이터를 insert 할 때 기본 키도 같이 할당해주어야 한다.</p>
<h4 id="identity">IDENTITY</h4>
<p>이 방식은 데이터베이스에 기본 키 생성을 전임하는 방식이다. 엔티티에서는 기본 키는 따로 건드리지 않더라도 데이터베이스에서 기본 키를 알아서 생성해준다.</p>
<p>JPA에서는 모든 엔티티를 기본 키 식별자로 구분한다. 그렇기 때문에 이 방식으로 데이터를 저장하게 되면, 식별자를 자바에서 모르는 상태이기 때문에 저장 직후 데이터베이스와 통신해서 식별자를 받아온다. 즉, 쓰기 지연이 동작하지 않는다.</p>
<pre><code class="language-java">@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = &quot;id&quot;)
private Long id;</code></pre>
<h4 id="sequence">SEQUENCE</h4>
<p>이 방식은 Oracle, PostgreSQL, DB2, H2와 같은 데이터베이스에서 사용 할 수 있는 생성 전략이다. 이 데이터베이스들은 시퀀스를 지원하고 있고 시퀀스를 이용해서 기본 키를 생성한다.</p>
<p>우선 아래와 같이 시퀀스가 먼저 생성되어 있어야 한다.</p>
<pre><code class="language-sql">CREATE SEQUENCE TEST_SEQ START WITH 1 INCREMENT BY 1;</code></pre>
<p>그리고 엔티티에 @SequenceGenerator를 명시해서 데이터베이스에 생성된 시퀀스를 먼저 매핑하고 이 Generator를 기본 키에 다시 매핑한다.</p>
<pre><code class="language-java">@Entity
@SequenceGenerator(name = &quot;TEST_SEQ_GENERATOR&quot;, sequenceName = &quot;TEST_SEQ&quot;,
           initialValue = 1, allocationSize = 1)
public class Member(

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                generator = &quot;TEST_SEQ_GENERATOR&quot;)
    @Column(name = &quot;id&quot;)
    private Long id;

)</code></pre>
<h4 id="table-1">TABLE</h4>
<p>이 방식은 시퀀스를 지원하지 않는 데이터베이스에서 테이블을 시퀀스처럼 사용하는 방식이다. 우선 시퀀스처럼 사용할 테이블을 아래와 같이 만들어져 있어야 한다.</p>
<pre><code class="language-sql">CREATE TABLE TEST_SEQUENCES(
    sequence_name varchar(255) not null,
    next_val bigint,
    primary key( sequence_name )
)</code></pre>
<p>이렇게 만들어진 테이블을 @TableGenerator로 매핑하고 이 Generator를 다시 기본 키에 매핑한다.</p>
<pre><code class="language-java">@Entity
@TableGenerator(name = &quot;TEST_SEQ_GENERATOR&quot;, table = &quot;TEST_SEQUENCES&quot;,
           pkColumnValue = &quot;TEST_SEQ&quot;, allocationSize = 1)
public class Member(

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
                generator = &quot;TEST_SEQ_GENERATOR&quot;)
    @Column(name = &quot;id&quot;)
    private Long id;

)</code></pre>
<p>TEST_SEQUENCE 테이블에서 TEST_SEQ라는 값이 sequence_name(PK)에 입력되며 next_val에 시퀀스 값이 1씩 올라가는 식으로 Generator를 생성해서 매핑했다.</p>
<h4 id="auto">AUTO</h4>
<p>위와 같은 기본 키 생성 전략들이 있지만 JPA에서는 AUTO 전략을 기본으로 제공한다. 데이터베이스 방언에 따라 전략을 다르게 제공하는 방식인데 오라클은 SEQUENCE, MySQL은 IDENTITY를 선택한다.</p>
<pre><code class="language-java">@Id @GeneratedValue
private Long id;</code></pre>
<p>보통 실무에서는 위와 같이 AUTO 전략을 많이 사용한다. 데이터베이스가 바뀌어 방언이 바뀌게 되더라도 자동으로 전략을 선택해주기 때문이다.</p>
<br>

<h3 id="필드와-컬럼-매핑">필드와 컬럼 매핑</h3>
<p>엔티티와 테이블을 매핑했으면 엔티티 안의 필드들과 테이블 안의 컬럼들을 매핑해야한다. 매핑하기 위한 어노테이션은 아래의 표와 같다.</p>
<p>매핑 어노테이션|설명
|---|---|
|@Column|컬럼을 매핑|
|@Enumerated|자바의 enum 타입 매핑|
|@Temporal|날짜 타입 매핑|
|@Lob|BLOB/CLOB 타입 매핑|
|@Transient|매핑하지 않는다|
|@Access|접근 방식 지정|</p>
<p>이 표의 내용을 하나씩 짚어보자.</p>
<h4 id="column">@Column</h4>
<p>컬럼을 매핑하는 어노테이션이다.  DDL을 사용하지 않는다면 기본적으로 사용할 속성은 name 속성뿐이다.  매핑할 컬럼명을 지정하는 속성이다. </p>
<p>하지만 nullable 속성은 예외적이다. nullable 속성은 기본적으로 true이고 false로 설정할 경우 not null 제약조건이 추가된다.</p>
<p>그런데 필드의 타입에 따라 nullable을 명시해줘야하는 경우가 발생할 수 있다. int 타입의 필드를 사용하는 경우에는 자바 기본타입이 때문에 null 값이 들어갈 수 없다. 그렇기 때문에 @Column(name = &quot;id&quot;, nullable = false)와 같이 not null 제약조건을 추가해줘야한다.</p>
<p>그게 아니라면 애초에 int 타입 대신 Integer 타입을 사용하는 방법도 있다. </p>
<h4 id="enumerated">@Enumerated</h4>
<p>Enumerated 어노테이션은 자바의 enum 타입을 매핑하는 어노테이션이다. value 속성을 가지며 EnumType.ORDINAL, EnumType.STRING을 골라서 입력할 수 있다.</p>
<p>전자의 경우에는 enum 객체에서 순서를 가지고 데이터베이스에 저장하는 방식이고, 후자는 내용을 가지고 데이터베이스에 저장하는 방식이다.</p>
<h4 id="temporal">@Temporal</h4>
<p>Temporal 어노테이션은 날짜 타입을 매핑할 때 사용한다. value 속성을 가지며 DATE, TIME, TIMESTAMP 값을 입력할 수 있다. </p>
<p>DATE는 날짜, TIME은 시간, TIMESTAMP는 날짜와 시간과 매핑된다. 필드의 타입은 Date 타입을 사용하면 된다.</p>
<h4 id="lob">@Lob</h4>
<p>데이터베이스의 BLOB, CLOB 타입과 매핑할 때 사용한다. </p>
<p>CLOB은 문자형 데이터를 저장하는 타입이고, 바이너리 데이터를 저장하는 타입은 BLOB이다.</p>
<h4 id="transient">@Transient</h4>
<p>이 필드를 매핑하지 않는 경우에 사용하는 어노테이션이다. 엔티티의 필드를 일종을 저장 공간으로 임시로 사용하는 경우에 사용한다.</p>
<h4 id="access">@Access</h4>
<p>JPA가 엔티티에 접근하는 방식을 지정한다. </p>
<pre><code class="language-java">@Entity
@Access(AccessType.FIELD) / @Access(AccessType.PROPERTY)</code></pre>
<p>위의 두 가지 방식으로 사용한다. 전자는 필드에 직접 접근하는 방식이고, 후자는 getter를 사용해서 접근하는 방식이다.</p>
<p>Id 어노테이션이 필드에 있으면 field 방식으로, getter에 있으면 property방식으로 자동으로 설정되므로 생략가능하다.</p>
<p>또한 필드에 Access 어노테이션을 명시하여 해당 필드에만 예외적으로 접근 방식을 달리할 수도 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 영속성]]></title>
            <link>https://velog.io/@jaeseok-go/JPA-%EC%98%81%EC%86%8D%EC%84%B1</link>
            <guid>https://velog.io/@jaeseok-go/JPA-%EC%98%81%EC%86%8D%EC%84%B1</guid>
            <pubDate>Sun, 18 Apr 2021 17:10:01 GMT</pubDate>
            <description><![CDATA[<h3 id="엔티티-매니저-팩토리와-엔티티-매니저">엔티티 매니저 팩토리와 엔티티 매니저</h3>
<p><img src="https://images.velog.io/images/jaeseok-go/post/d17814d7-e8be-48fd-b957-7cf0e2553ee8/image.png" alt=""></p>
<p>JPA를 시작하며 가장 기본적으로 사용되는 객체가 엔티티 매니저 팩토리와 엔티티 매니저이다. JPA를 기본적으로 데이터를 엔티티 단위로 받아오기 때문에 엔티티를 관리할 수 있는 수단이 필수적이다.</p>
<p>EntityManagerFactory는 하나의 애플리케이션에 하나만 생성하여 사용한다. 왜냐하면 하나의 인스턴스를 생성하는데에 드는 비용이 크기 때문이다. JPA를 구동시킬 기본적인 객체나 여러 커넥션이 함께 생성되기도 한다. 이렇게 생성된 하나의 EntityManagerFactory로 여러 개의 EntityManager를 생성하여 사용한다. </p>
<p>EntityManager는 하나의 가상 데이터베이스로 생각하여 사용하면 되는데, 이 인스턴스를 활용해 실제 엔티티를 핸들링하게 된다. JPA에서는 개발자가 논리적으로 Transaction을 구현하기 때문에 EntityManager를 동시에 접근하면 동시성 문제가 발생할 수 있다.</p>
<br>

<h3 id="영속성-컨텍스트란">영속성 컨텍스트란?</h3>
<p>영속성(Persistence)이란 JPA의 P에 해당하는 단어로 변하지 않는 성질을 의미한다. 그리고 Context는 물리적으로 어떤 리소스의 집합이고, 논리적으로는 어떤 것들이 실행될 수 있는 환경이다. 따라서 <code>영속성 컨텍스트</code>는 <code>데이터가 지속될 수 있도록 구성된 환경</code>이라고 볼 수 있다.</p>
<p>EntityManager가 트랜잭션을 만들어 어떤 로직을 처리할 때 엔티티 단위의 CRUD를 발생시킨다. 이 CRUD는 바로 데이터베이스에 반영되는 것이 아니라 영속성 컨텍스트에 먼저 반영된다.</p>
<h4 id="insert">INSERT</h4>
<pre><code class="language-java">em.persist(member);</code></pre>
<p>위와 같은 코드가 transaction안에서 실행되었다고 하자. 그렇다면 만들어놓은 member 객체를 바로 데이터베이스에 반영하지 않는다. 우선 영속성 컨텍스트에 반영한다.</p>
<p>영속성 컨텍스트에서는 Id, Instance의 하나의 Map이 있다. 이 Map에 member 인스턴스의  식별자 값을 Key에, member 인스턴스 자체를 Value에 넣어서 보관하고 있는다. 이를 <code>쓰기 지연</code>이라고 한다. 영속성 컨텍스트는 쓰기 지연을 구현하기 위해서 쓰기 지연 SQL 저장소를 내부적으로 가진다. 쓰기 지연된 엔티티에 대한 SQL을 이 저장소에 보관하고 있는다. 그리고 transaction이 commit될 때 한 번에 모든 비즈니스 로직 쿼리가 데이터베이스로 요청된다. </p>
<h4 id="update">UPDATE</h4>
<pre><code class="language-java">Member member = em.find(Member.class, 1);

member.setName(&quot;jaeseok-go&quot;);</code></pre>
<p>위와 같은 코드를 실행하면 자동으로 update 쿼리가 생성된다. 우선 find로 불러온 member entity는 영속성 컨텍스트에 추가된다. 영속성 컨텍스트에 어떤 엔티티가 추가 될 때 추가되는 시점의 상태를 저장하고 있는 스냅샷이 같이 추가된다. 그리고 트랜잭션이 커밋될 때 쿼리가 데이터베이스에 요청되기 전, 영속성 컨텍스트에서 관리하는 Map에 스냅샷과 비교하여 변경된 부분이 있는지 찾는다. 변경된 부분이 있다면 변경을 요청하는 Update 쿼리를 쓰기 지연 SQL 저장소에 저장하고 최종적으로 쿼리로 데이터베이스에 요청한다.</p>
<p>이 때 만들어지는 update문은 변경된 컬럼만 수정하는 update 문이 아니다. 모든 컬럼을 다시 한번 명시하여 만들어진다. 이렇게 만들어지는 이유는 수정 쿼리가 항상 같기에 쿼리의 재사용성을 활용하기 위함이다.</p>
<h4 id="delete">DELETE</h4>
<pre><code class="language-java">Member member = em.find(Member.class, 1);

em.remove(member);</code></pre>
<p>delete의 경우에도 update와 동작 원리는 같다. 하지만 remove 메소드는 영속성 컨텍스트에서 아예 해당 인스턴스를 삭제해버리기 때문에 인스턴스는 가비지 컬렉터가 메모리를 수거한다.</p>
<br>

<h3 id="영속성-컨텍스트로-얻는-이점">영속성 컨텍스트로 얻는 이점</h3>
<p>JPA에서 영속성 컨텍스트를 사용함으로써 얻는 다양한 이점이 존재한다.</p>
<ol>
<li>1차 캐시</li>
<li>동일성 보장</li>
<li>트랜잭션을 지원하는 쓰기 지연</li>
<li>변경 감지</li>
<li>지연 로딩</li>
</ol>
<p>위와 같은 이점을 하나씩 살펴보자.</p>
<h4 id="1차-캐시">1차 캐시</h4>
<p>비즈니스 로직에서 어떤 결과값을 요청할 때 먼저 영속성 컨텍스트에 있는지 확인한다. 영속성 컨텍스트에 원하는 결과 값이 없다면 그제서야 데이터베이스에 요청을 보낸다.</p>
<p>하지만 영속성 컨텍스트에 원하는 결과 값이 있다면 바로 그 값을 반환한다. 따라서 1차 캐시로서의 영속성 컨텍스트의 역할 덕분에 이러한 성능상 이점을 누릴 수 있다.</p>
<h4 id="동일성-보장">동일성 보장</h4>
<p>똑같은 쿼리를 두 번 요청했다고 생각해보자. 각 쿼리의 결과 값을 새로운 변수를 만들어서 반환받았다. 두 개의 새로운 변수에 내용은 같지만 따로 요청한 쿼리의 결과값이 들어가있기에 두 변수의 인스턴스는 다를 것으로 예상된다. </p>
<p>하지만 두 변수의 인스턴스는 동일하다. 첫 번째 쿼리가 실행되어 영속성 컨텍스트에 만들어진 인스턴스가 두 번째 쿼리가 실행될 때 영속성 컨텍스트에 이미 만들어져있기 때문에 그대로 반환되기 때문이다.</p>
<h4 id="트랜잭션을-지원하는-쓰기-지연">트랜잭션을 지원하는 쓰기 지연</h4>
<p>여러가지 로직을 처리하고 트랜잭션을 끝내는 메소드가 있다고 가정해보자. 각 로직이 각각 하나의 쿼리를 발생시킬 때, 모든 쿼리를 데이터베이스에 요청해야만 할 것이다. 하지만 영속성 컨텍스트는 1차 캐시기능을 가지고 있어 모든 로직이 실행되고 트랜잭션이 커밋되는 시점의 상태만을 요청하면 된다.</p>
<h4 id="변경-감지">변경 감지</h4>
<p>이미 만들어졌거나(persist) 불러왔던(find) 인스턴스를 영속성 컨텍스트에 저장해놓고 수정되거나(setter), 삭제된(remove) 내역만 감지하여 쿼리를 만들어낸다. </p>
<h4 id="지연-로딩">지연 로딩</h4>
<pre><code class="language-java">member.getTeam();</code></pre>
<p>위의 코드만으로 member와 연관관계가 있는 Team 테이블을 엔티티 리스트로 받아올 수 있다.</p>
<br>

<h3 id="플러시">플러시</h3>
<p>플러시는 개발자가 직접 사용하기도 하지만 대부분 트랜잭션이 커밋되는 시점에 자동으로 실행된다. 플러시 메소드가 실행되면 아래와 같은 과정이 발생된다.</p>
<ol>
<li>변경 감지가 동작한다. 모든 변경 사항이 SQL 저장소에 쿼리로 등록된다.</li>
<li>쓰기 지연 SQL 저장소에 있던 모든 쿼리를 데이터베이스로 전송한다.</li>
</ol>
<br>

<h3 id="여러가지-영속-상태">여러가지 영속 상태</h3>
<p>지금까지 말했던 영속성 컨텍스트에 엔티티가 등록되는 상태를 영속 상태라고 한다. 하지만 엔티티는 영속상태뿐 아니라 다양한 상태로 존재할 수 있다. 다양한 영속 상태를 알아보자.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/33b2b2ec-7fac-4754-a7fe-945dec986a8e/image.png" alt=""></p>
<p>우선 아무것도 아닌 비영속 상태가 있다. 엔티티 인스턴스가 생성되었지만 persist 메소드가 사용되기 전의 단계이다.</p>
<p>영속 상태는 엔티티가 관리되고 있는 상태라고 볼 수 있다. 위에서 설명되었던 모든 기능이 사용될 수 있다. 하지만 준영속 상태가 되면 거의 모든 기능을 사용할 수 없다. 특정 엔티티를 준영속 상태로 만들기 위한 방법으로는 detach(entity) 메소드를 사용하는 방법이 있고, 아예 영속성 컨텍스트를 close(), clear()하는 방법으로 모든 엔티티를 준영속 상태로 만들 수 있다. </p>
<p>이렇게 준영속 상태가 된 엔티티는 merge() 메소드로 다시 영속 상태로 만들 수 있다. 
<br></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 개요]]></title>
            <link>https://velog.io/@jaeseok-go/JPA-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@jaeseok-go/JPA-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Fri, 16 Apr 2021 16:43:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/jaeseok-go/post/5ebfc9c6-a8b9-476c-aa73-5d2242d8693e/image.png" alt=""></p>
<h3 id="jpa-기본-개념">JPA 기본 개념</h3>
<p>JPA 등장 전부터 사용되던 JDBC나 MyBatis의 경우에는 SQL을 Java위에서 작성해서 DB에서 적절한 ResultSet을 받아오는 방식이었다. JDBC는 하나의 쿼리마다 각 컬럼을 모두 get/set 해주다보니 코드가 지저분하고 길어지게되었다. MyBatis는 mapper를 구현하여 각 컬럼까지 일일히 매핑하지는 않게 되었지만 Java 위에서 SQL을 잘 작성해야만 했다.</p>
<p>그 이유는 관계형 데이터베이스와 객체지향언어의 패러다임이 다르기 때문이었다. </p>
<p>JPA는 이러한 패러다임의 차이를 개발자 대신 메꿔주는 역할을 하는 ORM이다. 기존에 존재하던 다양한 문제를 JPA를 사용함으로써 상당히 많이 해소할 수 있다. 지금까지 존재하던 대표적인 몇 가지 문제점을 JPA가 어떤 식으로 해소하는지 알아보자.</p>
<br>

<h3 id="구체적인-문제점들">구체적인 문제점들</h3>
<h4 id="sql에-의존적인-개발을-피하기-어렵다">SQL에 의존적인 개발을 피하기 어렵다.</h4>
<p>사실 이것이 가장 근본적인 문제점이다. DAO에 작성된 SQL이 잘못된 경우에는 무조건 에러가 발생할 수 밖에 없다. 그만큼 SQL을 잘 작성해야하고 이는 객체지향적인 관점에서 해소할 수 있는 문제가 아니다. </p>
<h4 id="진정한-의미의-계층-분할이-어렵다">진정한 의미의 계층 분할이 어렵다.</h4>
<p>Spring에서는 Controller, Service, DAO등 다양한 Layer를 분할하여 서버가 동작하게 된다. 비즈니스 로직은 Service Layer에서 구현되고 DAO는 단순 CRUD 메소드만 가지고 있는 것이 올바른 설계일 것이다. 그런데 수정사항이 생겼거나 에러가 생겨 코드를 열어봐야하는 경우에도 DAO를 열어보고 어떻게 조회되는지 확인하거나 쿼리를 직접 수정해야한다. 이는 진정한 의미의 계층 분할이라고 할 수 없다. 사실 이것 조차도 근본적인 문제는 SQL에 의존적인 개발을 피할 수 없기 때문인 것이다.</p>
<h4 id="엔티티를-신뢰할-수-없다">엔티티를 신뢰할 수 없다.</h4>
<p>이것 역시 마찬가지다. SQL에 의존적인 상황에서 개발자가 엔티티에 신뢰를 가질 수 없다. SQL이 잘못된 경우 테이블에서는 not null이지만, 엔티티에는 null 값이 들어가기도 한다.</p>
<br>

<h3 id="jpa를-활용한-crud">JPA를 활용한 CRUD</h3>
<p>그렇다면 JPA로 CRUD하는 것을 보고 기존의 문제점들이 얼마나 해소되었는지 알아보자.</p>
<h4 id="insert">INSERT</h4>
<pre><code class="language-java">jpa.persist(member);</code></pre>
<h4 id="select">SELECT</h4>
<pre><code class="language-java">// ID로 member 테이블 조회
String memberId = &quot;helloId&quot;;
Member member = jpa.find(Member.class, memberId);

// 조회된 테이블의 FK로 team 테이블 조회
Team team = member.getTeam();</code></pre>
<h4 id="update">UPDATE</h4>
<pre><code class="language-java">Member member = jpa.find(Member.class, memberId);
member.setName(&quot;updated name&quot;);</code></pre>
<p>기본적으로 위의 코드들을 보면 SQL에 의존적이지 않다. CRUD하고 싶은 테이블에 알맞는 엔티티만 잘 생성하고 persist, find등의 메소드로 SQL없이 처리할 수 있다. </p>
<p>또한, SELECT 코드의 두번째 코드를 보면 Member 엔티티에 Team 참조 변수를 가지고 있는 것을 확인할 수 있다. RDB의 경우에는 외래키로 참조를 설정하지만 객체지향언어의 경우엔 참조변수를 직접 가지고 있음으로써 참조를 설정할 수 있다. 따라서 member.getTeam만 하더라도 자동으로 member의 team_id로 team 테이블을 조회한다.</p>
<br>

<h3 id="객체-그래프-탐색">객체 그래프 탐색</h3>
<pre><code class="language-sql">SELECT *
FROM MEMBER A JOIN TEAM B
ON A.TEAM_ID = B.TEAM_ID;</code></pre>
<p>위의 쿼리를 JDBC로 실행시켜서 그에 맞는 resultSet을 가져왔다고 생각해보자. 그렇다면 Member, Team 엔티티를 가져올 것이다. 여기서 member와 연관관계가 있는 Order 엔티티를 가져오고 싶으면 어떻게 해야 할까?</p>
<p>다른 메소드를 만들거나 쿼리를 수정해야할 것이다. 이렇게 SQL에 종속적인 개발을 피할 수 없기 때문에 함부로 비즈니스 로직을 수정할 수 없다.</p>
<pre><code class="language-java">Member member = jpa.find(Member.class, memberId);

Team team = member.getTeam();
String team_name = team.getName();

Order order = member.getOrder();
String order_price = order.getPrice();</code></pre>
<p>그렇다면 위의 코드를 보자. 처음에 가져온 Member 엔티티에서 getTeam 메소드로 Team 엔티티를 가져와서 사용한다. 그리고 Order 엔티티를 가져오고 싶다는 요구사항이 추가되었기 때문에 아래와 같이 Order 엔티티도 member에서 바로 가져와서 사용하는 것을 확인할 수 있다.</p>
<p>여기서 사실 실제로 SQL이 실행되어 team 테이블을 조회하는 부분은 team.getName()이다. 실제로 엔티티를 사용하는 부분까지 기다렸다가 사용할 때 쿼리를 실행시킨다. 이를 <code>지연 로딩</code>이라고 한다. </p>
<p>어떤 관점에서는 member, team, order 테이블에 한번에 접근해서 데이터를 받아오는 것이 더 효율적이라고 보여지기도 한다. 이를 <code>즉시 로딩</code>이라고 한다. 지연 로딩과 즉시 로딩은 프로그램의 성향이나 정책상 더 알맞는 것을 사용하는 것이 올바르다.</p>
<p><img src="https://images.velog.io/images/jaeseok-go/post/d1bc7181-c270-4d3e-8b47-4ef466a0e562/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[정렬 알고리즘의 시간 복잡도]]></title>
            <link>https://velog.io/@jaeseok-go/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%98-%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84</link>
            <guid>https://velog.io/@jaeseok-go/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%98-%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84</guid>
            <pubDate>Thu, 08 Apr 2021 14:57:48 GMT</pubDate>
            <description><![CDATA[<h3 id="알고리즘-성능-분석">알고리즘 성능 분석</h3>
<p>프로그램들의 규모가 점차 커짐에 따라 처리하는 데이터의 양이 많아지고 있다. 이러한 경우 프로그램은 많은 데이터를 효율적으로 처리할 수 있어야 한다.</p>
<p>데이터를 처리하는 알고리즘이 입력 데이터 n개를 n^2번 처리하여 결과 값을 출력한다고 가정해보자.</p>
<p>입력 데이터가 100이면 10000번 처리한다.
입력 데이터가 5000이면 25000000번 처리한다.</p>
<p>입력 값은 50배 증가하였으나, 출력 값은 2500배 증가했다. 처리 횟수로만 따지면 24999000번 증가했다.</p>
<p>이러한 경우 알고리즘의 효율성이 떨어진다고 볼 수 있고 이를 개선할 필요성이 있어 보인다.</p>
<br>

<h3 id="시간-복잡도">시간 복잡도</h3>
<p>시간 복잡도는 알고리즘이 수행되는데 처리가 몇 번 수행되는지를 나타낸다. 이 시간복잡도를 표현하는 방법은 여러가지가 있지만, 그 중에서도 <code>빅오 표기법</code>을 가장 많이 사용한다.</p>
<p>빅오 표기법은 입력 값이 n개 들어왔을 때 최악의 경우 몇 번 수행되는지를 나타내는 표기법으로 도출되는 n에 대한 수식 중 가장 큰 지수를 가진 항만 남겨 표기한다.</p>
<p>말이 어려울 수 있으니.. 예를 들어보자.</p>
<pre><code class="language-java">int n = 5;
int sum = 0;

for(int i = 0; i &lt; n; i++){
    sum++;
}

for(int i = 0; i &lt; n; i++){
    for(int j = 0; j &lt; n; j++){
        for(int k = 0; k &lt; 2; k++){
            sum++;
        }
    }
}</code></pre>
<p>위의 코드에서 입력값 n은 5로 주어졌다. 맨 처음 단일 반복문에서는 sum++; 연산이 n번 도는 반복문안에 있기 때문에 n번 수행된다.</p>
<p>그리고 삼중 반복문에서는 n만큼 돌며 각 루프마다 n만큼 또 돌고 그 루프안에서 2만큼 또 돈다. 즉 sum++;이라는 연산이 n x n x 2 만큼 수행된다는 것이다.</p>
<p>따라서 n이 5로 주어진 위 알고리즘의 연산 횟수는 5 + 5 x 5 x 2 = 55회이다.</p>
<p>대입연산과 반복문 자체의 비교 연산 등을 제외하고 사칙연산만 놓고 봤을 때, 위 알고리즘의 시간복잡도는 2n^2 + n이라고 볼 수 있다. </p>
<p>빅오 표기법은 이 시간복잡도를 <code>O(n^2)</code>로 표현한다. n의 값에 따라 횟수가 크게 달라지지만 그 앞의 계수와 최고차항을 제외한 항들에 의해서는 횟수가 크게 달라지지 않으므로 가볍게 무시하고 표기하는 것이다.</p>
<br>

<h3 id="다양한-알고리즘과-시간-복잡도">다양한 알고리즘과 시간 복잡도</h3>
<p>출처 : <a href="https://www.bigocheatsheet.com/">https://www.bigocheatsheet.com/</a></p>
<p>시간복잡도에 따라서 알고리즘의 시간적인 성능을 한눈에 알아볼 수 있다. 아래의 차트는 시간복잡도 별로 연산횟수가 어느정도로 증가하는지를 나타내는 차트이다.
<img src="https://images.velog.io/images/jaeseok-go/post/ac68bce1-3c46-42fc-acd8-7354183d8fdc/image.png" alt=""></p>
<p>여러 개의 정렬 알고리즘으로 위의 시간복잡도로 알아보는 코드를 구현해보자.</p>
<h4 id="선택-정렬">선택 정렬</h4>
<p>선택 정렬은 내가 방문하지 않았던 배열에서 최솟값을 찾아서, 현재 위치로 옮기고 다음 위치로 넘어가는 정렬 알고리즘이다.</p>
<p>한 번 반복할 때마다 내 위치에 알맞은 값을 넣어준다고 생각하고 어떤 값을 넣을지에 대해서 전체 배열을 다 반복해서 찾아낸다.</p>
<pre><code class="language-java">public class SelectionSorting {

    public static void main(String[] args) {

        // element의 갯수가 input인 것으로 생각한다.
        int[] input_arr = {9, 6, 7, 3, 5};

        int cnt = selectionSorting(input_arr);

        System.out.println(Arrays.toString(input_arr));
        System.out.println(&quot;processing count : &quot; + cnt);
    }

    private static int selectionSorting(int[] input_arr){

        int cnt = 0;

        // i자리에 값을 가지고 비교한다.
        for(int i = 0; i &lt; input_arr.length; i++){

            // 최솟값은 i자리의 값으로 초기화해둔다.
            int minVal = input_arr[i];
            int minIdx = i;

            // i자리 이후의 값들과 비교하여 더 작은 값이 있으면 갱신한다.
            for(int j = i + 1; j &lt; input_arr.length; j++){
                cnt++;
                if(minVal &gt; input_arr[j]) {
                    minVal = input_arr[j];
                    minIdx = j;
                }
            }

            // i자리 이후의 값 중 가장 작은 값을 찾았으니 i자리와 바꿔준다.
            int temp = input_arr[i];
            input_arr[i] = input_arr[minIdx];
            input_arr[minIdx] = temp;
        }

        return cnt;
    }
}</code></pre>
<blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/cbec7991-97d4-4d98-94d6-39c66858651d/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/6d90eab9-5f8b-4a40-9a65-6c4e008cf9dc/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/8d81709d-9795-48c6-ab1b-bce33a80c1b1/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/76488e03-df1e-453b-a4e4-b4a0c7e47d00/image.png" alt=""></p>
</blockquote>
<p>코드만 봤을 때 두 개의 반복문으로 시간 복잡도가 <code>O(n^2)</code>인 것을 알 수 있다.</p>
<p>첫 번째 케이스는 비교 횟수가 4, 3, 2, 1 이여서 총 비교 카운트가 10이 출력되었다. 두 번째 케이스는 비교 횟수가 13, 12, ..., 2, 1이여서 총 비교 카운트가 91이 출력되었다.</p>
<p>시간복잡도를 O(n^2)로 표현했지만 연산 횟수가 정확히 5의 제곱인 25, 14의 제곱인 196이 나오진 않았다. 하지만 입력 값이 증가함에 따라 출력 값이 증가하는 추이가 5-&gt;14에 따라 10-&gt;91인 것은 25-&gt;196와 증가 추이가 비슷한 것을 확인할 수 있다.</p>
<h4 id="버블-정렬">버블 정렬</h4>
<p>버블 정렬은 인접한 두 원소를 비교하여 큰 원소를 뒤로 밀어내는 정렬 알고리즘이다. 1회전 마다 제일 뒤의 인덱스에 정렬되지 않은 배열의 최대값이 쌓여 정렬되어 나간다.</p>
<pre><code class="language-java">public class BubbleSorting {

    public static void main(String[] args) {

        int[] input_arr = {9, 6, 7, 3, 5, 4, 10, 14, 1, 2, 0, 12, 1111};

        int cnt = bubbleSorting(input_arr);

        System.out.println(Arrays.toString(input_arr));
        System.out.println(&quot;processing count : &quot; + cnt);
    }

    private static int bubbleSorting(int[] input_arr){

        int cnt = 0;

        // i는 1회전마다 어디 배열까지 돌아갈지 결정한다.
        for(int i = input_arr.length - 1; i &gt; 0; i--){

            // j는 몇 번째 원소를 비교할 지를 내타낸다.
            for(int j = 0; j &lt; i; j++){

                cnt++;

                // 고른 원소와 그 다음 원소를 비교한다.
                if(input_arr[j] &gt; input_arr[j + 1]) {
                    int temp = input_arr[j];
                    input_arr[j] = input_arr[j + 1];
                    input_arr[j + 1] = temp;
                }
            }
        }
        return cnt;
    }
}
</code></pre>
<blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/9972e9c6-b23b-4990-8b65-1c03a0db4e4f/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/9bceb1e6-5ae8-4dd8-b82b-0b990558b1ee/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/e6cdde1b-6581-4cec-b3f2-4ab57f39575b/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/cc8aa8b9-7da7-4957-954f-a761829e3d79/image.png" alt=""></p>
</blockquote>
<p>버블 정렬은 항상 반복문의 반복 횟수가 일정하다. 1회전마다 배열의 마지막 자리에 정렬되며 그 다음 회전은 그 앞까지 반복한다.</p>
<p>따라서 n-1, n-2, n-3, ..., 2, 1 번 반복하여.. n(n-1)/2의 시간 복잡도를 가진다. 이는 빅오 표기법으로는 O(n^2)이다.</p>
<h4 id="삽입-정렬">삽입 정렬</h4>
<p>삽입 정렬은 배열들을 미리 정렬해 나가면서, 정렬된 배열들의 중간에 다음 값을 끼워 넣는 식으로 구현되는 정렬 알고리즘이다.</p>
<p>한 번 반복할 때마다 내 앞의 배열 중 나보다 작은 값 다음에 나를 삽입하는 식으로 구현한다.</p>
<pre><code class="language-java">public class InsertionSorting {

    public static void main(String[] args) {

        // element의 갯수가 input인 것으로 생각한다.
        int[] input_arr = {8, 5, 6, 2, 4};

        int cnt = insertionSorting(input_arr);

        System.out.println(Arrays.toString(input_arr));
        System.out.println(&quot;processing count : &quot; + cnt);
    }

    private static int insertionSorting(int[] input_arr){

        int cnt = 0;

        // 앞에 자리들이랑 비교할 i자리
        for(int i = 1; i &lt; input_arr.length; i++){

            // 이미 정렬된 앞의 자리들 중 i자리 보다 작은 애가 나오기 전까지 자리를 바꿔간다.
            for(int j = i - 1; j &gt;= 0; j--){

                cnt++;

                if(input_arr[j] &lt; input_arr[j + 1]) break;

                int temp = input_arr[j];
                input_arr[j] = input_arr[j + 1];
                input_arr[j + 1] = temp;
            }
        }
        return cnt;
    }
}
</code></pre>
<blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/9f147476-c64b-4054-a24a-39a5c58d25e2/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/c85425ef-65a7-4c47-a7d6-39d866b44d13/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/c6b14980-b12f-48c3-a0c1-622e521da5cd/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/9ea2d169-428d-4cef-bafa-3a1334b4fe4a/image.png" alt=""></p>
</blockquote>
<p>삽입 정렬 역시 두 개의 반복문으로 구현되니 시간 복잡도는 <code>O(n^2)</code>이다. 하지만 배열을 정렬하며 처리를 수행하다보니, 이미 어느 정도 정렬되어 있는 경우에는 성능이 좋아질 수 있다. 두 번째 케이스의 경우에는 선택 정렬과 같은 크기의 입력 값(14개)을 주었으나, 이미 어느 정도 정렬된 값을 처리 횟수가 39번으로 두 배 가까이 좋은 성능으로 알고리즘이 수행되었다.</p>
<h4 id="합볍-정렬">합볍 정렬</h4>
<p>합병 정렬은 대표적인 분할 정복 알고리즘 중 하나이다. 전체 배열을 절반으로 나누고 계속 절반으로 나누어 원소의 갯수가 1개가 되었을 때 두 개의 배열을 정렬하며 하나로 합친다.</p>
<p>이 때 devide &amp; conquer 메소드를 사용하며 conquer 메소드에서 두 배열의 인덱스를 비교해가면 정렬된 배열을 합병해낸다.</p>
<pre><code class="language-java">public class MergeSorting {

    public static void main(String[] args) {

        int[] input_arr = {9, 6, 7, 3, 5, 4, 10, 14, 1, 2, 0, 12, 1111};
        int[] sorted_arr = new int[input_arr.length];

        int cnt = mergeSorting(input_arr, sorted_arr);

        System.out.println(Arrays.toString(input_arr));
        System.out.println(&quot;processing count : &quot; + cnt);
    }

    private static int mergeSorting(int[] input_arr, int[] sorted_arr){ return divide(input_arr, 0, input_arr.length - 1, sorted_arr); }


    private static int divide(int[] input_arr, int left, int right, int[] sorted_arr){

        int cnt = 0;

        int mid = (left + right) / 2;

        if(left &gt;= right) return cnt;

        cnt += divide(input_arr, left, mid, sorted_arr);
        cnt += divide(input_arr, mid + 1, right, sorted_arr);
        cnt += conquer(input_arr, left, mid, right, sorted_arr);

        return cnt;
    }

    private static int conquer(int[] input_arr, int left, int mid, int right, int[] sorted_arr){

        int cnt = 0;

        // 두 분할 배열의 원소의 크기를 비교할 인덱스
        int i = left;
        int j = mid + 1;

        // sorted_arr에 정렬되어갈 인덱스
        int k = left;

        // i와 j를 비교하며 더 작은걸 sorted_arr에 차례대로 넣는다.
        while(i &lt;= mid &amp;&amp; j &lt;= right){
            cnt++;
            if(input_arr[i] &lt;= input_arr[j]) sorted_arr[k++] = input_arr[i++];
            else sorted_arr[k++] = input_arr[j++];
        }

        // 만약 앞의 분할 배열이 남아있다면
        if(i &lt;= mid) {
            for(int l = i; l &lt;= mid; l++) {
                cnt++;
                sorted_arr[k++] = input_arr[l];
            }
        }

        // 만약 뒤의 분할 배열이 남아있다면
        if(j &lt;= right) {
            for(int l = j; l &lt;= right; l++){
                cnt++;
                sorted_arr[k++] = input_arr[l];
            }
        }

        // 정렬된 분할 배열을 원래 배열에 넣는다.
        for(int l = left; l &lt;= right; l++){
            input_arr[l] = sorted_arr[l];
        }

        return cnt;
    }
}</code></pre>
<blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/9e397513-0726-4346-a1e8-bf61e648f539/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/eaea6e8f-08d0-4598-a232-9d820861adb3/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/4ee06439-1ee7-4570-b070-9fee72cb9096/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/5737522e-baa7-4919-a13e-565aac9f7a60/image.png" alt=""></p>
</blockquote>
<h4 id="퀵-정렬">퀵 정렬</h4>
<p>퀵 정렬은 합병 정렬과 비슷하게 두 개의 배열로 나누어 분할 정복하는 알고리즘이다. 하지만 절반으로 나누어 가던 합병 정렬과는 달리 하나의 값을 기준으로 양옆으로 크고 작음을 정렬하여 비정형적인 크기로 분할해나간다.</p>
<pre><code class="language-java">public class QuickSorting {

    public static void main(String[] args) {

        int[] input_arr = {9, 6, 7, 3, 5, 4, 10, 14, 1, 2, 0, 12, 1111};

        int cnt = quickSorting(input_arr);

        System.out.println(Arrays.toString(input_arr));
        System.out.println(&quot;processing count : &quot; + cnt);
    }

    private static int quickSorting(int[] input_arr){ return divide(input_arr, 0, input_arr.length - 1); }

    private static int divide(int[] input_arr, int left, int right){

        if(left - right == 0 || left &gt; right) return 0;

        int cnt = 0;

        int pivot = input_arr[left];

        int low = left + 1;
        int high = right;

        // low와 high가 바뀌는 순간까지 정렬하면서 내려온다.
        while(low &lt;= high) {
            if(input_arr[low] &gt; pivot &amp;&amp; input_arr[high] &lt; pivot) {
                int temp = input_arr[low];
                input_arr[low++] = input_arr[high];
                input_arr[high--] = temp;
            }
            if(input_arr[low] &lt; pivot) low++;
            if(input_arr[high] &gt; pivot) high--;

            cnt++;
        }

        // 정렬 다하고 피벗을 사이에 끼운다.
        input_arr[left] = input_arr[high];
        input_arr[high] = pivot;
        cnt++;

        cnt += divide(input_arr, left, high - 1);
        cnt += divide(input_arr, low, right);


        return cnt;
    }
}
</code></pre>
<blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/858b5ccf-3b57-41c7-b992-b631305ea68e/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/dafdb137-1aeb-42b9-94ed-a89156c4e1a1/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://images.velog.io/images/jaeseok-go/post/acfb53be-42ec-449f-be28-c04d7492bf89/image.png" alt=""><img src="https://images.velog.io/images/jaeseok-go/post/154206b2-4164-4ddd-9115-6d93c1c20b7c/image.png" alt=""></p>
</blockquote>
<p>합병 정렬과 퀵 정렬은 n개의 입력이 주어졌을 때 이를 나누어 간다. 합병 정렬을 예를 들어 5개의 입력이 주어지면 1회전에 3, 2개로 나눈다. 그 다음 회전에 2, 1, 1, 1로 나눈다. 그 다음 회전에 1, 1, 1, 1, 1로 나눈다. 즉, log2(n)만큼 반복했을 때 1개짜리 배열로 모두 만들 수 있다.</p>
<p>log2(n)번으로 나누어 분할할 때 각 분할 마다 다시 합병해야하는 원소의 갯수는 n개이다. 따라서 합병 정렬은 O(nlogn)의 시간복잡도를 가진다. 퀵 정렬도 마찬가지이다.</p>
]]></description>
        </item>
    </channel>
</rss>