<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발 일기장</title>
    <link>https://way-be-developer.tistory.com/</link>
    <description>기초부터 탄탄하고 꾸준하게 공부하기</description>
    <language>ko</language>
    <pubDate>Fri, 14 Aug 2026 12:18:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>blue-curtain</managingEditor>
    <image>
      <title>개발 일기장</title>
      <url>https://tistory1.daumcdn.net/tistory/4083125/attach/e2c2d5a186b943b0b3d7bf794fac2ba7</url>
      <link>https://way-be-developer.tistory.com</link>
    </image>
    <item>
      <title>Reactive Programing</title>
      <link>https://way-be-developer.tistory.com/298</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reactive Programming&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 흐름과 전달에 관한 프로그래밍 패러다임&lt;/li&gt;
&lt;li&gt;리액티브 스트림을 사용하는 프로그래밍
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리액티브 스트림
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잠재적으로 무한정 들어올 수 있는 비동기 데이터를 순서대로, 그리고 블록하지 않고 (non-blocking) 역압력(backpressure, 배압, 메세지를 받는 쪽에서 보내는 쪽으로 지속적으로 메세지를 처리할 수 있는지 알려 요청의 양을 조절하는 기술)으로 처리하는 표준 기술&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;엽압력 (배압, Backpressure)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;발행 - 구독 프로토콜에서 이벤트 스트림(발행쪽)의 구독자가 발행된 이벤트를 처리하는 속도보다 느린 속도로 이벤트를 소비하면서 문제가 발생하지 않도록 보장하는 장치이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;왜 이런 패러다임이 생겼나?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빅데이터/모바일부터 클라우드 기반 클러스터에 이르는 다양한 환경/밀리초 단위의 응답시간을 기대하는 유저의 사용패턴 요구사항을 만족시켜주기 위해&lt;/li&gt;
&lt;li&gt;기존에는 Thread Pool 을 활용해서 일처리를 진행했다. 이 때 I/O 작업이 많은 일을 하게 되는 Thread 의 경우 대부분 기다리는 상황이 벌어진다. 특히 Thread Pool 의 크기가 정해져 있다보니 성능의 한계가 찾아온다. 이를 해결하기 위해서 Thread Pool 대신 비동기 &amp;amp; non-blocking 모델을 사용해서 메시지 커뮤니케이션으로 전환하게 된다. 이 중에서 비동기 &amp;amp; non-blocking 모델에서 발생할 수 있는 문제를 backpressure 로 해결한 리액티브 스트림을 채택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Reactive 의 속성 4가지 Keyword
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반응성 : 일정하고 예상할 수 있는 빠른 반응시간&lt;/li&gt;
&lt;li&gt;회복성 : 장애가 전파되지 않고 복구된다.&lt;/li&gt;
&lt;li&gt;탄력성 : 작업량의 변화와 무관하다. 병목이 발생하면 관련 컴포넌트에 할당된 자원 수를 늘리면 그만이다.&lt;/li&gt;
&lt;li&gt;메시지기반 : 컴포넌트 간의 결합이 약하다. 시스템은 비동기 메세지 전달에 의존한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JDK 에서 리액티브 프로그래밍을 제공하는 기술
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RxJava
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바로 리액티브 프로그래밍을 할 수 있는 라이브러리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 프로그래밍과 함수형 프로그래밍 기법을 활용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;복잡한 비동기 프로그램을 쉽게 개발할 수 있게 해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Project Reactor&lt;/li&gt;
&lt;li&gt;Spring Framework 5.0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;java.util.concurrent.Flow 클래스&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 9에서 리액티브 프로그래밍을 제공하기 위해서 Flow 클래스를 추가했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Akka, RxJava 등의 리액티브 라이브러리는 Flow 클래스에 정의된 인터페이스 표준을 따른다. 자세한 내용은 &lt;a href=&quot;https://way-be-developer.tistory.com/297&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;북마크&lt;/a&gt;를 참고해주세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://modimodi.tistory.com/m/47&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://modimodi.tistory.com/m/47&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>blue-curtain</author>
      <guid isPermaLink="true">https://way-be-developer.tistory.com/298</guid>
      <comments>https://way-be-developer.tistory.com/298#entry298comment</comments>
      <pubDate>Sat, 5 Mar 2022 17:07:31 +0900</pubDate>
    </item>
    <item>
      <title>Reactive Stream 이란?, backPressure 이란?</title>
      <link>https://way-be-developer.tistory.com/297</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;정의&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;non-blocking, backPressure 를 이용해서 비용기 서비스를 할 때 기본이 되는 스팩이다. Java RxJava, Spring5 Webflux의 Core 에 있는 ProjectReactor 프로젝트 모두 해당 스팩을 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 비동기 프로젝트를 잘 이해하기 위해서 기본 스펙이 되는 Reactive Stream 에 대한 이해가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;non-blocking backpressure 를 이용하여 비동기 스트림 처리의 표준을 제공하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목적, 설계, 범위&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지속적으로 들어오는 스트림 데이터를 효율적으로 처리하기 위해서는 비동기 시스템이 효과적이다. 비동기 처리를 하면서 가장 중요한 문제는 데이터 처리가 목적지의 리소스 소비를 예측가능한 범위에서 신중하게 제어할 수 있어야 한다는 것이다. 네크워크를 통한 서버간의 협업 또는 단일 서버에서 컴퓨팅 리소스를 동시에 사용할 때 주로 씁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reactive Stream 의 주된 목적은 비동기의 경계를 명확히하여 스트림 데이터의 교환을 효과적으로 관리하는 것에 있음. 즉, 비동기로 데이터를 처리하는 시스템이 어느정도의 data 가 들어올 지 예측가능하도록 하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reactive Stream 에서는 BackPressure가 이를 달성할 수 있게 해주는 중요한 부분이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 가 제공하는 Flow API 의 구현체인 SubmissionPublisher 라이브러리를 예시로 들자면, 비동기 요청으로 받는 쪽(subscriber)에서 요청을 보내는 쪽(publisher)에 &amp;ldquo;n개의 데이터 처리가 가능한 상태입니다&quot; 하고 알림을 주면 publisher 가 전송할 데이터가 있을 때 subscriber 에게 데이터를 보냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reactive Stream은 다음과 같은 스트림 지향 라이브러리에 대한 표준 및 사양입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잠재적으로 무한한 숫자의 데이터 처리&lt;/li&gt;
