<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>RR</title>
    <link>https://k3068.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 7 May 2026 02:20:47 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jay Joon</managingEditor>
    <item>
      <title>Spring boot CircuitBreaker 간단하게 알아보기</title>
      <link>https://k3068.tistory.com/110</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 개요 -&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 프로젝트에서 서킷브레이커를 적용하기 위해서는 아래의 2가지 프레임워크를 이용합니다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;hystrix circuit breaker&lt;/li&gt;
&lt;li&gt;resilience4j circuit breaker&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 hystrix circuit breaker는 2021년 11월 1일부로 공식적인 지원이 중단되어&lt;br /&gt;사용을 추천하지 않습니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이번 글에서는&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; resilience4j 의 circuit breaker을 설명하도록 하겠습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 의존성은 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;dependencies {
    implementation 'io.github.resilience4j:resilience4j-spring-boot2:{version}'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트 버전과 호환되는 각각의 resilience4j 은 아래와 같으니 현재 자신의 프로젝트에 맞는 &lt;br /&gt;version을 사용해 주세요!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot 2.6.x: Resilience4j-Spring Boot 1.7.x&lt;/li&gt;
&lt;li&gt;Spring Boot 2.5.x: Resilience4j-Spring Boot 1.6.x&lt;/li&gt;
&lt;li&gt;Spring Boot 2.4.x: Resilience4j-Spring Boot 1.5.x&lt;/li&gt;
&lt;li&gt;Spring Boot 2.3.x: Resilience4j-Spring Boot 1.4.x&lt;/li&gt;
&lt;li&gt;Spring Boot 2.2.x: Resilience4j-Spring Boot 1.3.x&lt;/li&gt;
&lt;/ul&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;b&gt;- 본문 -&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 CircuitBreaker를 사용하기 위해선 config 설정을 먼저 하여야 하는데 두 가지 방법이 존재합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직접 Config 객체를 만들어 설정하는 법&lt;/li&gt;
&lt;li&gt;yml 파일에 명시하여 설정하는 법 (이번글을 읽고 공식문서만 잠깐 참고하여도 이해하는데 크게 문제가 없음으로 이번 글에서는 설명하지 않겠습니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 Config 객체를 만들어 설정하는 법은 아래와 같습니다!&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;CircuitBreakerConfig config = CircuitBreakerConfig.custom() 
                .failureRateThreshold(50)
                .waitDurationInOpenState(Duration.ofMillis(1000))
                .permittedNumberOfCallsInHalfOpenState(3) 
                .slidingWindowSize(100) 
                .build();&lt;/code&gt;&lt;/pre&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;failureRateThreshold(50)&lt;/b&gt;&lt;/span&gt; : Circuit Breaker가 Open 상태로 전환될 실패율 임계값을 50%로 설정 &amp;rarr; slidingWindowSize 개의 실패한 호출의 비율이 50%를 초과하면 Circuit Breaker가 Open 상태로 변경 (100개 중 50개가 실패면 open)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;waitDurationInOpenState(Duration.ofMillis(1000))&lt;/b&gt;&lt;/span&gt; : Circuit Breaker가 Open 상태에서 Half-Open 상태로 전환될 때까지 대기하는 시간을 1초로 설정&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;permittedNumberOfCallsInHalfOpenState(3)&lt;/b&gt;&lt;/span&gt; : Circuit Breaker가 Half-Open 상태에서 허용되는 최대 호출 횟수를 3으로 설정. Half-Open 상태에서 3번 호출 시 모두 성공해야 다시 closed 상태로 변한다. (한 번이라도 실패한 경우 다시 open)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;slidingWindowSize(100)&lt;/b&gt;&lt;/span&gt; : Circuit Breaker가 Closed 상태일 때 통계를 수집하기 위한 window size를 100으로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 config 설정을 완료하였다면 CircuitBreaker를 아래와 같이 생성하면 됩니다!&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;CircuitBreaker circuitBreaker = CircuitBreaker.of(&quot;testCircuitBreaker&quot;, config);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 했다면 CircuitBreaker(testCircuitBreaker)가 정상적으로 생성되었습니다.&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;다음은 생성된 CircuitBreaker을 사용해 봅시다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;circuitBreaker.decorateCheckedSupplier(() -&amp;gt; {
            System.out.println(&quot;실행할 로직&quot;);
            return &quot;test&quot;;
        });
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게는 다음과 같으며 decorateCheckedSupplier 이외에도 다른 decorater 메서드들이 많으니 필요에 따라 사용하면 됩니다.&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;만약 실행할 로직에서 예외가 발생한 경우 fallback 코드도 아래와 같이 설정할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;CheckedFunction0&amp;lt;String&amp;gt; checkedSupplier = circuitBreaker.decorateCheckedSupplier(() -&amp;gt; {
            throw new RuntimeException(&quot;무조건 실패&quot;);
        });

String result = Try.of(checkedSupplier).recover(throwable -&amp;gt; {
            log.error(&quot;실행 로직 실패 fallback 동작  error: {}&quot;, throwable.getMessage());
            return &quot;fallback&quot;;
        }).get();

System.out.println(result);
&lt;/code&gt;&lt;/pre&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-origin-width=&quot;1280&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JMmqy/btrZK2RyttM/MhUwXJFamcURxeOD2kNl21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JMmqy/btrZK2RyttM/MhUwXJFamcURxeOD2kNl21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JMmqy/btrZK2RyttM/MhUwXJFamcURxeOD2kNl21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJMmqy%2FbtrZK2RyttM%2FMhUwXJFamcURxeOD2kNl21%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;706&quot; height=&quot;124&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 error 가 발생하여도 실패에 따른 동작을 지정할 수 있습니다.&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;이렇게 error 가 발생하면 앞서 설정한 config 설정대로 집계하다가 실패율 임계치가 넘는 순간 서킷의 상태는 &lt;b&gt;OPEN으로 변경되고&lt;/b&gt; 본 로직을 실행하지 않고 &lt;b&gt;바로 fallback 로직을 실행하게 됩니다.&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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;circuitBreaker.transitionToOpenState(); // 강제로 open 상태로 변경

CheckedFunction0&amp;lt;String&amp;gt; checkedSupplier = circuitBreaker.decorateCheckedSupplier(() -&amp;gt; {
    throw new RuntimeException(&quot;무조건 실패&quot;);
});

String result = Try.of(checkedSupplier).recover(throwable -&amp;gt; {
    log.error(&quot;실행 로직 실패 fallback 동작  error: {}&quot;, throwable.getMessage());
    return &quot;fallback&quot;;
}).get();
  
System.out.println(&quot;result :&quot; + result);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CBIk1/btrZJbVY5Xv/UDRU54hiPTuWcE9vtCFHOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CBIk1/btrZJbVY5Xv/UDRU54hiPTuWcE9vtCFHOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CBIk1/btrZJbVY5Xv/UDRU54hiPTuWcE9vtCFHOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCBIk1%2FbtrZJbVY5Xv%2FUDRU54hiPTuWcE9vtCFHOk%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;615&quot; height=&quot;55&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;114&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;조금 더 알아보기&lt;/b&gt;&lt;/h3&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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;CircuitBreaker circuitBreaker1 = CircuitBreaker.of(&quot;testCircuitBreaker&quot;, config);
CircuitBreaker circuitBreaker2 = CircuitBreaker.of(&quot;testCircuitBreaker&quot;, config);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;circuitBreaker1과 circuitBreaker2 은 동일한 객체일까요?&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; 서킷브레이커 등록 시 testCircuitBreaker에 해당하는 이름이 이미 존재할 시 같은 인스턴스를 반환하게 됩니다. ( circuitBreaker1 == circuitBreaker2 )&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;만약 다른 로직에서 생성된 서킷 브레이커(testCircuitBreaker)를 가져오고 싶을 땐 아래와 같이 가져오면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(&quot;testCircuitBreaker&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;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&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;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;yml 설정 (&lt;a href=&quot;https://resilience4j.readme.io/docs/getting-started-3&quot;&gt;문서&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등 다양한 설정이 필요하시다면 &lt;a href=&quot;https://resilience4j.readme.io/docs/examples&quot;&gt;공식문서&lt;/a&gt;를 참고하시면 도움이 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>jay Joon</author>
      <guid isPermaLink="true">https://k3068.tistory.com/110</guid>
      <comments>https://k3068.tistory.com/110#entry110comment</comments>
      <pubDate>Sat, 18 Feb 2023 01:09:01 +0900</pubDate>
    </item>
    <item>
      <title>[Spring-batch] Job 에 대해 알아보자!</title>
      <link>https://k3068.tistory.com/109</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본개념&lt;/h2&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;적어도 하나 이상의 step을 포함하고 있는 컨테이너&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 구현체&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SimpleJob
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순차적으로 Step을 실행시키는 Job&lt;/li&gt;
&lt;li&gt;모든 Job에서 유용하게 사용할 수 있는 표준 기능을 갖고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FlowJob
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정한 조건과 흐름에 따라 Step을 구성하여 실행시키는 Job&lt;/li&gt;
&lt;li&gt;Flow 객체를 실행시켜서 작업을 진행함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Job 은 어떻게 만들어지고 어떻게 실행되는 걸까??&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 &lt;code&gt;JobBuilderFactory&lt;/code&gt; 을 이용하여 Job 생성하게 된다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;jobBuilderFactory.get(&quot;simpleJob&quot;) // job 의 이름을 정한다.
            .start(simpleStep1()) // 실행할 step 을  지정한다.
            .next(simpleStep2()) //  step 은 여러개 가질 수 있다.
            .build(); // 최종적으로 Job 을 생성한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4cc51/btruolHjvVj/ExQx4sKqXIfYBR7BmFYhZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4cc51/btruolHjvVj/ExQx4sKqXIfYBR7BmFYhZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4cc51/btruolHjvVj/ExQx4sKqXIfYBR7BmFYhZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4cc51%2FbtruolHjvVj%2FExQx4sKqXIfYBR7BmFYhZk%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;639&quot; height=&quot;87&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Job 구현체 내부 stepList에 step을 순차적으로 저장하여 최종적으로 저장하여 생성하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;실행은 (스프링 부트 기준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;JobLauncherApplicationRunner&lt;/code&gt; 안에 있는 &lt;code&gt;JobLauncher&lt;/code&gt; 를 이용하여 Job을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;JobLauncher&lt;/code&gt; 는 인터페이스이며 실 구현체는 &lt;code&gt;SimpleJobLauncher&lt;/code&gt; 를 이용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq6JdQ/btrubMzOHRA/8aDKKKGZMtOnnozrfIzekk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq6JdQ/btrubMzOHRA/8aDKKKGZMtOnnozrfIzekk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq6JdQ/btrubMzOHRA/8aDKKKGZMtOnnozrfIzekk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq6JdQ%2FbtrubMzOHRA%2F8aDKKKGZMtOnnozrfIzekk%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;720&quot; height=&quot;138&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 &lt;code&gt;SimpleJobLauncher&lt;/code&gt; 를 이용하여 등록한 Job을 step 별로 실행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 &lt;code&gt;publisher&lt;/code&gt; 가 등록되어있다면 job 이 실행되고 이벤트도 발행시킬 수 있습니다.&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;그렇다면 애플리케이션에 여러 개의 Job 등록되어있으면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별 다른 설정을 하지 않고 애플리케이션을 실행하면 등록된 모든 Job을 실행하게 된다.&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;특정 job 만 실행시키고 싶다면 &lt;code&gt;application.yml&lt;/code&gt; 에 &lt;code&gt;spring.batch.job.names: ${job.name:NONE}&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵션을 추가하여 program arguments에 job 이름을 넣어주어 실행시켜주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjk2Uj/btrulSZygrt/2YZjxjaMKB1Obi8wVrSWJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjk2Uj/btrulSZygrt/2YZjxjaMKB1Obi8wVrSWJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjk2Uj/btrulSZygrt/2YZjxjaMKB1Obi8wVrSWJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjk2Uj%2FbtrulSZygrt%2F2YZjxjaMKB1Obi8wVrSWJk%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;638&quot; height=&quot;134&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;134&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;그렇다면 이 넘겨준 Job 이름은 어디서 사용되고 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;u&gt;어떻게 한 가지 Job 만 실행되는 걸까?&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring boot 가 실행될때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;JobLauncherApplicationRunner&lt;/code&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;등록되는 Config 이름은 &lt;code&gt;BatchAutoConfiguration&lt;/code&gt; 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0DDPN/btrufEPA13t/LTZFG1Ki56PPKd1i5ilZy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0DDPN/btrufEPA13t/LTZFG1Ki56PPKd1i5ilZy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0DDPN/btrufEPA13t/LTZFG1Ki56PPKd1i5ilZy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0DDPN%2FbtrufEPA13t%2FLTZFG1Ki56PPKd1i5ilZy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;220&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보면 properties 또는 args로 넘겨준 jobName 이 있다면 생성할 때 runner에 등록을 합니다.&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;code&gt;JobLauncherApplicationRunner&lt;/code&gt; 가 실질적으로 &lt;code&gt;SimpleJobLauncher&lt;/code&gt; 을 실행할 때 모든 Job을 실행할지 특정 Job 만 실행할지 결정하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcYcDk/btruc3OQCBM/5bFh7dE06o9YDwvdshukFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcYcDk/btruc3OQCBM/5bFh7dE06o9YDwvdshukFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcYcDk/btruc3OQCBM/5bFh7dE06o9YDwvdshukFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcYcDk%2Fbtruc3OQCBM%2F5bFh7dE06o9YDwvdshukFk%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;719&quot; height=&quot;122&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메서드에서 &lt;code&gt;executeLocalJobs&lt;/code&gt; , &lt;code&gt;executeRegisteredJobs&lt;/code&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;code&gt;executeLocalJobs&lt;/code&gt; 은 JobName 이 있다면 해당 job을 찾아 실행하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLCAOy/btrubLOujcK/zMdRBDuDH6rhHuGvMwxRgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLCAOy/btrubLOujcK/zMdRBDuDH6rhHuGvMwxRgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLCAOy/btrubLOujcK/zMdRBDuDH6rhHuGvMwxRgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLCAOy%2FbtrubLOujcK%2FzMdRBDuDH6rhHuGvMwxRgK%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;682&quot; height=&quot;251&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 &lt;code&gt;executeRegisteredJobs&lt;/code&gt; 가 불리지만 &lt;code&gt;JobLauncherApplicationRunner&lt;/code&gt; 에 jobName 이 설정되어 있다면 해당 메서드는 실행되지 않고 종료됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm6LEn/btrujIXPLdz/euIpdKKqnwWrUR15FaWjAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm6LEn/btrujIXPLdz/euIpdKKqnwWrUR15FaWjAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm6LEn/btrujIXPLdz/euIpdKKqnwWrUR15FaWjAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm6LEn%2FbtrujIXPLdz%2FeuIpdKKqnwWrUR15FaWjAk%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;685&quot; height=&quot;305&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;409&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;b&gt;앞선 살펴본 코드를 요약하자면 다음과 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;jobName을 넘겨준다고 해서 해당하는 Job의 Bean 만 등록이 되는 것이 아니다.&lt;/li&gt;
&lt;li&gt;단지 jobName으로 여러 개의 job 들 중 이름이 같은 job 만 실행이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;​&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JobInstance, JobExecution에 대해 알아보자&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nBvmT/btrulSSOYSB/cGgADoEjFrAXRmJTQDgxPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nBvmT/btrulSSOYSB/cGgADoEjFrAXRmJTQDgxPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nBvmT/btrulSSOYSB/cGgADoEjFrAXRmJTQDgxPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnBvmT%2FbtrulSSOYSB%2FcGgADoEjFrAXRmJTQDgxPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;343&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;JobExecution&lt;/code&gt;, &lt;code&gt;JobInstance&lt;/code&gt; 는 위 그림과 같은 구조로 생성이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림만으로도 충분히 이해하실 수 있지만 코드와 설명을 통해 조금 더 알아보죠!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JobInstance&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Job 이 실행될 때마다 &lt;b&gt;&lt;b&gt;BATCH_JOB_INSTANCE에&lt;/b&gt;&lt;/b&gt; 저장할 Entity&lt;/li&gt;
&lt;li&gt;이미 동일한 JobParameter, JobName 이 존재하면 저장되지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;중요) 저장이 안 된다는 거지 Job 실행이 안 되는 건 아닙니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉 동일한 Job에 대해서 JobParameter 가 다르다면 계속해서 생성됩니다.&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;Job (1) : JobInstance(N)&lt;/li&gt;
&lt;li&gt;JobInstance (1) : JobParameter(1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JobExecution&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JobInstance 가 정상적으로 성공했는지? 아니면 실패했는지 기록합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성공시간, 시작시간 , Job 실행 상태 기록&lt;/li&gt;
&lt;li&gt;기록하는 테이블은 &lt;b&gt;&lt;b&gt;BATCH_JOB_EXECUTION입니다.&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;JobInstance (1) : JobExecution(N)&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;앞서 언급한 것 처럼 중요한 부분은 기존에 동일한 JobInstance 있다고 해서 실행하고자 하는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Job 이 무조건 실패하는 건 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;s&gt;제가 처음 batch 을 접했을 땐 그런 줄 알았습니다.  &lt;/s&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;이미 존재하는 JobInstance에 대해 동일한 Job, JobParameter을 실행시 다음과 같이 동작합니다.&lt;br /&gt;(설명하는 내용은 JobInstance 존재하지 않아도 동일하게 검증을 진행합니다.)&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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;jobInstanceDao&lt;/code&gt; 로 부터 &lt;code&gt;JobInstance&lt;/code&gt; 를 가져옵니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JobInstance&lt;/code&gt; 이용&lt;code&gt;jobExecutionDao&lt;/code&gt; 으로부터 가장 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;마지막으로 등록된&lt;/b&gt;&lt;/span&gt; &lt;code&gt;JobExecution&lt;/code&gt; 가져옵니다.&lt;/li&gt;
&lt;li&gt;가져온 &lt;code&gt;JobExecution&lt;/code&gt; 의 하위의 &lt;code&gt;StepExecution&lt;/code&gt; 을 확인하여 실행을 할지 안 할지 결정합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;StepExecution&lt;/code&gt; 은 step을 설명할 때 다시 설명하도록 하겠습니다.&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;code&gt;SimpleJobLauncher&lt;/code&gt; 의 run() 메서드를 따라가면서 보시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biXYrk/btruiSTUVUy/MhtxXlFRkYn9ourfK09gV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biXYrk/btruiSTUVUy/MhtxXlFRkYn9ourfK09gV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biXYrk/btruiSTUVUy/MhtxXlFRkYn9ourfK09gV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiXYrk%2FbtruiSTUVUy%2FMhtxXlFRkYn9ourfK09gV0%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;693&quot; height=&quot;498&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드가 앞선 &lt;b&gt;첫 번째 단계&lt;/b&gt;에 대해 설명한 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째 검증&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;jobInstanceDao&lt;/code&gt; 로 부터 &lt;code&gt;JobInstance&lt;/code&gt; 를 가져옵니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JobInstance&lt;/code&gt; 이용 &lt;code&gt;jobExecutionDao&lt;/code&gt;으로부터 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;등록된 모든&lt;/b&gt;&lt;/span&gt; &lt;code&gt;JobExecution&lt;/code&gt; 가져옵니다.&lt;/li&gt;
&lt;li&gt;모든 &lt;code&gt;JobExecution&lt;/code&gt; 순회하며 상태를 확인하여 확인하여 실행을 할지 안 할지 결정합니다.&lt;/li&gt;
&lt;li&gt;위 과정이 이상이 없다면 새로운 &lt;code&gt;jobExecution&lt;/code&gt; 을 만들어 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드는 &lt;code&gt;SimpleJobLauncher&lt;/code&gt; run() 메서드 안의 &lt;code&gt;jobRepository.createJobExecution&lt;/code&gt; 을 참고&lt;br /&gt;하여 보시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sN9yX/btrufDQMZll/S2bR7zYKIb9kgk82krJCa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sN9yX/btrufDQMZll/S2bR7zYKIb9kgk82krJCa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sN9yX/btrufDQMZll/S2bR7zYKIb9kgk82krJCa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsN9yX%2FbtrufDQMZll%2FS2bR7zYKIb9kgk82krJCa1%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;704&quot; height=&quot;63&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;63&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jobRepository의 구현체는 &lt;code&gt;SimpleJobRepository&lt;/code&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;&lt;b&gt;해당 과정을 요약하자면&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 등록된 &lt;code&gt;JobInstance&lt;/code&gt; 가 존재하여도 &lt;code&gt;jobExecution&lt;/code&gt; 의 상태에 따라 Job 실행 여부가 결정됩니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&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;여기까지 Job 에 관련된 동작원리 및 속성에 대해 알아보았는데요!&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;br /&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;zwj;♂️&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&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;참고: &lt;a href=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/index-single.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;spring-batch reference&lt;/a&gt;&lt;/p&gt;</description>
      <category>Spring/batch</category>
      <author>jay Joon</author>
      <guid isPermaLink="true">https://k3068.tistory.com/109</guid>
      <comments>https://k3068.tistory.com/109#entry109comment</comments>
      <pubDate>Fri, 25 Feb 2022 13:39:39 +0900</pubDate>
    </item>
    <item>
      <title>Spring Config 서버는 필요할까?</title>
      <link>https://k3068.tistory.com/108</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 질문에 대해서는 서비스가 어떻게 구축되었는가에 따라 정답이 다르겠지만&lt;br /&gt;적어도 yml에서 관리되고 있는 property 가 동일한 값을 여러 프로젝트에서 사용하고 있다면 매우 필요하다고 느끼고 있다.&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;p data-ke-size=&quot;size16&quot;&gt;예를 들어 여러 프로젝트에서 Slack 알림을 발송하고 있다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack 알람을 발송하기 위해선 webHook 이 필요할 것이다. 또한 채널명도 알고 있어야 된다.&lt;br /&gt;간단하게 아래와 같은 그림이 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOlTLH/btrtKCKAErL/TL6C1UPPSIjiWIQQ0wtUe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOlTLH/btrtKCKAErL/TL6C1UPPSIjiWIQQ0wtUe0/img.png&quot; data-alt=&quot;예시 1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOlTLH/btrtKCKAErL/TL6C1UPPSIjiWIQQ0wtUe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOlTLH%2FbtrtKCKAErL%2FTL6C1UPPSIjiWIQQ0wtUe0%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;711&quot; height=&quot;315&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 1&lt;/figcaption&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;그러면 모든 프로젝트의 yml 파일에 가서 정보를 변경해야 한다 여기까지는 충분히 할 수 있다.&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;하지만 코드를 변경했지만 Slack 채널명은 변경하지 못한다.&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;하지만 프로젝트, 프로젝트 2, 프로젝트 3 이 모두 배포되고 나서야 Slack 채널명을 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 하나라도 &lt;b&gt;먼저 배포하거나&lt;/b&gt; &lt;b&gt;하나라도 빠진다면&lt;/b&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;만약&amp;nbsp; Config Server 가 있다면 편해질까?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9by2H/btrtLy8ZwIj/kEp9m0ZOGqRgRy5ivqc5W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9by2H/btrtLy8ZwIj/kEp9m0ZOGqRgRy5ivqc5W1/img.png&quot; data-alt=&quot;예시 2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9by2H/btrtLy8ZwIj/kEp9m0ZOGqRgRy5ivqc5W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9by2H%2FbtrtLy8ZwIj%2FkEp9m0ZOGqRgRy5ivqc5W1%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;706&quot; height=&quot;444&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림과 같이 나는 yml 이 저장소에 저장되어있는 파일만 수정하여 해당 파일이 변경되었다는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트만 config server로 발행하고 config server는 해당 서버를 구독하고 있는 &lt;br /&gt;프로젝트들에게 알림을 발송하여 새로운 yml 파일로 변경하라는 메시지만 발행해준다면&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;yml 파일을 3번 수정하거나&lt;/li&gt;
&lt;li&gt;누락되어 수정되지 않은 프로젝트가 있거나&lt;/li&gt;
&lt;li&gt;마지막으로 제일 행복한 재배포 과정을 3번이나 가지지 않아도 된다.&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;&lt;u&gt;Config Server 가 단일 장애점이 될 수도 있지 않을까?  &lt;/u&gt;&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;해당 본문은 Spring Cloud Config Server 만들어보기 전에 왜 구축해보고 싶었는지 도입 글?이라고 해야 할까요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직까지는 단점보다는 장점이 많아 보이긴 하지만 또 하다 보면 단점이 더 많을 수도 있겠죠!?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음 글은 구축 과정과 Spring Cloud Config과 관련된 글을 작성하고자 합니다.&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;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;&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;</description>
      <category>Spring</category>
      <author>jay Joon</author>
      <guid isPermaLink="true">https://k3068.tistory.com/108</guid>
      <comments>https://k3068.tistory.com/108#entry108comment</comments>
      <pubDate>Sun, 20 Feb 2022 03:02:09 +0900</pubDate>
    </item>
    <item>
      <title>신입 개발자가 되기까지(취업준비 후기)</title>
      <link>https://k3068.tistory.com/107</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;총평: 힘들었다..&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;졸업 후 취업까지 걸린 기간은 &lt;b&gt;&lt;u&gt;총 3개월&lt;/u&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;s&gt;(탈모 올 뻔)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 취업하면서 느꼈던 점과 저 자신을 다시 한번 돌아보는 글을 써보고자 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나는 &quot;비전공자&quot; 다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 비 전공자입니다.(너무 뜬금없나요?? )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;군대를 다녀온 후 &lt;u&gt;&lt;b&gt;2019년&lt;/b&gt; 에 처음으로 JAVA를 접했는데요&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 개발자를 하기 위해서 JAVA를 공부했던 것은 &lt;span style=&quot;color: #000000;&quot;&gt;아니었습니다&lt;/span&gt;&amp;nbsp;(학교 과제 때문에 처음으로 접하게 되었습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때 당시 진행했던 과제가 간단하게 JSP + Servlet으로 웹 환경을 구축하고 저만의 서비스를 만드는 수업이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;자료구조, 일반적인 기본 타입 마저 일체 모르는 상태였습니다.&lt;/u&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;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떻게 공부했는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 국비 학원 또는 취업프로그램 같은 것은 일체 듣지 않았습니다.&lt;br /&gt;딱히 큰 이유는 없지만 결국 취업프로그램이나 국비 학원을 통해서 배운걸 내 걸로 만들기 위해 개인적 공부는 필요하기 때문에 인강으로도 충분하다고 생각했습니다. &lt;br /&gt;인강의 장점은 &lt;b&gt;&lt;u&gt;계속해서 다시 들을수 있다는 게 제일 좋은 장점인 거&lt;/u&gt;&lt;/b&gt; 같습니다.&lt;br /&gt;(현장 강의도 충분히 매력 있지만 어떠한 부분에서 이해가 안 된다면 잠시 멈추고 공부하고 다시 들을 때마다 새로운 느낌을 받을 수 있습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S2adA/btrgA5Gi1x0/IhahxTIiD91uFNHVCko0qK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S2adA/btrgA5Gi1x0/IhahxTIiD91uFNHVCko0qK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S2adA/btrgA5Gi1x0/IhahxTIiD91uFNHVCko0qK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS2adA%2FbtrgA5Gi1x0%2FIhahxTIiD91uFNHVCko0qK%2Fimg.jpg&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;434&quot; height=&quot;127&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2020년까지 혼자 인프런을 보면서 계속 공부를 했습니다.(&lt;s&gt;백기선 님 강의 없었으면 취업 못했을 것 같습니다&lt;/s&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만.. 혼자 공부하다 보니 &lt;u&gt;&lt;b&gt;한계점&lt;/b&gt;&lt;/u&gt;을 느껴서 2020년 10월 달부터 스터디에 참여하여 스터디원 분들과 공부를 했습니다.&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;지금 돌이켜 생각해보면 혼자 공부했던 시간보다 &lt;u&gt;스터디를 하면서 공부했던 시간이 효율적으로 3배는 더 좋았던 것 같습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yZ4FF/btrgB4NrgxA/jHun91KE253lA5tzLlZ5hk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yZ4FF/btrgB4NrgxA/jHun91KE253lA5tzLlZ5hk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yZ4FF/btrgB4NrgxA/jHun91KE253lA5tzLlZ5hk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyZ4FF%2FbtrgB4NrgxA%2FjHun91KE253lA5tzLlZ5hk%2Fimg.jpg&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;464&quot; height=&quot;483&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;483&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;이렇게 한번 정리하고 나면 기억이 정말 오래갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;본격적인 취업준비 활동&lt;/h2&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;u&gt;&lt;b&gt;매우 감사하게 생각하고 있습니다.&lt;/b&gt;&lt;/u&gt; &amp;zwj;♂️&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;u&gt;&lt;b&gt;경력 3년 차 이상 또는 이에 준하는 실력이 있으신 분이라는&lt;/b&gt;&lt;/u&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm0Fzf/btrgDU4lLdb/ZqDX9UWKZaepluUdfHJPg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm0Fzf/btrgDU4lLdb/ZqDX9UWKZaepluUdfHJPg1/img.png&quot; data-alt=&quot;처참한 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm0Fzf/btrgDU4lLdb/ZqDX9UWKZaepluUdfHJPg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm0Fzf%2FbtrgDU4lLdb%2FZqDX9UWKZaepluUdfHJPg1%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;739&quot; height=&quot;147&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;처참한 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 따른 &lt;u&gt;&lt;b&gt;서류 합격률&lt;/b&gt;은 50개 정도를 넣어서 8개 정도가 붙었습니다.&lt;/u&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;코딩 테스트가 자신 없긴 했지만 공채로 뽑는 코딩 테스트에 비해 매우 쉬운 난이도의 문제이기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제만 꼼꼼히 읽으면 어느 정도 풀 수 있었습니다.(leetcode의 easy, medium 수준)&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;&lt;u&gt;면접은 제일 어려웠던 과정이면서 많이 배웠던 프로세스였습니다.&lt;/u&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 면접은 탈락이지!&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7iiRE/btrgB47GTk3/jDWaJrwHGpv9nE1Hr6wkBK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7iiRE/btrgB47GTk3/jDWaJrwHGpv9nE1Hr6wkBK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7iiRE/btrgB47GTk3/jDWaJrwHGpv9nE1Hr6wkBK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7iiRE%2FbtrgB47GTk3%2FjDWaJrwHGpv9nE1Hr6wkBK%2Fimg.jpg&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;619&quot; height=&quot;305&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운 좋게 과제 전형에서 통과했지만 &lt;u&gt;1차 면접에서&lt;/u&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;구체적인 면접 내용은 공개할 수 없지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝나고 느낀 점은 &lt;u&gt;정말 내가 특정 기술들을 사용하면서 정말 깊게 생각하고 썼을까?라는&lt;/u&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;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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qLz6K/btrgBJQg6LC/smScKHMpnmdcqNnJ7pZuy1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qLz6K/btrgBJQg6LC/smScKHMpnmdcqNnJ7pZuy1/img.jpg&quot; data-alt=&quot;피드백&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qLz6K/btrgBJQg6LC/smScKHMpnmdcqNnJ7pZuy1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqLz6K%2FbtrgBJQg6LC%2FsmScKHMpnmdcqNnJ7pZuy1%2Fimg.jpg&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;753&quot; height=&quot;105&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;피드백&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 포기할 수 없죠!  면접 과정에서 피드백을 요청하여 &lt;u&gt;저의 문제점&lt;/u&gt;을 찾고자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피드백 요청을 해서 모든 회사가 피드백을 제공해주지는 않지만 그래도 주는 회사도 있으니 꼭 &lt;u&gt;문의 메일을 보내는 것을 추천합니다&lt;/u&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;이렇게 탈락하면서 제가 느낀 점은&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 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;u&gt;&lt;b&gt;결국 가장 중요한 것은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;기초&lt;/span&gt;였습니다.&lt;/b&gt;&lt;/u&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;여기서 기초라고 하면 자료구조, JVM 동작원리, 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;&lt;u&gt;가장 중요한 건 &lt;b&gt;나의 이력서&lt;/b&gt;에 적은 &lt;b&gt;기술에 대해서 정말로 깊게 알고 있는가?&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장단점을 설명할 수 있는가?&lt;/li&gt;
&lt;li&gt;어떠한 부분에서 이 기술이 필요해서 적용했는가?&lt;/li&gt;
&lt;li&gt;적용했을 때의 발생할 수 있는 문제점을 알고 있는가?&lt;/li&gt;
&lt;li&gt;다른 방법을 고민해 보았는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 생각하기엔 이 네 가지가 가장 중요하다고 느껴졌습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;합격&lt;/h2&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;u&gt;아는 내용에 대해서는 최대한 아는 만큼 말하고 모르는 내용에 대해서는 쿨하게 인정하자!라는&lt;/u&gt; 마인드로 면접을 계속 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모르는 내용을 계속 억지로 끌고 가는 것보다는 &lt;u&gt;짧은 면접 시간 동안 제가 아는 부분을 말하기에도 시간이 부족하기 때문입니다.&lt;/u&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;여러 가지 합격한 회사 중에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 제가 가게 된 회사는 우아한형제들(배달의민족)입니다!  &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBBgMx/btrARhM1l2O/2GfzPyDcxhZxiVQMfhwjeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBBgMx/btrARhM1l2O/2GfzPyDcxhZxiVQMfhwjeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBBgMx/btrARhM1l2O/2GfzPyDcxhZxiVQMfhwjeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBBgMx%2FbtrARhM1l2O%2F2GfzPyDcxhZxiVQMfhwjeK%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;773&quot; height=&quot;172&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;172&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;끝맺음&lt;/h2&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;저희가 떨어진 건 회사가 인재를 놓친 것이니 힘들겠지만 일일이 신경 쓰지 않으셔도 됩니다.&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;a href=&quot;https://www.popit.kr/si-%EA%B0%9C%EB%B0%9C-10%EB%85%84%EC%B0%A8%EC%9D%B8%EB%8D%B0-%EC%BD%94%EB%93%9C-%EC%A2%80-%EB%B4%90%EC%A3%BC%EC%84%B8%EC%9A%94/&quot;&gt;아티클&lt;/a&gt; 중 가장 마음속에 남아있는 문구가 있는데요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3VExN/btrgB22Y87j/k0mykxxxwTwxtR4K4ha9k0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3VExN/btrgB22Y87j/k0mykxxxwTwxtR4K4ha9k0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3VExN/btrgB22Y87j/k0mykxxxwTwxtR4K4ha9k0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3VExN%2FbtrgB22Y87j%2Fk0mykxxxwTwxtR4K4ha9k0%2Fimg.jpg&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;741&quot; height=&quot;156&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;172&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;&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;size16&quot;&gt; &amp;zwj;♂️ &lt;b&gt;취업준비기간 동안 도와주신 스터디원 분들 정말 감사합니다.&lt;/b&gt; &amp;zwj;♂️&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;</description>
      <category>회고</category>
      <author>jay Joon</author>
      <guid isPermaLink="true">https://k3068.tistory.com/107</guid>
      <comments>https://k3068.tistory.com/107#entry107comment</comments>
      <pubDate>Sun, 3 Oct 2021 00:38:43 +0900</pubDate>
    </item>
    <item>
      <title>MySQL  LIKE  % 위치에 따른 인덱스 사용 여부</title>
      <link>https://k3068.tistory.com/106</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Table 내 어떠한 칼럼에 index를 설정하여 Like 문을 통해 검색하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INDEX 설정을 하였어도 &lt;u&gt;% 위치에 따라 INDEX 가 정상적으로 작동하는 경우가 있지만 반대로 잘못 사용한 경우 &lt;b&gt;Full Scan 이 발생&lt;/b&gt;할 수 있다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번글에서는 LIKE 검색에서 % 위치에 따른 INDEX 사용 여부를 하나씩 살펴볼 것이다.&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;Table 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 Member Table이며 더미 데이터수는 1만 개로 설정하였다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE `Member` (
  `id` int(9) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `address` varchar(255) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INDEX 칼럼&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE INDEX MEMBER_NAME ON Member (name);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 'ABC%' 데이터 시작 형식을 검색하는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;name like 'AB%'&lt;/code&gt; 검색인 경우 AB로 시작하는 데이터들을 조회하고자 하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;863&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8CCMm/btreEKWBka5/elkxCxd2ZCRc1HUvViJOg1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8CCMm/btreEKWBka5/elkxCxd2ZCRc1HUvViJOg1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8CCMm/btreEKWBka5/elkxCxd2ZCRc1HUvViJOg1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8CCMm%2FbtreEKWBka5%2FelkxCxd2ZCRc1HUvViJOg1%2Fimg.jpg&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;774&quot; height=&quot;190&quot; data-origin-width=&quot;863&quot; data-origin-height=&quot;211&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. '% ABC' 끝나는 데이터 형식을 검색하는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;name like '%AB'&lt;/code&gt; 검색인 경우 AB로 끝나는 데이터들을 조회하고자 하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fr7Dl/btreCT0OnW8/Qo7A282h7KYbD0fO0OMEVk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fr7Dl/btreCT0OnW8/Qo7A282h7KYbD0fO0OMEVk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fr7Dl/btreCT0OnW8/Qo7A282h7KYbD0fO0OMEVk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFr7Dl%2FbtreCT0OnW8%2FQo7A282h7KYbD0fO0OMEVk%2Fimg.jpg&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;749&quot; height=&quot;202&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;202&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. '%ABC%' 데이터에 포함하는지 검색하는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;name like '%AB%'&lt;/code&gt; 검색인 경우 AB가 포함된 데이터들을 조회하고자 하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T97Wc/btreFRnDLgq/CQCvzdnwwHfk5XW6Hrc681/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T97Wc/btreFRnDLgq/CQCvzdnwwHfk5XW6Hrc681/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T97Wc/btreFRnDLgq/CQCvzdnwwHfk5XW6Hrc681/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT97Wc%2FbtreFRnDLgq%2FCQCvzdnwwHfk5XW6Hrc681%2Fimg.jpg&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;753&quot; height=&quot;211&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;211&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 조회 같은 경우에는 인덱스가 적용되지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜? AB% 를 제외한 나머지 방식은 인덱스를 타지 않을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 인덱스의 자료구조는 대부분 B-TREE 구조로 이루어져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(B-TREE 은 노드의 자식 노드의 데이터들은 노드 데이터 기준으로 데이터보다 작은 값이 왼쪽부터 오른쪽으로 정렬되어 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 구조를 가지고 있다고 생각하고 &lt;code&gt;왜? % 위치에 따라 인덱스 조건이 타지 않는가?&lt;/code&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그럼 어떻게 검색해야 하는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 5.7 버전 이상부터 FullText Search(전문 검색) 방식을 사용하여 앞서 살펴본&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;% ABC%, % ABC 검색을 사용했을 때 인덱스를 사용하지 못한 부분을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;FullText Search&lt;/code&gt; 을 사용한다면 빠른 검색이 가능하다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 본문에서는 FullText Search 대해서 설명하지는 않겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추 후 FullText Search 사용방법과 어떻게 동작하는지에 대해 공부해서 포스팅하도록 하겠습니다.&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;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/index-btree-hash.html&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/index-btree-hash.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hoing.io/archives/16853&quot;&gt;https://hoing.io/archives/16853&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/2042269/how-to-speed-up-select-like-queries-in-mysql-on-multiple-columns&quot;&gt;https://stackoverflow.com/questions/2042269/how-to-speed-up-select-like-queries-in-mysql-on-multiple-columns&lt;/a&gt;&lt;/p&gt;</description>
      <category>나만의공부(이슈정리)</category>
      <author>jay Joon</author>
      <guid isPermaLink="true">https://k3068.tistory.com/106</guid>
      <comments>https://k3068.tistory.com/106#entry106comment</comments>
      <pubDate>Thu, 9 Sep 2021 22:57:43 +0900</pubDate>
    </item>
    <item>
      <title>나는 JPA 를 왜(알고) 사용하고 있을까?</title>
      <link>https://k3068.tistory.com/105</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;JPA(ORM) 기술은 왜 사용하는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;SQL 중심적인 개발을 벗어나게 해 준다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;기존 SQL 중심의 프로젝트는 &lt;b&gt;객체 중심의 코딩이 불가능했다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;   public static void main(String[] args) throws Exception {
        Class.forName(&quot;jdbc&quot;);
        Connection con = DriverManager.getConnection (&quot;DBURL&quot;,&quot;username&quot;, &quot;password&quot;);

        Statement stmt = con.createStatement();

        String query =&quot;query&quot; // name,age,address
        ResultSet rs = stmt.executeQuery(query);
        
        List&amp;lt;Person&amp;gt; persons = new ArrayList&amp;lt;&amp;gt;();
        while (rs.next()) {
          String name = rs.getString(&quot;name&quot;);
          int age = rs.getInt(&quot;age&quot;);
          String address = rs.getInt(&quot;address&quot;);
          persons.add(new Person(name,age,address));
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 예제 코드는 말 그대로 DB에 접근하여 자료를 읽어 가져오는 역할만 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 해당 프로젝트에서 name, age , address -&amp;gt; Person이라는 객체로 사용하기 위해서는 일일이 생성해줘야 한다.&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;하지만 예제 코드는 말 그대로 예제 코드일 뿐 현실은 그렇지 않다.&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;2. &lt;b&gt;객체와 관계형 데이터베이스의 패러다임 불일치를 해소해준다,&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상속&lt;/li&gt;
&lt;li&gt;연관관계 - &lt;b&gt;객체&lt;/b&gt;는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;참조를 사용&lt;/u&gt;&lt;/span&gt; , &lt;b&gt;테이블&lt;/b&gt;은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;외래 키를 사용&lt;/u&gt;&lt;/span&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;3. &lt;b&gt;객체 그래프 탐색&lt;/b&gt; Member -&amp;gt; Order -&amp;gt; OrderItem -&amp;gt; Item&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음 실행하는 SQL에 따라 탐색 가능 범위가 결정돼버림&lt;br /&gt;Member -&amp;gt; Order -&amp;gt; Item 인 경우에서 Member -&amp;gt; Order 까지만 로딩이 되었다 가정하고 나는 사용했지만 다른 사람이 보았을 때 Item을 접근할 수 있기 때문에 만약 접근한다면 NPE 가 발생한다.&lt;/li&gt;
&lt;li&gt;하지만 JPA 같은 경우 지연 로딩, 즉시 로딩을 설정할 수 있기 때문에 앞서 살펴본 오류를 방지할 수 있다.&lt;/li&gt;
&lt;li&gt;(단 N+1 문제든 예상치 못한 쿼리가 발생할 수 있다. 따라서 잘 알고 쓰자!)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JPA와 hibernate의 관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA를 사용하다 보면 hibernate 이야기는 항상 나오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 깊게 공부하지 않는다면 &lt;b&gt;hibernate와 JPA는 똑같다고 생각할 수 있다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 말은 100% 틀린 말은 아니지만 또한 100% 맞는 말도 아니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JPA는 단지 자바 진영의 표준 ORM 기술이다.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉 ORM의 기술을 사용하기 위한 표준 인터페이스를 제공한다는 뜻이다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;568&quot; width=&quot;569&quot; height=&quot;253&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9QgGG/btrb8gkdLKp/wZTdhlzsIkAMqFt4YRhkKk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9QgGG/btrb8gkdLKp/wZTdhlzsIkAMqFt4YRhkKk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9QgGG/btrb8gkdLKp/wZTdhlzsIkAMqFt4YRhkKk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9QgGG%2Fbtrb8gkdLKp%2FwZTdhlzsIkAMqFt4YRhkKk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;568&quot; width=&quot;569&quot; height=&quot;253&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;u&gt;인터페이스를 제공해준다는 뜻은&lt;/u&gt; &lt;b&gt;JPA를 사용한다면 하위 구현체로 hibernate 가 아닌 다른 하위 구현체로 변경할 수 있다는 뜻이다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;hibernate 이외에도 JPA 지원하는 벤더들도 많이 존재한다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;276&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3HhTF/btrcf7fh9rx/KMxNwEe54YC9VNkzz4gIS0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3HhTF/btrcf7fh9rx/KMxNwEe54YC9VNkzz4gIS0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3HhTF/btrcf7fh9rx/KMxNwEe54YC9VNkzz4gIS0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3HhTF%2Fbtrcf7fh9rx%2FKMxNwEe54YC9VNkzz4gIS0%2Fimg.jpg&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;276&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 하위 구현체로 hibernate 가 많이 사용될 뿐이지 JPA == hibernate 인건 아니다.&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;추 후 hibernate 보다 뛰어난 ORM 프로젝트가 나온다면 미래에는 얼마든지 교체될 수 있다.&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;하지만 우리가 진행하고 있는 프로젝트의 코드를 변경하지 않아도 새로운 구현체가 (ORM 프로젝트) JPA 인터페이스를 완벽하게 지원한다면 인터페이스의 하위 구현체만 변경하여 더 좋은 성능의 ORM 프로젝트를 사용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그렇다면 JPA는 단점이 없는가?&lt;/h2&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;h3 data-ke-size=&quot;size23&quot;&gt;편안함에서 오는 복잡함(무슨 소리야?  )&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제가 말하고자 하는 것은 &lt;u&gt;예상하지 못한 Query 구문, 발생 횟수뿐이&lt;/u&gt; 아닌&lt;/li&gt;
&lt;li&gt;&lt;u&gt;편리함을 추 구하기 위해 &lt;b&gt;과도하게 연관관계를 맺는 행위&lt;/b&gt;를 말하고자 합니다.&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체 그래프 탐색&lt;/b&gt;은 매우 매력적이고 개발을 함에 있어 매우 편리하여 양방향 연관관계를 맺어 사용하는 경우가 있습니다.&lt;/p&gt;
&lt;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;즉 CRUD에서 CUD 로직은 과도한 연관관계가 대부분 필요 없다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;209&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBrRSB/btrb8hpUAaD/HtBVwK0Me74SAvY5x5k8E0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBrRSB/btrb8hpUAaD/HtBVwK0Me74SAvY5x5k8E0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBrRSB/btrb8hpUAaD/HtBVwK0Me74SAvY5x5k8E0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBrRSB%2Fbtrb8hpUAaD%2FHtBVwK0Me74SAvY5x5k8E0%2Fimg.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;209&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제는 제가 프로젝트를 하면서 연관관계에 대해서 다시 생각해본 내용입니다.&lt;br /&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;Entity 간의 연관관계가 &lt;b&gt;꼭 필요한지 한 번쯤은 생각해보는 것도 좋을 듯합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;CRUD의 &lt;b&gt;Read(select)를&lt;/b&gt; 편하게 하기 위해서 맺는 경우가 있는지 한번 확인해보시면 좋을 것 같습니다.&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아직까지도 JPA를 사용하다 보면 N+1 문제&lt;/li&gt;
&lt;li&gt;영속성 콘텍스트 범위 관리를 하지 못하여 update 가 되지 않거나&lt;/li&gt;
&lt;li&gt;예상하지 못한 쿼리가 발생한다거나&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA를 주의하면서 쓴다고 해도 100% 해당 문제를 다시 안 만난다는 보장은 없습니다.&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;성능&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞서 말한 것처럼 간단한 Select Query는 JPA로 사용해도 괜찮지만&lt;/li&gt;
&lt;li&gt;복잡한 통계성 쿼리는 JPA의 JPQL을 이용하기보다는 Native query를 사용하거나 Mybatis를 이용하는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>why?</category>
      <author>jay Joon</author>
      <guid isPermaLink="true">https://k3068.tistory.com/105</guid>
      <comments>https://k3068.tistory.com/105#entry105comment</comments>
      <pubDate>Sun, 15 Aug 2021 00:15:11 +0900</pubDate>
    </item>
    <item>
      <title>나는 왜 JAVA 를 사용하게되었는가?</title>
      <link>https://k3068.tistory.com/104</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;해당 주제에 대한 답변으로 어떤 게 떠올르시나요?&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;자바가 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;u&gt;첫 언어&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;이기 때문에&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;언어는 중요하지 않고 개발에 먼저 익숙해지고 패러다임을 익히기 위해&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;혼자 공부하는 데에 있어 &lt;u&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;참고자료가 풍부&lt;/b&gt;&lt;/span&gt;하&lt;/u&gt;기 때문에&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;회사가 Java를 사용하기 때문에&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;막상 대답하려고 하면 막막합니다. 사실 위에 있는 답변이 저는 틀렸다고 생각하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사람마다 선택하는 기술을 선택하는 기준이 다르다고 생각하고 &lt;br /&gt;그 기준이 나쁘다고 생각하지도 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개발을 하다 보면 코드의 구조나 품질에 대해서는 많은 논의를 하고 배우고자 하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;단 한 번도 왜 자바를 쓰고 있는 건가?&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;즉 진행하고 있는 프로젝트가 꼭 자바로 구현하고자 하는 이유가 있는가?&lt;br /&gt;(다른 언어로는 구현하지 못하는가?)&lt;/i&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;Java 기반의 프레임워크를 익혀왔고 가장 친숙하고 자신 있고 아는 게 Java 뿐이라서&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 생각이 먼저 떠올르고 마음속 생각을 그대로 말할 수 없어 Java 언어의 장점을 생각하여 답변을 하기 위해 준비하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 답변으로는 &lt;b&gt;강력한 타입 체킹 언어, 플랫폼 독립적, GC를 통한 메모리 관리&lt;/b&gt;&amp;nbsp;등 &lt;u&gt;형식적인 답변만&lt;/u&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;u&gt;즉 원론적인 이유&lt;/u&gt;인 왜 해당 프로젝트에는 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;대부분 자바의 장점으로 알고 있는 형식적인 답변이 다른 언어에서는 지원하지 못하는가?&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;하지만 한 가지 느낀 점은 무의식적으로&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt; 익숙한 도구를 계속 사용해왔다는 점&lt;/u&gt;&lt;/span&gt;만 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국에는 조금 더 환경에 맞는 좋은 기술, 언어를 채택할 수 있음에도 불구하고&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;익숙하지 않은 환경을 피하고자 하는 마음이 먼저 들었다는 것&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;말로는 개발자로서 언어는 도구일 뿐이야 라고 말만 하고 행동은 전혀 그렇지 않았다는 것&lt;/b&gt;&lt;/li&gt;
&lt;/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;h3 data-ke-size=&quot;size23&quot;&gt;지금은 왜 사용하고 있는지 아는가?&lt;/h3&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왜 Spring 사용했는가?&lt;/li&gt;
&lt;li&gt;왜 JWT 기술은 왜 적용해보았는가?&lt;/li&gt;
&lt;li&gt;왜 QueryDSL을 사용해야 하는가?&lt;/li&gt;
&lt;li&gt;왜 ORM을 적용했는가?&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;&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;그래도 생각은 하고 사용했구나&lt;/b&gt;&lt;/span&gt; 정도가 저의 목표입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JAVA 언어를 왜 사용했는가? 에 대해 궁금해서 들어오셨다면 글을 읽고 도움이 전혀 되지 않았다는 것을 알고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 부분에서 사죄드립니다.  &amp;zwj;♂️&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;하지만 이 글을 읽으신 분들은 기술을 사용하는 것도 좋지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;가장 중요한것은 기술의 장단점을 파악하여 사용하는 것이라는 생각도 들었으면 좋겠습니다.&lt;/u&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>why?</category>
      <author>jay Joon</author>
      <guid isPermaLink="true">https://k3068.tistory.com/104</guid>
      <comments>https://k3068.tistory.com/104#entry104comment</comments>
      <pubDate>Wed, 11 Aug 2021 21:24:09 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] findById() , getById() 에 대한 생각</title>
      <link>https://k3068.tistory.com/103</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;code&gt;findById()&lt;/code&gt;, &lt;code&gt;getById()&lt;/code&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사전 설명&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beb7x0/btraBePl5aw/MzzSr0SalAVbYxM99QrqD1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beb7x0/btraBePl5aw/MzzSr0SalAVbYxM99QrqD1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beb7x0/btraBePl5aw/MzzSr0SalAVbYxM99QrqD1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbeb7x0%2FbtraBePl5aw%2FMzzSr0SalAVbYxM99QrqD1%2Fimg.jpg&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;632&quot; height=&quot;283&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 Entity 연관관계를 가지고 있을 때 저희는 일반적으로 게시판에 해당하는 댓글을 추가하기 위해서클라이언트 요청으로부터 &lt;code&gt;게시판의 Id, 댓글내용, 작성자&lt;/code&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;그러면 저희는 Serivce Layer에서 해당하는 게시판이 있는지 확인 후 Reply 객체를 생성해서 연관관계를 맺은 후 저장하게 됩니다.&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 class=&quot;reasonml&quot;&gt;&lt;code&gt;public void save(RequestReply requestReply) {

    // 해당하는 게시판이 존재하는가 ?
    Board board = boardRepository.findById(requestReply.getBoardId())
        .orElseThrow(EntityNotFoundException::new);

    // 댓글 생성
    Reply reply = Reply.builder()
        .content(requestReply.getContent())
        .board(board)
        .build();

    replyRepository.save(reply);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 로직 테스트 코드는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;댓글등록&quot;)
void replySave() {
    //given
    StudyBoard studyBoard = StudyBoard.builder()
        .title(&quot;토비의 스프링 스터디원 구해요&quot;)
        .studyName(&quot;초보를 위한 Spring Study&quot;)
        .description(&quot;이러한 이유로 모집합니다.&quot;)
        .place(&quot;서울&quot;)
        .recruitmentDeadline(LocalDateTime.now().plusDays(7))
        .recruiter(4)
        .createBy(&quot;KJJ&quot;)
        .build();
    StudyBoard study = boardRepository.save(studyBoard);


    RequestReply requestReply = new RequestReply(study.getId(), &quot;저요저요!&quot;);
    System.out.println(&quot;==================================================================&quot;);
    //when  save 호출시 쿼리 발생 갯수 확인
    replyService.save(requestReply);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;findById()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 살펴본 예제는 findById()를 통해 DB에 쿼리가 발생하여 객체를 찾게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GL7jn/btrZJVLZfcp/aOXo7CDkuI7rkjD56KD8g1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GL7jn/btrZJVLZfcp/aOXo7CDkuI7rkjD56KD8g1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GL7jn/btrZJVLZfcp/aOXo7CDkuI7rkjD56KD8g1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGL7jn%2FbtrZJVLZfcp%2FaOXo7CDkuI7rkjD56KD8g1%2Fimg.jpg&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;691&quot; height=&quot;39&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라 다음과 같이 총 2번의 쿼리가 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 드는 생각은 &lt;b&gt;왜?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Board 객체를 찾아야 하지?&lt;/li&gt;
&lt;li&gt;Id 값을 알고 있으니 Insert Query 하나만 발생해도 충분한 거 아닌가?&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;하지만 ORM 입장에서는 해당 ID 값 만으로는 어떤 타입의 객체인지 알 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 앞서 살펴본 것처럼 한번 찾고 Reply 객체를 생성하여 Board를 설정하는 과정이 발생하게 됩니다.&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;u&gt;이처럼 두 번 Query 가 발생하는 것이 무조건 나쁜 과정일까요?&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 질문은 아래에서 좀 더 다루겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;getById()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;getById()&lt;/code&gt; 메서드가 생소하신 분들도 있을 것 같은데요 Spring Data JPA 2.5.3 이후부터 getOne() 메서드가 Deprecated 되고추가된 메서드입니다 기존 getOne() 메서드와 하는 일은 동일합니다.&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;code&gt;findById()&lt;/code&gt; 로 Board를 찾는 대신 &lt;code&gt;getById()&lt;/code&gt; 를 통해 Board를 찾는다면 어떻게 될까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dR5xhE/btrav6R9qko/xSSncvQjvKQ9pT25OkKBw1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dR5xhE/btrav6R9qko/xSSncvQjvKQ9pT25OkKBw1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dR5xhE/btrav6R9qko/xSSncvQjvKQ9pT25OkKBw1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdR5xhE%2Fbtrav6R9qko%2FxSSncvQjvKQ9pT25OkKBw1%2Fimg.jpg&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;691&quot; height=&quot;48&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 하나의 Query 만 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 앞서 findById() 메서드를 사용했을 때 가진 생각을 해결할 수 있는데요!&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;code&gt;getById()&lt;/code&gt; 가 어떻게 작동하는지부터 알아보죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;25&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doBWks/btraBehyukk/E7wiB7LNxvx6anXAT0e8n1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doBWks/btraBehyukk/E7wiB7LNxvx6anXAT0e8n1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doBWks/btraBehyukk/E7wiB7LNxvx6anXAT0e8n1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoBWks%2FbtraBehyukk%2FE7wiB7LNxvx6anXAT0e8n1%2Fimg.jpg&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;353&quot; height=&quot;25&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;25&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;getById()&lt;/code&gt; 는 지연 로딩으로 작동하게 되는데요! ID 값을 제외한 나머지 필드에 접근했을 때 Query가 발생하게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public void save(RequestReply requestReply) {
    Board board = boardRepository.getById(requestReply.getBoardId());

    System.out.println(&quot;ID 값 :&quot;+ board.getId()); // 이때는 Query 가 발생하지않음
    System.out.println(&quot;=============================================&quot;);
    System.out.println(&quot;Title 값: &quot;+ board.getTitle()); //이때는 정보가 필요하기때문에 Query 가 발생함

    // 댓글 생성
    Reply reply = Reply.builder()
        .content(requestReply.getContent())
        .board(board)
        .build();

    replyRepository.save(reply);
}&lt;/code&gt;&lt;/pre&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-origin-width=&quot;706&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1HEOj/btraBePmV3A/e6YTtTSxIJEJHPXKzhCUsk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1HEOj/btraBePmV3A/e6YTtTSxIJEJHPXKzhCUsk/img.jpg&quot; data-alt=&quot;결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1HEOj/btraBePmV3A/e6YTtTSxIJEJHPXKzhCUsk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1HEOj%2FbtraBePmV3A%2Fe6YTtTSxIJEJHPXKzhCUsk%2Fimg.jpg&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;706&quot; height=&quot;114&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &lt;b&gt;어떠한 객체의 ID 값이 DB에 반드시 존재하고 &lt;span style=&quot;color: #ee2323;&quot;&gt;ID를 제외한 다른 필드에 접근하지 않을 때&lt;/span&gt; 사용하게 되면 좋을 것 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 생각 &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;findById()&lt;/code&gt;, &lt;code&gt;getById()&lt;/code&gt; 의 차이점은 &lt;u&gt;즉시 로딩, 지연 로딩으로 가져오는가?&lt;/u&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;왜 getOne() 메서드가 Deprecated 되고 getById()로 이름만 변경되었을까요?&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;code&gt;get&lt;/code&gt; 은 무엇을 &lt;span style=&quot;color: #ee2323;&quot;&gt;받다, 얻다&lt;/span&gt; 라는 의미가 있습니다. &lt;br /&gt;&lt;u&gt;&lt;b&gt;즉 어떠한 객체를 무조건 받는다는 가정이 있습니다&lt;/b&gt;.&lt;/u&gt; (null을 return 하면 안 된다.)&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;code&gt;find&lt;/code&gt; 은 &lt;span style=&quot;color: #ee2323;&quot;&gt;찾다&lt;/span&gt; 라는 의미를 가지고 있습니다. &lt;br /&gt;&lt;b&gt;&lt;u&gt;즉 객체를 찾을 수 있고 없을 수 도 있다는 가정이 있습니다.&lt;/u&gt; &lt;/b&gt;(Optional로 return 하여 선택권을 제공)&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;따라서 &lt;code&gt;이처럼 두 번 Query 가 발생하는 것이 무조건 나쁜 과정일까요?&lt;/code&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;비즈니스상 클라이언트로부터 들어온 ID 값이(게시글이) 어떠한 경우에도 항상 존재하는가?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Yes 인 경우에는 getById()로 메서드로 사용해도 문제가 발생하지 않습니다. (없다면 예외가 발생합니다.)&lt;/li&gt;
&lt;li&gt;NO 인 경우에는 findById()를 사용해서 존재 여부를 확인해야 하겠죠?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&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;</description>
      <category>Spring/JPA</category>
      <author>jay Joon</author>
      <guid isPermaLink="true">https://k3068.tistory.com/103</guid>
      <comments>https://k3068.tistory.com/103#entry103comment</comments>
      <pubDate>Tue, 27 Jul 2021 16:54:30 +0900</pubDate>
    </item>
    <item>
      <title>[Spring-boot] Master - Slave 구조에 따른 Read, Write  분기</title>
      <link>https://k3068.tistory.com/102</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스를 이용한다면 대부분 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;쓰기보다&lt;/b&gt; &lt;b&gt;읽기&lt;/b&gt;&lt;/span&gt; 의 행위가 더 많습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;296&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tr78z/btq96EtX4AQ/ZjOT5sSMZTI2dYSkJXb1U1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tr78z/btq96EtX4AQ/ZjOT5sSMZTI2dYSkJXb1U1/img.jpg&quot; data-alt=&quot;출처 : 오라클&amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tr78z/btq96EtX4AQ/ZjOT5sSMZTI2dYSkJXb1U1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftr78z%2Fbtq96EtX4AQ%2FZjOT5sSMZTI2dYSkJXb1U1%2Fimg.jpg&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;296&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : 오라클&amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB의 부하를 줄이기 위해 다음과 같이 &lt;span style=&quot;color: #006dd7;&quot;&gt;Master - Slave &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;이러한 구조를 가지고 있을 때 Transection의 속성이 &lt;code&gt;readOnly = true&lt;/code&gt; 인 경우&lt;br /&gt;&lt;u&gt;Slave&lt;/u&gt; 데이터베이스에 Select query 가 발생하게 해야합니다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;@Transactional(readOnly = true)&lt;/code&gt; 인 경우는 Slave DB 접근&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Transactional(readOnly = false)&lt;/code&gt; 인 경우에는 Master DB 접근&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 조건을 만족하기 위한 방법을 Spring-boot 기준으로 소개하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사전 환경설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 환경은 다른 것은 필요 없고 저는 MySQL 데이터베이스를 이용하였고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master - Slave 구조가 준비되었다고 가정합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;204&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BBjYq/btq91NrTsKi/DoldF37FXwZK5fxLZxtqD0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BBjYq/btq91NrTsKi/DoldF37FXwZK5fxLZxtqD0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BBjYq/btq91NrTsKi/DoldF37FXwZK5fxLZxtqD0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBBjYq%2Fbtq91NrTsKi%2FDoldF37FXwZK5fxLZxtqD0%2Fimg.jpg&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;204&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Docker를 이용하였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Master DB : 3306 PORT&lt;/li&gt;
&lt;li&gt;Slave DB - 3307 PORT&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저와 같은 환경을 설정하고 싶으신 분 &lt;a href=&quot;https://github.com/KJJ924/spring-data-jpa/tree/master/docker&quot;&gt;Github를&lt;/a&gt; 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;본문&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;application.yml 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 DB에 접근하기 위해 &lt;code&gt;application.yml&lt;/code&gt; 에 접근 정보를 정의해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;spring:
  datasource:
    master:
      hikari: 
        username: root
        password: password
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/examreplication
    slave:
      hikari:
        username: root
        password: password
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3307/examreplication&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Master, Slave DB를 2 개를 사용해야 하니 Datasource를 직접 생성해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 해당 접근 정보들을 이용하여 &lt;code&gt;DataSource&lt;/code&gt;을 만들어야 하니 정확히 입력해주세요!&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;DataSource Bean 등록하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 &lt;code&gt;readOnly&lt;/code&gt;속성 별로 분기를 하기 전에 앞서 설정한 정보로 DataSource를 Bean으로 등록하는 과정입니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
public class DataSourceConfiguration {

    public static final String MASTER_DATASOURCE = &quot;masterDataSource&quot;;
    public static final String SLAVE_DATASOURCE = &quot;slaveDataSource&quot;;

    @Bean(MASTER_DATASOURCE) 
    @ConfigurationProperties(prefix = &quot;spring.datasource.master.hikari&quot;) // (1)
    public DataSource masterDataSource() {
        return DataSourceBuilder.create() 
            .type(HikariDataSource.class) 
            .build();
    }

    @Bean(SLAVE_DATASOURCE)
    @ConfigurationProperties(prefix = &quot;spring.datasource.slave.hikari&quot;)
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create()
            .type(HikariDataSource.class)
            .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;code&gt;spring.datasource.master.hikari&lt;/code&gt; 에 해당하는 property를 DataSource를 생성하는데 이용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 1번 과정이 잘 이해가 안 되신다면 &lt;a href=&quot;https://www.baeldung.com/configuration-properties-in-spring-boot#bean&quot;&gt;해당 자료&lt;/a&gt;를 참고해주세요.&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;AbstractRoutingDataSource 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AbstractRoutingDataSource.class&lt;/code&gt; 는 조회 key 기반으로 등록된 Datasource 중 하나를 호출을 하게 해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말이 거창하긴 한데 소스코드를 보면 매우 간단합니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() { // (1)
        return (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) ? &quot;slave&quot; : &quot;master&quot;; //(2)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;code&gt;determineCurrentLookupKey()&lt;/code&gt; 메서드는 현재 조회 키를 반환받기 위해 구현해야 하는 추상 메서드입니다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;따라서 저희는 readOnly 속성을 구별하여 key를 반환하게 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AbstractRoutingDataSource Bean 등록하기&lt;/h3&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;@Bean
@Primary
@DependsOn({MASTER_DATASOURCE, SLAVE_DATASOURCE})
public DataSource routingDataSource(
    @Qualifier(MASTER_DATASOURCE) DataSource masterDataSource,
    @Qualifier(SLAVE_DATASOURCE) DataSource slaveDataSource) {

    RoutingDataSource routingDataSource = new RoutingDataSource();

    Map&amp;lt;Object, Object&amp;gt; datasourceMap = new HashMap&amp;lt;&amp;gt;() {
        {
            put(&quot;master&quot;, masterDataSource);
            put(&quot;slave&quot;, slaveDataSource);
        }
    };

    routingDataSource.setTargetDataSources(datasourceMap);
    routingDataSource.setDefaultTargetDataSource(masterDataSource);

    return routingDataSource;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;routingDataSource Bean으로 생성하여&lt;s&gt; @Primary를 선언하여 DataSource 타입에 바인딩되는 객체로 정의합니다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;꼭! 궁금한 부분까지 읽어주세요 ! 아직 설정해야하는 부분이있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과 확인&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
@Transactional
public class BoardService {

    private final BoardRepository boardRepository;
    private final DataSource dataSource;

    @Transactional(readOnly = true)
    public List&amp;lt;Board&amp;gt; getBoardList(){
        return boardRepository.findAll();
    }

    public List&amp;lt;Board&amp;gt; updateTitle() {
        List&amp;lt;Board&amp;gt; boards = boardRepository.findAll();
        for (Board board : boards) {
            board.setTitle(&quot;newTitle&quot;);
        }
        return getBoardList();
    }
}&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;462&quot; width=&quot;746&quot; height=&quot;228&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/odw7t/btrackuePjR/l5HdzAFjvoeigXC6zSbqpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/odw7t/btrackuePjR/l5HdzAFjvoeigXC6zSbqpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/odw7t/btrackuePjR/l5HdzAFjvoeigXC6zSbqpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fodw7t%2FbtrackuePjR%2Fl5HdzAFjvoeigXC6zSbqpK%2Fimg.png&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;462&quot; width=&quot;746&quot; height=&quot;228&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;getBoardList()&lt;/code&gt; 는 &lt;code&gt;readOnly = ture&lt;/code&gt; 임으로 -&amp;gt; jdbc:mysql://localhost:3307 으로&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;530&quot; width=&quot;639&quot; height=&quot;327&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H3JJs/btq93nzIvXu/83dTb56ebMdDODqMWHlTrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H3JJs/btq93nzIvXu/83dTb56ebMdDODqMWHlTrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H3JJs/btq93nzIvXu/83dTb56ebMdDODqMWHlTrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH3JJs%2Fbtq93nzIvXu%2F83dTb56ebMdDODqMWHlTrk%2Fimg.png&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;530&quot; width=&quot;639&quot; height=&quot;327&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;updateTitle()&lt;/code&gt; 는 &lt;code&gt;readOnly =false&lt;/code&gt; 임으로 -&amp;gt; jdbc:mysql://localhost:3306 으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 여기서 중요한 점은 method의 시작 트랜잭션이 읽기 전용이 아님으로 내부에서 &lt;code&gt;getBoardList()&lt;/code&gt; 을 호출하였을 때 Slave DB에 쿼리가 발생하는 것이 아닌 Master DB로 query 가 발생합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;궁금한 점 ??!!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 주제를 공부하면서 많은 예제와 블로그를 찾아본 결과&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Bean
@DependsOn(&quot;routingDataSource&quot;)
public LazyConnectionDataSourceProxy dataSource(DataSource routingDataSource){
    return new LazyConnectionDataSourceProxy(routingDataSource);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 예제처럼 AbstractRoutingDataSource를 &lt;br /&gt;LazyConnectionDataSourceProxy로 한번 감싸는 예제들이 많았다.&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;a href=&quot;http://kwon37xi.egloos.com/m/5364167&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;link&lt;/a&gt;)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;TransactionManager 선별 -&amp;gt; DataSource에서 Connection 획득 -&amp;gt; Transaction 동기화(Synchronization)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여기서 보면 트랜잭션 동기화를 마친 뒤에 ReplicationRoutingDataSource.java에서 커넥션을 획득해야만 &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이게 올바로 동작하는데 그 순서가 뒤바뀌어 있기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;따라서 LazyConnectionDataSourceProxy 한번 감싸서 다음과 같이 작동하게한다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;TransactionManager -&amp;gt; LazyConnectionDataSourceProxy에서 Connection Proxy 객체 획득 -&amp;gt; Transaction 동기화&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제 쿼리 호출시에 ReplicationRoutingDataSource.getConnection()/determineCurrentLookupKey() 호출&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;잘못된 테스트  &lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;그래서 정말 그런가 싶어서 LazyConnectionDataSourceProxy 감싸지 않고 &lt;/s&gt;&lt;br /&gt;&lt;s&gt;AbstractRoutingDataSource 을 빈으로 등록 사용하였는데&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;&lt;code&gt;readOnly = ture/false&lt;/code&gt; 속성에 따라 Master / Slave DB에 원하는 대로 잘 쿼리가 분배가 된다.&lt;/s&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;추 후 LazyConnectionDataSourceProxy에 대해 파악하여 본문에 추가하도록 하겠습니다.&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;LazyConnectionDataSourceProxy 으로 한번 감싸지 않고 사용할시 앞서 설명하고 있는것처럼&amp;nbsp; &lt;br /&gt;AbstractRoutingDataSource 로 사용할시 모든 쿼리가 Master DB 로 가는 현상이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;u&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Transaction 동기화 &lt;/span&gt;시점에 커넥션을 획득하기 위해&lt;/u&gt; LazyConnectionDataSourceProxy 로 한번감싸주어야 한다.&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;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oracle.com/technetwork/community/developer-day/mysql-replication-scalability-403030.pdf&quot;&gt;https://www.oracle.com/technetwork/community/developer-day/mysql-replication-scalability-403030.pdf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cheese10yun.github.io/spring-transaction/&quot;&gt;https://cheese10yun.github.io/spring-transaction/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://kwon37xi.egloos.com/m/5364167&quot;&gt;http://kwon37xi.egloos.com/m/5364167&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/boot</category>
      <author>jay Joon</author>
      <guid isPermaLink="true">https://k3068.tistory.com/102</guid>
      <comments>https://k3068.tistory.com/102#entry102comment</comments>
      <pubDate>Wed, 21 Jul 2021 00:39:28 +0900</pubDate>
    </item>
    <item>
      <title>[Spring boot] 여러 필드를 검사하기위한 Custom Valid Annotation 만들기</title>
      <link>https://k3068.tistory.com/101</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 컨트롤러에서 클라이언트로부터 받은 값을 검증하기 위해 Validation을 많이 사용하곤 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSR 380이 제공해주는 &lt;a href=&quot;https://www.baeldung.com/javax-validation&quot;&gt;Bean Validation 의 종류&lt;/a&gt;는 다양하지만 각자의 서비스에 따라 &lt;br /&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;따라서 상황에 따라 각 서비스에 맞는 커스텀한 Validation을 만들어서 활용하기도 합니다.&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;그렇다면 이번 글에서는 왜 커스텀 Validation을 만들었고 만드는 과정을 소개하고자 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;p data-ke-size=&quot;size16&quot;&gt;기존 &lt;code&gt;@NotNull, @NotEmpty, @NotBlank, @Email&lt;/code&gt; 등의 애노테이션으로 클라이언트 요청으로 값을 &lt;br /&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;&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;&lt;code&gt;예약시간&lt;/code&gt; , &lt;code&gt;예약종료시간&lt;/code&gt; 이라는 필드가 존재할 때 기본적으로 예약시간이 예약 종료시간보다 늦을 수는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 &lt;code&gt;예약시간&lt;/code&gt; , &lt;code&gt;예약종료시간&lt;/code&gt; 이 알맞게 들어왔는지 확인하기 위해 다음과 같은 코드를 작성하게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;if (reservationStart.isAfter(reservationEnd)){
  //TODO Exception
} &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;예약을 저장, 예약시간을 변경, 선택 시간 범위 내 존재하는 예약 리스트 반환 등등 클라이언트로 요청이 넘어온 &lt;code&gt;예약시간&lt;/code&gt; , &lt;code&gt;예약종료시간&lt;/code&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;단순히 입력값 자체를 비교하는 과정은 조금 더 외부 Layer에서 처리하는 것이 좋을 것 같습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&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;h2 data-ke-size=&quot;size26&quot;&gt;Custom Valid Annotation 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트로 받을 DTO는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@StartAndEndTimeCheck(startDate = &quot;reservationStart&quot;, endDate = &quot;reservationEnd&quot;) //해당 애노테이션을 만들고자합니다.
public class RequestOrder {

    @NotBlank
    @ApiModelProperty(value = &quot;메뉴이름&quot;, required = true, example = &quot;다운펌&quot;)
    private String menuName;

    @NotBlank
    @ApiModelProperty(value = &quot;회원 전화번호&quot;, required = true, example = &quot;01012345678&quot;)
    private String memberPhoneNumber;

    @NotBlank
    @ApiModelProperty(value = &quot;디자이너 이름&quot;, required = true, example = &quot;디자이너&quot;)
    private String designerName;

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @NotNull
    @ApiModelProperty(value = &quot;예약 시작시간&quot;, required = true, example = &quot;2021-06-16T11:40&quot;)
    private LocalDateTime reservationStart; // 필드 비교 대상

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @NotNull
    @ApiModelProperty(value = &quot;예약 끝나는시간&quot;, required = true, example = &quot;2021-06-16T12:40&quot;)
    private LocalDateTime reservationEnd; // // 필드 비교 대상

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희의 목적은 &lt;code&gt;@Valid&lt;/code&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;h3 data-ke-size=&quot;size23&quot;&gt;@StartAndEndTimeCheck 만들기&lt;/h3&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StartAndEndTimeCheckValidator.class)
public @interface StartAndEndTimeCheck {

    String message() default &quot;에약시작 시간이 예약종료 시간 보다 늦을수 없습니다.&quot;;  // 예외가 발생하면 출력할 메세지

    Class&amp;lt;?&amp;gt;[] groups() default {};

    Class&amp;lt;? extends Payload&amp;gt;[] payload() default {};

    String startDate(); // 대상 객체의 시작시간 필드 이름을 담을 그릇

    String endDate(); // 대상 객체의 종료시간 필드 이름을 담을 그릇
}&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;이제 커스텀 애노테이션을 만들었으니 작동할 validator를 생성하면 끝입니다.&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;StartAndEndTimeCheckValidator&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class StartAndEndTimeCheckValidator implements ConstraintValidator&amp;lt;StartAndEndTimeCheck, Object&amp;gt; {
    private String message;
    private String startDate;
    private String endDate;

    @Override
    public void initialize(StartAndEndTimeCheck constraintAnnotation) {
        message = constraintAnnotation.message();  // 애노테이션에 저장된 메세지
        startDate = constraintAnnotation.startDate(); // 애노테이션에 저장된 비교할 필드
        endDate = constraintAnnotation.endDate(); // 애노테이션에 저장된 비교할 필드
    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext context) {

        int invalidCount = 0;
        LocalDateTime reservationStart = getFieldValue(o, startDate);
        LocalDateTime reservationEnd = getFieldValue(o, endDate);
        if (reservationStart.isAfter(reservationEnd)) { // 검증 후 오류가 있다면
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(message)//context에 오류메세지와 
                .addPropertyNode(startDate)//대상 필드를 넣어줍니다.
                .addConstraintViolation();
            invalidCount += 1;
        }
        return invalidCount == 0;
    }
	
  	// 리플렉션을 이용하여 필드를 가져옴 
    private LocalDateTime getFieldValue(Object object, String fieldName) {
        Class&amp;lt;?&amp;gt; clazz = object.getClass();
        Field dateField;
        try {
            dateField = clazz.getDeclaredField(fieldName);
            dateField.setAccessible(true);
            Object target = dateField.get(object);
            if (!(target instanceof LocalDateTime)) {
                throw new ClassCastException(&quot;casting exception&quot;);
            }
            return (LocalDateTime) target;
        } catch (NoSuchFieldException e) {
            log.error(&quot;NoSuchFieldException&quot;, e);
        } catch (IllegalAccessException e) {
            log.error(&quot;IllegalAccessException&quot;, e);
        }
        throw new ServerErrorException(&quot;Not Found Field&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ConstraintValidator&amp;lt;StartAndEndTimeCheck, Object&amp;gt;&lt;/code&gt; 여기서 왜 Object 형태를 사용하는가?&lt;br /&gt;해당 애노테이션을 어떠한 &lt;u&gt;특정 DTO에 종속되게 사용하지 않고 범용적으로 재활용하기 위해서 Object 형태로 받았습니다.&lt;/u&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;따라서 Object 형태로는 필드의 값을 가져오지 못해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@StartAndEndTimeCheck(startDate = &quot;reservationStart&quot;, endDate = &quot;reservationEnd&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플랙션을 이용하여 특정 필드의 값을 가져올 수 있도록 하기 위해서 위처럼 애노테이션으로 대상 필드 Name을 받았습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;p data-ke-size=&quot;size16&quot;&gt;사용법은 간단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 커스텀 애노테이션의 값에 대상 필드를 작성합니다&lt;br /&gt;(Type safe 하지 않다는 단점이 존재하네요 필드 철자가 틀리면 오류가 발생합니다. )&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@StartAndEndTimeCheck(startDate = &quot;reservationStart&quot;, endDate = &quot;reservationEnd&quot;)
public class RequestOrder {
  //생략
    private LocalDateTime reservationStart;
    private LocalDateTime reservationEnd;

}

@StartAndEndTimeCheck(startDate = &quot;reservationStart&quot;, endDate = &quot;reservationEnd&quot;)
public class RequestOrderTimeEdit {
    private Long id;
    private LocalDateTime reservationStart;
    private LocalDateTime reservationEnd;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러에 @Valid를 붙여 검증을 하시면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@PostMapping(&quot;/order&quot;)
@ApiOperation(value = &quot;예약 추가&quot;, notes = &quot;예약 일정을 생성합니다.&quot;)
//@Valid 애노테이션으로 검증
public ResponseEntity&amp;lt;ResponseOrder&amp;gt; createOrder(@RequestBody @Valid RequestOrder requestOrder) { 
    ResponseOrder order = orderService.saveOrder(requestOrder);
    return ResponseEntity.ok(order);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오류 발생 시 응답 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;Request Post /order
{
  &quot;designerName&quot;: &quot;디자이너&quot;,
  &quot;memberPhoneNumber&quot;: &quot;01012345678&quot;,
  &quot;menuName&quot;: &quot;다운펌&quot;,
  &quot;reservationEnd&quot;: &quot;2021-06-16T12:40&quot;,  
  &quot;reservationStart&quot;: &quot;2021-06-16T13:40&quot;  // 현재 시작시간이 종료시간보다 늦습니다  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckESae/btq8aauGNJW/bEZMUdeHnX4E3XTk4Ithuk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckESae/btq8aauGNJW/bEZMUdeHnX4E3XTk4Ithuk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckESae/btq8aauGNJW/bEZMUdeHnX4E3XTk4Ithuk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckESae%2Fbtq8aauGNJW%2FbEZMUdeHnX4E3XTk4Ithuk%2Fimg.jpg&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;709&quot; height=&quot;212&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 오류 응답처럼 내보내기 위해 저는 GlobalExceptionController를 만들어서 처리하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KJJ924/hair_shop_managing_system/tree/master/src/main/java/hair_shop/demo/error&quot;&gt;프로젝트&lt;/a&gt;를 확인해주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 요청한 내부의 단순 값과 값의 비교에 따라 비즈니스 로직 처리가 필요한 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service Layer에서 일일이 검증하는 것보다는 Custom Valid Annotation을 이용해 보는 것도 좋을 것 같습니다.&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;/p&gt;</description>
      <category>Spring/boot</category>
      <author>jay Joon</author>
      <guid isPermaLink="true">https://k3068.tistory.com/101</guid>
      <comments>https://k3068.tistory.com/101#entry101comment</comments>
      <pubDate>Fri, 25 Jun 2021 20:33:25 +0900</pubDate>
    </item>
  </channel>
</rss>