&lt;li&gt;순서대로 처리&lt;/li&gt;
&lt;li&gt;컴포넌트간에 데이터를 비동기적으로 전달&lt;/li&gt;
&lt;li&gt;backpressure 를 이용한 데이터 흐름제어&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BackPressure (배압)&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.reactivemanifesto.org/ko&quot;&gt;리액티브 선언문&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 컴포넌트가 부하를 이겨내기 힘들 때, 시스템 전체가 합리적인 방법으로 대응해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 컴포넌트가 대처할 수 없고 장애가 발생해선 안 되기 때문에 컴포넌트는 상류 컴포넌트들에 자신이 과부하 상태라는 것을 알려 부하를 줄이도록 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 배압은 시스템이 부하로 인해 무너지지 않고 정상적으로 응답할 수 있게 하는 중요한 피드백 방법이다. 배압이 사용자에게까지 전달되어 응답성이 떨어질 수 있지만, 이 메커니즘은 부하에 대한 시스템의 복원력을 보장하고 시스템 자체가 부하를 분산할 수 있는 자원을 제공할 수 있는지 정보를 제공할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;API Components&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reactive Stream API 의 구성요소는 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher&lt;/li&gt;
&lt;li&gt;Subscriber&lt;/li&gt;
&lt;li&gt;Subscription&lt;/li&gt;
&lt;li&gt;Processor&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher 는 무한한 data를 제공합니다. 제공된 data 는 Subscriber 가 구독하는 형식으로 처리됩니다. Publisher.subscribe(Subscriber) 의 형식으로 data 제공자와 구독자가 연결을 맺게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 호출되는 순서는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;onSubscribe
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;publisher 가 생산하는 data를 Subscriber 가 받을 준비가 되어 있다는 의미이며, onNext로 데이터를 수신합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;onNext
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 수신한다는 의미입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;onError
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실패가 있는 경우 호출됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;onComplete
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 이상 사용할 수 있는 신호가 없을 경우 Publisher 는 onComplete 을 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;API Components 명세서&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface Publisher&amp;lt;T&amp;gt; {
    public void subscribe(Subscriber&amp;lt;? super T&amp;gt; s);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher 를 Subscriber 가 구독할 수 있도록 메서드를 제공합니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public interface Subscriber&amp;lt;T&amp;gt; {
    public void onSubscribe(Subscription s);
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscriber 는 Subscription 를 등록해야하고, Subscription 에서 오는 신호에 따라서 동작합니다. Subscriber 에게 오는 신호는 onNext, onError, onComplete 가 있습니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public interface Subscription {
    public void request(long n);
    public void cancel();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscription 은 Publisher 와 Subscriber 사이에서 중계하는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;request 메서드는 Subscriber 가 Publisher 에게 데이터를 요청하는 개수이며 (요청할 수 있다는 준비상태를 알림) cancel 은 구독을 취소하겠다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 interface 를 토대로 아래와 같은 flow 를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reactive Stream Flow,&amp;nbsp; 출처 :&amp;nbsp;&lt;a href=&quot;https://grokonez.com/java/java-9-flow-api-reactive-streams&quot;&gt;https://grokonez.com/java/java-9-flow-api-reactive-streams&lt;/a&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Publisher 에 본인이 소유할 Subscription 를 구현하고 publishing 할 data 를 만듭니다.&lt;/li&gt;
&lt;li&gt;Publisher 는 subscribe() 메서드를 통해서 Subscriber 를 등록합니다.&lt;/li&gt;
&lt;li&gt;Subscriber 는 onSubscribe() 메서드를 통해서 Subscription 을 등록하고 Publisher 를 구독하기 시작합니다. 이는 Publisher 에 구현된 Subscription 을 통해서 이루어집니다. 이렇게 하면 Publisher 와 Subscriber는 Subscription 을 통해 연결된 상태가 됩니다. onSubscribe() 내부에서 Subscription 의 request() 를 요청하면 그 때부터 data 에 대한 구독이 시작됩니다.&lt;/li&gt;
&lt;li&gt;Subscriber 는 Subscription 메서드의 request() 또는 cancel() 을 호출해서 data 흐름을 제어할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Subscription 의 request() 에는 조건에 따라 Subscriber 의 onNext(), onComplete() 또는 onError()를 호툴합니다. 그러면 Subscriber 의 해당 메서드 로직에 따라서 request() 또는 cancel() 로 제어하게 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플 예제&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Sample {
    public static void main(String[] args) {
				Flow.Publisher&amp;lt;String&amp;gt; publisher = new SubmissionPublisher&amp;lt;&amp;gt;();

        Flow.Subscriber&amp;lt;String&amp;gt; subscriber = new Flow.Subscriber&amp;lt;String&amp;gt;() {

            private Flow.Subscription subscription;

            @Override
            public void onSubscribe(Flow.Subscription subscription) {
                // 구독을 수행합니다.
                System.out.println(&quot;구독&quot;);
                // Subscription 를 등록합니다.
                this.subscription = subscription;
                // 1개의 데이터를 요청합니다.
                this.subscription.request(1);
            }

            @Override
            public void onNext(String item) {
                // Subscriber 가 데이터를 받으면 next 가 호출됩니다.
                System.out.println(&quot;onNext call&quot;);
                System.out.println(item);
                // 다음 메세지를 호출할 수 있는 상태인지 Subscription 을 통해서 Publisher 에게 알립니다.
                subscription.request(1);
            }

            @Override
            public void onError(Throwable throwable) {
                throwable.printStackTrace();
            }

            @Override
            public void onComplete() {
                System.out.println(&quot;completed&quot;);
            }
        };

        publisher.subscribe(subscriber);
        ((SubmissionPublisher)publisher).submit(&quot;submit1&quot;);
        ((SubmissionPublisher)publisher).submit(&quot;submit2&quot;);
        ((SubmissionPublisher)publisher).submit(&quot;submit3&quot;);
        ((SubmissionPublisher)publisher).submit(&quot;submit4&quot;);
        ((SubmissionPublisher)publisher).close();
        System.out.println(&quot;끝&quot;);

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;끝
구독
onNext call
submit1
onNext call
submit2
onNext call
submit3
onNext call
submit4
completed
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보면 비동기로 요청이 일어나기 때문에 &amp;ldquo;끝&quot; 이라는 메세지가 먼저 호출된 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구독 후에 publisher 에게 메세지를 submit 하면 Publisher는 Subscriber 에게 onNext 신호를 주고 데이터 처리를 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sabarada.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sabarada.tistory.com/98&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1646467571872&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Java] Reactive Stream 이란?&quot; data-og-description=&quot;reactive stream이란 non-blocking(넌블럭킹) backPressure(역압)을 이용하여 비동기 서비스를 할 때 기본이 되는 스펙입니다. java의 RxJava, Spring5 Webflux의 Core에 있는 ProjectReactor 프로젝트 모두 해당..&quot; data-og-host=&quot;sabarada.tistory.com&quot; data-og-source-url=&quot;https://sabarada.tistory.com/98&quot; data-og-url=&quot;https://sabarada.tistory.com/98&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/9vMyD/hyNC8H1PMv/y2HnTp7fc8kttgRcu74yp0/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/cF0TYW/hyNBXnN6bU/em03Yp2JwD8L0baVLht7j1/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/loGu9/hyNCZEjYHG/wvPEbQe4GWEPOWvOXKuSm1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://sabarada.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sabarada.tistory.com/98&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/9vMyD/hyNC8H1PMv/y2HnTp7fc8kttgRcu74yp0/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/cF0TYW/hyNBXnN6bU/em03Yp2JwD8L0baVLht7j1/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/loGu9/hyNCZEjYHG/wvPEbQe4GWEPOWvOXKuSm1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Java] Reactive Stream 이란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;reactive stream이란 non-blocking(넌블럭킹) backPressure(역압)을 이용하여 비동기 서비스를 할 때 기본이 되는 스펙입니다. java의 RxJava, Spring5 Webflux의 Core에 있는 ProjectReactor 프로젝트 모두 해당..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sabarada.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>blue-curtain</author>
      <guid isPermaLink="true">https://way-be-developer.tistory.com/297</guid>
      <comments>https://way-be-developer.tistory.com/297#entry297comment</comments>
      <pubDate>Sat, 5 Mar 2022 17:05:36 +0900</pubDate>
    </item>
    <item>
      <title>Java I/O (Input/Output)란?</title>
      <link>https://way-be-developer.tistory.com/296</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;IO 장치들은 여러 문제가 있음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 주변 장치가 존재
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서로 다른 포멧의 데이터를 전송&lt;/li&gt;
&lt;li&gt;서로 다른 속도로 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CPU 와 RAM 보다 I/O 작업이 느림&lt;/li&gt;
&lt;li&gt;입출력 모듈을 필요로 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 입출력 모듈이란 CPU 와 메모리의 인터페이스이고 하나 또는 그 이상의 주변 장치에 대한 인터페이스를 의미&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Programmed I/O (프로그램 입출력)&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 모듈 중에 프로그램 입출력은 IO 로 부터 제어권을 CPU 가 가져와서 작업을 진행하는 것으로 CPU 는 입출력 모듈이 명령어의 수행을 완료할 때까지 기다립니다. 따라서 이 방식은 CPU 의 시간을 소모합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java I/O 는 이런 문제점 때문에 파일 입출력이 느리다는 인상을 많이 주었음. 이런 문제점을 Non-blocking IO(nio, New IO) API를 제공해서 극복해왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Blocking, Non-Blocking&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://way-be-developer.tistory.com/295&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://way-be-developer.tistory.com/295&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1646467147023&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Synch/Async vs blocking/non-blocking 차이&quot; data-og-description=&quot;동기/비동기, blocking/non-blocking 의 차이는 동기/비동기, blocking/non-blocking 두 그룹의 차이는 관심사가 다르다. blocking/non-blocking 호출되는 함수가 바로 return 하느냐 마느냐가 관심사이다. 호출된..&quot; data-og-host=&quot;way-be-developer.tistory.com&quot; data-og-source-url=&quot;https://way-be-developer.tistory.com/295&quot; data-og-url=&quot;https://way-be-developer.tistory.com/295&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/UpVGr/hyNC3fDTZX/X8ahK8MPCand8Mx7FCvKY0/img.png?width=800&amp;amp;height=502&amp;amp;face=0_0_800_502,https://scrap.kakaocdn.net/dn/cCMdKC/hyNB1Kve8x/lzrpPKZDqEZAwCuBZaptb1/img.png?width=800&amp;amp;height=502&amp;amp;face=0_0_800_502,https://scrap.kakaocdn.net/dn/MOTTb/hyNB2ilNcr/D4Zbw0zgKgYBfZ7pssUuAK/img.png?width=980&amp;amp;height=980&amp;amp;face=0_0_980_980&quot;&gt;&lt;a href=&quot;https://way-be-developer.tistory.com/295&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://way-be-developer.tistory.com/295&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/UpVGr/hyNC3fDTZX/X8ahK8MPCand8Mx7FCvKY0/img.png?width=800&amp;amp;height=502&amp;amp;face=0_0_800_502,https://scrap.kakaocdn.net/dn/cCMdKC/hyNB1Kve8x/lzrpPKZDqEZAwCuBZaptb1/img.png?width=800&amp;amp;height=502&amp;amp;face=0_0_800_502,https://scrap.kakaocdn.net/dn/MOTTb/hyNB2ilNcr/D4Zbw0zgKgYBfZ7pssUuAK/img.png?width=980&amp;amp;height=980&amp;amp;face=0_0_980_980');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Synch/Async vs blocking/non-blocking 차이&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;동기/비동기, blocking/non-blocking 의 차이는 동기/비동기, blocking/non-blocking 두 그룹의 차이는 관심사가 다르다. blocking/non-blocking 호출되는 함수가 바로 return 하느냐 마느냐가 관심사이다. 호출된..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;way-be-developer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 북마크를 참고하면 Blocking, Non-Blocking 에 대한 개념을 잡을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NodeJS 의 장점은 이벤트 기반의 비동기 논블로킹 IO 지원으로 자원을 효율적으로 사용한다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Blocking API&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blocking API 란 API 를 호출한 Thread 가 API 의 작업이 끝날 때까지 다른 동작을 하지 않는 API 를 블로킹이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 기본 IO 관련 API 들이 Blocking 방식을 사용하는데 InputStream, OutputStream이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;InputStream inputstream = new FileInputStream(&quot;c:\\\\data\\\\input-text.txt&quot;);

int data = inputstream.read(); // Blocking!
while(data != -1) {
  doSomethingWithData(data);
  data = inputstream.read();
}
inputstream.close();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 Blocking API 들은 반환값을 받을 때까지 Blocking 되어서 Thread 는 idle (프로세스를 실행하고 있지 않은 상태)로 유지됩니다. 예시는 파일이지만 네트워킹 처럼 오래걸리는 작업의 경우 Blocking 으로 작업을 수행하면 bottle neck(병목) 의 원인이 되기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Non Blocking API&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Non-Blocking API 는 쉽게 말해서 API 호출시 요청한 작업의 완료 여부와 상관 없이 즉각적으로 현재 상태에 대한 응답이 옵니다. 그래서 API 호출 후 Thread 제어권이 있기 때문에 다른 작업을 진행할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java NIO (New IO) 의 등장으로 Non Blocking방식을 구현할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Thread 가 IO작업에 의존적이지 않기 때문에 하나의 Thread 로 다수의 IO를 처리할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NIO 와 IO 의 주요 차이점&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NIO 와 IO는 개념적으로는 Blocking, Non-Blocking 의 차이가 있지만, 그 외에도 차이점이 존재합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stream Oriented vs Buffer Oriented&lt;/li&gt;
&lt;li&gt;Blocking IO vs Non Blocking IO&lt;/li&gt;
&lt;li&gt;Selector&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stream 기반 vs Buffer 기반&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java NIO 와 IO 사이의 큰 차이는 IO는 Stream 기반이고, NIO는 Buffer 기반이라는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 스트림 기반의 Java IO는 스트림으로 부터 한번에 여러 바이트를 읽습니다. 읽은 바이트를 가지고 무엇을 할지는 사용하는 이에게 달려있고, 데이터는 어디에도 캐시되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 스트림 속 데이터에서 앞 뒤로 이동할 수도 없습니다. 만약 스트림으로 부터 읽은 데이터 내부에서 앞 뒤로 이동할 필요가 있다면 버퍼를 만들어 캐싱해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 기반의 Java NIO는 조금 다릅니다. 이미 처리된 buffer 로 부터 데이터를 읽어서 필요하다면 버퍼 내부에서 앞뒤로 이동할 수 있습니다. 이는 데이터를 처리하는 동안 좀 더 유연함을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Blocking vs Non-Blocking IO&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java IO의 여러 스트림들은 Blocking 방식입니다. 하나의 Thread가 read() or write()를 발생 시킬 때, 해당 thread는 데이터를 읽을 때까지 혹은 데이터를 적을 때까지 blocked 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java NIO의 Non-Blocking mode 는 thread 가 채널을 통해서 데이터를 읽는 것을 요청한 뒤, 이용 가능한 데이터만을 얻을 수도 있고, 만약 아무 데이터가 없다면 즉시 리턴하여 thread 가 동작할 수 있게 합니다. (스레드가 Blocking 되지않습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Non-Blocking writing 도 마찬가지로 thread 는 channel 을 통해서 어떤 데이터를 쓰도록 요청하고 데이터를 전부 다 작성할 동안 기다리지 않고 그 동안 다른 동작을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Non-Blocking thread이 block 되지 않는 순간엔 다른 채널에 IO를 수행할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 단 하나의 thread 가 다수의 input, output channels 를 관리 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Selector&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java NIO Selector는 하나의 thread 가 여러개의 input channel 들을 모니터링 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 특징은 다수의 Thread 로 IO를 관리하는 방식에 비해 Thread Switching 을 줄이기 때문에 이점을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 selector 를 사용해서 다수의 channels 를 등록할 수 있으며 하나의 스레드를 사용해서 input 을 처리할 수 있는 channel 을 선택할 수 있으며 또한 writing 을 위해 준비된 channel 을 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 매커니즘은 하나의 thread 가 여러개의 channel을 관리하기 쉽게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Java I/O 패키지에 대해서 좀 더 자세하게 알아보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java I/O 패키지란?&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  Provides for system input and output through data streams, serialization and the file system. Unless otherwise noted, passing a null argument to a constructor or method in any class or interface in this package will cause a NullPointerException to be thrown.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/io/package-summary.html&quot;&gt;java.io (Java SE 13 &amp;amp; JDK 13 )&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;쉽게 말하면 데이터 스트림, 직렬화 및 파일 시스템을 통한 시스템 입력 및 출력을 제공. 즉 Java I/O 패키지 않에는 파일 파일 입출력과 관련된 클래스을 포함하고 있다는 의미이다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;바이트 단위 스트림&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputStream, OutputStream 둘다 바이트 기반 입출력. 스트림의 최상위 클래스로 추상 클래스이다.&lt;/li&gt;
&lt;li&gt;관련된 모든 바이트 기반 입출력 스트림은 이 클래스를 상속 받아서 만들어졌다.&lt;/li&gt;
&lt;li&gt;그림, 멀티미디어, 문자 등 모든 종류의 데이터를 주고 받을 수가 있다.&lt;/li&gt;
&lt;li&gt;Java I/O 도식표&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Java.io 패키지의 주요 클래스 설명&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;td&gt;파일 시스템의 파일 정보를 얻기 위한 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Console&lt;/td&gt;
&lt;td&gt;콘솔로부터 문자를 입출력하기 위한 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InputStream / OutputStream&lt;/td&gt;
&lt;td&gt;바이트 단위 입출력을 위한 최상위 입출력 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FileInputStream / FileOutputStream&lt;/td&gt;
&lt;td&gt;바이트 단위 입출력을 위한 하위 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataInputStream / DataOutputStream&lt;/td&gt;
&lt;td&gt;바이트 단위 입출력을 위한 하위 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ObjectInputStream / ObjectOutputStream&lt;/td&gt;
&lt;td&gt;바이트 단위 입출력을 위한 하위 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PrintStream&lt;/td&gt;
&lt;td&gt;바이트 단위 입출력을 위한 하위 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BufferedInputStream / BufferedOutputStream&lt;/td&gt;
&lt;td&gt;바이트 단위 입출력을 위한 하위 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reader / Writer&lt;/td&gt;
&lt;td&gt;문자 단위 입출력을 위한 최상위 입출력 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FileReader / FileWriter&lt;/td&gt;
&lt;td&gt;문자 단위 입출력을 위한 하위 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InputStreamReader / InputStreamWriter&lt;/td&gt;
&lt;td&gt;문자 단위 입출력을 위한 하위 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PrintWriter&lt;/td&gt;
&lt;td&gt;문자 단위 입출력을 위한 하위 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BufferedReader / BufferedWriter&lt;/td&gt;
&lt;td&gt;문자 단위 입출력을 위한 하위 스트림 클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문자 단위 스트림&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Reader, Writer 둘다 문자 데이터 기반 입출력의 최상위 클래스이다. 관련된 모든 텍스트 기반 입출력은 이 클래스를 상속 받아서 만들어진다.&lt;/li&gt;
&lt;li&gt;문자데이터를 입출력할 때 사용하는 문자 기반의 스트림이다.&lt;/li&gt;
&lt;li&gt;오로지 문자 데이터를 주고 받기 위해서 특화되어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;보조 스트림&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스트림의 기능을 보완하기 위해서 나온 스트림이다. (입출력 성능 속도 향상, 데이터 포멧 지원 등)
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 데이터를 주고 받지 않는다.&lt;/li&gt;
&lt;li&gt;데이터를 주고 받을 수 없기 때문에 스트림을 생성해서 등록한 뒤에 사용해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;DataInputStream dataInputStream = new DataInputStream(new FileInputStream(&quot;test.txt&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실험&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 공개하고 싶긴하지만, 다른 사람이 작성한 코드이기 때문에 정확한 코드를 게시하진 않고 결과만 말씀 드리겠습니다. 코드는 아래 블로그에 들어가 직접 보시면 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[&lt;b&gt;FileReader로 47.2MB 파일을 읽을 경우] &lt;/b&gt;vs [&lt;b&gt;BufferReader로 47.2MB 파일을 읽을 경우]&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자는 3분이 넘어도 빌드하지 못했다고 한다. 후자는&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;24초만에 빌드를 성공했다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blackinkgj.github.io/IO/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blackinkgj.github.io/IO/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1646467382351&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[컴퓨터 구조] Input/Output&quot; data-og-description=&quot;입출력에 관해서 알아보도록 한다.&quot; data-og-host=&quot;blackinkgj.github.io&quot; data-og-source-url=&quot;https://blackinkgj.github.io/IO/&quot; data-og-url=&quot;https://blackinkgj.github.io/IO/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cfCO70/hyNC0iU3QC/kS7k50TU6ycFbY9ClUd1L1/img.png?width=198&amp;amp;height=200&amp;amp;face=0_0_198_200,https://scrap.kakaocdn.net/dn/8IMRB/hyNC4lkqS7/aGK1H51kS5h0YpEk2XHt00/img.png?width=198&amp;amp;height=200&amp;amp;face=0_0_198_200&quot;&gt;&lt;a href=&quot;https://blackinkgj.github.io/IO/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blackinkgj.github.io/IO/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cfCO70/hyNC0iU3QC/kS7k50TU6ycFbY9ClUd1L1/img.png?width=198&amp;amp;height=200&amp;amp;face=0_0_198_200,https://scrap.kakaocdn.net/dn/8IMRB/hyNC4lkqS7/aGK1H51kS5h0YpEk2XHt00/img.png?width=198&amp;amp;height=200&amp;amp;face=0_0_198_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[컴퓨터 구조] Input/Output&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;입출력에 관해서 알아보도록 한다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blackinkgj.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@jihoson94/BIO-vs-NIO&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@jihoson94/BIO-vs-NIO&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1646467390272&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Java IO vs NIO&quot; data-og-description=&quot;Basic IO와 New IO에 대해 알아보겠습니다. 차이점과 장단점을 비교하면 진행해보겠습니다.Referencehttp://tutorials.jenkov.com/java-nio/nio-vs-io.html비교할 요소는 아래 3가지입니다.Stream Orient&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@jihoson94/BIO-vs-NIO&quot; data-og-url=&quot;https://velog.io/@jihoson94/BIO-vs-NIO&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjs2b9/hyNBPQYiZG/7jWt5KTa5gGVZ1KAp74UGK/img.png?width=768&amp;amp;height=636&amp;amp;face=0_0_768_636,https://scrap.kakaocdn.net/dn/zwY3D/hyNBPXIRmC/BRwsO1U1KsbwZTGYKnIRgK/img.png?width=852&amp;amp;height=706&amp;amp;face=0_0_852_706&quot;&gt;&lt;a href=&quot;https://velog.io/@jihoson94/BIO-vs-NIO&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@jihoson94/BIO-vs-NIO&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjs2b9/hyNBPQYiZG/7jWt5KTa5gGVZ1KAp74UGK/img.png?width=768&amp;amp;height=636&amp;amp;face=0_0_768_636,https://scrap.kakaocdn.net/dn/zwY3D/hyNBPXIRmC/BRwsO1U1KsbwZTGYKnIRgK/img.png?width=852&amp;amp;height=706&amp;amp;face=0_0_852_706');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java IO vs NIO&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Basic IO와 New IO에 대해 알아보겠습니다. 차이점과 장단점을 비교하면 진행해보겠습니다.Referencehttp://tutorials.jenkov.com/java-nio/nio-vs-io.html비교할 요소는 아래 3가지입니다.Stream Orient&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ljs0429777/Java-IO&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@ljs0429777/Java-IO&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1646467395867&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Java I/O&quot; data-og-description=&quot;Input과 Output의 약자로 입출력을 의미함입출력의 간단한 예로 키보드로 텍스트를 입력하고, 모니터로 입력한 텍스트를 출력하는 것임Input -&amp;gt; Output (지극히 개인적인 비유법,,)  식에서 &amp;quot;-&amp;gt;&amp;quot; 스트&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@ljs0429777/Java-IO&quot; data-og-url=&quot;https://velog.io/@ljs0429777/Java-IO&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bNd0MB/hyNBZFUZ6w/5NrwyYXsGeDseusmw7ISA1/img.png?width=700&amp;amp;height=350&amp;amp;face=0_0_700_350,https://scrap.kakaocdn.net/dn/Vxpbw/hyNC886dPp/LDBAeVhmteSovqjnZwPkqk/img.png?width=700&amp;amp;height=350&amp;amp;face=0_0_700_350&quot;&gt;&lt;a href=&quot;https://velog.io/@ljs0429777/Java-IO&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@ljs0429777/Java-IO&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bNd0MB/hyNBZFUZ6w/5NrwyYXsGeDseusmw7ISA1/img.png?width=700&amp;amp;height=350&amp;amp;face=0_0_700_350,https://scrap.kakaocdn.net/dn/Vxpbw/hyNC886dPp/LDBAeVhmteSovqjnZwPkqk/img.png?width=700&amp;amp;height=350&amp;amp;face=0_0_700_350');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java I/O&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Input과 Output의 약자로 입출력을 의미함입출력의 간단한 예로 키보드로 텍스트를 입력하고, 모니터로 입력한 텍스트를 출력하는 것임Input -&amp;gt; Output (지극히 개인적인 비유법,,)  식에서 &quot;-&amp;gt;&quot; 스트&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>blue-curtain</author>
      <guid isPermaLink="true">https://way-be-developer.tistory.com/296</guid>
      <comments>https://way-be-developer.tistory.com/296#entry296comment</comments>
      <pubDate>Sat, 5 Mar 2022 17:03:19 +0900</pubDate>
    </item>
    <item>
      <title>자바 가비지컬렉터. 강한참조 (String Reference), 약한참조 (Weak Reference)</title>
      <link>https://way-be-developer.tistory.com/294</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가비지컬렉터&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 가비지 컬렉터는 많은 종류가 있지만 크게 두가지 작업을 공통적으로 수행합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;힙 메모리 내의 객체 중 가비지를 찾아낸다.&lt;/li&gt;
&lt;li&gt;찾아낸 가비지를 처리해서 힙 메모리를 회수한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초의 Java 에서는 이들 가비지 컬렉션 작업에 사용자 코드가 관여 하지 않도록 구현되어 있었습니다. 좀 더 다양한 방법으로 객체를 처리하려는 요구가 있었고 이에 따라 JDK 1.2 부터 java.lang.ref 패키지를 추가해서 제한적이나마 사용자 코드와 GC가 상호작용 할 수 있게 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java.lang.ref 패키지는 객체를 new로 생성했을 때 생기는 string reference 이외에도 soft, weak, phantom 3가지의 새로운 참조 방식을 각각의 Reference 클래스로 제공합니다. 이 3가지 Reference 클래스를 애플리케이션에 사용하면 앞서 설명하였듯이 GC에 일정 부분 관여할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가비지컬렉터와 Reachability&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 가비지컬렉터는 객체가 가비지인지 판별하기 위해서 reachability 라는 개념을 사용한다. 어떤 객체에 유효한 참조가 있으면 reachable 로 간주하고 없으면 unreachable 로 간주한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC는 unreachable 로 간주된 객체를 가비지로 판단하여 GC (Garbage Collection)을 수행합니다. 한 객체는 여러 다른 객체를 참조할 수 있고, 참조된 객체들도 또다른 객체를 참조할 수 있으므로 객체들의 참조는 사슬모양을 이룬다. 이런 상황에서 유효한 참조 여부를 파악하려면 항상 유효한 최초의 참조가 있어야 하는데 이를 객체 참조의 root set이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 에서 메모리 영역인 런타임 데이터 영역 (runtime data area) 의 구조를 그림으로 그리면 다음과 같다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bogcwH/btru3tETJV9/JEWM6GwKZghJxadOTn6341/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bogcwH/btru3tETJV9/JEWM6GwKZghJxadOTn6341/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bogcwH/btru3tETJV9/JEWM6GwKZghJxadOTn6341/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbogcwH%2Fbtru3tETJV9%2FJEWM6GwKZghJxadOTn6341%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;492&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런타임 데이터 영역은 위와 같이 크게 세 부분으로 나뉜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 차지하는 영역&lt;/li&gt;
&lt;li&gt;객체를 생성 및 보관하는 힙&lt;/li&gt;
&lt;li&gt;클래스 정보가 차지하는 영역인 메서드 영역&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(객체에 대한 참조는 화살표로 표시되어 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙에 있는 객체들에 대한 참조는 다음 4가지 종류 중 하나이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;힙 내의 다른 객체에 의한 참조 (1번)&lt;/li&gt;
&lt;li&gt;Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역변수와 파라미터들에 의한 참조&lt;/li&gt;
&lt;li&gt;네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조&lt;/li&gt;
&lt;li&gt;메서드 영역의 정적 변수에 의한 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 1번을 제외한 나머지가 root set 이고 reachability 를 판가름하는 기준이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reachability 를 더 자세히 설명하기 위해 root set과 힙 내의 객체를 중심으로 다시 그리면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (1).png&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8e94q/btruXbeFK2A/Hv6aakXlvryNPkLSsgrXR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8e94q/btruXbeFK2A/Hv6aakXlvryNPkLSsgrXR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8e94q/btruXbeFK2A/Hv6aakXlvryNPkLSsgrXR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8e94q%2FbtruXbeFK2A%2FHv6aakXlvryNPkLSsgrXR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;673&quot; height=&quot;450&quot; data-filename=&quot;Untitled (1).png&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서 보듯, root set으로 부터 시작한 참조 사슬에 속한 객체들은 reachable 객체이고 이 참조사슬 밖에 있는 객체는 참조가 있다고 해도 unreachable 객체로 GC의 대상이다. 오른쪽 아래 객체처럼 reachable 객체를 참조해도 다른 reachable 객체가 이 객체를 참조하지 않는다면 이 객체는 unreachable 객체이다. 이 그림에서의 참조는 모두 java.lang.ref 패키지를 사용하지 않은 일반적인 참조이며, 이를 흔히 strong reference 라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Soft, Weak, Phantom Reference&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java.lang.ref 는 soft reference 와 weak reference, phantom reference 를 클래스 형태로 제공한다. 예를 들어서 약한 참조를 생성하기 위해서 사용하는 클래스인 java.lang.ref.WeakReference 클래스는 참조 대상이 될 객체를 캡슐화(encapsulate)한다. Java 가비지 콜렉터는 이 객체를 일반적인 객체와 다르게 특별하게 취급한다. (자세한 설명은 뒤에서). 캡슐화된 내부 객체는 weak reference 에 의해서 참조된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 WeakReference 클래스가 객체를 생성하는 예제이다.&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;WeakReference&amp;lt;Sample&amp;gt; wr = new WeakReference&amp;lt;Sample&amp;gt;( new Sample());  
Sample ex = wr.get();  
...
ex = null;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 첫줄의 WeakReference 객체는 Sample 객체를 캡슐화한 객체이다. 이렇게 생성된 Sample 객체는 두 번째 줄에서 get() 메서드를 통해서 다른 참조에 대입된다. 이 시점에서는 WeakReference 객체 내의 참조와 ex 변수의 참조, 두 개의 참조가 처음 생성한 Sample 객체를 가리킨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (2).png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X7Z65/btruXTZaifM/cuvXJTJzmmwFUXhQZKlDQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X7Z65/btruXTZaifM/cuvXJTJzmmwFUXhQZKlDQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X7Z65/btruXTZaifM/cuvXJTJzmmwFUXhQZKlDQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX7Z65%2FbtruXTZaifM%2FcuvXJTJzmmwFUXhQZKlDQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;177&quot; data-filename=&quot;Untitled (2).png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드 마지막 줄에서 ex 변수에 null 을 대입하면 처음 생성한 Sample 객체는 오직 WeakReference 내부에서만 참조된다. 이 상태를 weakly reachable 객체라고 하는데 자세한 내용은 뒤에서 다룬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (3).png&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5OBr4/btruXTrieNe/fMcJull55gu0u9oaaXSAC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5OBr4/btruXTrieNe/fMcJull55gu0u9oaaXSAC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5OBr4/btruXTrieNe/fMcJull55gu0u9oaaXSAC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5OBr4%2FbtruXTrieNe%2FfMcJull55gu0u9oaaXSAC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;674&quot; height=&quot;175&quot; data-filename=&quot;Untitled (3).png&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 스펙에서는 SoftReference, WeakReference, PhantomReference 3가지 클래스에 의해 생성된 객체를 reference object 라고 부른다. 그리고 이들 reference object 에 의해 참조된 객체는 &amp;ldquo;referent&amp;rdquo; 라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 소스 코드에서 new WeakReference() 생성자로 의해 생성된 wr 객체는 reference object 이고 new Sample() 생성자로 생성된 객체는 referent 이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference와 Reachability&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 설명한 것처럼, 원래 GC 대상 여부는 reachable 인가 unreachable 인가로만 구분하였고, 이를 사용자 코드에서 관여할 수 없었다. 그러나 java.lang.ref 패키지를 이용해서 reachable 객체들을 strongly, softly, weakly, phantomly 4가지 상태로 더 자세히 구별해서 GC의 동작을 달리 지정할 수 있게 되었다. 즉, GC 대상 여부를 판별하는 부분에 사용자 코드가 개입할 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (4).png&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KwQop/btru3slFlaZ/KwWlTPYN1BK9rQiwaKhzH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KwQop/btru3slFlaZ/KwWlTPYN1BK9rQiwaKhzH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KwQop/btru3slFlaZ/KwWlTPYN1BK9rQiwaKhzH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKwQop%2Fbtru3slFlaZ%2FKwWlTPYN1BK9rQiwaKhzH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;673&quot; height=&quot;446&quot; data-filename=&quot;Untitled (4).png&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;녹색으로 표시한 중간의 두 객체는 WeakReference 로만 참조된 weakly reachable 객체이고, 파란색 객체는 strongly reachable 객체이다. GC 가 동작할 때 unreachable 객체 뿐 아니라, weakly reachable 객체도 가비지 객체로 간주되어 메모리에서 회수된다. (참조가 끊어지기 때문에) root set 으로 부터 시작된 참조 사슬에 있음에도 불구하고 GC가 동작할 때 회수 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서 WeaklyReference 객체 자체는 weakly reachable 객체가 아니라 strongly reachable 객체이다. A의 경우에는 WeakReference 에 의한 참조 이외에 Root set으로 부터 시작된 참조 사슬에 포함되어 있기 때문에 weakly reachable 객체가 아니라 strongly reachable 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC가 동작하여 어떤 객체를 weakly reachable 객체로 판명하면 GC가 WeakReference 객체의 weakly reachable 객체에 대한 참조를 null 로 설정한다. 그래서 weakly reachable 객체는 unreachable 객체와 마찬가지 상태가 되어 가비지로 판명이 나는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Softly Reachable과 SoftReference&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;softly reachable 객체, 즉 strong reachable 이 아니고 오직 SoftReference 객체로만 참조된 객체는 힙에 남아있는 메모리의 크기와 해당 객체의 사용 빈도에 따라서 GC 여부가 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 reachable 객체는 weakly reachable 객체와 달리 GC가 동작할 때마다 회수되는 것이 아니고, 자주 사용할 수록 더 오래 살아남는다. Oracle HotSpot VM 에서는 softly reachable 객체의 GC를 조절하기 위해 다음 JVM 옵션을 제공한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-XX:SoftRefLRUPolicyMSPerMB=&amp;lt;N&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션의 기본 값을 1000이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;softly reachable 객체의 GC 여부는 위 옵션 값으로 다음 수식을 계산한 결과에 의해 결정된다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;(마지막 strong reference 가 GC 된 때 부터 지금까지의 시간) &amp;gt; (옵션 설정값 N) * (힙에 남아있는 메모리 크기)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 객체가 사용된다는 것은 strong reference 에 의해 참조되는 것이고 위 수식의 좌변은 해당 객체가 얼마나 자주 사용되는지를 의미한다. 만약 N 이 1000이고 남은 힙 메모리 크키가 100MB 이면 수식의 우변은 1000 * 100 = 100,000ms = 100초가 된다. 즉 reachable 객체가 100초 이상 사용되지 않으면 GC 에 의해 회수 대상이 된다. 힙에 남아있는 메모리가 작을수록 우변의 값이 작아지므로, 힙이 거의 소진되면 대부분의 softly reachable 객체는 모두 메모리에서 회수되어 OutOfMemoryError 를 막게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;softly reachable 객체를 GC하기로 결정나면 WeakReference 의 경우와 마찬가지로 SoftReference 객체의 softly reachable 객체에 대한 참조를 null 로 설정한다. 이 후 softly reachable 객체는 unreachable 객체가 되어 GC에 의해 메모리에서 회수된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ReferenceQueue&lt;/b&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;phantomly reachable 객체의 동작과 PhantomReference를 설명하기 전에 java.lang.ref 패키지에서 제공하는 ReferenceQueue 클래스에 대해 설명할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SoftReference 객체나 WeakReference 객체가 참조하는 객체가 GC 대상이 되면 SoftReference 객체, WeakReference 객체 내의 참조는 null로 설정되고 SoftReference 객체, WeakReference 객체 자체는 ReferenceQueue에 enqueue된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReferenceQueue에 enqueue하는 작업은 GC에 의해 자동으로 수행된다. ReferenceQueue의 poll() 메서드를 활용해서 reference object 를 꺼내면 softly reachable 객체나 weakly reachable 객체가 GC되었는지를 파악할 수 있고 이와 관련한 후처리 작업을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 Java Collections 클래스 중에서 간단한 캐시를 구현하는 용도로 자주 사용되는 WeakHashMap 클래스는 ReferenceQueue 와 WeakReference 를 사용하여 구현되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SoftReference 와 WeakReference 는 ReferenceQueue 를 사용할 수도 있고 사용하지 않을 수도 있지만 PhantomReference 는 반드시 ReferenceQueue 를 사용해야만 한다. 그래서 딱 1개 있는 PhantomReference 클래스의 생성자는 ReferenceQueue를 인자로 받는다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;ReferenceQueue&amp;lt;Object&amp;gt; rq = new ReferenceQueue&amp;lt;Object&amp;gt;();
PhantomReference&amp;lt;Object&amp;gt; pr = new PhantomReference&amp;lt;Object&amp;gt;(referent, rq);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;phantomly reachable 는 파이널 라이즈와 메모리 회수 사이에 관여한다. strongly, softly, weakly 에 해당하지 않고 PhantomReference 로만 참조되는 객체는 파이널라이즈 된 이후에 phantomly reachable 로 간주된다. GC가 객체를 처리하는 순서는 항상 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;soft references&lt;/li&gt;
&lt;li&gt;weak references&lt;/li&gt;
&lt;li&gt;파이널라이즈 (가비지 컬렉션이 회수에하기 전에 객체가 리소스를 해제하고 다른 정리 작업을 수행할 수 있게 하는 것)&lt;/li&gt;
&lt;li&gt;phantom references&lt;/li&gt;
&lt;li&gt;메모리 회수&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, GC 여부를 판별할 때 어떤 객체의 reachability 를 strongly, softly, weakly 순서로 먼저 판별하고 모두 아니면 phantomly reachable 여부를 판별하기 전에 파이널라이즈 를 진행한다. 그리고 대상 객체를 참조하는 PhantomReference 가 있다면 phantomly reachable 로 간주해서 메모리 회수는 지연시키고 파이널 라이즈 이후 해야할 작업을 애플리케이션이 수행할 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 설명한 것처럼 PhatomReference는 항상 ReferenceQueue를 필요로 한다. 그리고 PhantomReference의 get() 메서드는 SoftReference, WeakReference와 달리 항상 null을 반환한다. 따라서 한 번 phantomly reachable로 판명된 객체는 더 이상 사용될 수 없게 된다. 그리고 phantomly reachable로 판명된 객체에 대한 참조를 GC가 자동으로 null로 설정하지 않으므로, 후처리 작업 후에 사용자 코드에서 명시적으로 clear() 메서드를 실행하여 null로 설정해야 메모리 회수가 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같이, PhantomReference를 사용하면 어떤 객체가 파이널라이즈된 이후에 할당된 메모리가 회수되는 시점에 사용자 코드가 관여할 수 있게 된다. 파이널라이즈 이후에 처리해야 하는 리소스 정리 등의 작업이 있다면 유용하게 사용할 수 있다. 그러나 개인적으로는 PhantomReference를 사용하는 코드를 거의 본 적이 없으며, 그 효용성에 대해서는 의문이 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가적인 설명&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;strongly reachable 객체는 unreachable 객체가 되면 GC에 의해서 메모리 상에서 회수된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발행/구독 패턴을 따르는 옵저버 디자인 패턴을 사용한 애플리케이션이나 웹소켓 연결에서 내부적으로 세션을 가지고 있는 경우, 또는 서버가 세션을 저장하는 방식의 애플리케이션의 경우 메소드 내부의 지역 변수 등등에서 해당 객체를 참조하고 있기 때문에 strongly reachable 객체이고 관련한 인스턴스가 살아있는 동안 메모리 상에서 사라지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Out Of Memory 가 발생할 수 있는데 이를 방지하고자 reference object 사용을 고려해볼 수 있다. 아래 테스트 코드를 보자&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class BigData {
    private int[] array = new int[2500];
}

public class ReferenceTest {
    private List&amp;lt;WeakReference&amp;lt;BigData&amp;gt;&amp;gt; weakReferences = new LinkedList&amp;lt;&amp;gt;();
    private List&amp;lt;SoftReference&amp;lt;BigData&amp;gt;&amp;gt; softReferences = new LinkedList&amp;lt;&amp;gt;();
    private List&amp;lt;BigData&amp;gt; bigDatas = new LinkedList&amp;lt;&amp;gt;();

    public void weakReferenceTest() {
        try {
            while (true) {
                weakReferences.add(new WeakReference&amp;lt;BigData&amp;gt;(new BigData()));
            }
        } catch (OutOfMemoryError e) {
            System.out.println(&quot;out of memory&quot;);
        }
    }

    public void softReferenceTest() {
        try {
            while (true) {
                softReferences.add(new SoftReference&amp;lt;BigData&amp;gt;(new BigData()));
            }
        } catch (OutOfMemoryError e) {
            System.out.println(&quot;out of memory&quot;);
        }
    }

    public void strongReferenceTest() {
        try {
            while (true) {
                bigDatas.add(new BigData());
            }
        } catch (OutOfMemoryError e) {
            System.out.println(&quot;out of memory&quot;);
        }
    }

    public static void main(String[] args) {
        System.out.println(&quot;실행&quot;);

//        ReferenceQueue&amp;lt;Test&amp;gt; rq = new ReferenceQueue&amp;lt;Test&amp;gt;();
//        PhantomReference&amp;lt;Test&amp;gt; pr = new PhantomReference&amp;lt;Test&amp;gt;(new Test(&quot;test&quot;), rq);


        ReferenceTest test = new ReferenceTest();
        test.weakReferenceTest();
//        test.softReferenceTest();
//        test.strongReferenceTest();

        System.out.println(&quot;종료&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 출처 : &lt;a href=&quot;https://ktko.tistory.com/entry/자바-강한참조Strong-Reference와-약한참조Weak-Reference&quot;&gt;https://ktko.tistory.com/entry/자바-강한참조Strong-Reference와-약한참조Weak-Reference&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;weakReferenceTest&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;weakReferenceTest 메소드는 동작하면 weakReferences 에 데이터를 쌓는다. 메모리 부족 타이밍에 GC 가 동작하고 WeakReference 에 의한 참조 값이 null 이 되어 unreachable 상태가 되어 메모리 상에서 회수 된다. 즉, OutOfMemoryError 가 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;softReferenceTest&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;softReferenceTest 메소드가 동작하면 softReferences 에 데이터를 쌓는다. 힙 메모리 영역이 점점 작아지면 위에서 언급한 공식에 의해 GC 대상이 되고 SoftReference 에 의한 참조 값이 null 이 되어 unreachable 상태가 된다. 메모리에서 회수되어 OutOfMemoryError 가 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;strongReferenceTest&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;strongReferenceTest 메소드가 동작하면 위와 같은 방식으로 bigDatas 에 데이터를 쌓는다. 이 ReferenceTest 의 메소드의 지역변수에서 참조하고 있기 때문에 GC 대상이 되지 않으며 결국 OutOfMemoryError 가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;출처&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/329631&quot;&gt;https://d2.naver.com/helloworld/329631&lt;/a&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>blue-curtain</author>
      <guid isPermaLink="true">https://way-be-developer.tistory.com/294</guid>
      <comments>https://way-be-developer.tistory.com/294#entry294comment</comments>
      <pubDate>Thu, 3 Mar 2022 22:26:23 +0900</pubDate>
    </item>
    <item>
      <title>spring framework - @Value 가 동작하지 않는다면</title>
      <link>https://way-be-developer.tistory.com/292</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;환경 : java, springframework&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;코드&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Component
public class TestBean {
    @Value(&quot;${test}&quot;)
    private String test;

    private String defaultTestValue = getTest();

    public TestBean() {
        System.out.println(&quot;Constructor test = &quot; + test);
        System.out.println(&quot;Constructor defaultTestValue = &quot; + defaultTestValue);
    }

    @PostConstruct
    public void init() {
        System.out.println(&quot;PostConstruct test = &quot; + test);
        System.out.println(&quot;PostConstruct defaultTestValue = &quot; + defaultTestValue);
    }

    public String getTest() {
        return test;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;결과&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;Constructor test = null
Constructor defaultTestValue = null
PostConstruct test = test
PostConstruct defaultTestValue = null
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;원인&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이렇게 동작했는지 알려면 스프링 빈의 생명주기를 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링&amp;nbsp;컨테이너&amp;nbsp;생성&amp;nbsp;-&amp;gt;&amp;nbsp;스프링&amp;nbsp;빈&amp;nbsp;생성&amp;nbsp;-&amp;gt;&amp;nbsp;의존관계&amp;nbsp;주입&amp;nbsp;-&amp;gt;&amp;nbsp;초기화&amp;nbsp;콜백&amp;nbsp;-&amp;gt;&amp;nbsp;사용&amp;nbsp;-&amp;gt;&amp;nbsp;소멸전&amp;nbsp;콜백&amp;nbsp;-&amp;gt;&amp;nbsp;스프링&amp;nbsp;종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입의 방법은 대표적으로 세가지가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성자 주입
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성 시점에 의존성 모듈을 찾지 못하면 빈 생성을 하지 못한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프링 빈 생성시점에 동작한다&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;필드 주입
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필드에 @Autowired를 붙이면 주입&lt;/li&gt;
&lt;li&gt;의존성 관계 주입 시점에 동작한다. (런타임)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Setter 주입 (메소드를 통한 주입)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메소드에 @Autowired를 붙이면 주입&lt;/li&gt;
&lt;li&gt;의존성 관계 주입 시점에 동작한다. (런타임)&lt;/li&gt;
&lt;li&gt;의존성이 순환참조 문제 날 때, 유용하게 사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Value 는 필드주입과 동일하게 의존관계 주입 시점에 동작하는데, 이는 생성자 호출 시점 이후이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;defaultTestValue 생성 타이밍은 생성자 호출 시점에 생성 되는 것이기 때문에 @Value 어노테이션이 동작하기 전이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기화 콜백인 @PostConstruct 이 동작하는 시점에는 @Value 가 동작한 이후이기 때문에 필드값이 출력이 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리고 new 로 생성할 때도 주입 받을 수 없는 값이기 때문에 주의를 해야한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참고&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javax.annotation.PostConstruct 는 스프링 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기화 콜백 : @PostConstruct&lt;/li&gt;
&lt;li&gt;소멸전&amp;nbsp;콜백 : @PreDestroy&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프레임워크/Spring</category>
      <author>blue-curtain</author>
      <guid isPermaLink="true">https://way-be-developer.tistory.com/292</guid>
      <comments>https://way-be-developer.tistory.com/292#entry292comment</comments>
      <pubDate>Sun, 13 Feb 2022 23:34:55 +0900</pubDate>
    </item>
    <item>
      <title>JPA &amp;amp; Mysql 환경에서 데드락 관련 이슈</title>
      <link>https://way-be-developer.tistory.com/291</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참고&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2664/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/2664/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1644054003901&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할 &quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/2664/&quot; data-og-url=&quot;https://techblog.woowahan.com/2664/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zD7Si/hyNjPCVVuI/wR7koVR2j7ttHKYZdsZygk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/flVvB/hyNjB5Ljg3/Av78o93z4PHHnxUOpzjgEk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2664/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/2664/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zD7Si/hyNjPCVVuI/wR7koVR2j7ttHKYZdsZygk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/flVvB/hyNjB5Ljg3/Av78o93z4PHHnxUOpzjgEk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2664/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/2664/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1644054008926&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할 &quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/2664/&quot; data-og-url=&quot;https://techblog.woowahan.com/2664/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zD7Si/hyNjPCVVuI/wR7koVR2j7ttHKYZdsZygk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/flVvB/hyNjB5Ljg3/Av78o93z4PHHnxUOpzjgEk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2664/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/2664/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zD7Si/hyNjPCVVuI/wR7koVR2j7ttHKYZdsZygk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/flVvB/hyNjB5Ljg3/Av78o93z4PHHnxUOpzjgEk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2664/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/2664/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1644054015970&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할 &quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/2664/&quot; data-og-url=&quot;https://techblog.woowahan.com/2664/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zD7Si/hyNjPCVVuI/wR7koVR2j7ttHKYZdsZygk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/flVvB/hyNjB5Ljg3/Av78o93z4PHHnxUOpzjgEk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2664/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/2664/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zD7Si/hyNjPCVVuI/wR7koVR2j7ttHKYZdsZygk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/flVvB/hyNjB5Ljg3/Av78o93z4PHHnxUOpzjgEk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;테스트 코드&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/donghyeon0725/jpaDeadLockTest&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/donghyeon0725/jpaDeadLockTest&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1644054040741&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - donghyeon0725/jpaDeadLockTest&quot; data-og-description=&quot;Contribute to donghyeon0725/jpaDeadLockTest development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/donghyeon0725/jpaDeadLockTest&quot; data-og-url=&quot;https://github.com/donghyeon0725/jpaDeadLockTest&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cB10qW/hyNjHx8cT8/kLMD6t3CKIMXaKEIplhD01/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/donghyeon0725/jpaDeadLockTest&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/donghyeon0725/jpaDeadLockTest&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cB10qW/hyNjHx8cT8/kLMD6t3CKIMXaKEIplhD01/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - donghyeon0725/jpaDeadLockTest&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to donghyeon0725/jpaDeadLockTest development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  발생한 예외&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644054055780&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Caused by: java.sql.SQLTransactionRollbackException: (conn:...) Deadlock found when trying to get lock; try restarting transaction&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데드락이 걸린 상황입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  문제 상황&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java, spring, jpa 사용 중입니다. Food 엔티티를 insert 하고 이 엔티티를 다시 조회해서 update 하는 간단한 코드였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;Food 엔티티&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1644054192104&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Setter
@Entity
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long foodId;

    private String name;

    private Integer price;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;FoodRepository.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1644054210081&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface FoodRepository extends JpaRepository&amp;lt;Food, Long&amp;gt; {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;FoodService.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1644054226675&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...

@Transactional
public Long saveFood(FoodCommand foodCommand) {
    Food food = new Food();

    food.setName(foodCommand.getName());
    food.setPrice(foodCommand.getPrice());

    foodRepository.save(food);

    return food.getFoodId();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;FoodController.java&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1644054242344&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...

@PostMapping(&quot;/v3.0/food&quot;)
public void createFoodV3(@RequestBody FoodCommand foodCommand) {
    Long foodId = foodService.saveFood(foodCommand).getFoodId();

    Food food = foodRepository.findById(foodId).orElseThrow(() -&amp;gt; new RuntimeException(&quot;못 찾음&quot;));
    food.setStatus(FoodStatus.REGISTERED);
    foodRepository.save(food); // 예외 터진 부분!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foodRepository.save(food) 메소드에서 예외가 터졌습니다. 이때, 예외가 계속 터지지는 않았고 사용자가 몰릴 때만 예외가 터졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  처음에 생각(예상) 했던 문제 원인&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 트랜잭션을 사용하고 있었고 Open-Session-In-View 옵션이 true 이였기 때문에 DB 락의 Isolation 레벨에 의한 예외라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 아직 Write 트랜잭션이 닫히지 않아서 Lock 이 걸려있는 상황에서 다른 트랜잭션이 이 row에 대한 접근을 시도할 것이라고 예상했었고, row 가 없기 때문에 무한정 대기를 타다가 예외가 터져버릴 것으로 예상했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 예상은 틀렸습니다. 그 이유는 트랜잭션이 열려있는 상황에도 명시적인 트랜잭션이 끝날 때는 insert 쿼리가 실제로 나가기 때문입니다. 이러면 사실상 DB 입장에서는 트랜잭션 처리가 끝난 것이라고 보는 것이 맞을 것이고 이렇게 되면 Isolation 레벨과는 상관없이 읽을 수 있을 것이기 때문입니다. 또한 제가 예상한 문제가 맞다면 사용자 수와 관계없이 늘 예외가 터져야 하는데 그렇지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 테스트 코드를 여럿 만들었고 예외가 나기를 기대했는데, 같은 예외를 만들 수 없었습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  진짜 문제 원인&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 쓰레드가 2개의 커넥션을 요청해서 사용했기 때문에 문제가 된 상황입니다. (MYSQL DB를 사용할 때, JPA의 특정한 아이디 생성 전략을 사용하면 키 생성을 위해서 별도의 connection 이 하나 더 필요합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순간적으로 connection pool에 저장된 connection수를 넘는 연결 시도가 있었고, connection pool의 connection이 동난 상황에서 이미 connection을 얻어서 작업 중이던 쓰레드가 JPA 엔티티의 키 생성을 위해서 또 다른 connection을 요구하니까 pool 입장에선 더이상 내어줄 connection이 없었던 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드 입장에서는 키 자원에 대한 요청을 했는데 응답이 오지않으니, &amp;ldquo;아! 키 자원이 데드락 상태이구나&quot; 판단해버린 것입니다. 그래서, 사용자가 많아질 때에만 순간적으로 DeadLock이 걸렸던 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  문제 환경 정리&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 문제의 원인을 풀어 나가기 전에, 문제에 직접적으로 원인이 되었던 요소를 정리하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring JPA 사용&lt;/li&gt;
&lt;li&gt;DB : MYSQL (테스트할 때 버전은 8.0.28)&lt;/li&gt;
&lt;li&gt;엔티티의 키 생성 전략 : TABLE (SEQUENCE, AUTO 포함)&lt;/li&gt;
&lt;li&gt;커넥션 수 : 커넥션 풀의 개수가 최대 생성될 수 있는 쓰레드의 수와 같거나 작을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  기존 쓰레드 풀 동작&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 상황을 가정&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쓰레드 개수 : 4개&lt;/li&gt;
&lt;li&gt;커넥션 수 : 3개&lt;/li&gt;
&lt;li&gt;요청 하나를 처리하는데 필요한 커넥션 수 : 2개&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정상적인 상황 &lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1-1.png&quot; data-origin-width=&quot;3291&quot; data-origin-height=&quot;2239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HfbwY/btrsC9nZgNP/gW59vSHeEAy15a7YJd7Lp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HfbwY/btrsC9nZgNP/gW59vSHeEAy15a7YJd7Lp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HfbwY/btrsC9nZgNP/gW59vSHeEAy15a7YJd7Lp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHfbwY%2FbtrsC9nZgNP%2FgW59vSHeEAy15a7YJd7Lp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3291&quot; height=&quot;2239&quot; data-filename=&quot;1-1.png&quot; data-origin-width=&quot;3291&quot; data-origin-height=&quot;2239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청을 받는다.&lt;/li&gt;
&lt;li&gt;DB 커넥션 풀에서 커넥션을 요청해 받아온다.&lt;/li&gt;
&lt;li&gt;받은 커넥션을 이용해서 DB 작업이 진행된다.&lt;/li&gt;
&lt;li&gt;처리가 끝나면 응답을 내어준다. 그리고 커넥션은 풀에 돌려준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;동시에 4개의 요청을 받았을 때&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때는 작업을 위한 커넥션의 수가 모자란 상황입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1-2 (1).png&quot; data-origin-width=&quot;2980&quot; data-origin-height=&quot;2239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJfJL2/btrszmnRon6/MpKATDsiH9PBBCHSfYVqD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJfJL2/btrszmnRon6/MpKATDsiH9PBBCHSfYVqD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJfJL2/btrszmnRon6/MpKATDsiH9PBBCHSfYVqD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJfJL2%2FbtrszmnRon6%2FMpKATDsiH9PBBCHSfYVqD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2980&quot; height=&quot;2239&quot; data-filename=&quot;1-2 (1).png&quot; data-origin-width=&quot;2980&quot; data-origin-height=&quot;2239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업을 시작하기 위해 3개의 쓰레드에서 커넥션 풀을 요청해서 받아갔고, 3번째 쓰레드는 커넥션을 얻기 위해서 대기합니다. (또는 예외를 만납니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;모든 쓰레드가 1개의 connection 을 이미 가지고 간 상황&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 상황으로 가정하겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쓰레드 수 : 2개&lt;/li&gt;
&lt;li&gt;커넥션 수 : 2개&lt;/li&gt;
&lt;li&gt;요청 하나를 처리하는데 필요한 커넥션 수 : 2개&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2 (1).png&quot; data-origin-width=&quot;1869&quot; data-origin-height=&quot;1972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Iumr/btrszmuCCIn/QvwysAYdoVFt4Lq4D8x0u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Iumr/btrszmuCCIn/QvwysAYdoVFt4Lq4D8x0u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Iumr/btrszmuCCIn/QvwysAYdoVFt4Lq4D8x0u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Iumr%2FbtrszmuCCIn%2FQvwysAYdoVFt4Lq4D8x0u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1869&quot; height=&quot;1972&quot; data-filename=&quot;2 (1).png&quot; data-origin-width=&quot;1869&quot; data-origin-height=&quot;1972&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 모든 쓰레드는 키 삽입을 위해서 커넥션이 하나 더 필요한데 없기 때문에 대기합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 설정 시간인 30초가 지나면 그제야 TimeoutException 을 발생시키면서 가지고 있던 커넥션을 모두 풀에 돌려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  문제 상황을 피하려면&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 이 이슈는 HikariCP github에서도 issue로 등록되었고, HikariCP wiki에서 Dead lock을 해결하는 방법을 제시하고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Github:&amp;nbsp;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP&quot;&gt;https://github.com/brettwooldridge/HikariCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;issue:&amp;nbsp;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP/issues/442#issuecomment-146096704&quot;&gt;https://github.com/brettwooldridge/HikariCP/issues/442#issuecomment-146096704&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;wiki:&amp;nbsp;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&quot;&gt;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;방법 1&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;pool size = Tn x (Cm - 1) + 1&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tn&amp;nbsp;: 전체 Thread 개수&lt;/li&gt;
&lt;li&gt;Cm&amp;nbsp;: 하나의 Task에서 동시에 필요한 최대 Connection 수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HikariCP wiki에서는 이 공식대로 Maximum pool size를 설정하면 Dead lock을 피할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서, 아래와 같은 상황일 땐 다음과 같이 계산하면 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 쓰레드 개수 : 2&lt;/li&gt;
&lt;li&gt;하나의 쓰레드에서 동시에 필요한 최대 커넥션 수 : 2&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;pool size = 2 * (2 - 1) + 1 = 3&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;방법 2&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID 대역을 언제든지 변경할 수 있어야 하는 상황이 아닌 경우에는 시퀀스 생성 전략을 바꾸는 것이 방법이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 문제가 되는 키 전략이었던 TABLE 전략의 경우엔 성능 자체도 그렇게 좋은 편이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  해결방안 공식 검증 (방법 1번 검증)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 Thread가 동시에 HikariCP에 Connection을 요청하고 2개의 Connection을 골고루 나눠 가졌습니다. 그럼에도 불구하고 1개의 Connection이 남아있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 1개의 Connection이 Dead lock을 피할 수 있게 해주는 Key Connection이 됩니다. 남은 1개의 커넥션을 2개의 쓰레드가 번갈아 사용하면서 작업을 마칠 수 있게 되는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식에 따라서 여분의 커넥션을 하나 늘렸고, 이제 커넥션 개수가 2개가 아니라 3개가 되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여분의 커넥션이 색은 주황색으로 표기했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3 (3).png&quot; data-origin-width=&quot;1684&quot; data-origin-height=&quot;1603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PUZBe/btrsytt0a1f/5qSmqnsHlNptCvWxEBGCik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PUZBe/btrsytt0a1f/5qSmqnsHlNptCvWxEBGCik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PUZBe/btrsytt0a1f/5qSmqnsHlNptCvWxEBGCik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPUZBe%2Fbtrsytt0a1f%2F5qSmqnsHlNptCvWxEBGCik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1684&quot; height=&quot;1603&quot; data-filename=&quot;3 (3).png&quot; data-origin-width=&quot;1684&quot; data-origin-height=&quot;1603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 개의 요청이 동시에 들어와서 두 쓰레드 모두 각각 하나의 커넥션을 가져갔고, 이 상황에서 두 쓰레드에서 추가적으로 커넥션 하나를 더 얻으려는 시도가 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4 (2).png&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;1575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4Yows/btrszliahcs/VHQGeDKEOIoQ8Egq8B4xJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4Yows/btrszliahcs/VHQGeDKEOIoQ8Egq8B4xJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4Yows/btrszliahcs/VHQGeDKEOIoQ8Egq8B4xJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4Yows%2Fbtrszliahcs%2FVHQGeDKEOIoQ8Egq8B4xJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1630&quot; height=&quot;1575&quot; data-filename=&quot;4 (2).png&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;1575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우측에 쓰레드가 먼저 커넥션을 받아가 작업을 처리합니다.&lt;/li&gt;
&lt;li&gt;그렇게 되면 왼쪽 쓰레드는 여분의 커넥션이 생길 때까지 대기합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5 (2).png&quot; data-origin-width=&quot;1665&quot; data-origin-height=&quot;1657&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buiZ2q/btrsvfjeln9/yabZJisqt4KI29KrXqKgr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buiZ2q/btrsvfjeln9/yabZJisqt4KI29KrXqKgr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buiZ2q/btrsvfjeln9/yabZJisqt4KI29KrXqKgr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuiZ2q%2Fbtrsvfjeln9%2FyabZJisqt4KI29KrXqKgr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1665&quot; height=&quot;1657&quot; data-filename=&quot;5 (2).png&quot; data-origin-width=&quot;1665&quot; data-origin-height=&quot;1657&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 때 2번째 커넥션을 먼저 받은 쓰레드가 처리하고 키 생성이 끝나면 여분의 connection 만 풀에 반납 후 나머지 작업을 진행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6 (2).png&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;1549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKuOxO/btrswU6Us3P/e1aCpDK8i8CzyEt3VAk9FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKuOxO/btrswU6Us3P/e1aCpDK8i8CzyEt3VAk9FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKuOxO/btrswU6Us3P/e1aCpDK8i8CzyEt3VAk9FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKuOxO%2FbtrswU6Us3P%2Fe1aCpDK8i8CzyEt3VAk9FK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1622&quot; height=&quot;1549&quot; data-filename=&quot;6 (2).png&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;1549&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이후 왼쪽 쓰레드가 여분의 Connection 을 받아 요청을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 여분의 커넥션 덕에 DeadLock 이 걸리는 상황은 막을 수 있습니다. 물론 쓰레드 성능과 개수에 따라서 Timeout 이 날 가능성은 여전히 존재하지만요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  적절한 커넥션의 개수는?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 운이 없게 모든 커넥션이 모두 찰 수 있고 1개의 여분 커넥션으로 이 상황을 돌파하려고 하면, 여분의 커넥션을 기다리면서 30초 이상의 타임아웃이 발생할 가능성 또한 있기 때문에 &lt;b&gt;문제가 발생한다면&lt;/b&gt; 아래와 같이 DBCP (DataBase Connection Pool) 수를 설정하는 것이 어떨까 싶습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;nbsp;pool size = 최대쓰레드수 * (최대 사용하는 커넥션 수 - 1) + 최대쓰레드수 * 0.1 +&amp;nbsp; alpha&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 다른 관점도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.holaxprogramming.com/2013/01/10/devops-how-to-manage-dbcp/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.holaxprogramming.com/2013/01/10/devops-how-to-manage-dbcp/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1644054765443&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;DB Connection Pool에 대한 이야기 &amp;middot; 안녕 프로그래밍&quot; data-og-description=&quot;웹 애플리케이션을 운영하다 보면 에러 로그로는 식별 할 수 없는 잠재적인 이슈가 발생 할 때가 있다. 애플리케이션내의 오류가 명확히 확인이 되지 않은 상태에서 Out of memory가 발생 하거나, DB&quot; data-og-host=&quot;www.holaxprogramming.com&quot; data-og-source-url=&quot;https://www.holaxprogramming.com/2013/01/10/devops-how-to-manage-dbcp/&quot; data-og-url=&quot;https://www.holaxprogramming.com/2013/01/10/devops-how-to-manage-dbcp/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.holaxprogramming.com/2013/01/10/devops-how-to-manage-dbcp/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.holaxprogramming.com/2013/01/10/devops-how-to-manage-dbcp/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;DB Connection Pool에 대한 이야기 &amp;middot; 안녕 프로그래밍&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹 애플리케이션을 운영하다 보면 에러 로그로는 식별 할 수 없는 잠재적인 이슈가 발생 할 때가 있다. 애플리케이션내의 오류가 명확히 확인이 되지 않은 상태에서 Out of memory가 발생 하거나, DB&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.holaxprogramming.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBPC가 WAS Thread 수를 넘어설 경우 비효율적이라는 말입니다. 따라서, 예외가 발생할 경우에만 위 설정을 따르거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID 대역을 언제든지 변경할 수 있어야 하는 상황이 아닌 경우에는 시퀀스 생성 전략을 바꾸는 것이 방법이 될 수 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 좋은 것은 현재 운영되는 서비스의 동접자 수를 확인하고 상황에 맞춰서 대처하는 것일 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; &amp;nbsp;왜 하나의 쓰레드에서 2개의 커넥션을 사용할까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 왜 1개의 쓰레드에서 2개의 커넥션 요청이 생길 수밖에 없었는지 그 상황에 대해서 이해해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, mybatis 와 jpa 를 같이 사용하고 있는 경우 서로 다른 커넥션을 사용하기 때문에, 데드락은 아니지만(예외의 모양새가 데드락인지, 아닌지 정확하게 확인 못함)&amp;nbsp; 위와 같은 timeout 문제가 나타날 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, 두번 째는 JPA의 키 생성 전략 때문에 나타나는 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이이해하려면 MYSQL DB 특성과 JPA 의 키 생성 전략에 대해서 이해하고 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;키 생성 방식 종류 (@GeneratedValue 어노테이션)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;strategy = GenerationType.AUTO
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA 구현체가 자동으로 생성 전략을 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;strategy = GenerationType.IDENTITY
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본키 생성을 데이터베이스에게 위임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;strategy = GenerationType.SEQUENCE
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스의 오브젝트인 시퀀스를 사용하여 기본키를 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;strategy = GenerationType.TABLE
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스에 키 생성 전용 테이블을 만들고 이를 사용하여 기본키 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mysql 일 경우 SEQUENCE 전략을 사용해도 의미가 없는데 이유는, Mysql 자체가 시퀀스 객체를 지원하지 않기 때문입니다. 따라서 SEQUENCE 를 사용하면 TABLE 전략과 동일하게 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TABLE 전략은 테이블을 이용해서 시퀀스 객체를 흉내 내는 기법으로, 테이블에 저장된 값을 시퀀스 값으로 사용합니다. 그리고 jpa 는 hibernate_sequence 이라는 테이블을 하나 만들어서 이 값을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TABLE 전략의 구현 방식은 키 생성 쿼리 로그를 보면 확인할 수 있습니다. jpa repository의 메소드인 save 가 호출되는 시점에 다음과 같은 로그를 확인할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;select next_val as id_val from hibernate_sequence for update
update hibernate_sequence set next_val= ? where next_val=?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 값을 조회해서 키 값으로 사용하고 이 값을 + n 해놓습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 for update 가 붙었는데, Mysql 에서 select 쿼리에 for update 가 붙으면 row 에 트랜잭션이 끝나기 전까지 row 에 lock 이 걸립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID 값을 담당하는 값이기에 중복이 되면 안 되고, 이 값을 select 하는 시점에, 동시성 문제가 발생하면 키 값이 중복될 수 있기 때문에 락 처리를 위해 이런 쿼리를 구성한 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편, 이런 lock 처리가 걸려 있으면 이 트랜잭션이 끝날 때까지 다른 트랜잭션에서 접근할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 jpa 는 이렇게 hibernate_sequence 테이블 조회하고 업데이트하는 과정에서 별도의 sub 트랜잭션을 생성해서 실행하는데 이유는 다음과 같이 추측됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jpa는 영속성 관리를 위해서 key 값이 필요한데 이를 위해서 전략이 IDENTITY 인 경우에 persist 메소드 호출 시점에 바로 쿼리를 날려서 키를 받아옵니다. (데이터를 insert 할 때 key 값을 받는 방식 때문)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 비슷한 이유에서 TABLE 전략을 사용하면 persist 할 때 key 를 먼저 받아오기 위해서 위와 같은 쿼리를 날리는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, lock 이 걸리는 위 쿼리 특성상 트랜잭션이 끝나기 전까지 다른 스레드에서 이 key 값을 읽을 수 없기 때문에 별도의 sub 트랜잭션을 열어서 처리하는 것으로 추측하고 있습니다. (의도는 다를 수 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여기서 순간적으로 connection 을 2개 사용하게 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  테스트 코드로 증명하려던 예외&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드는 위에서 언급한 환경이 갖춰졌을 때, 쓰레드 개수가 1개라고 가정을 하고 최대 커넥션을 2개 요구하는 상황을 만들었습니다. 그리고, DB 커넥션 풀의 개수를 1로 맞춰 테스트를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순간적으로 connection 을 2개 사용하려고 시도하면 SQLTransactionRollbackException 예외가 날 것을 예상하고 테스트를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; 시퀀스 생성 전략이 AUTO 인데도 환경에서 예외가 난 이유는?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시퀀스 생성 전략이 AUTO 일 때 기본값으로 설정된 전략을 따라가게 되는데 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring boot 1.5 이상 2.0 미만 : SEQUENCE&lt;/li&gt;
&lt;li&gt;spring boot 2.0 이상 : TABLE&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기본 설정은 바꿀 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;spring.jpa.hibernate.use-new-id-generator-mappings=true&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설정 값을 true 로 하면 TABLE 전략을 따라가고 false 하면 SEQUENCE 전략을 따라갑니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring boot 1.5 이상 2.0 미만 : SEQUENCE (use-new-id-generator-mappings = false)&lt;/li&gt;
&lt;li&gt;spring boot 2.0 이상 : TABLE(use-new-id-generator-mappings = true)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시퀀스 생성 전략이 AUTO인 경우 설정 값에 따라서 시퀀스 생성 전략이 바뀔 수 있는데 이는 코드에서 살펴볼 수 있습니다. 위에 언급한 배민 기술 블로그에서 자료를 긁어왔습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Id Generator에 대해 hibernate 공식 문서에서는 아래와 같이 설명하고 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;This is the default strategy since Hibernate 5.0. For older versions, this strategy is enabled through the hibernate.id.new_generator_mappingsconfiguration property . When using this strategy, AUTO always resolvesto SequenceStyleGenerator. If the underlying database supportssequences, then a SEQUENCE generator is used. Otherwise, a TABLEgenerator is going to be used instead.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 내용 + 실제 코드를 기반으로 작성한 Flow chart입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Flow chart&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;determine-generator.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crzmK1/btrsxE3Cz2q/yQ7izggOZpiE4W7ZdWtf21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crzmK1/btrsxE3Cz2q/yQ7izggOZpiE4W7ZdWtf21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crzmK1/btrsxE3Cz2q/yQ7izggOZpiE4W7ZdWtf21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrzmK1%2FbtrsxE3Cz2q%2FyQ7izggOZpiE4W7ZdWtf21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;942&quot; data-filename=&quot;determine-generator.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저&amp;nbsp;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;hibernate.id.new_generator_mappings=false&lt;/span&gt;&amp;nbsp;인 경우입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;DefaultIdentifierGeneratorFactory.getIdentifierGeneratorClass&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;determine-native-1.png&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pgmB2/btrsveECKWf/7PYdIposx1cg4TANbxDFJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pgmB2/btrsveECKWf/7PYdIposx1cg4TANbxDFJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pgmB2/btrsveECKWf/7PYdIposx1cg4TANbxDFJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpgmB2%2FbtrsveECKWf%2F7PYdIposx1cg4TANbxDFJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1648&quot; height=&quot;730&quot; data-filename=&quot;determine-native-1.png&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;strategy가&amp;nbsp;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;native&lt;/span&gt;&amp;nbsp;인 경우 사용하는 Dialect에 의해 Generator가 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Dialect.getNativeIdentifierGeneratorStrategy&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;determine-native-2.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxFkVT/btrswUsiNz3/kxGtqbo2mEn14tRq08Zfl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxFkVT/btrswUsiNz3/kxGtqbo2mEn14tRq08Zfl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxFkVT/btrswUsiNz3/kxGtqbo2mEn14tRq08Zfl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxFkVT%2FbtrswUsiNz3%2FkxGtqbo2mEn14tRq08Zfl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1238&quot; height=&quot;550&quot; data-filename=&quot;determine-native-2.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dialect에서 supportsIdentityColumns()가&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;true&lt;/span&gt; 인 경우 IdentityGenerator를 사용하게 됩니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;false&lt;/span&gt;&amp;nbsp;인 경우 SequenceStyleGenerator를 사용하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;hibernate.id.new_generator_mappings=true&lt;/span&gt;&amp;nbsp;인 경우입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SequenceStyleGenerator.buildDatabaseStructure&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;buildDatabaseStructure (1).png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YU1cR/btrsytneFrm/gg3WNkUlRnyny4f1XVkDc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YU1cR/btrsytneFrm/gg3WNkUlRnyny4f1XVkDc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YU1cR/btrsytneFrm/gg3WNkUlRnyny4f1XVkDc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYU1cR%2FbtrsytneFrm%2Fgg3WNkUlRnyny4f1XVkDc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1646&quot; height=&quot;1070&quot; data-filename=&quot;buildDatabaseStructure (1).png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;1070&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dialect에서 Sequence기능 제공 여부에 따라 내부적으로 사용하는 DatabaseStructure가 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SequenceStyleGenerator.isPhysicalSequence&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;isPhysicalSequence.png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dz4zKN/btrsC83G1nw/ExWoKy0UC7LJ6HGoKDFV0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dz4zKN/btrsC83G1nw/ExWoKy0UC7LJ6HGoKDFV0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dz4zKN/btrsC83G1nw/ExWoKy0UC7LJ6HGoKDFV0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdz4zKN%2FbtrsC83G1nw%2FExWoKy0UC7LJ6HGoKDFV0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1646&quot; height=&quot;134&quot; data-filename=&quot;isPhysicalSequence.png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Sequence기능을 지원하는 경우 SequenceStructure를 사용&lt;/li&gt;
&lt;li&gt;Sequence기능을 지원하지 않는 경우 TableStructure를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 결정된 DatabaseStructure는 아래 코드에서 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SequenceStyleGenerator.generate&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SequenceStyleGenerator-generate.png&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cn63Gv/btrsC8JojJH/2S9rremQSbkLc9SBflAdak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cn63Gv/btrsC8JojJH/2S9rremQSbkLc9SBflAdak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cn63Gv/btrsC8JojJH/2S9rremQSbkLc9SBflAdak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcn63Gv%2FbtrsC8JojJH%2F2S9rremQSbkLc9SBflAdak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1634&quot; height=&quot;266&quot; data-filename=&quot;SequenceStyleGenerator-generate.png&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  키 생성 시점에 2개의 connection 를 사용하는 게 맞는지 코드 검증&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;save 시점에 Key 생성과 관련한 로직이 동작할 것이기 때문에 save 메서드 코드를 열어보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;과정 확인 없이 맨 아래에 클래스 하나만 확인하셔도 충분합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;CrudRepository.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;@NoRepositoryBean
public interface CrudRepository&amp;lt;T, ID&amp;gt; extends Repository&amp;lt;T, ID&amp;gt; {

	/**
	 * Saves a given entity. Use the returned instance for further operations as the save operation might have changed the
	 * entity instance completely.
	 *
	 * @param entity must not be {@literal null}.
	 * @return the saved entity; will never be {@literal null}.
	 * @throws IllegalArgumentException in case the given {@literal entity} is {@literal null}.
	 */
	&amp;lt;S extends T&amp;gt; S save(S entity);

	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jpa 가 제공하는 CrudRepository 인터페이스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;SimpleJpaRepository.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository&amp;lt;T, ID&amp;gt; implements JpaRepositoryImplementation&amp;lt;T, ID&amp;gt; {
	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
	 */
	@Transactional
	@Override
	public &amp;lt;S extends T&amp;gt; S save(S entity) {

		Assert.notNull(entity, &quot;Entity must not be null.&quot;);

		if (entityInformation.isNew(entity)) {
			em.persist(entity);
			return entity;
		} else {
			return em.merge(entity);
		}
	}

	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CrudRepository 구현체인 SimpleJpaRepository 를 보면 엔티티를 EntityManager 를 통해 persist 하는 코드를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;EntityManager.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;public interface EntityManager {

    /**
     * Make an instance managed and persistent.
     * @param entity  entity instance
     * @throws EntityExistsException if the entity already exists.
     * (If the entity already exists, the &amp;lt;code&amp;gt;EntityExistsException&amp;lt;/code&amp;gt; may 
     * be thrown when the persist operation is invoked, or the
     * &amp;lt;code&amp;gt;EntityExistsException&amp;lt;/code&amp;gt; or another &amp;lt;code&amp;gt;PersistenceException&amp;lt;/code&amp;gt; may be 
     * thrown at flush or commit time.) 
     * @throws IllegalArgumentException if the instance is not an
     *         entity
     * @throws TransactionRequiredException if there is no transaction when
     *         invoked on a container-managed entity manager of that is of type 
     *         &amp;lt;code&amp;gt;PersistenceContextType.TRANSACTION&amp;lt;/code&amp;gt;
     */
    public void persist(Object entity);

		...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EntityManager 인터페이스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;SessionImpl.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AbstractSessionImpl 클래스 - SessionImplementor 인터페이스 - Session 인터페이스를 보면 EntityManager 인터페이스를 상속받는 부분이 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class SessionImpl
		extends AbstractSessionImpl
		implements EventSource, SessionImplementor, HibernateEntityManagerImplementor {

	@Override
	public void persist(Object object) throws HibernateException {
		checkOpen();
		firePersist( new PersistEvent( null, object, this ) );
	}

	private void firePersist(final PersistEvent event) {
		try {
			checkTransactionSynchStatus();
			checkNoUnresolvedActionsBeforeOperation();

			fastSessionServices.eventListenerGroup_PERSIST.fireEventOnEachListener( event, PersistEventListener::onPersist );
		}
		catch (MappingException e) {
			throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) );
		}
		catch (RuntimeException e) {
			throw getExceptionConverter().convert( e );
		}
		finally {
			try {
				checkNoUnresolvedActionsAfterOperation();
			}
			catch (RuntimeException e) {
				throw getExceptionConverter().convert( e );
			}
		}
	}

	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EntityManager 구현체인 SessionImpl 를 보면 persist 메소드가 있다. 코드 흐름을 따라가보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PersistEvent 이벤트를 PersistEventListener 에 발행하고 있는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;PersistEventListener.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public interface PersistEventListener extends Serializable {

    /** 
     * Handle the given create event.
     *
     * @param event The create event to be handled.
     * @throws HibernateException
     */
	public void onPersist(PersistEvent event) throws HibernateException;
	
	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PersistEventListener 인터페이스를 보면 onPersist 메소드를 통해서 이벤트를 핸들링함을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;DefaultPersistEventListener.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class DefaultPersistEventListener
		extends AbstractSaveEventListener
		implements PersistEventListener, CallbackRegistryConsumer {

	/**
	 * Handle the given create event.
	 *
	 * @param event The create event to be handled.
	 *
	 */
	public void onPersist(PersistEvent event, Map createCache) throws HibernateException {
			
		...

		final EntityEntry entityEntry = source.getPersistenceContextInternal().getEntry( entity );
		EntityState entityState = EntityState.getEntityState( entity, entityName, entityEntry, source, true );
		if ( entityState == EntityState.DETACHED ) {
			...
		}

		switch ( entityState ) {
			case DETACHED: {
				throw new PersistentObjectException(
						&quot;detached entity passed to persist: &quot; +
								EventUtil.getLoggableName( event.getEntityName(), entity )
				);
			}
			case PERSISTENT: {
				entityIsPersistent( event, createCache );
				break;
			}
			case TRANSIENT: {
				entityIsTransient( event, createCache );
				break;
			}
			case DELETED: {
				entityEntry.setStatus( Status.MANAGED );
				entityEntry.setDeletedState( null );
				event.getSession().getActionQueue().unScheduleDeletion( entityEntry, event.getObject() );
				entityIsDeleted( event, createCache );
				break;
			}
			default: {
				throw new ObjectDeletedException(
						&quot;deleted entity passed to persist&quot;,
						null,
						EventUtil.getLoggableName( event.getEntityName(), entity )
				);
			}
		}

	}

	protected void entityIsTransient(PersistEvent event, Map createCache) {
		LOG.trace( &quot;Saving transient instance&quot; );

		final EventSource source = event.getSession();
		final Object entity = source.getPersistenceContextInternal().unproxy( event.getObject() );

		if ( createCache.put( entity, entity ) == null ) {
			saveWithGeneratedId( entity, event.getEntityName(), createCache, source, false );
		}
	}

	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PersistEventListener 구현체인 onPersist 를 따라가면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티 상태가 영속성 관리 전, 즉 TRANSIENT 일 때 entityIsTransient 메서드가 동작하고 있음을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;saveWithGeneratedId 메서드가 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;AbstractSaveEventListener.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;public abstract class AbstractSaveEventListener
		extends AbstractReassociateEventListener
		implements CallbackRegistryConsumer {

	/**
	 * Prepares the save call using a newly generated id.
	 *
	 * @param entity The entity to be saved
	 * @param entityName The entity-name for the entity to be saved
	 * @param anything Generally cascade-specific information.
	 * @param source The session which is the source of this save event.
	 * @param requiresImmediateIdAccess does the event context require
	 * access to the identifier immediately after execution of this method (if
	 * not, post-insert style id generators may be postponed if we are outside
	 * a transaction).
	 *
	 * @return The id used to save the entity; may be null depending on the
	 *         type of id generator used and the requiresImmediateIdAccess value
	 */
	protected Serializable saveWithGeneratedId(
			Object entity,
			String entityName,
			Object anything,
			EventSource source,
			boolean requiresImmediateIdAccess) {
		callbackRegistry.preCreate( entity );

		if ( entity instanceof SelfDirtinessTracker ) {
			( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes();
		}

		EntityPersister persister = source.getEntityPersister( entityName, entity );
		Serializable generatedId = persister.getIdentifierGenerator().generate( source, entity );
		
		...
	}

	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Serializable generatedId = persister.getIdentifierGenerator().generate( source, entity ); 부분을 보면 generate 메소드가 동작한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;IdentifierGenerator.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public interface IdentifierGenerator extends Configurable, ExportableProducer {
	/**
	 * Generate a new identifier.
	 *
	 * @param session The session from which the request originates
	 * @param object the entity or collection (idbag) for which the id is being generated
	 *
	 * @return a new identifier
	 *
	 * @throws HibernateException Indicates trouble generating the identifier
	 */
	Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException;

	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IdentifierGenerator 인터페이스를 보면 식별자를 생성하는 메소드임을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;SequenceStyleGenerator.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class SequenceStyleGenerator
		implements PersistentIdentifierGenerator, BulkInsertionCapableIdentifierGenerator {

	@Override
	public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
		return optimizer.generate( databaseStructure.buildCallback( session ) );
	}

	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현체인 SequenceStyleGenerator 를 보면 generate 메소드가 동작하면서 buildCallback 메소드가 동작한다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;DatabaseStructure.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;public interface DatabaseStructure extends ExportableProducer {
	
	/**
	 * A callback to be able to get the next value from the underlying
	 * structure as needed.
	 *
	 * @param session The session.
	 * @return The next value.
	 */
	AccessCallback buildCallback(SharedSessionContractImplementor session);

	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;buildCallback 메소드를 통해서 다음 식별자를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;TableStructure.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class TableStructure implements DatabaseStructure {
	@Override
	public AccessCallback buildCallback(final SharedSessionContractImplementor session) {
		final SqlStatementLogger statementLogger = session.getFactory().getServiceRegistry()
				.getService( JdbcServices.class )
				.getSqlStatementLogger();
		if ( selectQuery == null || updateQuery == null ) {
			throw new AssertionFailure( &quot;SequenceStyleGenerator's TableStructure was not properly initialized&quot; );
		}

		final SessionEventListenerManager statsCollector = session.getEventListenerManager();

		return new AccessCallback() {
			@Override
			public IntegralDataTypeHolder getNextValue() {
				return session.getTransactionCoordinator().createIsolationDelegate().delegateWork(
						...
				);
			}

			...
		};
	}

	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DatabaseStructure 구현체인 TableStructure 클래스의 buildCallback 메소드를 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;session.getTransactionCoordinator().createIsolationDelegate().delegateWork() 메소드가 동작하는데 인터페이스를 열어보며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 트랜젝션의 고립 레벨 안으로 주어진 일을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;IsolationDelegate.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;zephir&quot;&gt;&lt;code&gt;public interface IsolationDelegate {
	/**
	 * Perform the given work in isolation from current transaction.
	 *
	 * @param work The work to be performed.
	 * @param transacted Should the work itself be done in a (isolated) transaction?
	 *
	 * @return The work result
	 *
	 * @throws HibernateException Indicates a problem performing the work.
	 */
	public &amp;lt;T&amp;gt; T delegateWork(WorkExecutorVisitable&amp;lt;T&amp;gt; work, boolean transacted) throws HibernateException;

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;IsolationDelegate 인터페이스&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;JdbcIsolationDelegate.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class JdbcIsolationDelegate implements IsolationDelegate {

	@Override
	public &amp;lt;T&amp;gt; T delegateWork(WorkExecutorVisitable&amp;lt;T&amp;gt; work, boolean transacted) throws HibernateException {
		boolean wasAutoCommit = false;
		try {
			Connection connection = jdbcConnectionAccess().obtainConnection();
			
			...
		}
		catch (SQLException sqle) {
			throw sqlExceptionHelper().convert( sqle, &quot;unable to obtain isolated JDBC connection&quot; );
		}
	}

	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jdbcConnectionAccess().obtainConnection(); 메소드가 동작하면서 새로운 connection 을 얻고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;JdbcConnectionAccess.java&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public interface JdbcConnectionAccess extends Serializable {
	/**
	 * Obtain a JDBC connection
	 *
	 * @return The obtained connection
	 *
	 * @throws SQLException Indicates a problem getting the connection
	 */
	Connection obtainConnection() throws SQLException;
	
	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 새로운 커넥션을 얻고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발생한 SQLTransactionRollbackException.java 를 열어보면 SQLTransientException.java 를 통해서 SQLException.java 를 상속 받고 있음을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  왜 문제 원인을 예상 못 했을까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;connection 을 모두 사용했을 때 이를 초과하는 요청이 오면 해당 사용자는 두가지중 하나의 상황에 직면합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연결할 수 없다는 예외를 만나기&lt;/li&gt;
&lt;li&gt;커넥션 풀에 커넥션이 찰 때까지 기다리기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;또는 요청 처리 자체를 제 시간 내에 시작하지 못하고, 일정 시간이 초과되어 CannotGetJdbcConnectionException 만나기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두 상황은 모두 데드락 상황이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 spring framework 내에서 1개의 Request에 1개의 Thead를 생성해서 처리하고 해당 쓰레드는 1개의 connection 을 얻어서 처리하는 것을 당연하게 생각하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 위 생각(가정)이 맞다면 데드락이 아니라, 다른 예외가 나야 할 것이라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 전혀 connection pool 이 문제일 것이라고 예상하지 못했습니다.&lt;/p&gt;</description>
      <author>blue-curtain</author>
      <guid isPermaLink="true">https://way-be-developer.tistory.com/291</guid>
      <comments>https://way-be-developer.tistory.com/291#entry291comment</comments>
      <pubDate>Sat, 5 Feb 2022 19:05:49 +0900</pubDate>
    </item>
    <item>
      <title>github 중간점검</title>
      <link>https://way-be-developer.tistory.com/290</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;1038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oq9rX/btrstKX3Znf/xyuRF1ykisd15bpoDk6XOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oq9rX/btrstKX3Znf/xyuRF1ykisd15bpoDk6XOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oq9rX/btrstKX3Znf/xyuRF1ykisd15bpoDk6XOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foq9rX%2FbtrstKX3Znf%2FxyuRF1ykisd15bpoDk6XOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1540&quot; height=&quot;1038&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;1038&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 시작할 때가 엊그제 같았는데 이제 65일 남았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금만 더..&lt;/p&gt;</description>
      <category>GitHub 1일 1커밋</category>
      <author>blue-curtain</author>
      <guid isPermaLink="true">https://way-be-developer.tistory.com/290</guid>
      <comments>https://way-be-developer.tistory.com/290#entry290comment</comments>
      <pubDate>Sat, 5 Feb 2022 17:37:10 +0900</pubDate>
    </item>
    <item>
      <title>메세지 브로커 push vs pull 방식 (RebbitMQ vs Kafka)</title>
      <link>https://way-be-developer.tistory.com/289</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.ibm.com/kr-ko/cloud/learn/message-brokers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.ibm.com/kr-ko/cloud/learn/message-brokers&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Message Broker&lt;/b&gt;(메시지 브로커)는&amp;nbsp;&lt;b&gt;Publisher&lt;/b&gt;(송신자)로부터 전달받은 메시지를&amp;nbsp;&lt;b&gt;Subscriber&lt;/b&gt;(수신자)로 전달해주는 중간 역할이며 응용 소프트웨어 간에 메시지를 교환할 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 To 애플리케이션으로 직접 메세지를 송수신 하는 방식과 비교해, 애플리케이션 간의 결합을 낮추고 확장에 보다 더 유연하게 대처할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메세지 브로커는 메세지를 전달하는 방식에 따라서 크게 2가지 종류가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;포인트-투-포인트 메시징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지의 송신자와 수신자 간에 일대일 관계를 지닌 메시지 대기열에서 사용되는 분배 패턴입니다. 큐의 각 메시지는 하나의 수신자에게만 전송되며, 오직 한 번만 이용됩니다. 포인트-투-포인트 메시징은 메시지가 한 번만 실행되어야 하는 경우에 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해서, 브로커가 직접 클라이언트로 메세지를 보내주는 방식으로 push 방식에 해당한다. 대표적으로 RabbitMQ(pull 방식을 지원하는 API 가 있지만 성능이 좋지 않다.) 가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;발행/구독 메시징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메시지 분배 패턴(종종 &quot;발행/구독&quot;이라고 함)에서 각 메시지의 생성자는 이를 토픽에 발행하고, 다수의 메시지 이용자는 해당 메시지를 수신하고자 하는 토픽을 구독합니다. 토픽에 대해 발행된 모든 메시지는 이를 구독한 모든 애플리케이션에 분배됩니다. 이는 메시지의 발행자와 이용자 사이에 일대다 관계가 있는 브로드캐스트 스타일의 분배 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해서, 브로커에 데이터가 있고 이를 클라이언트가 요청해서 받아가는 방식으로 pull 방식에 해당한다. 대표적으로 Kafka 가 이에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지속적인 요청을 보내서 결과를 기다리는 방법을 polling 이라고 하는데, 발행/구독 메시징 브로커의 경우 일부 polling 방식을 기반으로 메세지를 받아와 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;polling 방식의 경우 서버에 주는 부담이 크다. 그래서 대안으로 long polling 방식을 많이 사용한다. Redis 를 예시로 long polling 을 설명해본다. long polling 방식은 서버측이 클라이언트의 연결을 물고 있다가, 서버 측에 락 획득 가능 이벤트가 일어나면 클라이언트 측에 알림을 보내고 이후 클라이언트가 락 획득을 시도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 바로 응답을 하는 것이 아니라 연결만 물고 있다가 응답 가능 상태가 되면 클라이언트에 알림 응답을 보내주고 이때 클라이언트가 재 요청을 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kafka 가 공식적으로 내놓은 Java 클라이언트 라이브러리인 apache kafka-clients 의 경우 long polling 방식을 사용하고 있다. 여기서 KafkaConsumer.poll(long) 메소드를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이는, 서버 측에서 이벤트가 일어나지 않을 경우 연결을 계속 물고 있기 때문에 poll 메소드가 무한정 대기하는 상태가 발생할 수 있다. 따라서 kafka 2 점대 버전 부터는 응답이 없어 timeout 이 날 경우, 빈 collection 을 반환하는 KafkaConsumer.poll(Duration) 메소드 사용을 권장하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 방식에 따라서 특징이 있는데 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;push 방식&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 접근 방식은 브로커가 클라이언트에게 메세지를 보내줍니다.&amp;nbsp;따라서 데이터가 클라이언트에 의해 처리되는지 확인하는 의무는 클라이언트가 아닌 브로커에 있습니다. 또한 클라이언트는 브로커만 제어하고 모니터링하기 때문에 브로커로부터 이벤트 수신 지연이나 지연에 대해 걱정할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 클라이언트 앱이 다운되면 브로커가 지속적으로 데이터 푸시를 시도 해야하는 상황이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황 속에서 여러 클라이언트가 메세지를 요청한다고 가정하면, 브로커는 각 클라이언트에게 데이터를 푸시하는 것이 큰 부담이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;pull 방식&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 브로커에게 요청했던 마지막 메세지에 대한 내용을 브로커의 로그에 기록합니다. 그래서, 다음에 메세지를 요청 할 때는 해당 기록을 보고 이미 받은 메세지 이후의 것만 브로커에 요청할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식 때문에 브로커에겐 정보가 잘 처리 되었는지 확인할 의무가 없습니다. 클라이언트 앱이 다운되어 메세지 처리가 뒤쳐질 수 있는데, 이런 경우 브로커는 클라이언트 이러한 문제에 대해 걱정할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀 기반의 유일한 문제는 브로커에 더 이상 데이터가 없고 클라이언트가 새 이벤트를 기다리거나 요청하려고 할 때 지속적인 polling 요청이 브로커에 들어오면서 불필요한 트래픽을 유발한다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 극복하기 위해 브로커에 이벤트가 생길 때까지 (보내줄 메세지가 생길 때까지) 대기 하는&amp;nbsp;long polling 방식을 이용하기도 합니다.&lt;/p&gt;</description>
      <author>blue-curtain</author>
      <guid isPermaLink="true">https://way-be-developer.tistory.com/289</guid>
      <comments>https://way-be-developer.tistory.com/289#entry289comment</comments>
      <pubDate>Thu, 3 Feb 2022 18:24:40 +0900</pubDate>
    </item>
    <item>
      <title>담당 사수분께 배웠던 것들</title>
      <link>https://way-be-developer.tistory.com/288</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;동시성을 고려한 프로그래밍&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 짜면 동시성에 대해서 늘 고민하게 된다. 여러 WAS 가 동시에 이 코드를 실행 했을 때 이 후 API 나 배치 등등의 작업이 어떻게 될 것인가? 동시에 같은 row 를 select 한 후 상태를 검사하고 다음 코드를 실행하면 어떤 상황이 되는지 고민을 많이 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서 인증을 위한 램덤 코드를 생성한다고 가정하자. code 값이 DB에서 unique 제약 조건이 걸려있고 이 때문에 값을 insert 할 때 만든 랜덤의 코드값으로 DB 를 조회하고 이 값이 없으면 insert 를 해서 코드를 생성하는 코드를 짰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에 동시성 처리를 해주지 않으면 어떻게 될까? 고민을 했다. 만약 랜덤의 코드 값을 생성했는데 이 코드가 중복된 코드는 아니고, 서로 다른 WAS 의 두 스레드에서 동시에 같은 코드를 생성했다고 가정하자. 이를 통해 DB를 조회하는 코드에 다달았을 때 기존에 DB에서 가지고 있던 코드가 아니기 때문에 if 문 내부로 들어갈 것이고 DB에 insert 를 시도 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 입장에서 생각하면, unique 제약조건이 걸려있고 RDBS 특성상 &lt;b&gt;일관성(Consistency)&lt;/b&gt;을 보장하기 때문에 데이터가 중복되는 일은 없을 것이다. 즉, 하나의 스레드에서는 insert 하는 시점에 예외를 만날 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 한명의 사용자는 정상적으로 서비스 이용을 시도했는데 예외 상황을 만나게 되는 것이다. 이는 좋지 않은 사용자 경험을 유발할 수 있다. 이 때문에 캐시 서버인 Redis 서버를 lock 용도로 해당 데이터에 대한 lock 을 획득 했을 때 데이터 insert 를 할 수 있도록 처리했고, 속도는 조금 느려질 수 있지만 이렇게 처하는 것이 낫겠다 라고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;테스트 코드와 설계 문서를 작성하면서&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나를 담당해주시는 시니어 개발자 분께서는 항상 작업에 들어가기 전에 설계에 공을 들인다. 그런데, 설계라는게 롤에서 티모 버섯을 설계하는 그런 느낌이 아니다. 설치할 수 있는 버섯의 종류가 다 다르고, 이에 따른 특성이 모두 나뉜다. 지형 마다 버섯의 폭발 시간이 지연 될 수도 있고 어떤 데에서는 버섯이 아니라 포션이 되는 느낌이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롤에서 티모 버섯 설치는 직관에 의존하지만, 코드 설계는 모두 머리에서 일어나는 계산에 의존해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모르는 것이 하나라도 있으면 전혀 다른 동작을 할 수 있기 때문에 작업을 하기 전에 관련한 코드가 있으면 모두 뒤져봐야 한다. 현재 상황은 어떠한가? 어떤 기술이 사용되었고 발생할 수 있는 사이드이펙트는 어떻게 되나? 동시성 이슈는 해결했는가? 이 값이 굳이 DB 필드에 저장되어야 하는가? 캐시서버를 이용하면 안되나? 수정으로 작업을 해야하는가? 아니면 새로운 api 를 만들어야 하는가? 서버에 최대 어느정도까지 부담이 될 수 있는가? 발생할 수 있는 모든 예외 상황은? 그 때 응답할 코드는? 응답 메세지는? 응답 메세지는 클라이언트에서 바로 사용할 것인가? 아니면 코드로 정제해서 보여줄 것인가? 비동기 MQ 의 pub/sub 서버나 batch 서버를 이용해서 해결해야할 문제일까? 레거시와 호환이 될 것인가? null 예외가 발생할 수 있는 경우의 수는? DB에 얼마나 많은 row 가 쌓일 수 있는지? 인덱싱을 어떤 필드로 잡아야 할지? 롱폴링으로 해결할까? 콜백으로 해결할까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 설계할 때가 제일 지루한 작업인 것 같다. 모든 경우의 수를 머리속에 그리고, 모든 코드를 찾아보고 이게 말로 작성하니 아무렇지 않아보이는데 많이 힘들고 어려운 작업인 것 같다. 그런데 선배 개발자 분께서는 내게 항상 말씀해주신다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 작업은 그냥 설계한 대로 타이핑 하는 작업에 불과하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞는 말이다. 그리고 이것이 설계를 미리 해 놓았을 때의 장점인 것 같다. 설계를 미리 해 놓으면 코드를 짤 때 고민하는 것은 아래 몇가지 정도로 단축 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단어를 뭐를 쓸까?&lt;/li&gt;
&lt;li&gt;가독성을 덜 헤치고 성능이 더 잘 나 올만한 코드가 없을까?&lt;/li&gt;
&lt;li&gt;설계한 대로 작성했나?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 설계를 하며 모든 코드를 다시 보고 머리속에서 경우의 수를 생각하기 때문에 어디에 어떤 사소한 코드가 있는지까지 모두 알게된다는 점이다. 내가 그렇다는 것은 아니고.. 선배님의 경우에 어떤 컨트롤러의 어떤 메소드에서 데이터가 어떻게 분기되고 이 데이터의 상태가 변했을 때 어떤 api 가 영향을 받는지까지.. 정말 사소한 것 하나하나까지 모두 기억하고 계신다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기존의 레거시를 돌보면서&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 개선하다 보면 많은 점을 느낀다.&amp;nbsp;레거시에 대한 것을 어떻게 돌볼 것인가 고민을 하게 된다.&amp;nbsp;특히 version 2 api 출시가 아니라, 기존의 api 를 수정하는 작업을 하게 되면 이 코드가 기존의 레거시 코드에 어떤 문제를 줄 것 인가에 대해 고민을 많이 하게 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 코드가 들어갔을 때 클라이언트의 동작이 달라질 것인가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;달라진다면 프론트엔드에서 별도의 작업을 해주어야 하는 규모의 수정인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 코드를 동작시켰을 때 기존과는 다른 방식으로 데이터를 삽입한다면 다른 api 에서 이 데이터를 사용하는데 문제가 되지는 않을 것인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에 근본적인 문제에 대해서 고민하기도 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;굳이 수정으로 작업을 해야하나? version 2 를 출시하면 안되는 문제인가?&lt;/li&gt;
&lt;li&gt;어떻게 하면 서버 자원은 덜 먹으면서, 가독성이 좋은 코드를 짤 수 있는지?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 이 클래스를 만들어 놓으면 다른 개발자가 사용할 때 문제가 생기지는 않을까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어서 가변인자를 통해서 받은 파라미터를 String 에 parsing 해주는 클래스를 만들었다고 하자. 그러면 실수로 인자 1개를 덜 넣어줘서 문제가 생길 수도 있다. 이 때 언제 문제가 발생하는가? 런타임인가? 애플리케이션 로드 시점인가? 발생했을 때 얼마나 크리티컬한 이슈가 되는가? 이렇게 쌓인 데이터는 마이그레이션(최악인 경우)을 하던 뭘하던 여튼 해결할 수 있는 수준인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;작성한 코드 때문에 트랜젝션을 스레드가 물고 있는다면 그 시간은 얼마나 더 길어지지?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 케이스에 대해서 고민을 할 수 있게 되는 것이 너무 즐겁다. 물론 선배 개발자에게 배워가면서 작업을 하고, 그 과정을 통해서 배운 것들이지만 그래도 이런 지식들이 점점 나의 것이 되어가는 자체만으로 행복하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사랑합니데이 선배님&lt;/p&gt;</description>
      <category>개발자 일기</category>
      <author>blue-curtain</author>
      <guid isPermaLink="true">https://way-be-developer.tistory.com/288</guid>
      <comments>https://way-be-developer.tistory.com/288#entry288comment</comments>
      <pubDate>Thu, 13 Jan 2022 18:52:22 +0900</pubDate>
    </item>
    <item>
      <title>JPA - 예상치 못한 변경 감지가 동작하는 경우</title>
      <link>https://way-be-developer.tistory.com/287</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참고&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/donghyeon0725/jpaTest&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/donghyeon0725/jpaTest&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1640780637388&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - donghyeon0725/jpaTest&quot; data-og-description=&quot;Contribute to donghyeon0725/jpaTest development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/donghyeon0725/jpaTest&quot; data-og-url=&quot;https://github.com/donghyeon0725/jpaTest&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/byGACz/hyMTORji0t/NN9n99K0zSkN2hG9uqEIfk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/donghyeon0725/jpaTest&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/donghyeon0725/jpaTest&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/byGACz/hyMTORji0t/NN9n99K0zSkN2hG9uqEIfk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - donghyeon0725/jpaTest&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to donghyeon0725/jpaTest development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 에서 변경 감지가 동작하려면 다음과 같은 조건이 필요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명시적인(선언적인) 트랜잭션을 열어야 한다. (이 때 선언적인 @Transactional 이 존재해야하고, 이 트랜젝션이 commit 되는 시점에 변경 감지가 일어난다.)&lt;/li&gt;
&lt;li&gt;영속성 관리중인 객체에 데이터 변경이 일어나야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 Service 에서 트랜잭션(@Transactional)을 열고 Service 에서 변경감지로 JPA Entity 를 수정 한 뒤, Service 를 빠져 나왔을 때, 더 이상의 어떤 변경 감지도 동작하지 않을 것을 기대합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 샘플 소스 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;User Entity&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1640780663254&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Setter
@Entity
public class User {

    @Id
    @GeneratedValue
    private Long userId;

    private String name;

    private String profileUrl;

    private Integer age = 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 유저 엔티티&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;UserController&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1640780682550&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
public class UserController {
		// 유저 정보 조회 api
    @GetMapping(&quot;/my/{userId}&quot;)
    public Map getMyInfo1(@PathVariable Long userId) {
        Map&amp;lt;String, Object&amp;gt; info = new HashMap&amp;lt;&amp;gt;();

        User user = userRepository.findById(userId).orElseThrow(() -&amp;gt; new RuntimeException(&quot;유저가 없습니다.&quot;));
        user.setProfileUrl(&quot;https://test.com/&quot; + user.getProfileUrl());

        info.put(&quot;user&quot;, user);

        return info;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드의 의도는 user 의 정보를 보여주는 것입니다. 이 때, 프로필 url 앞에 도메인 주소에 해당 하는 값을 붙여서 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 질문.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 의 변경 감지가 동작해서 유저의 프로필이 &lt;b&gt;&quot;https://test.com/&quot; + user.getProfileUrl()&lt;/b&gt; 로 변경된 값이 반영 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답은 No. 왜냐하면, 명시적으로 트랜잭션을 열지 않았기 때문입니다. 명시적으로 연 트랜잭션이 커밋되는 시점에 변경감지가 일어나기 때문에 위 코드에서는 변경감지는 일어나지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에초에 조회용도로 method가 GET 인 API 에서 유저의 profile_url 필드 값을 변경하고 싶진 않았을 것입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서, 추가 사항이 생겼다고 가정합니다. 이 사용자가 주문을 한 내역을 확인하고 싶다고 합니다. 요구 사항에 따라서 다음과 같은 Service 로직이 나왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1640780715477&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class OrdersService {
    private final OrdersRepository ordersRepository;
		//&amp;nbsp;주문 내역이 있는지 확인
    @Transactional
    public boolean hasOrderThatDoesNotStart(User user) {

        return ordersRepository.countByUserAndDeliveryStatus(user, DeliveryStatus.WAIT) &amp;gt; 0;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 주문한 내역이 있는지 확인하는 간단한 로직입니다. (정확하게는 주문한 것중 아직 배송 전인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문을 했으면 true, 안했으면 false 로 결과를 리턴 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 유저 정보를 조회하는 api 에 다음과 같이 hasOrderThatDoesNotStart 라는 필드로, 주문 여부를 확인할 수 있도록 API 에 다음과 같은 소스를 추가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1640780777589&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
public class UserController {
		
    @GetMapping(&quot;/my/{userId}&quot;)
    public Map getMyInfo1(@PathVariable Long userId) {
        Map&amp;lt;String, Object&amp;gt; info = new HashMap&amp;lt;&amp;gt;();

        User user = userRepository.findById(userId).orElseThrow(() -&amp;gt; new RuntimeException(&quot;유저가 없습니다.&quot;));
        user.setProfileUrl(&quot;https://test.com/&quot; + user.getProfileUrl());

        info.put(&quot;user&quot;, user);
				// 추가
				info.put(&quot;hasOrderThatDoesNotStart&quot;, ordersService.hasOrderThatDoesNotStart(user));
        
				return info;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 API를 호출하면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 호출할 때 마다, profileUrl 의 값은 &quot;https://test.com/&quot;&amp;nbsp;가 붙어 계속 길이가 늘어난 값이 DB 에 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 변경 감지가 일어났을까? 다음과 같은 조건이 마련되었기 때문 입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;user 를 영속성 컨텍스트에서 관리 중이다.&lt;/li&gt;
&lt;li&gt;user 의 값이 변경 되었다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ordersService.hasOrderThatDoesNotStart(user)&lt;/b&gt; 를 호출하면서 명시적 트랜젝션이 열렸고 커밋 되는 시점에 user 가 영속성 컨텍스트에 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 이상하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 열리는 시점은 ordersService.hasOrderThatDoesNotStart(user) 가 호출되는 시점이고 이 전에 이미 트랜잭션(묵시적)이 끝난 user 를 왜 영속성 컨텍스트에서 관리중인 것일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 Open-Session-In-View (OSIV) 옵션이 켜져 있기 때문입니다. JPA 에서는 default 설정이 OSIV true입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OSIV 는 트랜잭션이 끝나고도 프록시 엔티티의 Lazy 로딩을 사용할 수 있도록 트랜잭션을 열어 놓습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 여전히 트랜잭션이 끝나도 엔티티를 영속 상태로 유지시키는데, 이렇게 관리중인 엔티티가 있는 상태에서 명시적인 트랜잭션(@Transactional)이 열렸고 이 트랜잭션이 커밋될 때 JPA 의 변경 감지가 동작했기 때문에 이와 같은 결과를 보이는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 코드를 사용하면 엔티티가 영속 상태인지 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1640780860942&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;entityManager.contains(user)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 로그를 찍어 확인하면 다음과 같은 결과를 얻을 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1640780879964&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
public Map getMyInfo(@PathVariable Long userId) {
    Map&amp;lt;String, Object&amp;gt; info = new HashMap&amp;lt;&amp;gt;();


    User user = userRepository.findById(userId).orElseThrow(() -&amp;gt; new RuntimeException(&quot;유저가 없습니다.&quot;));
    user.setProfileUrl(&quot;https://test.com/&quot; + user.getProfileUrl());

    System.out.println(&quot;영속성 컨텍스트에서 관리중인가? =&amp;gt; &quot; + entityManager.contains(user));

    info.put(&quot;user&quot;, user);

    return info;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OSIV 를 켰을 때 : &lt;b&gt;영속성 컨텍스트에서 관리중인가? =&amp;gt; true&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;OSIV 를 껐을 때 : &lt;b&gt;영속성 컨텍스트에서 관리중인가? =&amp;gt; false&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션을 읽기 전용으로 해두었을 때에도 JPA 변경감지가 일어나지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1640780911971&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional(readOnly = true)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이 코드의 가장 &lt;b&gt;근본적인 문제&lt;/b&gt;는 Entity 를 리턴 한다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Entity 는 변경감지가 언제든 일어날 가능성이 있는 객체 입니다. 우리는 JPA 를 사용하면서 예상치 못한 동작을 막기 위해서 Command 와 Query 를 분리해야 합니다. 엔티티를 다룰 때, 같은 API 에서 &lt;b&gt;Entity 는&lt;/b&gt; &lt;b&gt;데이터 변경 &amp;amp; 조회 두가지 용도로 사용 되서는 안됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 변경이 있는 api 이고 조회 용 데이터도 보여주어야 하는 API 라면 return 용도의 DTO 클래스를 하나 만드는 것이 맞다. &lt;b&gt;Entity 를 리턴 해서는 안됩니다. &lt;/b&gt;같은 맥락으로 서비스단의 메소드를 설계할 때에는 데이터 변경 또는 입력이 있는 메소드의 경우 대상 &lt;b&gt;Entity 를 리턴해서는 안됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO 를 별도로 만들어 관리하는 것이 정 어렵다면, 영속성 관리를 하지 않는 Entity (new 로 생성한, 또는 엔티티 매니저에서 detach 를 통해서 영속성 관리를 떼어낸) 에 &lt;b&gt;BeanUtils.copyProperties&lt;/b&gt; 으로 필드를 복사해서 리턴하는 것이 훨씬 더 낫습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 OSIV 옵션은 Lazy 로딩이라는 강력한 기능을 사용하기 위해서 (편리함을 위해서) 켜 놓는 경우가 많이 있습니다. 따라서, 근본적인 해결책은 Command 와 Query 를 분리하는 것입니다. 엔티티는 언제는 변경 감지가 일어날 수 있다는 사실을 늘 고려해야 합니다.&lt;/p&gt;</description>
      <category>프레임워크/JPA</category>
      <author>blue-curtain</author>
      <guid isPermaLink="true">https://way-be-developer.tistory.com/287</guid>
      <comments>https://way-be-developer.tistory.com/287#entry287comment</comments>
      <pubDate>Wed, 29 Dec 2021 21:29:21 +0900</pubDate>
    </item>
  </channel>
</rss>