<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코딩하는 대학생에서 개발자까지</title>
    <link>https://solution-is-here.tistory.com/</link>
    <description>Java Developer, Open Source Enthusiast, Proud Son</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 22:11:18 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코딩하는_대학생</managingEditor>
    <image>
      <title>코딩하는 대학생에서 개발자까지</title>
      <url>https://tistory1.daumcdn.net/tistory/4973252/attach/6837332e98724bc385b24642f628345b</url>
      <link>https://solution-is-here.tistory.com</link>
    </image>
    <item>
      <title>Streamlining Maven POM Management in Gradle: Introducing kotlin-pom-gradle Plugin</title>
      <link>https://solution-is-here.tistory.com/239</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Managing Maven POM metadata in Gradle-based projects, especially in multi-module setups, can quickly become a complex and error-prone task. During my recent Google Summer of Code project, I developed a solution to address these challenges: the &lt;b&gt;kotlin-pom-gradle&lt;/b&gt; plugin.&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote id=&quot;the-problem&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;The Problem&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Many developers working with Gradle publishing face recurring issues:&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;Repetitive POM configuration&lt;/b&gt; across multiple modules&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Inconsistent metadata&lt;/b&gt; between parent and child projects&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Manual artifact signature verification&lt;/b&gt; processes&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Lack of automated POM validation&lt;/b&gt; before publishing&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;These pain points often lead to deployment failures, inconsistent artifacts, and significant maintenance overhead in large codebases.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;enter-kotlin-pom-gradle&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Enter kotlin-pom-gradle&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The kotlin-pom-gradle plugin tackles these challenges head-on with three core capabilities:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. Hierarchical POM Management&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Define your POM configuration once in the root project and automatically inherit it across all submodules. This approach ensures consistency while dramatically reducing boilerplate configuration. Child projects can override specific properties when needed, maintaining flexibility without sacrificing standardization.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. Automated Artifact Signature Verification&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Built-in signature verification ensures the integrity of your published artifacts. The plugin validates signatures during the build process, catching potential issues before they reach your artifact repository.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. Comprehensive POM Validation&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Before publishing, the plugin performs thorough validation of your POM files, checking for required fields, proper formatting, and Maven Central compliance requirements. This proactive approach prevents common publishing failures and ensures your artifacts meet repository standards.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;getting-started&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Getting Started&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The plugin is available in two variants on the Gradle Plugin Portal:&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;Core hierarchical POM management&lt;/b&gt;: io.github.yonggoose.kotlin-pom-gradle-project&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Full feature set with signing &amp;amp; validation&lt;/b&gt;: io.github.yonggoose.kotlin-pom-gradle-artifact-check-project&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Installation is straightforward using the plugins DSL:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1755657247771&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins { id(&quot;io.github.yonggoose.kotlin-pom-gradle-project&quot;) version &quot;0.1.6&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Detailed usage instructions, including a comprehensive demo video, are available in the &lt;a href=&quot;https://github.com/YongGoose/kotlin-pom-gradle&quot;&gt;project README&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;current-status-and-future-vision&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Current Status and Future Vision&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Version 0.1.6 represents a solid foundation, but this is just the beginning. The plugin architecture is designed for extensibility, with plans for additional validation rules, enhanced integration with popular Gradle plugins, and expanded customization options based on community feedback.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;community-engagement&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Community Engagement&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;As with any open-source project, community input is invaluable. Whether you're managing a simple multi-module project or a complex enterprise build system, I encourage you to try the plugin and share your experience. Your feedback will directly shape future development priorities and help identify new use cases.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The project welcomes contributions of all kinds&amp;mdash;from bug reports and feature requests to documentation improvements and code contributions. Visit the &lt;a href=&quot;https://github.com/YongGoose/kotlin-pom-gradle&quot;&gt;GitHub repository&lt;/a&gt; to get started.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;The kotlin-pom-gradle plugin represents the practical application of lessons learned from real-world Gradle publishing challenges. By automating repetitive tasks and providing built-in validation, it aims to make Maven artifact publishing more reliable and developer-friendly.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend</category>
      <author>코딩하는_대학생</author>
      <guid isPermaLink="true">https://solution-is-here.tistory.com/239</guid>
      <comments>https://solution-is-here.tistory.com/239#entry239comment</comments>
      <pubDate>Wed, 20 Aug 2025 11:37:33 +0900</pubDate>
    </item>
    <item>
      <title>Spring 관점에서 보는 Seata의 내부 통신</title>
      <link>https://solution-is-here.tistory.com/236</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글을 이해하기 위해선 이전 글을 보고 오셔야 됩니다. (특히 TC, TM, RM의 관계에 대해 인지하고 계셔야 합니다)&lt;br /&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;a style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; href=&quot;https://solution-is-here.tistory.com/235&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://solution-is-here.tistory.com/235&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1746704713228&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Apache Seata란?&quot; data-og-description=&quot;1. Seata란?Apache Seata는 마이크로서비스 아키텍처에서 고성능과 사용 편의성을 제공하는 분산 트랜잭션 프레임워크입니다. 알리바바에 의해 시작되었으며, 2023년에 Apache 재단에 기부되었습니다. S&quot; data-og-host=&quot;solution-is-here.tistory.com&quot; data-og-source-url=&quot;https://solution-is-here.tistory.com/235&quot; data-og-url=&quot;https://solution-is-here.tistory.com/235&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ciKKW4/hyYRutDRin/VM6b1EUTHMTiOI0fmadS5k/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/u4oys/hyYM4wCTes/DDrHSz3it1RVuFc2pEfflk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bLppyy/hyYRlwHpq2/TjJ2gHEMyzjqKZ49cfPGU0/img.png?width=1280&amp;amp;height=889&amp;amp;face=0_0_1280_889&quot;&gt;&lt;a href=&quot;https://solution-is-here.tistory.com/235&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://solution-is-here.tistory.com/235&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ciKKW4/hyYRutDRin/VM6b1EUTHMTiOI0fmadS5k/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/u4oys/hyYM4wCTes/DDrHSz3it1RVuFc2pEfflk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bLppyy/hyYRlwHpq2/TjJ2gHEMyzjqKZ49cfPGU0/img.png?width=1280&amp;amp;height=889&amp;amp;face=0_0_1280_889');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Apache Seata란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. Seata란?Apache Seata는 마이크로서비스 아키텍처에서 고성능과 사용 편의성을 제공하는 분산 트랜잭션 프레임워크입니다. 알리바바에 의해 시작되었으며, 2023년에 Apache 재단에 기부되었습니다. S&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;solution-is-here.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Seata를 실행했을 때 발생하는 로그를 통해 어떻게 TC와 TM, RM이 연결되는지 알아보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 로그에서 필요한 정보를 제외한 나머지 정보는 생략했습니다. (시간, 프로젝트 이름)&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;초기화 과정 분석&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Seata 자동 구성&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1746707694509&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;o.a.s.s.b.a.SeataAutoConfiguration : Automatically configure Seata&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 애플리케이션이 시작될 때 SeataAutoConfiguration이 자동으로 Seata를 구성합니다. 이 과정에서 Seata의 핵심 구성 요소가 초기화됩니다.&lt;/p&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;@DependsOn&lt;/b&gt; 어노테이션을 통해 &lt;b&gt;SpringApplicationContextProvider&lt;/b&gt;와 &lt;b&gt;failureHandler&lt;/b&gt;를 초기화한 후, 빈을 생성하는 것을 볼 수 있습니다. (seata가 &lt;b&gt;springContext&lt;/b&gt;에 접근을 해야되기 때문에 SpringApplicationContextProvider 빈이 초기화 된 뒤, 빈이 생성되어야 합니다)&lt;/p&gt;
&lt;pre id=&quot;code_1746708346784&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
@DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})
@ConditionalOnMissingBean(GlobalTransactionScanner.class)
public static GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, FailureHandler failureHandler,
        ConfigurableListableBeanFactory beanFactory,
        @Autowired(required = false) List&amp;lt;ScannerChecker&amp;gt; scannerCheckers) {
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info(&quot;Automatically configure Seata&quot;);
    }

    // set bean factory
    GlobalTransactionScanner.setBeanFactory(beanFactory);

    // add checkers
    // '/META-INF/services/org.apache.seata.spring.annotation.ScannerChecker'
    GlobalTransactionScanner.addScannerCheckers(EnhancedServiceLoader.loadAll(ScannerChecker.class));
    // spring beans
    GlobalTransactionScanner.addScannerCheckers(scannerCheckers);

    // add scannable packages
    GlobalTransactionScanner.addScannablePackages(seataProperties.getScanPackages());
    // add excludeBeanNames
    GlobalTransactionScanner.addScannerExcludeBeanNames(seataProperties.getExcludesForScanning());
    //set accessKey and secretKey
    GlobalTransactionScanner.setAccessKey(seataProperties.getAccessKey());
    GlobalTransactionScanner.setSecretKey(seataProperties.getSecretKey());
    // create global transaction scanner
    return new GlobalTransactionScanner(seataProperties.getApplicationId(), seataProperties.getTxServiceGroup(),
            seataProperties.isExposeProxy(), failureHandler);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 글로벌 트랜잭션 클라이언트 초기화&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1746707733208&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;o.a.s.s.a.GlobalTransactionScanner : Initializing Global Transaction Clients ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 단계에서 bean으로 생성된 &lt;b&gt;GlobalTransactionScanner&lt;/b&gt;가 글로벌 트랜잭션 클라이언트(&lt;b&gt;TM&lt;/b&gt;과 &lt;b&gt;RM&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;GlobalTransactionScanner&lt;/b&gt;은 &lt;b&gt;InitializingBean&lt;/b&gt;을 구현한 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;InitializingBean &lt;/b&gt;인터페이스에는&amp;nbsp;&lt;b&gt;afterPropertiesSet&lt;/b&gt; 메서드가 있는데, 해당 메서드는 &lt;b&gt;의존성 주입&lt;/b&gt;이 끝난 뒤, 호출되는 특징을 가진 메서드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746709116092&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GlobalTransactionScanner extends AbstractAutoProxyCreator
        implements CachedConfigurationChangeListener, InitializingBean, ApplicationContextAware, DisposableBean {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GlobalTransactionScanner&lt;/b&gt;에서 &lt;b&gt;afterPropertiesSet&lt;/b&gt;을 &lt;b&gt;Override&lt;/b&gt; 한 것을 보면 &lt;b&gt;initClient&lt;/b&gt; 메서드를 호출하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746709268935&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public void afterPropertiesSet() {
    if (disableGlobalTransaction) {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info(&quot;Global transaction is disabled.&quot;);
        }
        ConfigurationFactory.getInstance().addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, (CachedConfigurationChangeListener) this);
        return;
    }
    if (initialized.compareAndSet(false, true)) {
        initClient();
    }

    this.findBusinessBeanNamesNeededEnhancement();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;initClient &lt;/b&gt;메서드에서 글로벌 트랜잭션 클라이언트(TM, RM)을 &lt;b&gt;초기화&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746709335354&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protected void initClient() {
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info(&quot;Initializing Global Transaction Clients ... &quot;);
    }
    if (DEFAULT_TX_GROUP_OLD.equals(txServiceGroup)) {
        LOGGER.warn(&quot;the default value of seata.tx-service-group: {} has already changed to {} since Seata 1.5, &quot; +
                        &quot;please change your default configuration as soon as possible &quot; +
                        &quot;and we don't recommend you to use default tx-service-group's value provided by seata&quot;,
                DEFAULT_TX_GROUP_OLD, DEFAULT_TX_GROUP);
    }
    if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
        throw new IllegalArgumentException(String.format(&quot;applicationId: %s, txServiceGroup: %s&quot;, applicationId, txServiceGroup));
    }
    //init TM
    TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info(&quot;Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]&quot;, applicationId, txServiceGroup);
    }
    //init RM
    RMClient.init(applicationId, txServiceGroup);
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info(&quot;Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]&quot;, applicationId, txServiceGroup);
    }

    if (LOGGER.isInfoEnabled()) {
        LOGGER.info(&quot;Global Transaction Clients are initialized. &quot;);
    }
    registerSpringShutdownHook();

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 코드를 통해 &lt;b&gt;GlobalTransactionScanner&lt;/b&gt; 빈이 의존성 주입이 완료되면 자동으로 트랜잭션 클라이언트(TM, RM)가 초기화됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. TM 등록 과정&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1746707762985&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;o.a.s.c.rpc.netty.NettyPoolableFactory   : NettyPool create channel to transactionRole:TMROLE,address:127.0.0.1:8091,msg:&amp;lt; RegisterTMRequest{version='2.3.0', applicationId='distributed-transactions-at-demo', transactionServiceGroup='my_test_tx_group', extraData='ak=null&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TM&lt;/b&gt;은 TC 서버에 자신을 등록하기 위해 &lt;b&gt;물리적 TCP 연결&lt;/b&gt;을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746713412406&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//init TM
TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GlobalTransactionScanner&lt;/b&gt;의 &lt;b&gt;initClient&lt;/b&gt;에서 T&lt;b&gt;MClient.init&lt;/b&gt;을 통해 TM을 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 TM을 등록하는데 필요한, &lt;b&gt;applicationId&lt;/b&gt;, &lt;b&gt;txServiceGroup&lt;/b&gt;, &lt;b&gt;accessKey&lt;/b&gt;, &lt;b&gt;secretKey 등을&lt;/b&gt; 함께 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746713493486&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void init(String applicationId, String transactionServiceGroup, String accessKey, String secretKey) {
    TmNettyRemotingClient tmNettyRemotingClient = TmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup, accessKey, secretKey);
    tmNettyRemotingClient.init();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TmClient&lt;/b&gt;는 &lt;b&gt;GlobalTransactionScanner&lt;/b&gt;로부터 받은 정보를 바탕으로 &lt;b&gt;TmNettyRemotingClient&lt;/b&gt; 인스턴스를 생성한 뒤, &lt;b&gt;init&lt;/b&gt; 메서드를 통해 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746713546319&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public void init() {
    // registry processor
    registerProcessor();
    if (initialized.compareAndSet(false, true)) {
        super.init();
        if (isNotBlank(transactionServiceGroup)) {
            initConnection();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TmNettyRemotingClient&lt;/b&gt;에서 부모 클래스의 &lt;b&gt;init&lt;/b&gt; 메서드를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746713587854&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public void init() {
    timerExecutor.scheduleAtFixedRate(
            () -&amp;gt; {
                try {
                    clientChannelManager.reconnect(getTransactionServiceGroup());
                } catch (Exception ex) {
                    LOGGER.warn(&quot;reconnect server failed. {}&quot;, ex.getMessage());
                }
            },
            SCHEDULE_DELAY_MILLS,
            SCHEDULE_INTERVAL_MILLS,
            TimeUnit.MILLISECONDS);
    if (this.isEnableClientBatchSendRequest()) {
        mergeSendExecutorService = new ThreadPoolExecutor(
                MAX_MERGE_SEND_THREAD,
                MAX_MERGE_SEND_THREAD,
                KEEP_ALIVE_TIME,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue&amp;lt;&amp;gt;(),
                new NamedThreadFactory(getThreadPrefix(), MAX_MERGE_SEND_THREAD));
        mergeSendExecutorService.submit(new MergedSendRunnable());
    }
    super.init();
    clientBootstrap.start();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 클래스인 &lt;b&gt;AbstractNettyRemotingClient&lt;/b&gt;의 &lt;b&gt;init&lt;/b&gt; 메서드에서 try-catch를 통해 &lt;b&gt;NettyClientChannelManager&lt;/b&gt; 클래스의 &lt;b&gt;reconnect&lt;/b&gt;를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746713752674&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private Channel doConnect(String serverAddress) {
    Channel channelToServer = channels.get(serverAddress);
    if (channelToServer != null &amp;amp;&amp;amp; channelToServer.isActive()) {
        return channelToServer;
    }
    Channel channelFromPool;
    try {
        NettyPoolKey currentPoolKey = poolKeyFunction.apply(serverAddress);
        poolKeyMap.put(serverAddress, currentPoolKey);
        channelFromPool = nettyClientKeyPool.borrowObject(currentPoolKey);
        channels.put(serverAddress, channelFromPool);
    } catch (Exception exx) {
        LOGGER.error(&quot;{} register RM failed.&quot;, FrameworkErrorCode.RegisterRM.getErrCode(), exx);
        throw new FrameworkException(&quot;can not register RM,err:&quot; + exx.getMessage());
    }
    return channelFromPool;
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;NettyClientChannelManager의 reConnect가 doConnect를 호출하는데 중간 과정이 복잡해 해당 과정은 생략하겠습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NettyClientChannelManager&lt;/b&gt;의 &lt;b&gt;doConnect&lt;/b&gt;에서 nettyClientKeyPool.borrowObject를 통해 채널을 획득하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 초기화를 하는 과정에서는 &lt;b&gt;nettyClientKeyPool&lt;/b&gt;에 저장된 &lt;b&gt;채널&lt;/b&gt;이 없기에 &lt;b&gt;borrowObject&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;makeObject&lt;/b&gt; 메서드가 호출됩니다. &lt;br /&gt;&lt;u&gt;&lt;b&gt;(이 메서드가 호출되는 것을 설명하기 위해 이렇게 길게 설명했습니다.....)&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746714683268&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public Channel makeObject(NettyPoolKey key) {
    InetSocketAddress address = NetUtil.toInetSocketAddress(key.getAddress());
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info(&quot;NettyPool create channel to &quot; + key);
    }
    Channel tmpChannel = clientBootstrap.getNewChannel(address);
    long start = System.currentTimeMillis();
    Object response;
    Channel channelToServer = null;
    if (key.getMessage() == null) {
        throw new FrameworkException(&quot;register msg is null, role:&quot; + key.getTransactionRole().name());
    }
    try {
        response = rpcRemotingClient.sendSyncRequest(tmpChannel, key.getMessage());
        if (!isRegisterSuccess(response, key.getTransactionRole())) {
            rpcRemotingClient.onRegisterMsgFail(key.getAddress(), tmpChannel, response, key.getMessage());
        } else {
            channelToServer = tmpChannel;
            rpcRemotingClient.onRegisterMsgSuccess(key.getAddress(), tmpChannel, response, key.getMessage());
        }
    } catch (Exception exx) {
        if (tmpChannel != null) {
            tmpChannel.close();
        }
        throw new FrameworkException(
            &quot;register &quot; + key.getTransactionRole().name() + &quot; error, errMsg:&quot; + exx.getMessage());
    }
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info(&quot;register success, cost &quot; + (System.currentTimeMillis() - start) + &quot; ms, version:&quot; + getVersion(
            response, key.getTransactionRole()) + &quot;,role:&quot; + key.getTransactionRole().name() + &quot;,channel:&quot;
            + channelToServer);
    }
    return channelToServer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;makeObject&lt;/b&gt;에서 &lt;b&gt;InetSocketAddress&lt;/b&gt; 객체를 생성하면서 &lt;b&gt;Netty&lt;/b&gt;의 &lt;b&gt;Bootstrap&lt;/b&gt;을 사용해 물리적 &lt;b&gt;TCP&lt;/b&gt; 연결을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. TM 등록 성공&lt;/h3&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1746707779349&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;o.a.s.c.rpc.netty.TmNettyRemotingClient  : register TM success. client version:2.3.0, server version:2.3.0,channel:[id: 0xd0eadd3f, L:/127.0.0.1:56429 - R:/127.0.0.1:8091]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TC 서버가 TM의 등록 요청을 승인하고, TM과 TC 간의 &lt;b&gt;Netty&lt;/b&gt; 채널이 설정됩니다. 이 채널은 글로벌 트랜잭션의 시작, 커밋, 롤백 등을 위한 통신에 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1746715198670&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public Channel makeObject(NettyPoolKey key) {
    InetSocketAddress address = NetUtil.toInetSocketAddress(key.getAddress());
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info(&quot;NettyPool create channel to &quot; + key);
    }
    Channel tmpChannel = clientBootstrap.getNewChannel(address);
    long start = System.currentTimeMillis();
    Object response;
    Channel channelToServer = null;
    if (key.getMessage() == null) {
        throw new FrameworkException(&quot;register msg is null, role:&quot; + key.getTransactionRole().name());
    }
    try {
        response = rpcRemotingClient.sendSyncRequest(tmpChannel, key.getMessage());
        if (!isRegisterSuccess(response, key.getTransactionRole())) {
            rpcRemotingClient.onRegisterMsgFail(key.getAddress(), tmpChannel, response, key.getMessage());
        } else {
            channelToServer = tmpChannel;
            rpcRemotingClient.onRegisterMsgSuccess(key.getAddress(), tmpChannel, response, key.getMessage());
        }
    } catch (Exception exx) {
        if (tmpChannel != null) {
            tmpChannel.close();
        }
        throw new FrameworkException(
            &quot;register &quot; + key.getTransactionRole().name() + &quot; error, errMsg:&quot; + exx.getMessage());
    }
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info(&quot;register success, cost &quot; + (System.currentTimeMillis() - start) + &quot; ms, version:&quot; + getVersion(
            response, key.getTransactionRole()) + &quot;,role:&quot; + key.getTransactionRole().name() + &quot;,channel:&quot;
            + channelToServer);
    }
    return channelToServer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3단계에서 생성한 &lt;b&gt;InetSocketAddress&lt;/b&gt;를 바탕으로 채널을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, 생성된 채널을 통해 등록 메시지를 TC 서버로 전송합니다. &lt;br /&gt;메시지 전송이 성공한 경우 &quot;&lt;b&gt;register TM success.&lt;/b&gt; ~~&quot;와 같은 메시지를 출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. RM 초기화 및 등록&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1746707799360&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;o.a.s.c.rpc.netty.NettyPoolableFactory   : NettyPool create channel to transactionRole:RMROLE,address:127.0.0.1:8091,msg:&amp;lt; RegisterRMRequest{resourceIds='jdbc:mysql://localhost:3306/user-service', version='2.3.0', applicationId='distributed-transactions-at-demo', transactionServiceGroup='my_test_tx_group', extraData='null'} &amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RM은 TC 서버에 자신을 등록하기 위해 &lt;b&gt;물리적 TCP 연결&lt;/b&gt;을 생성합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;TM과 RM의 초기화 &amp;amp; 등록 방법은 동일합니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. RM 등록 성공&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1746707817870&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;o.a.s.c.rpc.netty.RmNettyRemotingClient  : register RM success. client version:2.3.0, server version:2.3.0,channel:[id: 0xb44a1918, L:/127.0.0.1:56432 - R:/127.0.0.1:8091]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TC 서버가 RM의 등록 요청을 승인하고, RM과 TC 간의 &lt;b&gt;Netty&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;u&gt;&lt;b&gt;이러한 일련의 과정을 통해 TC와 TM, RM이 연결되는 것입니다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;h2 id=&quot;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Seata의 분산 트랜잭션 처리는 TC, TM, RM 세 가지 역할이 상호 협력하여 이루어집니다. TC는 중앙 코디네이터 역할을 하며, TM은 글로벌 트랜잭션의 시작과 종료를 관리하고, RM은 리소스 작업과 브랜치 트랜잭션을 처리합니다. Spring Boot 애플리케이션에서 Seata가 시작될 때, &lt;b&gt;TM과 RM이 TC에 등록되고 Netty 기반의 통신 채널이 설정됩니다.&lt;/b&gt; 이후 글로벌 트랜잭션이 시작되면 TM, RM, TC 간의 메시지 교환을 통해 분산 트랜잭션의 일관성이 보장됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Backend</category>
      <category>apache</category>
      <category>RM</category>
      <category>seata</category>
      <category>TC</category>
      <category>TM</category>
      <author>코딩하는_대학생</author>
      <guid isPermaLink="true">https://solution-is-here.tistory.com/236</guid>
      <comments>https://solution-is-here.tistory.com/236#entry236comment</comments>
      <pubDate>Tue, 6 May 2025 07:56:32 +0900</pubDate>
    </item>
    <item>
      <title>Apache Seata란?</title>
      <link>https://solution-is-here.tistory.com/235</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Seata란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apache Seata는 마이크로서비스 아키텍처에서 고성능과 사용 편의성을 제공하는 분산 트랜잭션 프레임워크입니다. 알리바바에 의해 시작되었으며, 2023년에 Apache 재단에 기부되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Seata의 주요 특징으로는 다양한 트랜잭션 모델을 지원합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;AT&lt;/b&gt;, &lt;b&gt;TCC&lt;/b&gt;, &lt;b&gt;Saga&lt;/b&gt;, &lt;b&gt;XA&lt;/b&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;Seata는 3 계층 아키텍처(TC, TM, RM)로 이루어져 있어, 분산 트랜잭션 환경에서 데이터 일관성, 서비스 확장성, 관리 편의성을 보다 효과적으로 제공합니다. 또한, 한국에서 많은 백엔드 엔지니어분들이 사용하시는 Spring-Boot와도 호환성이 좋다는 장점이 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Seata의 내부 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Seata는 TC, TM, RM으로 이루어져있습니다.&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;TC&lt;/b&gt; : 트랜잭션 조정자 (Transaction Coordinator) / 서버에 위치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TM&lt;/b&gt; : 트랜잭션 관리자 (Transaction Manager) / 클라이언트에 위치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RM&lt;/b&gt; : 리소스 관리자 (Resource Manager) / 클라이언트에 위치&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TC (트랜잭션 조정자)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TC는 여러 마이크로서비스에 걸친 분산 트랜잭션을 조정하고 관리하는 역할을 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;글로벌 트랜잭션 상태를 유지하고, 브랜치 트랜잭션 등록을 관리하며, 커밋 / 롤백 프로세스를 조율하는 중앙 제어 노드라고 생각하시면 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TC는 주로 독립형 서비스로 실행되는 서버 애플리케이션으로 구현됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Netty 기반의 원격 서버로 구현됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TM(트랜잭션 관리자)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TM은 글로벌 트랜잭션의 범위를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TM은 애플리케이션 측에서 작동하며 서버 측 트랜잭션 조정자(TC)와 협력하여 글로벌 트랜잭션의 수명을 관리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745840054096&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GlobalTransactional
public void businessMethod() {
    // Business logic that may span multiple microservices
    serviceA.doSomething();
    serviceB.doSomething();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드와 같이 메서드에 @GlobalTransactional 어노테이션이 붙으면 TM은 자동으로 메서드가 실행되기 전에 글로벌 트랜잭션을 시작하고, 메서드가 끝난 뒤 자동으로 커밋 / 롤백을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션과 같은 선언형 프로그래밍 방식이 아닌 방식으로도 범위를 지정할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745840375591&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Begin a global transaction
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
try {
    tx.begin();
    
    // Business logic
    serviceA.doSomething();
    serviceB.doSomething();
    
    // Commit the transaction if successful
    tx.commit();
} catch (Exception e) {
    // Rollback the transaction on any exception
    tx.rollback();
    throw e;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC의 Connection과 같은 명령형 프로그래밍 방식으로도 범위를 지정할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;일반적으로 @GlobalTransaction은 &lt;b&gt;Controller&lt;/b&gt;, &lt;b&gt;DAO&lt;/b&gt;보단 &lt;b&gt;Service&lt;/b&gt; 계층에 배치하는 것을 추천합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RM(리소스 관리자)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RM은 분산 트랜잭션에 참여하는 리소스(Database)를 관리합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;로컬 트랜잭션 브랜치를 유지하고, Undo Log를 생성하며, TC와 협력해 트랜잭션의 일관성을 유지하는 역할을 한다고 생각하시면 됩니다.&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;RM은 트랜잭션의 타입에 따라 행동이 달라집니다.&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;AT Mode&lt;/b&gt; : RM은 DB 프록시를 통해 SQL 구문을 분석하여 자동으로 Undo Log를 생성합니다. (이때 생성한 Undo Log는 롤백 단계에서 사용합니다)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TCC Mode&lt;/b&gt; : RM은 트랜잭션 자원을 try / confirm / cancel 단계로 나누어 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SAGA Mode&lt;/b&gt; : RM은 각 단계별 로컬 트랜잭션 및 보상 트랜잭션을 실행하며, 상태 머신과 연동하여 분산 트랜잭션의 각 분기를 실제로 처리하는 역할을 담당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;XA Mode&lt;/b&gt; : XA 표준에 따라 트랜잭션의 준비, 커밋, 롤백을 수행하며, TC와 협력하여 전체 분산 트랜잭션의 원자성과 일관성을 보장합니다.&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;794&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YPdyT/btsNEFU3Cm5/CrV2VV0aMLBYmhyfiVLYC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YPdyT/btsNEFU3Cm5/CrV2VV0aMLBYmhyfiVLYC0/img.png&quot; data-alt=&quot;출처 : apache/seata&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YPdyT/btsNEFU3Cm5/CrV2VV0aMLBYmhyfiVLYC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYPdyT%2FbtsNEFU3Cm5%2FCrV2VV0aMLBYmhyfiVLYC0%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;794&quot; height=&quot;478&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : apache/seata&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TM이 TC에게 글로벌 트랜잭션 시작을 요청하면, TC는 XID(글로벌 트랜잭션 고유 식별자)를 생성합니다.&lt;/li&gt;
&lt;li&gt;각 서비스의 RM이 로컬 트랜잭션을 실행한 후, 해당 트랜잭션을 TC에 브랜치 트랜잭션으로 등록합니다.&lt;/li&gt;
&lt;li&gt;모든 브랜치 트랜잭션이 준비되면 TM은 TC에게 글로벌 커밋 / 롤백을 요청합니다.&lt;/li&gt;
&lt;li&gt;TC는 TM에게 온 요청에 따라 모든 브랜치 트랜잭션에 대해 커밋 또는 롤백 명령을 전달합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 이해를 하셨다면 여러분은 Seata에 공통적으로 적용되는 핵심 아키텍처를 이해하신 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Seata에서 지원하는 트랜잭션 모드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Seata는 마이크로서비스 아키텍처 전반의 다양한 분산 트랜잭션 시나리오를 처리하기 위해 4가지의 트랜잭션 모드를 제공합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;트랜잭션 모드를 이해하시기 전에 반드시 TC, TM, RM에 대해 이해를 하셔야 합니다.&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;각 트랜잭션 모드의 흐름에 대해 설명을 하고 TC, TM, RM이 해당 트랜잭션 모드에서 어떤 역할을 하는지에 대해 작성하겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. AT&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AT 모드&lt;/b&gt;는 Seata의 기본 트랜잭션 모드로, &lt;b&gt;비즈니스 로직을 수정하지 않고 어노테이션만 추가하면 분산 트랜잭션을 손쉽게 적용할 수 있다는 장점&lt;/b&gt;이 있습니다. 이는 기존의 2PC(Two-Phase Commit) 개념을 바탕으로 성능을 개선한 방식으로, &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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;889&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XIWYw/btsNEn8hM1W/R15XaoBlY0C6SAK1zyij20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XIWYw/btsNEn8hM1W/R15XaoBlY0C6SAK1zyij20/img.png&quot; data-alt=&quot;출처 : https://www.alibabacloud.com/blog/597868&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XIWYw/btsNEn8hM1W/R15XaoBlY0C6SAK1zyij20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXIWYw%2FbtsNEn8hM1W%2FR15XaoBlY0C6SAK1zyij20%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;1280&quot; height=&quot;889&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;889&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://www.alibabacloud.com/blog/597868&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순서&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TM이 TC에 XID 발급 요청 -&amp;gt; TC가 글로벌 트랜잭션 시작&lt;/li&gt;
&lt;li&gt;RM이 JDBC 프록시로 SQL 실행 전 / 후 스냅샷 캡처&lt;/li&gt;
&lt;li&gt;RM이 로컬 트랜잭션 내에서 &lt;b&gt;UNDO_LOG&lt;/b&gt; &lt;b&gt;INSERT&lt;/b&gt; 준비 (캡처한 스냅샷을 JSON으로 직렬화, INSERT 쿼리 준비)&lt;/li&gt;
&lt;li&gt;RM이 TC에 글로벌 락 획득 시도 (최대 30회 재시도)&lt;/li&gt;
&lt;li&gt;락 획득 성공 시 트랜잭션 커밋, (비즈니스 데이터 업데이트, UNDO_LOG INSERT 수행)&lt;/li&gt;
&lt;li&gt;커밋 시:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TC가 모든 RM에 UNDO_LOG 삭제 지시&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;롤백 시
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Dirty Write 검증 수행 후, before_image 기반 복원&lt;/li&gt;
&lt;li&gt;UNDO_LOG 삭제 및 TC에 결과 보고&lt;/li&gt;
&lt;li&gt;UNDO_LOG 미존재시 GlobalFinished 레코드 삽입&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AT 모드의 핵심 메커니즘은 바로 &lt;b&gt;UNDO_LOG입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RM은 SQL 실행 전 후로 beforeImage, afterImage를 메모리로 캡처한 후, JSON 형식으로 UNDO_LOG 테이블에 INSERT 준비를 합니다. 이후, 트랜잭션이 커밋될 때 비즈니스 데이터를 업데이트하며, 함께 INSERT 되어 영구적으로 저장합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745916583265&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BEGIN;
-- 1. 비즈니스 데이터 업데이트
UPDATE product SET stock=49 WHERE id=100;

-- 2. UNDO_LOG 기록
INSERT INTO undo_log (xid, branch_id, rollback_info) 
VALUES ('xid:xxx', 641789253, '{&quot;beforeImage&quot;:{&quot;stock&quot;:50}, &quot;afterImage&quot;:{&quot;stock&quot;:49}}');

COMMIT; -- 두 작업이 동시에 커밋/롤백됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UNDO_LOG를 기록할 때 undo_log 테이블에 저장하는 beforeImage, afterImage를 활용해서 롤백을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세히 설명하자면, 롤백 단계 중, Dirty Write 검증 수행 후, &lt;b&gt;beforeImage&lt;/b&gt; 기반 복원이 있는데 여기에서 &lt;b&gt;Dirty Write&lt;/b&gt; 검증을 할 때 &lt;b&gt;현재 데이터&lt;/b&gt;와 &lt;b&gt;afterImage&lt;/b&gt;를 비교해 검증을 합니다. 이때 현재 데이터와 afterImage가 같으면 검증에 문제가 없으나, 데이터가 다르면 다른 트랜잭션이 데이터를 변경을 했다고 판단을 해 Dirty Write를 감지해 사용자에게 &lt;b&gt;수동 개입&lt;/b&gt;이 필요하다는 알림을 전송합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-end=&quot;257&quot; data-start=&quot;76&quot; data-ke-size=&quot;size16&quot;&gt;그러나 AT 모드에서 @GlobalTransactional 어노테이션을 사용하면 &lt;b&gt;글로벌 락(Global Lock)&lt;/b&gt; 메커니즘이 활성화되어 Dirty Write 발생 가능성이 크게 줄어듭니다. 이는 Seata가 관리하는 글로벌 락을 통해, 하나의 트랜잭션이 완료될 때까지 해당 자원에 대한 다른 글로벌 트랜잭션의 접근을 제한하기 때문입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;351&quot; data-start=&quot;259&quot; data-ke-size=&quot;size16&quot;&gt;이 글로벌 락은 &lt;b&gt;데이터베이스 락이 아닌, Seata가 별도로 관리하는 락&lt;/b&gt;으로, 다른 트랜잭션이 동일 자원에 접근하려 할 경우 이를 제어하여 충돌을 방지합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;477&quot; data-start=&quot;353&quot; data-ke-size=&quot;size16&quot;&gt;반면, 일반적인&lt;b&gt; DB 락&lt;/b&gt;은 로컬 트랜잭션 커밋 시 해제되므로, 글로벌 트랜잭션 외부에서 SQL 문이 실행될 경우 Dirty Write가 발생할 수 있습니다. (2PC의 경우 DB락을 글로벌 락의 커밋 / 롤백 시점까지 유지합니다)&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;b&gt;TC, TM, RM의 역할&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;TC&lt;/b&gt; : 트랜잭션의 롤백, 커밋을 결정한 후 RM에 전파, 글로벌 락을 관리해 RM 간 연산 격리성 보장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TM&lt;/b&gt; : 글로벌 트랜잭션 범위 정의, 글로벌 트랜잭션 시작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RM&lt;/b&gt; : 로컬 트랜잭션 관리, SQL문 전, 후로 데이터 스냅샷 캡처 후 UNDO_LOG 기록, TC의 명령에 맞게 커밋, 롤백 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;AT는 여러 테이블이 단일 데이터베이스에 속할 때 사용해야 합니다.&lt;br /&gt;만약 여러 데이터베이스에 속하는 경우 &lt;b&gt;TCC&lt;/b&gt;, &lt;b&gt;SAGA&lt;/b&gt;등의 모드를 사용해 구현해야 합니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. TCC&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCC모드는 Try, Commit, Cancel 작업을 개발자가 직접 구현해야 하는 침입형 분산 트랜잭션 솔루션입니다.&lt;br /&gt;AT 모드에 비해 TCC 모드는 비즈니스 코드에 상당한 영향을 미칠 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Try, Commit, Cancel 3단계는 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Try&lt;/b&gt; : 각 서비스가 트랜잭션에 필요한 자원을 사전 점유하는 단계입니다. @TwoPhasebusinessAction 어노테이션이 붙은 메서드가 실행됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Confirm&lt;/b&gt; : 실제 비즈니스 로직을 수행하는 커밋 단계입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Cancel&lt;/b&gt; : 트랜잭션을 롤백하는 취소 단계입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 코드와 함께 더 자세히 설명하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746006352522&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface TccActionOne {
    @TwoPhaseBusinessAction(name = &quot;DubboTccActionOne&quot;, commitMethod = &quot;commit&quot;, rollbackMethod = &quot;rollback&quot;)
    public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = &quot;a&quot;) String a);
    public boolean commit(BusinessActionContext actionContext);
    public boolean rollback(BusinessActionContext actionContext);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 Seata 공식 홈페이지에서 제공하는 예제코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Try&lt;/b&gt; 단계에서는 &lt;b&gt;@TwoPhaseBusinessAction&lt;/b&gt;이 붙은 메서드인 &lt;b&gt;prepare&lt;/b&gt; 메서드가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;Confirm&lt;/b&gt;, &lt;b&gt;Cancel&lt;/b&gt; 단계는 &lt;b&gt;@TwoPhaseBusinessAction&lt;/b&gt;의 &lt;b&gt;commitMethod&lt;/b&gt;, &lt;b&gt;rollbackMethod&lt;/b&gt; 속성 값에서 정의한 commit, rollback 메서드가 실행됩니다.&lt;/p&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;@GlobalTransactional&lt;/b&gt;을 통해 글로벌 트랜잭션이 시작되면 자동으로 TCC 모드가 적용이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Od4hh/btsNFOyEehb/BbInNSZDSStUZYEJ0Iypmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Od4hh/btsNFOyEehb/BbInNSZDSStUZYEJ0Iypmk/img.png&quot; data-alt=&quot;출처 : https://seata.apache.org/docs/user/mode/tcc/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Od4hh/btsNFOyEehb/BbInNSZDSStUZYEJ0Iypmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOd4hh%2FbtsNFOyEehb%2FBbInNSZDSStUZYEJ0Iypmk%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;853&quot; height=&quot;482&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://seata.apache.org/docs/user/mode/tcc/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순서&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TM이 글로벌 트랜잭션 시작을 TC에게 요청해서 TC가 글로벌 트랜잭션을 시작합니다.&lt;/li&gt;
&lt;li&gt;RM은 Try 단계에서 자원을 예약하고 브랜치 트랜잭션을 TC에 등록합니다.&lt;/li&gt;
&lt;li&gt;TM은 모든 RM의 Try 단계가 성공한 것을 확인합니다.&lt;/li&gt;
&lt;li&gt;모든 RM의 Try 단계가 성공했을 때 (커밋 시)&amp;nbsp;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TM은 TC에게 Commit 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;TC는 모든 RM에게 Commit 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;RM은 실제 비즈니스 로직을 실행합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;RM의 Try 단계가 실패했을 때 (롤백 시)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TM은 TC에게 Rollback 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;TC는 모든 RM에게 Rollback 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;RM은 예약된 자원을 해제하고, 원상복구 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;TCC 모드의 장점은 데이터베이스에 종속되지 않는다는 점입니다. 개발자가 직접 Try, Confirm, Cancel 단계를 구현하기 때문에 비즈니스 리소스 잠금을 세밀하게 조정할 수 있으며, 그 수준 또한 유연하게 선택할 수 있습니다.&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;b&gt;TC, TM, RM의 역할&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;TC : Commit, Rollback 여부를 RM에게 전달, RM의 Try 단계 성공 여부 확인&lt;/li&gt;
&lt;li&gt;TM : RM에게 Commit, Rollback 요청 전달&lt;/li&gt;
&lt;li&gt;RM : Try 실행 후, TC에 브랜치 트랜잭션 등록, Commit / Rollback 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;TCC는 데이터베이스에 의존하지 않고, 애플리케이션 레벨에서 리소스 잠금을 세밀하게 제어 가능합니다.&lt;br /&gt;정확성이 필수인 시나리오에서 사용하면 좋습니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;3. SAGA&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Saga&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 그림은 SAGA 패턴을 표현한 그림입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5KvRF/btsNGM73vvh/v9p5Am1BP9kVBLU7qyIEfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5KvRF/btsNGM73vvh/v9p5Am1BP9kVBLU7qyIEfk/img.png&quot; data-alt=&quot;출처 : https://seata.apache.org/docs/dev/mode/saga-mode&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5KvRF/btsNGM73vvh/v9p5Am1BP9kVBLU7qyIEfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5KvRF%2FbtsNGM73vvh%2Fv9p5Am1BP9kVBLU7qyIEfk%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;445&quot; height=&quot;444&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://seata.apache.org/docs/dev/mode/saga-mode&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;우선, Seata에서의 SAGA 패턴을 이해하기 위해선 &lt;b&gt;State Machine Engine&lt;/b&gt;에 대해 알아야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LVm9f/btsNG9In9yg/zGNK75nknbhmyjhA0Y8k11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LVm9f/btsNG9In9yg/zGNK75nknbhmyjhA0Y8k11/img.png&quot; data-alt=&quot;출처 : https://seata.apache.org/docs/dev/mode/saga-mode&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LVm9f/btsNG9In9yg/zGNK75nknbhmyjhA0Y8k11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLVm9f%2FbtsNG9In9yg%2FzGNK75nknbhmyjhA0Y8k11%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;1044&quot; height=&quot;702&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://seata.apache.org/docs/dev/mode/saga-mode&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;b&gt;State Machine Engine&lt;/b&gt;은 총 3개의 계층으로 나눠져 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Eventing 레이어&lt;/b&gt; : 이벤트 기반 아키텍처를 구현합니다. 이벤트를 Push 하고 소비자가 이벤트를 소비할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Process Controller 레이어&lt;/b&gt; : 상위 Eventing 레이어에 의해 실행되는 &quot;empty&quot;한 프로세스 엔진입니다. (상태의 구체적인 동작이나 라우팅 로직이 정의되지 않은 최소한의 엔진)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;StateMachineEngine 레이어&lt;/b&gt; : State Machine Engine의 각 상태에 대한 행동과 라우팅 로직을 작성합니다. API를 제공하며, State Machine Engine 저장소를 제공합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;라우팅 로직이란 State Machine Engine이 각 상태를 실행한 뒤, 다음에 어떤 상태로 넘어갈지 결정하는 로직입니다.&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;936&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bINfez/btsNHNEOMYp/R1meC7OIGLpc5GKHOMuoX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bINfez/btsNHNEOMYp/R1meC7OIGLpc5GKHOMuoX0/img.png&quot; data-alt=&quot;출처 : https://seata.apache.org/docs/dev/mode/saga-mode&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bINfez/btsNHNEOMYp/R1meC7OIGLpc5GKHOMuoX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbINfez%2FbtsNHNEOMYp%2FR1meC7OIGLpc5GKHOMuoX0%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;936&quot; height=&quot;672&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://seata.apache.org/docs/dev/mode/saga-mode&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;b&gt;State Machine Engine&lt;/b&gt;의 작동 원리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 부분을 설명하자면, State Machine Engine의 내부 계층에서도 알 수 있듯이, State의 실행은 &lt;b&gt;이벤트 주도 모델&lt;/b&gt;에 기반하여 실행됩니다. 즉, StateA가 실행된 뒤, 라우팅 메시지는 생성되고 이벤트 큐에 추가됩니다. 그리고 소비자는 이벤트 큐에서 라우팅 메시지를 가져온 뒤, StateB를 실행하는 구조입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 상태 실행이 완료가 되면 State Machine Engine의 완료 이벤트가 로컬 데이터베이스에 기록되고 Seata 서버는 분산 트랜잭션에 대해 Commit, Rollback을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 State Machine Engine에서 어떤 상태를 실행시킬지는 상태 다이어그램에 의해 생성된 JSON 상태 정의 파일에 의해 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON의 세부 내용을 확인하고 싶으신 분은 아래의 접은 글을 확인해 주세요.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 상태 정의 파일&lt;/p&gt;
&lt;pre id=&quot;code_1746084003057&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;Name&quot;: &quot;reduceInventoryAndBalance&quot;,
    &quot;Comment&quot;: &quot;reduce inventory then reduce balance in a transaction&quot;,
    &quot;StartState&quot;: &quot;ReduceInventory&quot;,
    &quot;Version&quot;: &quot;0.0.1&quot;,
    &quot;States&quot;: {
        &quot;ReduceInventory&quot;: {
            &quot;Type&quot;: &quot;ServiceTask&quot;,
            &quot;ServiceName&quot;: &quot;inventoryAction&quot;,
            &quot;ServiceMethod&quot;: &quot;reduce&quot;,
            &quot;CompensateState&quot;: &quot;CompensateReduceInventory&quot;,
            &quot;Next&quot;: &quot;ChoiceState&quot;,
            &quot;Input&quot;: [
                &quot;$.[businessKey]&quot;,
                &quot;$.[count]&quot;
            ],
            &quot;Output&quot;: {
                &quot;reduceInventoryResult&quot;: &quot;$.#root&quot;
            },
            &quot;Status&quot;: {
                &quot;#root == true&quot;: &quot;SU&quot;,
                &quot;#root == false&quot;: &quot;FA&quot;,
                &quot;$Exception{java.lang.Throwable}&quot;: &quot;UN&quot;
            }
        },
        &quot;ChoiceState&quot;:{
            &quot;Type&quot;: &quot;Choice&quot;,
            &quot;Choices&quot;:[
                {
                    &quot;Expression&quot;:&quot;[reduceInventoryResult] == true&quot;,
                    &quot;Next&quot;:&quot;ReduceBalance&quot;
                }
            ],
            &quot;Default&quot;:&quot;Fail&quot;
        },
        &quot;ReduceBalance&quot;: {
            &quot;Type&quot;: &quot;ServiceTask&quot;,
            &quot;ServiceName&quot;: &quot;balanceAction&quot;,
            &quot;ServiceMethod&quot;: &quot;reduce&quot;,
            &quot;CompensateState&quot;: &quot;CompensateReduceBalance&quot;,
            &quot;Input&quot;: [
                &quot;$.[businessKey]&quot;,
                &quot;$.[amount]&quot;,
                {
                    &quot;throwException&quot; : &quot;$.[mockReduceBalanceFail]&quot;
                }
            ],
            &quot;Output&quot;: {
                &quot;compensateReduceBalanceResult&quot;: &quot;$.#root&quot;
            },
            &quot;Status&quot;: {
                &quot;#root == true&quot;: &quot;SU&quot;,
                &quot;#root == false&quot;: &quot;FA&quot;,
                &quot;$Exception{java.lang.Throwable}&quot;: &quot;UN&quot;
            },
            &quot;Catch&quot;: [
                {
                    &quot;Exceptions&quot;: [
                        &quot;java.lang.Throwable&quot;
                    ],
                    &quot;Next&quot;: &quot;CompensationTrigger&quot;
                }
            ],
            &quot;Next&quot;: &quot;Succeed&quot;
        },
        &quot;CompensateReduceInventory&quot;: {
            &quot;Type&quot;: &quot;ServiceTask&quot;,
            &quot;ServiceName&quot;: &quot;inventoryAction&quot;,
            &quot;ServiceMethod&quot;: &quot;compensateReduce&quot;,
            &quot;Input&quot;: [
                &quot;$.[businessKey]&quot;
            ]
        },
        &quot;CompensateReduceBalance&quot;: {
            &quot;Type&quot;: &quot;ServiceTask&quot;,
            &quot;ServiceName&quot;: &quot;balanceAction&quot;,
            &quot;ServiceMethod&quot;: &quot;compensateReduce&quot;,
            &quot;Input&quot;: [
                &quot;$.[businessKey]&quot;
            ]
        },
        &quot;CompensationTrigger&quot;: {
            &quot;Type&quot;: &quot;CompensationTrigger&quot;,
            &quot;Next&quot;: &quot;Fail&quot;
        },
        &quot;Succeed&quot;: {
            &quot;Type&quot;:&quot;Succeed&quot;
        },
        &quot;Fail&quot;: {
            &quot;Type&quot;:&quot;Fail&quot;,
            &quot;ErrorCode&quot;: &quot;PURCHASE_FAILED&quot;,
            &quot;Message&quot;: &quot;purchase failed&quot;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkf9aH/btsNHPo8H3v/1qJa9diTVfb7nMtiVuYAM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkf9aH/btsNHPo8H3v/1qJa9diTVfb7nMtiVuYAM0/img.png&quot; data-alt=&quot;출처 : https://seata.apache.org/docs/dev/mode/saga-mode&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkf9aH/btsNHPo8H3v/1qJa9diTVfb7nMtiVuYAM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdkf9aH%2FbtsNHPo8H3v%2F1qJa9diTVfb7nMtiVuYAM0%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;508&quot; height=&quot;543&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://seata.apache.org/docs/dev/mode/saga-mode&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;b&gt;순서&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TM이 State Machine Engine을 호출하며 글로벌 트랜잭션 시작을 요청합니다.&lt;/li&gt;
&lt;li&gt;State Machine Engine이 TC에 XID을 요청합니다.&lt;/li&gt;
&lt;li&gt;State Machine Engine이 로컬 데이터베이스에 &quot;State Machine Instance&quot; 시작 이벤트를 기록합니다.&lt;/li&gt;
&lt;li&gt;State Machine Engine이 RM을 통해 JSON 상태 파일에 의해 첫 번째 상태를 실행합니다.&lt;/li&gt;
&lt;li&gt;State Machine Engine이 TC에 브랜치 트랜잭션을 등록해 브랜치 식별자를 발급 받습니다.&lt;/li&gt;
&lt;li&gt;State Machine Engine이 로컬 데이터베이스에 &quot;State Machine Instance&quot; 실행 이벤트를 기록합니다.&lt;/li&gt;
&lt;li&gt;상태 실행 완료 후, 라우팅 메시지를 생성합니다.&lt;/li&gt;
&lt;li&gt;소비자가 라우팅 메시지를 가져온 뒤, 다음 상태를 실행합니다.&lt;/li&gt;
&lt;li&gt;상태 실행 중 예외 발생 (롤백 시)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;State Machine Engine이 실행된 트랜잭션에 대해 보상 노드를 역순으로 실행합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;모든 상태 실행 완료 (커밋 시)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;State Machine Engine이 TC에 트랜잭션 커밋 요청&lt;/li&gt;
&lt;li&gt;TC가 최종 트랜잭션 상태 확정&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;SAGA 모드는 보상 로직을 직접 작성할 수 있기에, 다단계로 구성된 복잡한 워크플로우에서 효율적으로 사용할 수 있습니다.&lt;br /&gt;하지만, 글로벌 락이 존재하지 않아 Dirty write의 위험이 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 Seata의 TC와 TM, RM이 어떻게 통신을 하는지 작성하려고 합니다.&lt;/p&gt;</description>
      <category>Backend</category>
      <category>apache</category>
      <category>seata</category>
      <author>코딩하는_대학생</author>
      <guid isPermaLink="true">https://solution-is-here.tistory.com/235</guid>
      <comments>https://solution-is-here.tistory.com/235#entry235comment</comments>
      <pubDate>Mon, 28 Apr 2025 16:10:31 +0900</pubDate>
    </item>
    <item>
      <title>내가 JUnit5에 글로벌 Extension 필터링 기능을 추가한 이야기</title>
      <link>https://solution-is-here.tistory.com/233</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 junit5, spring-boot, fixture-monkey 등 여러 오픈소스에 기여를 활발히 기여하고 있는 &lt;a href=&quot;https://github.com/YongGoose&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;YongGoose&lt;/a&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 제가 JUnit5에 처음으로 기여를 한 글로벌 Extension 필터링 기능에 대해 작성하려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 GitHub과 블로그를 찾아봐 주신 분들, 그리고 커피챗을 신청해 주신 분들 덕분에 뿌듯하고 기분 좋은 시간을 보내고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.  &amp;zwj;♂️&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JUnit in action 3판의 엮은이이신 동준님께도 이 글을 빌려, 감사의 말씀을 전합니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Extension이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, JUnit5에서 Extension이란 테스트 클래스나 메서드에 추가적인 기능을 제공하는 메커니즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Extension을 활용해 테스트의 생명 주기나 이벤트에 관여할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이때 Extension을 등록하는 방법은 여러 가지가 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 선언적 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit5에서는 @ExtendWith 어노테이션을 통해 테스트 클래스, 메서드, 필드에 Extension을 적용할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JUnit4를 주로 사용하시던 분들이라면 @RunWith를 생각하시면 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 코드와 같이 적용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745317363668&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ExtendWith(MyExtension.class)
public class MyTest {
    @Test
    void test1() {
        // 테스트 코드
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 자동 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit5에서는 ServiceLoader 메커니즘을 활용해 특정 파일에 작성된 Extension들을 자동으로 등록할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이때 하나의 단점이 있는데 필터링 기능이 없어 Extension이 모두 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;본 글은 새로운 기능을 소개하는 것이 목적이기 때문에 Extension은 간략하게 소개하겠습니다.&lt;br /&gt;Extension에 대해 더 알고싶으신 분은 공식문서를 보시는 것을 추천합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이슈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해, 자동 등록된 Extension 중에서도 특정 Extension만 선택적으로 활성화할 수 있는 메커니즘을 도입하자는 이슈가 제기되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2440&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csso9w/btsNvgulCIf/TRyDBwHfD8hm2nG4o1Pkmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csso9w/btsNvgulCIf/TRyDBwHfD8hm2nG4o1Pkmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csso9w/btsNvgulCIf/TRyDBwHfD8hm2nG4o1Pkmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcsso9w%2FbtsNvgulCIf%2FTRyDBwHfD8hm2nG4o1Pkmk%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;2440&quot; height=&quot;824&quot; data-origin-width=&quot;2440&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;간략하게 설명하자면, 현재 junit.jupiter.extensions.autodetection.enabled=true 구성 매개변수를 통해 모든 글로벌 Extension을 활성화하면 잠재적인 문제가 발생할 수 있습니다. 그로 인해 본인이 사용하고 싶은 Extension만 활성화시키고 싶다는 이슈입니다.&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;1872&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mNJ7I/btsNs6erFE3/HouCxu3OTHR6jm7hCaoo4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mNJ7I/btsNs6erFE3/HouCxu3OTHR6jm7hCaoo4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mNJ7I/btsNs6erFE3/HouCxu3OTHR6jm7hCaoo4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmNJ7I%2FbtsNs6erFE3%2FHouCxu3OTHR6jm7hCaoo4k%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;1872&quot; height=&quot;352&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이슈가 등록되고 5일 후, 메인테이너님께서 JUnit-team 분들과 회의를 하시고, 특정 글로벌 확장을 선택적으로 활성화할 수 있도록, &lt;b&gt;junit.jupiter.extensinos.autodetection.include&lt;/b&gt; / &lt;b&gt;exclude&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;a href=&quot;https://github.com/junit-team/junit5/issues/3717&quot;&gt;https://github.com/junit-team/junit5/issues/3717&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745317673643&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Introduce mechanism to enable specific global extensions in JUnit Jupiter &amp;middot; Issue #3717 &amp;middot; junit-team/junit5&quot; data-og-description=&quot;Enabling all global extensions on the class/module path with junit.jupiter.extensions.autodetection.enabled=true is asking for surprises (and potentially trouble). Instead, I'd like to explicitly e...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/junit-team/junit5/issues/3717&quot; data-og-url=&quot;https://github.com/junit-team/junit5/issues/3717&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit5/issues/3717&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/junit-team/junit5/issues/3717&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Introduce mechanism to enable specific global extensions in JUnit Jupiter &amp;middot; Issue #3717 &amp;middot; junit-team/junit5&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Enabling all global extensions on the class/module path with junit.jupiter.extensions.autodetection.enabled=true is asking for surprises (and potentially trouble). Instead, I'd like to explicitly e...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동 등록 방식에서는 &lt;b&gt;ServiceLoader&lt;/b&gt; 메커니즘을 통해 &lt;b&gt;Extension&lt;/b&gt;을 조회한 뒤, 등록하는 방식으로 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이때 구성 매개변수를 통해 &lt;b&gt;include&lt;/b&gt; / &lt;b&gt;exclude&lt;/b&gt; 필터를 등록하고 ServiceLoader를 통해 Extension을 조회하는 시점에 해당 필터를 적용해 필요한 Extension만 등록할 수 있도록 하면 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7Mbvp/btsNvg2dNYD/vMBbMBQ6elw5GX5mt5nNWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7Mbvp/btsNvg2dNYD/vMBbMBQ6elw5GX5mt5nNWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7Mbvp/btsNvg2dNYD/vMBbMBQ6elw5GX5mt5nNWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7Mbvp%2FbtsNvg2dNYD%2FvMBbMBQ6elw5GX5mt5nNWK%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;1702&quot; height=&quot;1198&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드와 함께 더 자세히 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745319792689&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static void registerAutoDetectedExtensions(MutableExtensionRegistry extensionRegistry,
		JupiterConfiguration configuration) {

	Predicate&amp;lt;Class&amp;lt;? extends Extension&amp;gt;&amp;gt; filter = configuration.getFilterForAutoDetectedExtensions();
	List&amp;lt;Class&amp;lt;? extends Extension&amp;gt;&amp;gt; excludedExtensions = new ArrayList&amp;lt;&amp;gt;();

	ServiceLoader&amp;lt;Extension&amp;gt; serviceLoader = ServiceLoader.load(Extension.class,
		ClassLoaderUtils.getDefaultClassLoader());
	ServiceLoaderUtils.filter(serviceLoader, clazz -&amp;gt; {
		boolean included = filter.test(clazz);
		if (!included) {
			excludedExtensions.add(clazz);
		}
		return included;
	}) //
			.forEach(extensionRegistry::registerAutoDetectedExtension);

	logExcludedExtensions(excludedExtensions);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 필터를 생성한 뒤, &lt;b&gt;ServiceLoader&lt;/b&gt;에서 &lt;b&gt;Extension&lt;/b&gt;을 조회하는 시점에 필터를 적용해 사용자가 지정한 Extension만 가져오도록 하는 코드입니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아까 전부터 궁금했던 내용인데 ServiceLoader는 Extension을 어떻게 가져오는 거야?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 좋은 질문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 자세히 알기 위해서는 ServiceLoader과 ClassLoader에 대해 알아야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745320163239&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ServiceLoader&amp;lt;Extension&amp;gt; serviceLoader = ServiceLoader.load(Extension.class,
	ClassLoaderUtils.getDefaultClassLoader());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 ServiceLoader를 활용해 Extension 인터페이스의 구현체를 동적으로 로드하는 코드입니다.&lt;/p&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;ClassLoader&lt;/b&gt;는 자바에서 클래스 파일을 메모리에 로드하고 초기화하는 역할을 하는 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Java는 JVM을 통해 실행이 되는데 이때 JVM 내부에 위치한 ClassLoader가 컴파일러에 의해 바이트 코드로 변환된 클래스 파일을 메모리에 로드하는 역할을 합니다.&lt;/p&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;ServiceLoader&lt;/b&gt;는 자바에서 특정 서비스의 구현체를 동적으로 검색하고 로드하는 기능을 제공하는 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;META-INF/services&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;이때 ServiceLoader에서는 ClassLoader를 활용해 &lt;b&gt;META-INF/services&lt;/b&gt; 디렉토리에 텍스트 파일로 등록된 클래스의 이름을 읽어온 뒤, 이름을 통해 해당 클래스 파일을 메모리에 로드합니다. 이후, ServiceLoader에서 내부적으로 클래스 파일의 구현체를 생성하는 것입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. ServiceLoader.load() 호출&lt;br /&gt;- ClassLoader 지정&lt;br /&gt;2. 지연 초기화(Lazy Initialization)&lt;br /&gt;- 실제 순회 시점에 리소스 탐색&lt;br /&gt;3. 리소스 탐색&lt;br /&gt;- ClassLoader.getResources()로 모든 META-INF/services 파일 수집&lt;br /&gt;4. 구현체 인스턴스화&lt;br /&gt;- 각 클래스명을 Class.forName()으로 로드 후 인스턴스 생성&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745321518443&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static &amp;lt;T&amp;gt; Stream&amp;lt;T&amp;gt; filter(ServiceLoader&amp;lt;T&amp;gt; serviceLoader,
		Predicate&amp;lt;? super Class&amp;lt;? extends T&amp;gt;&amp;gt; providerPredicate) {
	return StreamSupport.stream(serviceLoader.spliterator(), false).filter(it -&amp;gt; {
		@SuppressWarnings(&quot;unchecked&quot;)
		Class&amp;lt;? extends T&amp;gt; type = (Class&amp;lt;? extends T&amp;gt;) it.getClass();
		return providerPredicate.test(type);
	});
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 필터를 통해 ServiceLoader에서 생성한 구현체들의 인스턴스를 선별하는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세히 보면 serviceLoader.spliterator()을 통해 Stream 연산을 하는 것을 볼 수 있습니다.&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;1266&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDQJaU/btsNvJJn9EU/mApriowctauEjsf80sPuk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDQJaU/btsNvJJn9EU/mApriowctauEjsf80sPuk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDQJaU/btsNvJJn9EU/mApriowctauEjsf80sPuk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDQJaU%2FbtsNvJJn9EU%2FmApriowctauEjsf80sPuk0%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;580&quot; height=&quot;297&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;spliterator API&lt;/b&gt;를 사용하기 위해선 &lt;b&gt;iterator&lt;/b&gt;가 필요한 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 &lt;b&gt;ServiceLoader&lt;/b&gt;에서 어떻게 &lt;b&gt;iterator&lt;/b&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;760&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2OGoT/btsNu0ljQLZ/a4YFC2HTml17qqTEaeEHSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2OGoT/btsNu0ljQLZ/a4YFC2HTml17qqTEaeEHSk/img.png&quot; data-alt=&quot;ServiceLoader의 iterator API 내부 코드입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2OGoT/btsNu0ljQLZ/a4YFC2HTml17qqTEaeEHSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2OGoT%2FbtsNu0ljQLZ%2Fa4YFC2HTml17qqTEaeEHSk%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;207&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ServiceLoader의 iterator API 내부 코드입니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ServiceLoader&lt;/b&gt;에서는 iterator API를 통해 Iterator을 생성할 때 &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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;1130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu5UwG/btsNuZmpdtU/HoV8YGKSxCnwhbWbtxkryK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu5UwG/btsNuZmpdtU/HoV8YGKSxCnwhbWbtxkryK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu5UwG/btsNuZmpdtU/HoV8YGKSxCnwhbWbtxkryK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu5UwG%2FbtsNuZmpdtU%2FHoV8YGKSxCnwhbWbtxkryK%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;600&quot; height=&quot;486&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;1130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;newLookupIterator&lt;/b&gt; API는 &lt;b&gt;ModuleServicesLookupItertor&lt;/b&gt;, &lt;b&gt;LazyClassPathLookupIterator&lt;/b&gt;을 사용해 타입에 맞는 요소를 탐색합니다. 이때 탐색 우선순위는 &lt;b&gt;모듈 시스템 &amp;rarr; 클래스패스&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&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;color: #333333; text-align: start;&quot;&gt;이때 &lt;b&gt;LazyClassPathLookupIterator&lt;/b&gt;가 바로 지연 로드를 하는 반복자이면서, META-INF/services/에 정의된 확장자들을 가져와 객체로 생성해 주는 반복자입니다. (코드의 양이 많아 링크로 대체합니다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;a href=&quot;https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.base/share/classes/java/util/ServiceLoader.java#L1108&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.base/share/classes/java/util/ServiceLoader.java#L1108&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;color: #333333; text-align: start;&quot;&gt;이렇게 생성된 iterator을 ServiceLoaderUtils Spliterator로 변환해 stream 연산을 수행하고, 이때 사용자로부터 입력받은 패턴을 적용해서 특정 Extension만 적용이 되게 하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745322639279&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void registryIncludesAllAutoDetectedExtensionsAndExcludesNone() {
	when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true);
	when(configuration.getFilterForAutoDetectedExtensions()).thenReturn(extensionFilter(&quot;*&quot;, &quot;&quot;));
	registry = createRegistryWithDefaultExtensions(configuration);

	List&amp;lt;Extension&amp;gt; extensions = registry.getExtensions(Extension.class);

	assertEquals(NUM_DEFAULT_EXTENSIONS + 2, extensions.size());
	assertDefaultGlobalExtensionsAreRegistered(4);

	assertExtensionRegistered(registry, ServiceLoaderExtension.class);
	assertExtensionRegistered(registry, ConfigLoaderExtension.class);
	assertEquals(4, countExtensions(registry, BeforeAllCallback.class));
}

---

@Test
void registryIncludesSpecificAutoDetectedExtensionsAndExcludesAll() {
	when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true);
	when(configuration.getFilterForAutoDetectedExtensions()).thenReturn(
		extensionFilter(ServiceLoaderExtension.class.getName(), &quot;*&quot;));
	registry = createRegistryWithDefaultExtensions(configuration);

	List&amp;lt;Extension&amp;gt; extensions = registry.getExtensions(Extension.class);

	assertEquals(NUM_CORE_EXTENSIONS, extensions.size());
	assertDefaultGlobalExtensionsAreRegistered(2);

	assertExtensionNotRegistered(registry, ServiceLoaderExtension.class);
	assertEquals(2, countExtensions(registry, BeforeAllCallback.class));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기능을 테스트하기 위해 작성한 통합 테스트입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 제가 작성한 공식 문서를 보면 기능을 사용하는 방법이 자세히 나와있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://junit.org/junit5/docs/current/user-guide/#extensions-registration-automatic-filtering&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://junit.org/junit5/docs/current/user-guide/#extensions-registration-automatic-filtering&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745322777477&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JUnit 5 User Guide&quot; data-og-description=&quot;Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo&quot; data-og-host=&quot;junit.org&quot; data-og-source-url=&quot;https://junit.org/junit5/docs/current/user-guide/#extensions-registration-automatic-filtering&quot; data-og-url=&quot;https://junit.org/junit5/docs/current/user-guide/#extensions-registration-automatic-filtering&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kKEMt/hyYIhahNxd/zncUwbxSD3NE0MVge9YWD1/img.png?width=1662&amp;amp;height=1578&amp;amp;face=0_0_1662_1578,https://scrap.kakaocdn.net/dn/PSneO/hyYFC7fv9G/f1KqiSjB8RUch5OJj4iOz1/img.png?width=1548&amp;amp;height=898&amp;amp;face=0_0_1548_898,https://scrap.kakaocdn.net/dn/WCdN0/hyYIfp1fEs/PjxEODoBhpPNuFovYi6hYk/img.png?width=997&amp;amp;height=1173&amp;amp;face=0_0_997_1173&quot;&gt;&lt;a href=&quot;https://junit.org/junit5/docs/current/user-guide/#extensions-registration-automatic-filtering&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://junit.org/junit5/docs/current/user-guide/#extensions-registration-automatic-filtering&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kKEMt/hyYIhahNxd/zncUwbxSD3NE0MVge9YWD1/img.png?width=1662&amp;amp;height=1578&amp;amp;face=0_0_1662_1578,https://scrap.kakaocdn.net/dn/PSneO/hyYFC7fv9G/f1KqiSjB8RUch5OJj4iOz1/img.png?width=1548&amp;amp;height=898&amp;amp;face=0_0_1548_898,https://scrap.kakaocdn.net/dn/WCdN0/hyYIfp1fEs/PjxEODoBhpPNuFovYi6hYk/img.png?width=997&amp;amp;height=1173&amp;amp;face=0_0_997_1173');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JUnit 5 User Guide&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;junit.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend</category>
      <category>junit5</category>
      <category>junit5 extension</category>
      <category>junit5 기여</category>
      <category>오픈소스 기여</category>
      <author>코딩하는_대학생</author>
      <guid isPermaLink="true">https://solution-is-here.tistory.com/233</guid>
      <comments>https://solution-is-here.tistory.com/233#entry233comment</comments>
      <pubDate>Tue, 22 Apr 2025 20:26:41 +0900</pubDate>
    </item>
    <item>
      <title>내가 JUnit5에 병렬화를 도입한 이야기 - 메서드 단위</title>
      <link>https://solution-is-here.tistory.com/231</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;안녕하세요, Junit-team/junit5, spring/spring-boot, apache/seata, naver/fixture-monkey 등 여러 오픈소스 프로젝트에 기여한&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/YongGoose&quot;&gt;YongGoose&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: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;처음에는 오픈소스 기여에 대한 관심을 높이려는 의도로 글을 쓰기 시작했지만, 점점 나의 소중한 자식들(?)을 소개하는 재미가 생기네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이번 글은 아래의 글의 후속 편입니다. (미리 읽고 오시면 좋습니다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://devocean.sk.com/blog/techBoardDetail.do?page=&amp;amp;query=&amp;amp;ID=167270&amp;amp;boardType=writer&amp;amp;searchData=kevin0928&amp;amp;subIndex=&amp;amp;idList=&amp;amp;pnwriterID=kevin0928&amp;amp;searchText=&amp;amp;techType=&amp;amp;searchDataSub=&amp;amp;searchDataMain=&amp;amp;comment=&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://devocean.sk.com/blog/techBoardDetail.do?page=&amp;amp;query=&amp;amp;ID=167270&amp;amp;boardType=writer&amp;amp;searchData=kevin0928&amp;amp;subIndex=&amp;amp;idList=&amp;amp;pnwriterID=kevin0928&amp;amp;searchText=&amp;amp;techType=&amp;amp;searchDataSub=&amp;amp;searchDataMain=&amp;amp;comment=&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1743309213683&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JUnit5에 병렬화를 도입한 이야기 - 클래스 단위&quot; data-og-description=&quot; &quot; data-og-host=&quot;devocean.sk.com&quot; data-og-source-url=&quot;https://devocean.sk.com/blog/techBoardDetail.do?page=&amp;amp;query=&amp;amp;ID=167270&amp;amp;boardType=writer&amp;amp;searchData=kevin0928&amp;amp;subIndex=&amp;amp;idList=&amp;amp;pnwriterID=kevin0928&amp;amp;searchText=&amp;amp;techType=&amp;amp;searchDataSub=&amp;amp;searchDataMain=&amp;amp;comment=&quot; data-og-url=&quot;https://devocean.sk.com/blog/techBoardDetail.do?ID=167270&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uCPPV/hyYviV9oBX/ik4qQPBctqAZgLxcH9s5TK/img.png?width=360&amp;amp;height=360&amp;amp;face=155_181_178_207,https://scrap.kakaocdn.net/dn/bbXUaK/hyYyRCAf11/YYoabtw0v2XTaPSv7H6hIK/img.png?width=360&amp;amp;height=360&amp;amp;face=155_181_178_207,https://scrap.kakaocdn.net/dn/bajXTt/hyYyVLLeTr/8uvGGOKLtWDOaZ4KV8z13k/img.png?width=2254&amp;amp;height=1350&amp;amp;face=0_0_2254_1350&quot;&gt;&lt;a href=&quot;https://devocean.sk.com/blog/techBoardDetail.do?page=&amp;amp;query=&amp;amp;ID=167270&amp;amp;boardType=writer&amp;amp;searchData=kevin0928&amp;amp;subIndex=&amp;amp;idList=&amp;amp;pnwriterID=kevin0928&amp;amp;searchText=&amp;amp;techType=&amp;amp;searchDataSub=&amp;amp;searchDataMain=&amp;amp;comment=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devocean.sk.com/blog/techBoardDetail.do?page=&amp;amp;query=&amp;amp;ID=167270&amp;amp;boardType=writer&amp;amp;searchData=kevin0928&amp;amp;subIndex=&amp;amp;idList=&amp;amp;pnwriterID=kevin0928&amp;amp;searchText=&amp;amp;techType=&amp;amp;searchDataSub=&amp;amp;searchDataMain=&amp;amp;comment=&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uCPPV/hyYviV9oBX/ik4qQPBctqAZgLxcH9s5TK/img.png?width=360&amp;amp;height=360&amp;amp;face=155_181_178_207,https://scrap.kakaocdn.net/dn/bbXUaK/hyYyRCAf11/YYoabtw0v2XTaPSv7H6hIK/img.png?width=360&amp;amp;height=360&amp;amp;face=155_181_178_207,https://scrap.kakaocdn.net/dn/bajXTt/hyYyVLLeTr/8uvGGOKLtWDOaZ4KV8z13k/img.png?width=2254&amp;amp;height=1350&amp;amp;face=0_0_2254_1350');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JUnit5에 병렬화를 도입한 이야기 - 클래스 단위&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devocean.sk.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;소개&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;저번 글에서는 JUnit의 Vintage 엔진에 클래스 단위의 병렬화를 도입한 것을 설명했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;여러 개의 테스트 클래스를 실행할 때는 해당 기능으로 인해 성능 향상을 기대할 수 있지만, 만약 하나의 테스트 클래스에 있는 여러 개의 메서드를 실행할 때는 성능 향상을 기대하기 어렵습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그로 인해 메서드 단위의 병렬화도 메인테이너님께 제안을 드렸고, 긍정적인 답변을 받아 작업을 하게 되었습니다.&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;2292&quot; data-origin-height=&quot;1264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wcfm8/btsM1jkIFiK/9z4SaNRkrazUoT8Ex29joK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wcfm8/btsM1jkIFiK/9z4SaNRkrazUoT8Ex29joK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wcfm8/btsM1jkIFiK/9z4SaNRkrazUoT8Ex29joK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwcfm8%2FbtsM1jkIFiK%2F9z4SaNRkrazUoT8Ex29joK%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;2292&quot; height=&quot;1264&quot; data-origin-width=&quot;2292&quot; data-origin-height=&quot;1264&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: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;메인테이너님의 코멘트에서 볼 수 있다시피 클래스 단위 병렬화에서 사용하던 스레드 풀을 활용하면 쉽게 해결이 되는 듯했습니다.&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;1354&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yY119/btsM0Iy21dD/KLcJTxAKK83EFR0ba850Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yY119/btsM0Iy21dD/KLcJTxAKK83EFR0ba850Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yY119/btsM0Iy21dD/KLcJTxAKK83EFR0ba850Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyY119%2FbtsM0Iy21dD%2FKLcJTxAKK83EFR0ba850Sk%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;1354&quot; height=&quot;778&quot; data-origin-width=&quot;1354&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: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 위 다이어그램과 같이 Runner가 ParentRunner인 경우, 모든 childStatement(메서드)를 스레드 풀을 활용해 비동기로 실행하도록 구현했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;교착상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&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;1270&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEMlFR/btsMZ4PHsW9/ttJYTkZxSwlbzpk6jYSekK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEMlFR/btsMZ4PHsW9/ttJYTkZxSwlbzpk6jYSekK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEMlFR/btsMZ4PHsW9/ttJYTkZxSwlbzpk6jYSekK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEMlFR%2FbtsMZ4PHsW9%2FttJYTkZxSwlbzpk6jYSekK%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;1270&quot; height=&quot;696&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;696&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: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위 이미지는 Github Actions의 워크플로우 결과 이미지인데 6시간 동안 실행을 한 뒤, 시간이 초과되어 자동으로 실패한 것을 볼 수 있습니다.&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: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이때 교착 상태는 클래스와 메서드에서 모두 병렬화를 활성화시키고, 테스트 클래스의 수보다 스레드 풀의 크기가 작을 때 발생하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;클래스와 메서드 단위에서 병렬화를 모두 활성화시키면 다음과 같은 순서로 실행이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;상위 작업(클래스) 제출&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;각 상위 작업에 대해 실행 시작&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;상위 작업에 대한 하위작업(메서드) 제출&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;각 하위 작업에 대해 실행 시작&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;상위 작업(클래스)은 하위 작업(메서드) 완료 대기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;상위 작업 완료 대기 후, 결과 반환&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이때 교착 상태는 테스트 클래스의 개수보다 스레드 풀의 크기가 작을 때 발생했습니다. (동일할 때도 발생했습니다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;자세히 디버깅한 결과, 현재 사용 중인 스레드 풀의 특성과 연관하여 교착 상태가 발생한 원인을 확인할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이전 글에서 작성했듯이, 현재 스레드 풀은 고정된 크기를 가지는 newFixedThreadPool을 사용 중입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;해당 스레드 풀의 대표적인 특징으로는 작업이 추가되더라도 지정된 스레드 수를 초과하지 않으며, 초과된 작업은 대기열에 저장되는 특성을 가지고 있습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;실행 순서 중, 2번(각 상위 작업에 대해 실행 시작)을 할 때 하나의 스레드가 할당이 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 해당 스레드는 모든 하위 작업이 완료되어 상위 작업이 완료가 되면 스레드가 반환이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;만약 스레드 풀의 크기가 3이고, 테스트 클래스의 개수가 3, 각 하위 메서드의 개수도 3이라면 모든 스레드는 상위 클래스가 점유하게 되어 하위 메서드는 작업 큐에만 저장이 될 뿐 실행이 되지 않아 교착상태가 발생하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;교착상태가 일어나는 4가지 조건과 함께 더 자세히 설명드리겠습니다.&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: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;우선, 교착 상태가 일어나는 4가지 조건은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;교착상태가 일어나는 4가지 조건&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. 상호 배제 : 자원을 한 번에 하나의 프로세스(스레드)만 사용할 수 있음&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. 점유 대기 : 이미 자원을 가진 상태에서 다른 자원을 기다리는 상황&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;3. 비선점 : 다른 프로세스가 점유한 자원을 강제로 빼앗을 수 없음&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;4. 순환 대기 : 프로세스들이 순환적으로 서로의 자원을 기다리는 상황&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: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그러면 현재의 상태에 대입해 설명하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;상호 배제 : 스레드는 한 번에 하나의 작업만 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;점유 대기 : 상위 작업(클래스)은 각각 스레드를 점유한 상태에서 하위 작업(메서드)을 제출하고 완료를 기다립니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;비선점 : 현재 사용 중인 스레드는 점유된 스레드를 강제로 해제하거나 다른 작업에 재할당 하지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;순환 대기 : 상위 작업(클래스)은 각각 하위 작업(메서드)의 완료를 기다리고, 하위 작업(메서드)은 상위 작업(클래스)이 완료되어 스레드가 반환되기를 기다리고 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&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;1366&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhDcY0/btsM232jdYq/4TEyhMpGIaK9qnNYVMxGqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhDcY0/btsM232jdYq/4TEyhMpGIaK9qnNYVMxGqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhDcY0/btsM232jdYq/4TEyhMpGIaK9qnNYVMxGqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhDcY0%2FbtsM232jdYq%2F4TEyhMpGIaK9qnNYVMxGqk%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;1366&quot; height=&quot;830&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 메인테이너님과 현재 상황에 대해 토론을 한 결과 ForkJoinPool을 사용하기로 결정이 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;결국 돌고 돌아 ForkJoinPool...&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: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ForkJoinPool은 작업 큐에 작업이 남아있는 경우 스레드가 블로킹되지 않고 다른 작업을 처리하는 특성을 가지고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 교착상태가 발생하는 4가지 조건 중, 순환 대기를 해결할 수 있다는 장점이 있습니다.&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;color: #000000; text-align: left; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;각각의 작업이 서로의 작업의 완료를 기다리고 있는 상태가 순환 대기인데 ForkJoinPool을 활용하여 서로의 작업의 완료를 기다리지 않고 다른 작업을 처리하도록 구현하였습니다.&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;color: #000000; text-align: left; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Jupiter 엔진의 경우 이미 병렬화에서 ForkJoinPool을 사용하고 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Vintage 엔진의 경우 단순히 ForkJoinPool을 활용했는데 Jupiter 엔진의 경우 자원 잠금, 작업 연기 등 흥미로운 로직이 많았습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;관심이 있으신 분이 계시다면 해당 내용도 한 번 분석해 글을 작성해 보겠습니다.&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit5/blob/main/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java&quot;&gt;https://github.com/junit-team/junit5/blob/main/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1743312790806&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;junit5/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorSe&quot; data-og-description=&quot;✅ The 5th major version of the programmer-friendly testing framework for Java and the JVM - junit-team/junit5&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/junit-team/junit5/blob/main/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java&quot; data-og-url=&quot;https://github.com/junit-team/junit5/blob/main/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nqFol/hyYvhCVYV5/pgTsu6ZPgS0uWGGVqdNyfk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ggHFu/hyYvh3ZXXU/M0YkbD7Ksbd5t5KpgNUvDk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit5/blob/main/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/junit-team/junit5/blob/main/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nqFol/hyYvhCVYV5/pgTsu6ZPgS0uWGGVqdNyfk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ggHFu/hyYvh3ZXXU/M0YkbD7Ksbd5t5KpgNUvDk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;junit5/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorSe&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;✅ The 5th major version of the programmer-friendly testing framework for Java and the JVM - junit-team/junit5&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 코드는 ForkJoinPool을 활용해 교착상태를 해결하고 메서드 단위의 병렬화를 구현한 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;work stealing을 허용하기 위해 Future.get()을 각각 호출한 것을 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1743312991805&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void setExecutorService(ExecutorService executorService) {
	Runner runner = getRunnerToReport();
	if (runner instanceof ParentRunner) {
		((ParentRunner&amp;lt;?&amp;gt;) runner).setScheduler(new RunnerScheduler() {

			private final List&amp;lt;Future&amp;lt;?&amp;gt;&amp;gt; futures = new CopyOnWriteArrayList&amp;lt;&amp;gt;();

			@Override
			public void schedule(Runnable childStatement) {
				futures.add(executorService.submit(childStatement));
			}

			@Override
			public void finished() {
				ThrowableCollector collector = new OpenTest4JAwareThrowableCollector();
				AtomicBoolean wasInterrupted = new AtomicBoolean(false);
				for (Future&amp;lt;?&amp;gt; future : futures) {
					collector.execute(() -&amp;gt; {
						// We're calling `Future.get()` individually to allow for work stealing
						// in case `ExecutorService` is a `ForkJoinPool`
						try {
							future.get();
						}
						catch (ExecutionException e) {
							throw e.getCause();
						}
						catch (InterruptedException e) {
							wasInterrupted.set(true);
						}
					});
				}
				collector.assertEmpty();
				if (wasInterrupted.get()) {
					logger.warn(() -&amp;gt; &quot;Interrupted while waiting for runner to finish&quot;);
					Thread.currentThread().interrupt();
				}
			}
		});
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 이미지는 교착상태를 해결한 흐름을 나타내는 다이어그램입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VDwXe/btsM1kqm2Lm/4UsFXNF2hdfqIZM6sdHocK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VDwXe/btsM1kqm2Lm/4UsFXNF2hdfqIZM6sdHocK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VDwXe/btsM1kqm2Lm/4UsFXNF2hdfqIZM6sdHocK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVDwXe%2FbtsM1kqm2Lm%2F4UsFXNF2hdfqIZM6sdHocK%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;1434&quot; height=&quot;740&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;740&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;결과적으로는 6시간 동안 실행을 했던 Github Actions도 정상적으로 완료가 되었고, 스레드 풀의 크기에 상관없이 교착상태가 더 이상 발생하지 않고 정상적으로 실행이 되는 것을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아래의 공식 문서에서 클래스, 메서드 수준의 병렬화를 스레드 사용 예시와 함께 볼 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://junit.org/junit5/docs/snapshot/user-guide/index.html#migrating-from-junit4-parallel-execution&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://junit.org/junit5/docs/snapshot/user-guide/index.html#migrating-from-junit4-parallel-execution&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1743418676197&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JUnit 5 User Guide&quot; data-og-description=&quot;Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo&quot; data-og-host=&quot;junit.org&quot; data-og-source-url=&quot;https://junit.org/junit5/docs/snapshot/user-guide/index.html#migrating-from-junit4-parallel-execution&quot; data-og-url=&quot;https://junit.org/junit5/docs/snapshot/user-guide/index.html#migrating-from-junit4-parallel-execution&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OFFbR/hyYBhHvq8V/f0SgZWljkaTYtxkOqWFOl0/img.png?width=1592&amp;amp;height=1802&amp;amp;face=0_0_1592_1802,https://scrap.kakaocdn.net/dn/u774s/hyYxGB95lS/JRkAhDVI0WafttKFPozhf1/img.png?width=1548&amp;amp;height=898&amp;amp;face=0_0_1548_898,https://scrap.kakaocdn.net/dn/vKSXX/hyYyLilcwf/cqMF4Km7SPLFPmcEYcpqUK/img.png?width=997&amp;amp;height=1173&amp;amp;face=0_0_997_1173&quot;&gt;&lt;a href=&quot;https://junit.org/junit5/docs/snapshot/user-guide/index.html#migrating-from-junit4-parallel-execution&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://junit.org/junit5/docs/snapshot/user-guide/index.html#migrating-from-junit4-parallel-execution&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OFFbR/hyYBhHvq8V/f0SgZWljkaTYtxkOqWFOl0/img.png?width=1592&amp;amp;height=1802&amp;amp;face=0_0_1592_1802,https://scrap.kakaocdn.net/dn/u774s/hyYxGB95lS/JRkAhDVI0WafttKFPozhf1/img.png?width=1548&amp;amp;height=898&amp;amp;face=0_0_1548_898,https://scrap.kakaocdn.net/dn/vKSXX/hyYyLilcwf/cqMF4Km7SPLFPmcEYcpqUK/img.png?width=997&amp;amp;height=1173&amp;amp;face=0_0_997_1173');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JUnit 5 User Guide&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;junit.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다음 글 예고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글이 올라왔을 때는 아래의 PR이 병합되었을 것이라고 생각합니다. (현재 문서화 작업 마무리 중)&lt;/p&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;ExtensionContext&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 PR은 JUnit 플랫폼에서 자원 공유를 &lt;b&gt;request&lt;/b&gt; / &lt;b&gt;session&lt;/b&gt; 이라는 두 가지 범위로 나누어 관리할 수 있도록 하는 기능을 추가하고 있습니다. 이를 통해 자원을 더 세밀하게 제어하고, 테스트 실행 간의 자원의 재사용성을 높이는 것이 목표입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2534&quot; data-origin-height=&quot;1188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fczis/btsM0M8Sxds/tvaarXBs2UnycWUKCXc0r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fczis/btsM0M8Sxds/tvaarXBs2UnycWUKCXc0r0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fczis/btsM0M8Sxds/tvaarXBs2UnycWUKCXc0r0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFczis%2FbtsM0M8Sxds%2FtvaarXBs2UnycWUKCXc0r0%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;2534&quot; height=&quot;1188&quot; data-origin-width=&quot;2534&quot; data-origin-height=&quot;1188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기능은 잘 모르고 사용한다면 기존의 &lt;b&gt;ExtensionContext&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각종 tech blog들에서도 분명 해당 기능이 merge가 된 후 배포가 되면 자세히 다룰 것으로 예상합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 문서와 코드를 보고 글을 작성하는 분들보다는 직접 만든 사람이 더 잘 알 것이라고 생각합니다. ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글도 devocean와 같이 접근성이 좋은 곳에 외부 기고를 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>Backend</category>
      <category>junit5</category>
      <category>오픈소스 기여</category>
      <author>코딩하는_대학생</author>
      <guid isPermaLink="true">https://solution-is-here.tistory.com/231</guid>
      <comments>https://solution-is-here.tistory.com/231#entry231comment</comments>
      <pubDate>Sun, 30 Mar 2025 14:15:12 +0900</pubDate>
    </item>
    <item>
      <title>내가 JUnit5에 병렬화를 도입한 이야기 - 클래스 단위</title>
      <link>https://solution-is-here.tistory.com/230</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, Junit-team/junit5, spring/spring-boot, apache/seata, naver/fixture-monkey 등 여러 오픈소스 프로젝트에 기여한 &lt;a href=&quot;https://github.com/YongGoose&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;YongGoose&lt;/a&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니 JUnit5에는 전 세계에서 35번째로 많이 기여를 했더라고요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Commit 순이 아닌, Additions 순으로 하면 17번째입니다. &lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbm7gS/btsMtF9ec32/0IXl12QXkM4y3eJoDDPli0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbm7gS/btsMtF9ec32/0IXl12QXkM4y3eJoDDPli0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbm7gS/btsMtF9ec32/0IXl12QXkM4y3eJoDDPli0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbm7gS%2FbtsMtF9ec32%2F0IXl12QXkM4y3eJoDDPli0%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;938&quot; height=&quot;462&quot; data-origin-width=&quot;938&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 제가 JUnit5 Vintage엔진에 병렬화를 도입한 이야기를 해보려고 합니다.&lt;/p&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-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;잠깐만 JUnit Vintage Engine이 뭐야..?&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;우선, 간단히 JUnit5의 구조 및 Vintage Engine에 대해 설명을 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qH3v6/btsMuwcNYDc/kPV5NwaH1HFXxtAGFWvDr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qH3v6/btsMuwcNYDc/kPV5NwaH1HFXxtAGFWvDr0/img.png&quot; data-alt=&quot;출처 : https://www.swtestacademy.com/junit-5-architecture/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qH3v6/btsMuwcNYDc/kPV5NwaH1HFXxtAGFWvDr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqH3v6%2FbtsMuwcNYDc%2FkPV5NwaH1HFXxtAGFWvDr0%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;600&quot; height=&quot;478&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://www.swtestacademy.com/junit-5-architecture/&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;b&gt;JUnit5&lt;/b&gt;은 테스트 프레임워크를 JVM에서 실행하기 위해 &lt;b&gt;JUnit Platform&lt;/b&gt;을 제공합니다.&lt;br /&gt;&lt;b&gt;Platform&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;TestEngine API 정의&lt;/b&gt; : 플랫폼에서 실행될 테스트 프레임워크를 개발할 수 있는 API를 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Console Launcher 제공&lt;/b&gt; : 명령줄에서 플랫폼을 실행할 수 있게 해줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IDE 및 빌드 도구 지원&lt;/b&gt; : Intellij, Eclipse, VSCode 등에서 테스트를 실행할 수 있게 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TestEngine&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;(Discovery) : Launcher, IDE가 테스트 대상을 인식할 수 있게 도와줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 실행&lt;/b&gt;(Execution) : 발견된 테스트들을 실행할 수 있게 도와줍니다. (시작, 완료, 성공, 실패등의 이벤트를 발생시킵니다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JUnit Jupiter&lt;/b&gt;는 &lt;b&gt;JUnit5&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;Jupiter TestEngine&lt;/b&gt;을 통해 최신 방식으로 테스트를 작성하고 확장할 수 있게 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JUnit Vintage&lt;/b&gt;는 기존의 JUnit 3, JUnit 4 기반 테스트를 JUnit 5 플랫폼에서 실행할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit user-guide에서 더욱 자세히 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://junit.org/junit5/docs/current/user-guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://junit.org/junit5/docs/current/user-guide/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740209041164&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JUnit 5 User Guide&quot; data-og-description=&quot;Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo&quot; data-og-host=&quot;junit.org&quot; data-og-source-url=&quot;https://junit.org/junit5/docs/current/user-guide/&quot; data-og-url=&quot;https://junit.org/junit5/docs/current/user-guide/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/feYA4/hyYjhhhacC/KPtrNWkPVUz5qRVKAHXaeK/img.png?width=997&amp;amp;height=1173&amp;amp;face=0_0_997_1173,https://scrap.kakaocdn.net/dn/bLVFQu/hyYfRLjNdM/41drbL3yBm5pMKRv4YKnlk/img.png?width=862&amp;amp;height=834&amp;amp;face=0_0_862_834&quot;&gt;&lt;a href=&quot;https://junit.org/junit5/docs/current/user-guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://junit.org/junit5/docs/current/user-guide/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/feYA4/hyYjhhhacC/KPtrNWkPVUz5qRVKAHXaeK/img.png?width=997&amp;amp;height=1173&amp;amp;face=0_0_997_1173,https://scrap.kakaocdn.net/dn/bLVFQu/hyYfRLjNdM/41drbL3yBm5pMKRv4YKnlk/img.png?width=862&amp;amp;height=834&amp;amp;face=0_0_862_834');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JUnit 5 User Guide&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;junit.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서 어떤 기여를 했는데?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배경에 대해 자세히 설명하자면, JUnit Jupiter-Engine에는 테스트를 병렬로 실행시킬 수 있는 기능이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740209113204&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JUnit 5 User Guide&quot; data-og-description=&quot;Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo&quot; data-og-host=&quot;junit.org&quot; data-og-source-url=&quot;https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution&quot; data-og-url=&quot;https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JUnit 5 User Guide&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;junit.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Vintage-Engine에는 테스트를 병렬로 실행시킬 수 있는 기능이 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 모든 테스트는 순차적으로 실행이 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;아래의 이슈에서 자세히 볼 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Issue: &lt;a href=&quot;https://github.com/junit-team/junit5/issues/2229&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/junit-team/junit5/issues/2229&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740206539997&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Support some level of parallelization in junit-vintage-engine &amp;middot; Issue #2229 &amp;middot; junit-team/junit5&quot; data-og-description=&quot;Goal Add support for some level of parallelization inside the Junit Vintage engine. Ideally the level of configuration should be similar to maven surefire parallel options. Why When including junit...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/junit-team/junit5/issues/2229&quot; data-og-url=&quot;https://github.com/junit-team/junit5/issues/2229&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vtfRb/hyYjpGnTmD/uzhXzoT9dInHnd4OjS7ws1/img.png?width=1200&amp;amp;height=600&amp;amp;face=997_133_1042_181,https://scrap.kakaocdn.net/dn/Cs3vD/hyYf4cPvUL/4VplU2oCOytfY9Ml5CZkI0/img.png?width=1200&amp;amp;height=600&amp;amp;face=997_133_1042_181&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit5/issues/2229&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/junit-team/junit5/issues/2229&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vtfRb/hyYjpGnTmD/uzhXzoT9dInHnd4OjS7ws1/img.png?width=1200&amp;amp;height=600&amp;amp;face=997_133_1042_181,https://scrap.kakaocdn.net/dn/Cs3vD/hyYf4cPvUL/4VplU2oCOytfY9Ml5CZkI0/img.png?width=1200&amp;amp;height=600&amp;amp;face=997_133_1042_181');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Support some level of parallelization in junit-vintage-engine &amp;middot; Issue #2229 &amp;middot; junit-team/junit5&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Goal Add support for some level of parallelization inside the Junit Vintage engine. Ideally the level of configuration should be similar to maven surefire parallel options. Why When including junit...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 시퀀스 다이어그램처럼, for 반복문을 사용하여 테스트 요소를 순차적으로 실행하고, 처리한 요소를 제거하는 방식으로 진행됩니다.&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;2254&quot; data-origin-height=&quot;1350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sACPb/btsMr0NGI91/xWtiBDlAEus8qNmO5WAiq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sACPb/btsMr0NGI91/xWtiBDlAEus8qNmO5WAiq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sACPb/btsMr0NGI91/xWtiBDlAEus8qNmO5WAiq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsACPb%2FbtsMr0NGI91%2FxWtiBDlAEus8qNmO5WAiq0%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;2254&quot; height=&quot;1350&quot; data-origin-width=&quot;2254&quot; data-origin-height=&quot;1350&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;아래의 사진과 같이 JUnit Vintage 엔진에서도 병렬화를 지원해 주라는 요구가 많아 제가 작업을 하게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcJIbd/btsMshBO37d/TrHOTimH4mhIKJXrZJH8nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcJIbd/btsMshBO37d/TrHOTimH4mhIKJXrZJH8nK/img.png&quot; data-alt=&quot;지피티 사진 잘 만드네요.....&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcJIbd/btsMshBO37d/TrHOTimH4mhIKJXrZJH8nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcJIbd%2FbtsMshBO37d%2FTrHOTimH4mhIKJXrZJH8nK%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;1024&quot; height=&quot;1024&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 병렬화를 도입하기 위해 어디에 도입을 하면 좋을지에 대해 생각을 해보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1888&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7QyNd/btsMtHTsaCz/NzJrWiZuvEidK8Lj4sIj01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7QyNd/btsMtHTsaCz/NzJrWiZuvEidK8Lj4sIj01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7QyNd/btsMtHTsaCz/NzJrWiZuvEidK8Lj4sIj01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7QyNd%2FbtsMtHTsaCz%2FNzJrWiZuvEidK8Lj4sIj01%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;1888&quot; height=&quot;886&quot; data-origin-width=&quot;1888&quot; data-origin-height=&quot;886&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;이때, 이슈에서 JUnit5의 메인테이너인 Marc가 RunnerExecutor를 병렬로 호출하라는 힌트를 주는 메시지를 확인했습니다.&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;1084&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MofDT/btsMr0G1paZ/0DZbAoXKjbjf966oERZAO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MofDT/btsMr0G1paZ/0DZbAoXKjbjf966oERZAO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MofDT/btsMr0G1paZ/0DZbAoXKjbjf966oERZAO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMofDT%2FbtsMr0G1paZ%2F0DZbAoXKjbjf966oERZAO1%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;1084&quot; height=&quot;318&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;318&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: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;병렬화를 위해 스레드 풀을 생성하여 여러 작업을 동시에 실행하는 로직을 구현하기로 결정했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 테스트라는 큰 단위를 여러 개의 작은 작업으로 분할하여 실행함으로써, ForkJoinPool이 제공하는 work-stealing 기능을 활용해 성능 향상을 기대하며 이를 적용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 메인테이너님께서는 ForkJoinPool 사용이 현재 구조에서는 별다른 이점을 제공하지 않는다는 의견을 주셨습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqP9NA/btsMsabwgEq/RT313AVwdaw4nnx2jJ2YR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqP9NA/btsMsabwgEq/RT313AVwdaw4nnx2jJ2YR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqP9NA/btsMsabwgEq/RT313AVwdaw4nnx2jJ2YR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqP9NA%2FbtsMsabwgEq%2FRT313AVwdaw4nnx2jJ2YR0%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;1880&quot; height=&quot;494&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;494&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;그래서 왜 ForkJoinPool을 사용하는 것이 이점이 없을까..?라는 고민을 해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, ForkJoinPool은 하나의 Task가 여러 개의 SubTask로 나뉘는 작업일 때 효율적입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여러 개의 SubTask로 나눠야 다른 스레드가 더 이상 처리할 작업이 없을 때 작업을 가져올 수 있기 때문입니다.&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;1200&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IhlZT/btsMtaBOWsk/bslhf7A4hoZqRSrKk12v30/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IhlZT/btsMtaBOWsk/bslhf7A4hoZqRSrKk12v30/img.jpg&quot; data-alt=&quot;출처 : Infoworld&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IhlZT/btsMtaBOWsk/bslhf7A4hoZqRSrKk12v30/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIhlZT%2FbtsMtaBOWsk%2Fbslhf7A4hoZqRSrKk12v30%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;543&quot; height=&quot;460&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : Infoworld&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;&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;처음에는 테스트(&lt;b&gt;클래스&lt;/b&gt;)가 여러 작업(&lt;b&gt;메서드&lt;/b&gt;)으로 나누어져 병렬 실행될 것으로 기대하며 작업을 진행했습니다. 하지만 자세히 살펴보니, 실제로는 각 테스트(&lt;b&gt;클래스&lt;/b&gt;)가 하나의 독립된 작업으로 구성되어 있다는 것을 알게 되었습니다. 이런 이유로 메인테이너님께서는 ForkJoinPool을 사용하는 것에는 별다른 이점이 없다고 말씀하셨던 것입니다.&lt;/span&gt;&lt;span style=&quot;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 data-ke-style=&quot;style2&quot;&gt;결과적으로 고정된 크기를 가지는 newFixedThreadPool을 통해 스레드 풀을 생성했습니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG0XNx/btsMurily6z/kgsfXSV9heGnUki8tfnYXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG0XNx/btsMurily6z/kgsfXSV9heGnUki8tfnYXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG0XNx/btsMurily6z/kgsfXSV9heGnUki8tfnYXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG0XNx%2FbtsMurily6z%2FkgsfXSV9heGnUki8tfnYXk%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;1892&quot; height=&quot;752&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;752&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;1908&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K7GcZ/btsMsiOcl3X/zUZYvlGKcEERN55cNYpRtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K7GcZ/btsMsiOcl3X/zUZYvlGKcEERN55cNYpRtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K7GcZ/btsMsiOcl3X/zUZYvlGKcEERN55cNYpRtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK7GcZ%2FbtsMsiOcl3X%2FzUZYvlGKcEERN55cNYpRtK%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;1908&quot; height=&quot;528&quot; data-origin-width=&quot;1908&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 하나의 테스트를 전체적으로 실행하는 것보다 각 메서드를 개별적으로 실행하는 것이 성능 면에서 분명히 이점이 있을 것이라고 생각했습니다. 그래서 메인테이너님께 코멘트를 통해 제안을 했습니다.&lt;/p&gt;
&lt;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;1626&quot; data-origin-height=&quot;876&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6iMJW/btsMt84nhqh/TEVKuwoLl7krPOGkveKHy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6iMJW/btsMt84nhqh/TEVKuwoLl7krPOGkveKHy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6iMJW/btsMt84nhqh/TEVKuwoLl7krPOGkveKHy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6iMJW%2FbtsMt84nhqh%2FTEVKuwoLl7krPOGkveKHy0%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;1626&quot; height=&quot;876&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;876&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코멘트의 길이가 길어 한 페이지에 모두 담기지가 않아 링크를 별도로 남깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit5/pull/4135#issuecomment-2543164929&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/junit-team/junit5/pull/4135#issuecomment-2543164929&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740224097323&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Support parallelization in junit-vintage-engine by YongGoose &amp;middot; Pull Request #4135 &amp;middot; junit-team/junit5&quot; data-og-description=&quot;Overview Resolves #2229 This PR implements parallel execution support for the JUnit Vintage engine, addressing issue #2229. Implemented concurrent execution of test descriptors when parallel execu...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/junit-team/junit5/pull/4135#issuecomment-2543164929&quot; data-og-url=&quot;https://github.com/junit-team/junit5/pull/4135&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iy4ti/hyYjNtHEem/zEHzWsIvFLZW1oKkRbxvWk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/PXxoG/hyYji8mnNw/2QzfpaOLxyptcfSgcj7Ctk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit5/pull/4135#issuecomment-2543164929&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/junit-team/junit5/pull/4135#issuecomment-2543164929&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iy4ti/hyYjNtHEem/zEHzWsIvFLZW1oKkRbxvWk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/PXxoG/hyYji8mnNw/2QzfpaOLxyptcfSgcj7Ctk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Support parallelization in junit-vintage-engine by YongGoose &amp;middot; Pull Request #4135 &amp;middot; junit-team/junit5&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Overview Resolves #2229 This PR implements parallel execution support for the JUnit Vintage engine, addressing issue #2229. Implemented concurrent execution of test descriptors when parallel execu...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제안을 한 덕분에 저는 별도의 PR을 통해 메서드 단위 병렬화를 진행할 수 있었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;다음 글에서 자세히 소개하겠습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 코드는 비동기를 통해 병렬화를 적용한 부분입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1740224351198&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private boolean executeInParallel(VintageEngineDescriptor engineDescriptor,
        EngineExecutionListener engineExecutionListener, ExecutionRequest request) {
    ExecutorService executorService = Executors.newFixedThreadPool(getThreadPoolSize(request));
    RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener);

    List&amp;lt;CompletableFuture&amp;lt;Void&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();
    for (Iterator&amp;lt;TestDescriptor&amp;gt; iterator = engineDescriptor.getModifiableChildren().iterator(); iterator.hasNext();) {
        TestDescriptor descriptor = iterator.next();
        CompletableFuture&amp;lt;Void&amp;gt; future = CompletableFuture.runAsync(() -&amp;gt; {
            runnerExecutor.execute((RunnerTestDescriptor) descriptor);
        }, executorService);

        futures.add(future);
        iterator.remove();
    }

    CompletableFuture&amp;lt;Void&amp;gt; allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture&amp;lt;?&amp;gt;[0]));
    boolean wasInterrupted = false;
    try {
        allOf.get();
    }
    catch (InterruptedException e) {
        logger.warn(e, () -&amp;gt; &quot;Interruption while waiting for parallel test execution to finish&quot;);
        wasInterrupted = true;
    }
    catch (ExecutionException e) {
        throw ExceptionUtils.throwAsUncheckedException(e.getCause());
    }
    finally {
        shutdownExecutorService(executorService);
    }
    return wasInterrupted;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 설명은 아래의 이미지에서 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2342&quot; data-origin-height=&quot;1318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/do8kPM/btsMt8QO7Yi/nzvCpjmyj3QnRqtEkhmtE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/do8kPM/btsMt8QO7Yi/nzvCpjmyj3QnRqtEkhmtE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/do8kPM/btsMt8QO7Yi/nzvCpjmyj3QnRqtEkhmtE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdo8kPM%2FbtsMt8QO7Yi%2FnzvCpjmyj3QnRqtEkhmtE0%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;933&quot; height=&quot;525&quot; data-origin-width=&quot;2342&quot; data-origin-height=&quot;1318&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;전체적인 코드 및 제가 메인테이너님과 나눈 대화를 볼 수 있는 Pull Request입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pull Request: &lt;a href=&quot;https://github.com/junit-team/junit5/pull/4135&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/junit-team/junit5/pull/4135&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740206577195&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Support parallelization in junit-vintage-engine by YongGoose &amp;middot; Pull Request #4135 &amp;middot; junit-team/junit5&quot; data-og-description=&quot;Overview Resolves #2229 This PR implements parallel execution support for the JUnit Vintage engine, addressing issue #2229. Implemented concurrent execution of test descriptors when parallel execu...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/junit-team/junit5/pull/4135&quot; data-og-url=&quot;https://github.com/junit-team/junit5/pull/4135&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XBAEr/hyYf09lMVJ/V37zmSvveoi25K5mmbFw8K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cmmHfU/hyYjJSkHG6/5foMeWlORpNJ7nXUQKSAp1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit5/pull/4135&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/junit-team/junit5/pull/4135&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XBAEr/hyYf09lMVJ/V37zmSvveoi25K5mmbFw8K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cmmHfU/hyYjJSkHG6/5foMeWlORpNJ7nXUQKSAp1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Support parallelization in junit-vintage-engine by YongGoose &amp;middot; Pull Request #4135 &amp;middot; junit-team/junit5&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Overview Resolves #2229 This PR implements parallel execution support for the JUnit Vintage engine, addressing issue #2229. Implemented concurrent execution of test descriptors when parallel execu...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서 지금 사용할 수 있어?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어제, JUnit5 5.12 버전이 배포되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.12 버전에서 제가 생성한 기능(JUnit-vintage 클래스/메서드 단위 병렬화)을 사용할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JUnit-vintage 병렬화 기능을 사용하고 싶은데 질문이 있으신 분은 깃허브에 있는 이메일로 연락 주시면 감사하겠습니다.&lt;br /&gt;또한 커피챗, 오픈소스 관련 얘기도 환영합니다.  &lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPRnfn/btsMsaJo26J/1DkjQXshkfF6tR9xi1Zji0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPRnfn/btsMsaJo26J/1DkjQXshkfF6tR9xi1Zji0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPRnfn/btsMsaJo26J/1DkjQXshkfF6tR9xi1Zji0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPRnfn%2FbtsMsaJo26J%2F1DkjQXshkfF6tR9xi1Zji0%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;1862&quot; height=&quot;362&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://junit.org/junit5/docs/5.12.0/release-notes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://junit.org/junit5/docs/5.12.0/release-notes/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740225635271&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JUnit 5 Release Notes&quot; data-og-description=&quot;Date of Release: September 25, 2024 Scope: Bug fixes and enhancements since 5.11.0 For a complete list of all closed issues and pull requests for this release, consult the 5.11.1 milestone page in the JUnit repository on GitHub. JUnit Platform Bug Fixes Fi&quot; data-og-host=&quot;junit.org&quot; data-og-source-url=&quot;https://junit.org/junit5/docs/5.12.0/release-notes/&quot; data-og-url=&quot;https://junit.org/junit5/docs/5.12.0/release-notes/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://junit.org/junit5/docs/5.12.0/release-notes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://junit.org/junit5/docs/5.12.0/release-notes/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JUnit 5 Release Notes&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Date of Release: September 25, 2024 Scope: Bug fixes and enhancements since 5.11.0 For a complete list of all closed issues and pull requests for this release, consult the 5.11.1 milestone page in the JUnit repository on GitHub. JUnit Platform Bug Fixes Fi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;junit.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend</category>
      <category>junit 기여</category>
      <category>junit-vintage</category>
      <category>junit5</category>
      <category>병렬화</category>
      <category>오픈소스</category>
      <category>오픈소스 기여</category>
      <author>코딩하는_대학생</author>
      <guid isPermaLink="true">https://solution-is-here.tistory.com/230</guid>
      <comments>https://solution-is-here.tistory.com/230#entry230comment</comments>
      <pubDate>Sat, 15 Feb 2025 22:06:35 +0900</pubDate>
    </item>
    <item>
      <title>다양한 시각에서 바라본 Java (3) - 동시성 프로그래밍</title>
      <link>https://solution-is-here.tistory.com/229</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아래의 링크는 JUnit 5에서 JUnit 3, 4의 테스트 코드 실행에 병렬화를 지원하자고 하는 이슈입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit5/issues/2229&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/junit-team/junit5/issues/2229&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732160317587&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Support some level of parallelization in junit-vintage-engine &amp;middot; Issue #2229 &amp;middot; junit-team/junit5&quot; data-og-description=&quot;Goal Add support for some level of parallelization inside the Junit Vintage engine. Ideally the level of configuration should be similar to maven surefire parallel options. Why When including junit...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/junit-team/junit5/issues/2229&quot; data-og-url=&quot;https://github.com/junit-team/junit5/issues/2229&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkDoOE/hyXzHhW4kO/4ypnpSjHvPj5ZcNUPNcsN0/img.png?width=1200&amp;amp;height=600&amp;amp;face=997_133_1042_181,https://scrap.kakaocdn.net/dn/btSJWD/hyXDaCJUJG/QyxLkkgjTZmLd2msoHerr1/img.png?width=1200&amp;amp;height=600&amp;amp;face=997_133_1042_181&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit5/issues/2229&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/junit-team/junit5/issues/2229&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkDoOE/hyXzHhW4kO/4ypnpSjHvPj5ZcNUPNcsN0/img.png?width=1200&amp;amp;height=600&amp;amp;face=997_133_1042_181,https://scrap.kakaocdn.net/dn/btSJWD/hyXDaCJUJG/QyxLkkgjTZmLd2msoHerr1/img.png?width=1200&amp;amp;height=600&amp;amp;face=997_133_1042_181');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Support some level of parallelization in junit-vintage-engine &amp;middot; Issue #2229 &amp;middot; junit-team/junit5&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Goal Add support for some level of parallelization inside the Junit Vintage engine. Ideally the level of configuration should be similar to maven surefire parallel options. Why When including junit...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;갑자기 해당 이슈를 설명하는 이유는 제가 기여하고 있는 이슈이기 때문입니다....ㅎ&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해당 이슈를 작업 중인데 아직 동시성과 병렬성을 완벽하게 이해하지 못 해, 이번 글을 통해 이해 해보려 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;1564&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGtOo/btsKR4CaeqM/Pgscri6dunkdKgZ2ISxvK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGtOo/btsKR4CaeqM/Pgscri6dunkdKgZ2ISxvK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGtOo/btsKR4CaeqM/Pgscri6dunkdKgZ2ISxvK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGtOo%2FbtsKR4CaeqM%2FPgscri6dunkdKgZ2ISxvK0%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;1564&quot; height=&quot;454&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;오픈소스 기여의 길은 험난하네요... 조금이라도 모르는 개념이 있으면 날카롭게 지적이 들어옵니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. 동시성과 병렬성&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우선 동시성과 병렬성의 공통점은 동시에 실행되는 것처럼 보인다는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만, 자세히 살펴보면 동시성은 정확히 말해 동시에 실행되는 것은 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여러 작업들이 시간을 두고 실행이 되어 사용자로 하여끔 동시에 실행되는 것처럼 보이게 하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&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;700&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sRLRJ/btsKQQriENB/wXbpAAhFQ3Go0yUPlba820/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sRLRJ/btsKQQriENB/wXbpAAhFQ3Go0yUPlba820/img.png&quot; data-alt=&quot;출처 : https://velog.io/@nyong_i/ttiw2pf5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sRLRJ/btsKQQriENB/wXbpAAhFQ3Go0yUPlba820/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsRLRJ%2FbtsKQQriENB%2FwXbpAAhFQ3Go0yUPlba820%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;700&quot; height=&quot;250&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://velog.io/@nyong_i/ttiw2pf5&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그림을 보면 순차, 동시성, 병렬성이 나와있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;순차적으로 하는 것은 하나의 작업이 끝나면 그 다음 작업으로 넘어가는 것을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 동시성은 하나의 스레드에서 시간을 두고 여러 작업을 진행해 동시에 진행되는 것처럼 보이게 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그에 비해 병렬성은 두 개의 스레드에서 동시에 처리하는 것을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;단일 스레드의 관점에서 본 동시성 : 하나의 스레드에서 시간을 두고 여러 작업을 진행해 동시에 진행되는 것처럼 보이게 함&lt;br /&gt;멀티 스레드의 관점에서 본 동시성 : 하나의 CPU가 Context Switch를 통해 여러 스레드의 작업이 동시에 진행되는 것처럼 보이게 함&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;동시성 문제란?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그러면 동시성 문제는 무엇일까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;동시성 문제는 여러 프로세스나 스레드가 동시에 같은 데이터에 접근하려 할 때 발생해 개발자의 의도와 다르게 작동하는 문제를 뜻합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. 원자성 문제&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;원자성 문제는 여러 개의 연산이 하나의 단위 작업으로 취급되어야 할 때 작업이 중간에 중단되거나 일부만 실행되는 상황을 말합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아래의 코드는 싱글 코어에서 여러 개의 스레드를 실행시킬 때 발생한 원자성 문제를 나타낸 코드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732163264834&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Counter {
	private int count = 0;

	public void increment() {
		int temp = count;
		temp = temp + 1;
		count = temp;
	}

	public long getCount() {
		return count;
	}
}

---

public class ConcurrencyExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread threadA = new Thread(() -&amp;gt; {
            for (int i = 0; i &amp;lt; 1000; i++) {
                counter.increment();
            }
        });

        Thread threadB = new Thread(() -&amp;gt; {
            for (int i = 0; i &amp;lt; 1000; i++) {
                counter.increment();
            }
        });

        threadA.start();
        threadB.start();

        threadA.join();
        threadB.join();

        System.out.println(&quot;Final count: &quot; + counter.getCount());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;코드에 대해 간단히 설명을 하자면, 스레드 A와 스레드 B가 count라는 공유 자원에 접근해 increment api를 실행시키는 로직입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ThreadA에서 1,000번 ThreadB에서 1,000번 실행을 시키므로 counter의 count 값은 2,000이 되어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 실제로 확인을 해보니 2,000보다 작은 값이 나왔습니다.&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;558&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O71eY/btsKQNuNaPS/orP4dLa77xnXytVCkPTLOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O71eY/btsKQNuNaPS/orP4dLa77xnXytVCkPTLOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O71eY/btsKQNuNaPS/orP4dLa77xnXytVCkPTLOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO71eY%2FbtsKQNuNaPS%2ForP4dLa77xnXytVCkPTLOk%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;558&quot; height=&quot;150&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;150&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그러면 왜 해당 로직에서 Final count의 값이 1318이 나왔는지 설명하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스레드 A가 count 값(0)을 읽음&lt;/li&gt;
&lt;li&gt;컨텍스트 스위칭 발생, 스레드 B로 전환&lt;/li&gt;
&lt;li&gt;스레드 B가 count 값(여전히 0)을 읽음&lt;/li&gt;
&lt;li&gt;스레드 B가 값을 1 증가시키고 저장 (count는 1)&lt;/li&gt;
&lt;li&gt;스레드 A로 다시 전환&lt;/li&gt;
&lt;li&gt;스레드 A가 이전에 읽은 값(0)을 1 증가시키고 저장 (count는 여전히 1)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;쉽게 말을 하면 하나의 스레드에서 count의 값을 변경하는 도중에 다른 스레드로 전환이 돼, 문제가 발생하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 원자적으로 처리를 하지 못해 문제가 발생하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때 원자적이란?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하나의 작업이 중간에 끊기지 않고 완전히 수행되거나 아예 수행되지 않은 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;쉽게 트랜잭션의 원자성을 생각하셔도 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 가시성 문제&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&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;500&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yI9mi/btsKQrsjPai/YkbhNrCc0BaiHd1GsbPrWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yI9mi/btsKQrsjPai/YkbhNrCc0BaiHd1GsbPrWK/img.png&quot; data-alt=&quot;https://velog.io/@sherlockid8/Java-Atomic-type%EC%9D%80-%EC%99%9C-%EC%93%B0%EB%8A%94%EC%A7%80-CAS-Compared-And-Swap%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%B4%EB%9E%80-AtomicInteger%EC%9D%98-%ED%99%9C%EC%9A%A9%EB%B2%95&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yI9mi/btsKQrsjPai/YkbhNrCc0BaiHd1GsbPrWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyI9mi%2FbtsKQrsjPai%2FYkbhNrCc0BaiHd1GsbPrWK%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;500&quot; height=&quot;340&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://velog.io/@sherlockid8/Java-Atomic-type%EC%9D%80-%EC%99%9C-%EC%93%B0%EB%8A%94%EC%A7%80-CAS-Compared-And-Swap%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%B4%EB%9E%80-AtomicInteger%EC%9D%98-%ED%99%9C%EC%9A%A9%EB%B2%95&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그림과 함께 설명하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;각 CPU는 자체 cache를 가지고 있어, 하나의 core에서 변수를 수정해도 다른 코어의 캐시에는 즉시 반영이 안 될수 있습니다. 즉, core 1에서 공유 변수의 값을 2로 변경했는데 RAM에 반영하지 않았다면 core 2는 공유 변수의 값을 조회할 때 2가 아닌 이전의 값으로 조회를 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 것이 가시성 문제입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;동시성 문제 해결 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;동시성 문제를 해결하는 방법에는 여러 가지 방법이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;외부 서비스를 이용하지 않고 Java를 통해 해결하는 방법에 대해 말씀드리겠습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. &lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;synchronized&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;영어를 잘 하시는 분이라면 이미 뜻을 알고 계셨을 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;동기화 됨이라는 뜻을 가졌습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;synchronized 키워드를 메서드, 변수에 붙이게 되면 여러 스레드가 동시에 접근을 하지 못 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 경쟁 상태를 방지하는 것입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;경쟁상태란?&lt;br /&gt;여러 개의 프로세스가 공유 자원에 동시에 접근할 때 실행 순서에 따라 결과값이 달라질 수 있는 현상을 의미합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만, synchronized는 특정 블록 전체를 lock 하기 때문에 다른 thread는 아무 작업을 못 하고 기다리는 상황이 되어 낭비가 심하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;synchronized 키워드는 JVM에서 MonitorEnter과 MoniterExit라는 바이트코드 명령어로 변환됩니다.&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;MoniterEnter
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 모니터 락을 획득하고 임계영역에 진입한다.&lt;/li&gt;
&lt;li&gt;만약 락이 다른 스레드에 의해 점유 중이면, 락을 획득할 때까지 대기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MoniterExit
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 락을 해제하고 임계 영역을 벗어난다.&lt;/li&gt;
&lt;li&gt;대기중인 스레드가 락을 획득할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. &lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;volatile&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;volatile을 사용하면 가시성 문제를 해결할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존 가시성 문제의 원인은 RAM이 아닌 cpu의 cache에 데이터를 저장해, 다른 cpu에서 읽지 못 하는 것이 문제였는데 volatile을 사용하면 cache에 데이터를 저장하는 것이 아니라 RAM에 저장을 하게 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 조회또한 RAM에서 조회를 하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그로 인해 cpu의 cache로 인한 가시성 문제를 해결할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. Atomic type&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Atomic Type에는 여러가지 타입들이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아래의 링크를 보면 java의 concurrent.atomic 패키지 아래에 있는 클래스들을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api///?java/util/concurrent/atomic/package-summary.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api///?java/util/concurrent/atomic/package-summary.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732168222760&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Java Platform SE 8&quot; data-og-description=&quot;&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api///?java/util/concurrent/atomic/package-summary.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api///?java%2Futil%2Fconcurrent%2Fatomic%2Fpackage-summary.html=&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api///?java/util/concurrent/atomic/package-summary.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api///?java/util/concurrent/atomic/package-summary.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java Platform SE 8&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존 synchronized는 경쟁 상태를 제거한다는 장점을 가지고 있었지만, 하나의 block을 전부 blocking 한다는 단점이 있었습니다. 하지만 Atomic은 non-blocking을 통해 동기화 문제를 해결한다는 특징을 가지고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그러기 위해선 CAS 알고리즘을 알아야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아래의 그림은 CAS 알고리즘을 나타낸 그림입니다.&amp;nbsp;&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;1006&quot; data-origin-height=&quot;1104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ds7ayG/btsKRDNpnkK/77KZbkIwWUR5lk53ychnRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ds7ayG/btsKRDNpnkK/77KZbkIwWUR5lk53ychnRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ds7ayG/btsKRDNpnkK/77KZbkIwWUR5lk53ychnRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fds7ayG%2FbtsKRDNpnkK%2F77KZbkIwWUR5lk53ychnRK%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;616&quot; height=&quot;676&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;1104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인자로 기존 값과 변경할 값을 보냅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 현재 메모리가 가지고 있는 값(value)와 비교를 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;비교를 한 후, 기존 값과 현재 메모리가 가지고 있는 값이 동일하면 true를 반환한 뒤, 값을 변경합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;만약 일치하지 않는다면 false를 반환한 뒤, 값을 변경하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때 false를 반환하는 경우에는 값이 원하는대로 변경되지 않은 것이기 때문에 무한 루프를 구성하여 변경된 값을 읽고 다시 같은 시도를 반복하는 로직을 고려해봐도 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;결국 synchronized, volatile, Atomic Type을 통해서 동시성 문제를 해결할 수 있는데 이는 Thread-safe한 상태로 만드는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Thread-safe란 공유 자원에 여러 개의 스레드, 프로세스가 접근해도 예상치 못한 결과나 오류 없이 개발자가 예상한 값이 정확하게 실행되는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클래스&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGtOo/btsKR4CaeqM/Pgscri6dunkdKgZ2ISxvK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGtOo/btsKR4CaeqM/Pgscri6dunkdKgZ2ISxvK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGtOo/btsKR4CaeqM/Pgscri6dunkdKgZ2ISxvK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGtOo%2FbtsKR4CaeqM%2FPgscri6dunkdKgZ2ISxvK0%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;1564&quot; height=&quot;454&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그러면 다시 Comment로 돌아오겠습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;왜? CopyOnWriteArrayList를 사용했나요? 동시 접근이 없어보이는데 단순한 ArrayList로도 해결할 수 있을 것 같습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우선 해당 comment를 이해하기 위해서는 CopyOnWriteArrayList에 대해 이해를 해야 합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CopyOnWriteArrayList vs SynchronizedList&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;CopyOnWriteArrayList는 클래스 이름 그대로 쓰기 작업에서 복사본을 만들고 그 복사본을 수정한 후 참조를 교체하는 방식으로 진행하는 클래스입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때 쓰기 작업에서는 synchronized를 통해 상호 배제를 보장하기 때문에 여러 스레드가 접근해도 하나의 스레드를 제외한 나머지 스레드는 대기를 해야 합니다. 이로 인해 성능상 저하가 생길 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732256473805&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public boolean add(E e) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1);
        es[len] = e;
        setArray(es);
        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 그에 비해 읽기 작업에서는 lock을 사용하지 않아 빠르게 읽을 수 있으며, volatile 키워드를 통해 최신 상태의 배열을 읽을 수 있습니다.&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그에 비해 SynchronizedList는 모든 Api에 synchronized 키워드가 붙어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;mutex를 통해 한 번에 하나의 스레드의 접근만 허용한 것을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public int hashCode() {
    synchronized (mutex) {return list.hashCode();}
}

public E get(int index) {
    synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
    synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
    synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
    synchronized (mutex) {return list.remove(index);}
}

public int indexOf(Object o) {
    synchronized (mutex) {return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
    synchronized (mutex) {return list.lastIndexOf(o);}
}

public boolean addAll(int index, Collection&amp;lt;? extends E&amp;gt; c) {
    synchronized (mutex) {return list.addAll(index, c);}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;쓰기, 읽기 작업에서 둘의 차이를 비교하자면:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우선 쓰기 작업의 경우 둘 다 한 번에 하나의 스레드의 접근만 허용하긴 하나, CopyOnWriteArrayList의 경우 복사를 하기 때문에 SynchronizedList를 사용하는 것이 성능 상 좋을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 읽기 작업의 경우 CopyOnWriteArrayList의 경우 lock이 없으므로 SynchronizedList에 비해 성능상 장점을 보입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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-size=&quot;size18&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다시 코멘트로 돌아와서 설명을 하면 우선 동시 접근이 없다고 하셨습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우선 구현한 코드를 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;ExecutorService executorService = Executors.newFixedThreadPool(getThreadPoolSize());
RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener);

List&amp;lt;CompletableFuture&amp;lt;Void&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();
for (Iterator&amp;lt;TestDescriptor&amp;gt; iterator = engineDescriptor.getModifiableChildren().iterator(); iterator.hasNext();) {
    TestDescriptor descriptor = iterator.next();
    CompletableFuture&amp;lt;Void&amp;gt; future = CompletableFuture.runAsync(() -&amp;gt; {
       RunnerTestDescriptor testDescriptor = (RunnerTestDescriptor) descriptor;
       try {
          runnerExecutor.execute(testDescriptor);
       }
       catch (Exception e) {
          engineExecutionListener.executionSkipped(testDescriptor, e.getMessage());
       }
    }, executorService);

    futures.add(future);
    iterator.remove();
}

CompletableFuture&amp;lt;Void&amp;gt; allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture&amp;lt;?&amp;gt;[0]));
try {
    allOf.get();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;코드가 너무 길어 주요한 부분만 가져왔습니다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 ExecutorService를 통해 스레드 풀을 생성했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이때 newFixedThreadPool을 통해 고정된 크기의 스레드 풀을 생성했습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 CompletableFuture을 통해 비동기 작업을 실행시키고, 해당 작업의 결과를 추가 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해당 코드를 한줄로 평가하자면, 멀티 스레드를 통한 비동기 로직입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그러면 왜 해당 로직에서 동시 접근이 없을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우선, 첫 번째로 execute 안에서 실행되는 task는 원자적입니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JUnit의 테스트는 원자적입니다.&lt;br /&gt;즉, 하나의 테스트는 작업이 중간에 끊기지 않고, 완전히 수행되거나 아예 수행되지 않습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;두 번째로 별도의 별도의 CompletableFuture에서 실행되므로, 동시 접근이 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 이유로 동시 접근이 불가능 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;731&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r1lJS/btsKQFcTPAd/Q31qVXZ4yY5vcmNSamGQGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r1lJS/btsKQFcTPAd/Q31qVXZ4yY5vcmNSamGQGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r1lJS/btsKQFcTPAd/Q31qVXZ4yY5vcmNSamGQGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr1lJS%2FbtsKQFcTPAd%2FQ31qVXZ4yY5vcmNSamGQGk%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;731&quot; height=&quot;394&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 이유로 인해 메인테이너님이 단순한 ArrayList로도 접근이 가능하시다고 한겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ConcurrentHashMap vs SynchronizedMap&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ConcurrentHashMap은 java8 이후로 개별 버킷 수준에서 동시성을 제어하는 방식으로 개선됐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그에 비해 SynchronizedMap은 모든 읽기 작업과 쓰기 작업에 대해 전체 맵을 잠급니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;읽기 작업에서는 SynchronizedMap의 경우 전체 맵에 대한 lock을 설정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만, ConcurrentHashMap의 경우 락을 사용하지 않습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;쓰기 작업의 경우 SynchronizedMap의 경우 전체 맵에 대한 lock을 설정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만, ConcurrentHashMap의 경우 개별 버킷에 대한 락을 설정합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래서 다른 스레드도 병렬로 사용 가능합니다. 그리고 volatile 키워드를 통해 다른 스레드에서도 변경된 값을 읽을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JAVA/개념 및 정리</category>
      <author>코딩하는_대학생</author>
      <guid isPermaLink="true">https://solution-is-here.tistory.com/229</guid>
      <comments>https://solution-is-here.tistory.com/229#entry229comment</comments>
      <pubDate>Thu, 21 Nov 2024 15:59:53 +0900</pubDate>
    </item>
    <item>
      <title>다양한 시각에서 바라본 Java (2)</title>
      <link>https://solution-is-here.tistory.com/228</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 자바를 사용하며 주로 다루게 되는 예외와 자바의 핵심 기능인 리플렉션, 그리고 그 외 기능에 대해 다뤄보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 예외&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 예외 vs 에러&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;예외 발생했어요&quot;, &quot;에러 발생했어요&quot;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 두 문구는 제가 스프링을 개발하며 들었던 말입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;보통 예외 == 에러라고 생각할 수 있으나, 둘은 &lt;b&gt;천지차이&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예외는 주로 프로그램 실행 중 발생할 수 있는 예상 가능한 문제상황을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;프로그램의 코드나 문제, 사용자 입력으로 인해 발생하는 오류가 예외죠.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Null 객체를 호출하거나 Null 객체의 메서드, 필드에 접근할 때 발생하는 NullPointerException 등이 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그에 비해 에러는 시스템 레벨의 심각한 문제를 나타내며 예외에 비해 복구하기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;시스템 자원 부족, 하드웨어 오류 등 프로그램 외부의 요인으로 발생합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;무한 재귀, 순환 참조로 인해 JVM에 할당된 스택 메모리보다 프로그램의 실행에 필요한 스택 메모리가 많은 경우 발생하는 StackOverFlowError 등이 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1 - 1 Throwable vs Exception&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Error와 Exception에 대해 조금 더 자세히 알아보려면 Throwable에 대해 알아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하기의 사진은 Throwable에 정의되어 있는 메서드들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;2266&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PD3uI/btsKxw8KHtS/KdodRS5EiPdwmQ5JyuN4IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PD3uI/btsKxw8KHtS/KdodRS5EiPdwmQ5JyuN4IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PD3uI/btsKxw8KHtS/KdodRS5EiPdwmQ5JyuN4IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPD3uI%2FbtsKxw8KHtS%2FKdodRS5EiPdwmQ5JyuN4IK%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;2266&quot; height=&quot;1024&quot; data-origin-width=&quot;2266&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Error와 Exception은 둘 다 Throwable을 상속해서 공통으로 위의 메서드들을 가지고 있던 것입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Throwable&lt;/b&gt; -&amp;gt; Exception과 Error가 모두 상속을 하고 있는 상위 클래스&lt;br /&gt;&lt;b&gt;Exception&lt;/b&gt; -&amp;gt; Throwable을 상속하고 있는 하위 클래스&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 구조를 내부 코드와 함께 더 자세히 살펴보자면:&lt;/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;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4uwU5/btsKzhbkzrO/pkWIHtQ3YFJjvjk8TMGaHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4uwU5/btsKzhbkzrO/pkWIHtQ3YFJjvjk8TMGaHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4uwU5/btsKzhbkzrO/pkWIHtQ3YFJjvjk8TMGaHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4uwU5%2FbtsKzhbkzrO%2FpkWIHtQ3YFJjvjk8TMGaHK%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;724&quot; height=&quot;450&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 코드를 실행시키면 다음과 같은 메시지가 출력됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Exception in thread &quot;main&quot; java.lang.NullPointerException: Cannot read field &quot;name&quot; because &quot;apple&quot; is null at Main.main(Main.java:11)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 코드가 출력되는 것을 간단히 살펴보면 NullPointerException이 발생하고, RuntimeException, Exception, Throwable의 순서로 호출이 돼 해당 메시지가 출력이 되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;802&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cngaDL/btsKyel7sRH/tZdwvGUj0DLmbEpWIk1XAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cngaDL/btsKyel7sRH/tZdwvGUj0DLmbEpWIk1XAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cngaDL/btsKyel7sRH/tZdwvGUj0DLmbEpWIk1XAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcngaDL%2FbtsKyel7sRH%2FtZdwvGUj0DLmbEpWIk1XAk%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;802&quot; height=&quot;144&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;NullPointerException의 생성자에서 부모 클래스의 생성자를 호출합니다. (RuntimeException의 생성자 호출)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mYT3a/btsKxTP5YYt/Rn8uRHbPpfAuA8vVmVKXm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mYT3a/btsKxTP5YYt/Rn8uRHbPpfAuA8vVmVKXm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mYT3a/btsKxTP5YYt/Rn8uRHbPpfAuA8vVmVKXm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmYT3a%2FbtsKxTP5YYt%2FRn8uRHbPpfAuA8vVmVKXm0%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;1222&quot; height=&quot;262&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;RuntimeException의 생성자에서 부모 클래스의 생성자를 호출합니다. (Exception의 생성자 호출)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BE6Dx/btsKydgql7g/EqyNCKpcJtOVvrqcz8F250/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BE6Dx/btsKydgql7g/EqyNCKpcJtOVvrqcz8F250/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BE6Dx/btsKydgql7g/EqyNCKpcJtOVvrqcz8F250/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBE6Dx%2FbtsKydgql7g%2FEqyNCKpcJtOVvrqcz8F250%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;1154&quot; height=&quot;344&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Exception의 생성자에서 부모 클래스의 생성자를 호출합니다. (Throwable의 생성자 호출)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9qUjv/btsKyyEEvRL/L6KINOSNIbQSGVg82LS1bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9qUjv/btsKyyEEvRL/L6KINOSNIbQSGVg82LS1bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9qUjv/btsKyyEEvRL/L6KINOSNIbQSGVg82LS1bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9qUjv%2FbtsKyyEEvRL%2FL6KINOSNIbQSGVg82LS1bk%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;1264&quot; height=&quot;374&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Throwable의 생성자에서 fillInStackTrace를 호출합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XSVde/btsKxR5PLM8/X1Ckuu3DGraWQviljoLFWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XSVde/btsKxR5PLM8/X1Ckuu3DGraWQviljoLFWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XSVde/btsKxR5PLM8/X1Ckuu3DGraWQviljoLFWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXSVde%2FbtsKxR5PLM8%2FX1Ckuu3DGraWQviljoLFWk%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;900&quot; height=&quot;366&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 메서드에서 현재 스택 트레이스의 정보를 캡쳐합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그로 인해 예외 메시지가 출력이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 예외 클래스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Exception.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Exception.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730873269394&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Java Platform SE 8&quot; data-og-description=&quot;&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Exception.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java%2Flang%2FException.html=&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Exception.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Exception.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java Platform SE 8&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 링크를 보면 어떤 클래스들이 Exception 클래스를 상속했는지 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;주요한 예외 몇 개를 설명하자면:&lt;/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;NullPointerException&lt;/b&gt; : Null 객체의 속성이나 메서드에 접근하려 할 때 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IllegarArgumentException&lt;/b&gt; : 메서드에 부적절한 인자를 전달했을 때 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IOException&lt;/b&gt; : 입출력 작업 중 오류가 발생했을 때 발생하는 예외입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 이러한 예외는 &lt;b&gt;CheckedException&lt;/b&gt;과 &lt;b&gt;UnCheckedException&lt;/b&gt;으로 나뉩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;코드 단계에서 반드시 처리를 해야 하는 예외 vs 반드시 처리할 필요가 없는 예외로 쉽게 생각할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이미 아실 분도 있겠지만 RuntimeException을 상속하지 않은 예외 vs 상속하는 예외로도 나눌 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하기의 링크를 보시고 해당 클래스를 상속하지 않은 예외는 CheckedException으로 생각하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/RuntimeException.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/RuntimeException.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730873890596&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Java Platform SE 8&quot; data-og-description=&quot;&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/RuntimeException.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java%2Flang%2FRuntimeException.html=&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/RuntimeException.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/RuntimeException.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java Platform SE 8&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FileNotFoundException은 IOException을 상속했고 IOException은 Exception을 상속했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, RuntimeException을 상속하지 않았으므로 CheckedException이고 반드시 예외 처리를 해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ycKx6/btsKxv9PdAK/NUuLcGxZ9zA09aY0ArHjDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ycKx6/btsKxv9PdAK/NUuLcGxZ9zA09aY0ArHjDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ycKx6/btsKxv9PdAK/NUuLcGxZ9zA09aY0ArHjDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FycKx6%2FbtsKxv9PdAK%2FNUuLcGxZ9zA09aY0ArHjDk%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;1170&quot; height=&quot;164&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;164&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;그에 비해 해당 코드에서 발생하는 NullPointerException은 RuntimeException을 상속했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, UnCheckedException이고 예외처리를 강제되지 않습니다.&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;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4uwU5/btsKzhbkzrO/pkWIHtQ3YFJjvjk8TMGaHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4uwU5/btsKzhbkzrO/pkWIHtQ3YFJjvjk8TMGaHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4uwU5/btsKzhbkzrO/pkWIHtQ3YFJjvjk8TMGaHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4uwU5%2FbtsKzhbkzrO%2FpkWIHtQ3YFJjvjk8TMGaHK%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;724&quot; height=&quot;450&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;593&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bejYqz/btsKyJTN2tl/uj1kEydxQnDgUWEDqMlmpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bejYqz/btsKyJTN2tl/uj1kEydxQnDgUWEDqMlmpk/img.png&quot; data-alt=&quot;출처 : https://www.manishsanger.com/java-exception-hierarchy/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bejYqz/btsKyJTN2tl/uj1kEydxQnDgUWEDqMlmpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbejYqz%2FbtsKyJTN2tl%2Fuj1kEydxQnDgUWEDqMlmpk%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;647&quot; height=&quot;593&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;593&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://www.manishsanger.com/java-exception-hierarchy/&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;size18&quot;&gt;Throwable을 포함한 전체적인 클래스 구조도입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. throw vs throws&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CheckedException은 반드시 예외 처리를 해야 하는데 그중 throws를 통해 예외 처리가 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IyQhV/btsKyIN962F/72cSApgVNMAYoKIDAiehi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IyQhV/btsKyIN962F/72cSApgVNMAYoKIDAiehi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IyQhV/btsKyIN962F/72cSApgVNMAYoKIDAiehi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIyQhV%2FbtsKyIN962F%2F72cSApgVNMAYoKIDAiehi0%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;1166&quot; height=&quot;226&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 throws를 통해 해당 CheckedException을 정의하면 더 이상 컴파일 에러가 일어나지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;throws에 대해 조금 더 자세히 설명을 하자면 즉 예외를 선언하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예외 처리를 해당 메서드에서 하지 않고 호출한 메서드에 위임할 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 메서드를 호출한 메서드에서는 똑같이 throws를 통해 예외 처리를 하거나 try-catch와 같이 다른 방법으로 예외 처리를 반드시 해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그에 비해 throw는 개발자가 예외를 발생시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;객체를 속성을 통해 조회했는데 DB에 해당 정보를 가지고 있는 데이터가 없다면 null이 반환되고 해당 객체의 속성이나 메서드에 접근하려고 하면 NullPointerException이 발생합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yGnd3/btsKyJl0dGP/SG3gK2lJG8GpGrugrVepTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yGnd3/btsKyJl0dGP/SG3gK2lJG8GpGrugrVepTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yGnd3/btsKyJl0dGP/SG3gK2lJG8GpGrugrVepTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyGnd3%2FbtsKyJl0dGP%2FSG3gK2lJG8GpGrugrVepTk%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;1052&quot; height=&quot;136&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러므로 보통의 경우 개발자가 예외를 생성한 뒤 해당 예외를 발생시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이와 같이 개발자가 직접 예외를 발생시키고 싶을 때 사용하는 것이 throw입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. try-catch-finally&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예외를 강제로 발생시킬 때는 throw를 할 수도 있지만 발생할 수 있는 예외 (checked Exception, unCheckedException)을 관리하고자 할 때는 try-catch-finally를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 try 내에 있는 코드가 실행이 되고, 해당 코드에서 예외가 일어나면 코드를 더 이상 진행하지 않고 catch로 넘어가 예외 처리를 합니다. (catch에서 해당 예외를 처리하고 있을 때)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 catch에서 예외 처리를 마친 후 finally에서 마무리 작업을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 try와 catch의 역할은 분명한 것에 비해 finally의 역할은 불분명할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 try-catch-finally에서 finally의 역할은 예외 여부와 상관없이 실행되기 때문에 try에서 사용한 리소스를 해제하거나 정리 작업 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 java 7부터 try-with-resources라는 구문이 나왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 구문에서는 try가 끝나면 try 안에서 선언한 리소스들이 자동으로 해제가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 finally의 역할은 정리 작업 역할이라고 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;개인적으로 동시성 관련 작업을 할 때 finally에서 unLock을 해준 경험이 있습니다.&lt;br /&gt;이와 같이 예외가 발생하더라도 꼭 실행돼야 하는 코드를 finally에 정의해주는 것이 좋습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 리플렉션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;리플렉션이란 실행 중인 Java 프로그램이 스스로를 검사하고 내부 속성을 변경할 수 있게 해주는 속성입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;쉽게 스프링으로 설명해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;스프링에는 DI(의존성 주입)이라는 개념이 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;객체 간의 의존성을 외부에서 주입을 해주는 것으로 생각하시면 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Spring 컨테이너는 애플리케이션이 시작될 때 설정 파일이나 어노테이션을 통해 빈을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 과정에서 리플렉션을 통해 클래스 정보를 가져오고 인스턴스를 동적으로 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 @Autowired와 같은 어노테이션을 통해 명시된 의존성을 리플렉션을 통해 주입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 어노테이션을 찾고, 클래스 정보를 가져오고 의존성을 주입하는 것에서 리플렉션이 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 어노테이션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;@Autowired을 통해 명시된 의존성을 주입하고, 어노테이션을 통해 빈을 생성하고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 어노테이션이 뭔지 자세히 설명하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;어노테이션은 코드에 메타데이터를 추가하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;클래스, 메서드, 필드, 매개변수 등 다양한 프로그램 요소에 적용할 수 있고, 컴파일러에게 정보를 제공하거나 런타임시 특정 동작을 수행하도록 지시할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Java가 지원하는 어노테이션인 @Override를 통해 설명하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선, @Override는 오버라이드를 하는 메서드에 정의를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 @Override 어노테이션은 해당 메서드가 슈퍼 클래스의 메서드를 오버라이드 하고 있음을 컴파일 단계에서 컴파일러에게 정보를 전달하는 것입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;@Deprecated, @SuppressWarnings도 똑같이 컴파일러에게 정보를 제공하는 것입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730992570211&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value();
}

public class MyClass {
    @MyAnnotation(value = &quot;Hello&quot;)
    public void myMethod() {
        System.out.println(&quot;My Method&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;코드 출처 : f-lab 자바 어노테이션과 그 활용법&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 코드를 보면 MyAnnotation이라는 커스텀 어노테이션을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 Retention을 보면 해당 어노테이션이 런타임에도 유지가 됨을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;런타임 어노테이션의 경우 해당 어노테이션이 붙은 클래스, 메서드, 필드가 사용될 때 처리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;myMethod가 실행되면 MyAnnotation이 처리되고 이때 리플렉션을 통해 어노테이션 정보를 읽고 Hello라는 값을 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실제로 제가 직접 만든 어노테이션과 함께 설명드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://github.com/IDMaker-io/MaKer&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/IDMaker-io/MaKer&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730993320252&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - IDMaker-io/MaKer&quot; data-og-description=&quot;Contribute to IDMaker-io/MaKer development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/IDMaker-io/MaKer&quot; data-og-url=&quot;https://github.com/IDMaker-io/MaKer&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://github.com/IDMaker-io/MaKer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/IDMaker-io/MaKer&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - IDMaker-io/MaKer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to IDMaker-io/MaKer development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730993333146&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface IDMaker {

	/**
	 * The length of the random part of the ID.
	 *
	 * @return the length of the random part of the ID
	 */
	int length() default 7;

	/**
	 * The type of characters to use for the random part of the ID.
	 *
	 * @return the type of characters to use for the random part of the ID
	 */
	GenerationType type() default GenerationType.EN;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 어노테이션을 보면 length, type 등의 메타데이터를 가지고 있음을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730993406292&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Checks if a field is annotated with {@link IDMaker} and if it is of type String.
 *
 * @param field the field to check
 * @return true if the field is annotated with {@link IDMaker} and is of type String, false otherwise
 * @throws IDMakerInvalidArgumentException if the field is annotated with
 * {@link IDMaker} but is not of type String
 */
private boolean isIDMakerAnnotated(Field field) {
	if (!field.isAnnotationPresent(IDMaker.class)) {
		return false;
	}

	if (field.getType() != String.class) {
		throw new IDMakerInvalidArgumentException(IDMAKER_ANNOTATION_ON_NON_STRING.getMessage());
	}

	return true;
}

/**
 * Generates an ID for a field if it is null.
 *
 * @param entity the entity that contains the field
 * @param field the field for which to generate an ID
 * @throws IDMakerAccessException if there is an error accessing the field
 */
private void generateIdForField(Object entity, Field field) throws IDMakerAccessException {
	field.setAccessible(true);

	try {
		if (field.get(entity) == null) {
			IDMaker idMaker = field.getAnnotation(IDMaker.class);
			String generatedId = IDMakerUtils.createTimestampedRandomID(idMaker.length(), idMaker.type());
			field.set(entity, generatedId);
		}
	} catch (IllegalAccessException e) {
		throw new IDMakerAccessException(FAILED_TO_ACCESS_FIELD.getMessage(), e);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 코드에서 field에서 어노테이션이 적용되어 있는지 확인하고, getAnnotation을 통해 어노테이션의 메타데이터를 가져옴을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한, field.set을 통해 런타임 도중 필드의 정보를 변경함을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 field에 어노테이션이 적용되어 있는지 확인하고, 필드의 정보를 변경하는 것에 활용된 것이 &lt;b&gt;리플렉션&lt;/b&gt;입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;결과적으로 어노테이션이 적용된 필드에 랜덤한 값이 생성됩니다.&amp;nbsp;&lt;br /&gt;jpa의 @GenerateValue와 같은 역할을 함.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 제네릭&lt;/h2&gt;
&lt;pre id=&quot;code_1731047364854&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; integers = new ArrayList&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 코드를 한 번이라도 보신 분이 있다면 여러분은 이미 제네릭을 사용하고 계십니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;제네릭이란 컴파일 시점에 클래스나 메서드 내부에서 사용할 데이터 타입을 지정하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이를 통해 다양한 객체의 타입을 다룰 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실제로 ArrayList 클래스를 보시면 제네릭을 볼 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class ArrayList&amp;lt;E&amp;gt; extends AbstractList&amp;lt;E&amp;gt;
        implements List&amp;lt;E&amp;gt;, RandomAccess, Cloneable, java.io.Serializable&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;타입 파라미터를 통해 E(Element)를 받는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 E는 여러분이 타입 파라미터 안에 작성하는 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서 Integer.class를 넣으면 E는 Integer.class가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;자연스레 리스트의 요소를 가져오는 get 메서드의 반환 타입도 사용자가 타입 파라미터 안에 작성하는 클래스입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Integer를 넣으면 Integer를 반환하기 때문입니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Generic vs Object&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;size18&quot;&gt;실제로 FixtureMonkey에서 여러 객체를 받아야 할 때 제네릭을 사용한 적이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 제네릭과 Object를 고민했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Object는 모든 클래스들의 상위 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉 모든 클래스는 Object로 형 변환(업 캐스팅)을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만, 제네릭은 컴파일 시점에 데이터 타입을 결정하지만, Object는 런타임 도중 형 변환이 일어나기 때문에 ClassCastException이 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 점을 고려해 Generic을 선택하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다시 프로젝트로 돌아와서 설명을 하자면, 연산을 선택하는 로직에 연산에 대한 metadata가 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 연산의 metadata는 String이 될 수도 있고 특정 클래스가 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그로 인해 확장성을 고려해 다양한 클래스를 사용할 수 있게 설계를 하려고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 Generic을 사용해 데이터 타입을 컴파일 시점에 결정하도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://github.com/naver/fixture-monkey/blob/c7dc4fbdbad56f471b381b6ccbff129d7d1c0d28/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/matcher/MatcherMetadata.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/naver/fixture-monkey/blob/c7dc4fbdbad56f471b381b6ccbff129d7d1c0d28/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/matcher/MatcherMetadata.java&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731049007285&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;fixture-monkey/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/matcher/MatcherMetadata.java at c7dc4fbdbad56f47&quot; data-og-description=&quot;Let Fixture Monkey generate test instances including edge cases automatically - naver/fixture-monkey&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/naver/fixture-monkey/blob/c7dc4fbdbad56f471b381b6ccbff129d7d1c0d28/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/matcher/MatcherMetadata.java&quot; data-og-url=&quot;https://github.com/naver/fixture-monkey/blob/c7dc4fbdbad56f471b381b6ccbff129d7d1c0d28/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/matcher/MatcherMetadata.java&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BioWL/hyXwuVLu6p/bxLVbrKMkK5CWXIc3o5wkK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bk2oxN/hyXwgb7ZJn/ZmN2TIZ0l5hQhZ7Fkq39n0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/naver/fixture-monkey/blob/c7dc4fbdbad56f471b381b6ccbff129d7d1c0d28/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/matcher/MatcherMetadata.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/naver/fixture-monkey/blob/c7dc4fbdbad56f471b381b6ccbff129d7d1c0d28/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/matcher/MatcherMetadata.java&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BioWL/hyXwuVLu6p/bxLVbrKMkK5CWXIc3o5wkK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bk2oxN/hyXwgb7ZJn/ZmN2TIZ0l5hQhZ7Fkq39n0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;fixture-monkey/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/matcher/MatcherMetadata.java at c7dc4fbdbad56f47&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Let Fixture Monkey generate test instances including edge cases automatically - naver/fixture-monkey&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 람다, 스트림&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 람다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;람다란 메서드를 간단하게 식으로 표현한 것입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;쉽게 익명 함수를 만드는 것으로 생각하셔도 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;람다를 적용하면 메서드의 이름과 반환 값이 없어지기 때문에 익명 함수라고도 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1731058171817&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기존 익명 클래스 방식
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println(&quot;Hello, world!&quot;);
    }
};

Runnable runnable = () -&amp;gt; System.out.println(&quot;Hello, world!&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;람다 표현식을 사용하기 위해서는 함수형 인터페이스가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;함수형 인터페이스는 자바에서 함수형 프로그래밍을 지원하기 위해 도입ㅈ&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;함수형 인터페이스는 default, static 메서드를 제외한 단 하나만의 추상 메서드만을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 @FunctionalInterface 어노테이션을 통해 명시를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 규칙 덕분에 자바는 람다 표현식이 어떤 메서드를 구현하는지 명확하게 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;함수형 인터페이스는 자바에서 메서드를 1급 객체로 다룰 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, 메서드를 인자처럼 전달하고 메서드의 반환값으로 사용 가능하게 해줍니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1급 객체란?&amp;nbsp;&lt;br /&gt;함수의 인자로도 넘겨질 수 있고 변수에 대입도 가능한 다른 요소와 차별이 없는 객체&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;함수형 프로그래밍은 선언적 코드 스타일을 지향하는 패러다임으로, 함수와 데이터 변환을 중심으로 구성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;주로 입력을 받아 결과를 반환하는 함수들을 조합하여 프로그램을 설계합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;람다는 자바에서 이러한 함수형 프로그래밍이 가능하게 했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기존의 객체지향 프로그래밍은 함수형 프로그래밍과 다르게 객체와 클래스를 중심으로 프로그램을 구성합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 스트림&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;자바에서 스트림은 Java 8에 도입된 강력한 데이터 처리 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;스트림은 데이터 소스를 추상화하고 데이터를 일관된 방식으로 처리할 수 있게 해주는 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;중간 연산, 최종 연산으로 나뉘며 코드의 가독성과 유지보수성이 향상됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1731060880014&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class StreamExample {
    public static void main(String[] args) {
        // 예제 리스트 생성
        List&amp;lt;String&amp;gt; names = Arrays.asList(&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;, &quot;David&quot;, &quot;Eve&quot;);

        // 중간 연산 및 최종 연산 수행
        List&amp;lt;String&amp;gt; filteredNames = names.stream()
            .filter(name -&amp;gt; name.length() &amp;gt; 3)        // 중간 연산: 길이가 3 초과인 이름 필터링
            .map(String::toUpperCase)                 // 중간 연산: 이름을 대문자로 변환
            .sorted()                                 // 중간 연산: 이름을 알파벳 순으로 정렬
            .collect(Collectors.toList());            // 최종 연산: 리스트로 수집

        // 결과 출력
        System.out.println(&quot;Filtered and transformed names: &quot; + filteredNames);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;스트림은 람다와 동일하게 함수형 프로그래밍을 자바에 적용시키기 위해 도입됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한 대용량 데이터를 처리할 때 병렬 처리를 용이하게 하기 위해 도입 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. System.out.println의 성능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현업에 계신 분께 코드리뷰를 받으면 항상 말을 하시는게 System.out.println은 사용하면 안 돼요입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;오늘은 그 이유를 한 번 살펴보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하기의 코드는 println의 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;synchronized 키워드가 붙은 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이는 여러 스레드가 println 메서드에 동시에 접근했을 때 하나의 스레드만 접근할 수 있는 것을 의미합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그로인해 A 스레드에서 println을 실행시키면 모두 사용하고 잠금을 해제한 뒤, 다른 스레드에서 println에 접근할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;한 번 요청 시 5000명의 사용자를 요청하고, 처리 과정에서 응답시간이 20초 걸리는 사이트가 있는데, 원인을 알아보니 5000명의 정보를 다 System.out.println()으로 처리하고있던 것이다. 이는 System.out.println()을 줄임으로써 응답시간이 6초까지 줄었다. - 이상민, 자바 성능 튜닝이야기, 인사이트, 2013&lt;/blockquote&gt;</description>
      <category>JAVA/개념 및 정리</category>
      <author>코딩하는_대학생</author>
      <guid isPermaLink="true">https://solution-is-here.tistory.com/228</guid>
      <comments>https://solution-is-here.tistory.com/228#entry228comment</comments>
      <pubDate>Wed, 6 Nov 2024 15:53:06 +0900</pubDate>
    </item>
    <item>
      <title>다양한 시각에서 바라본 Java (1)</title>
      <link>https://solution-is-here.tistory.com/227</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 자바의 기초적인 개념에 대해 다뤄보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자바의 장점이 무엇인가요?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 객체지향&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그로 인해 캡슐화, 상속, 다형성, 추상화라는 장점이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 캡슐화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;캡슐화는 객체 내부의 상세 구현을 감추고 외부에서는 인터페이스를 통해 접근할 수 있도록 하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이를 통해 코드의 유지보수성과 보안을 향상시킬 수 있다는 장점이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;오픈소스 기여 활동에서 받은 &lt;a href=&quot;https://github.com/naver/fixture-monkey/pull/1062&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;코멘트&lt;/a&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;1486&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC2aan/btsKrNALQgf/DeJkM4RrK7OJkuphcUPiv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC2aan/btsKrNALQgf/DeJkM4RrK7OJkuphcUPiv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC2aan/btsKrNALQgf/DeJkM4RrK7OJkuphcUPiv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcC2aan%2FbtsKrNALQgf%2FDeJkM4RrK7OJkuphcUPiv1%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;1486&quot; height=&quot;130&quot; data-origin-width=&quot;1486&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 Matcher는 이름과 객체의 타입을 기반으로 비교 작업을 수행하는 로직을 가지고 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그러나 이 비교 작업에 필요한 이름이 MatcherOperator라는 외부 객체에 위치해 있는 상황입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이로 인해 Matcher의 내부 구현 세부 사항이 외부로 노출되는 문제가 발생했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이러한 문제를 개선하기 위해 정보를 담고 있는 ArbitraryBuilderContext에 이름을 저장하는 방식으로 수정을 하였습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 상속&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상속은 한 클래스가 다른 클래스의 속성과 메서드를 물려받는 개념입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 부모 클래스의 멤버 변수나 메서드가 private 접근자인 경우 자식 클래스에서 접근할 수 없지만, public이나 protected인 경우 자식 클래스에서도 접근이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상속의 장점으로는 코드 재사용성과 다형성이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;공통 코드를 부모 클래스에 작성하면 자식 클래스가 이를 접근할 수 있어 코드의 재사용성이 높아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한, 상속을 통해 다형성을 구현하여 여러 자식 클래스가 부모 클래스를 확장하면서 각기 다른 동작을 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상속은 위에서 설명했다시피 부모 클래스의 속성과 메서드를 물려받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 특징은 코드 재사용성이라는 장점이 될 수도 있지만, 부모 클래스의 변경이 모든 자식클래스에 영향을 끼친다는 단점이 되기도 합니다. 또한, 자식 클래스가 부모 클래스의 내부 구현을 알아야 한다는 점에서 캡슐화를 위배할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2 - 1. 조합 (Composition)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상속과 조합의 차이에 대해 먼저 설명드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상속은 맥북은 노트북이다 (is-a)입니다. 그에 비해 구성은 노트북에는 키보드가 있다(has-a)입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;상속은 하위 클래스가 상위 클래스에 포함된다는 의미이고, 조합은 하나의 클래스에서 다른 클래스를 포함하고 있다는 의미입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그로 인해 구성은 상속과 다르게 한 객체가 다른 객체를 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 특징으로 인해 구성 또한 상속과 마찬가지로 코드를 재사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 조합 클래스가 다른 클래스의 객체를 포함하게 되므로 한 클래스의 변경이 다른 클래스에 영향을 미칠 수 있다는 단점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.&amp;nbsp; 다형성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다형성은 하나의 객체가 여러 가지 형태로 취급될 수 있다는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다형성은 주로 두 가지 방법을 통해 구현됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. 상속을 통한 다형성&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;부모 클래스 타입의 참조 변수로 자식 클래스의 인스턴스를 참조할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 자식 클래스에서 오버라이딩을 통해 부모 클래스의 메서드를 재정의 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 인터페이스를 통한 다형성&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;인터페이스 타입의 참조 변수로 구현 클래스의 인스턴스를 참조할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 자식 클래스에서 오버라이딩을 통해 인터페이스의 메서드를 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;1234&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zlAyy/btsKqCVdxN9/dZvqd83JjiXykNm7l9qG7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zlAyy/btsKqCVdxN9/dZvqd83JjiXykNm7l9qG7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zlAyy/btsKqCVdxN9/dZvqd83JjiXykNm7l9qG7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzlAyy%2FbtsKqCVdxN9%2FdZvqd83JjiXykNm7l9qG7k%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;1234&quot; height=&quot;276&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 코드를 보면 @Override라는 어노테이션이 붙어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 어노테이션은 해당 메서드가 상위 클래스 또는 인터페이스의 메서드를 오버라이드 한다는 것을 명시적으로 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, 오버라이딩은 상속이나 구현 관계에서 메서드를 재정의 하는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다형성의 장점 중 하나는 객체가 여러 형태로 취급될 수 있다는 점인데, 이는 오버라이딩을 통해 실현됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하위 클래스에서 메서드를 재정의 하면 부모 클래스의 참조 변수를 통해 자식 클래스 인스턴스를 사용할 때 각기 다른 형태로 동작하게 할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오버라이딩, 오버로딩..? 차이가 뭐야!&lt;/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;size18&quot;&gt;오버로딩은 메서드 이름이 같지만 파라미터를 다르게 해서 여러 개의 메서드를 정의하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이는 오버라이딩과는 전혀 관련이 없고 이름이 비슷해 오해를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3 - 1. 인터페이스와 추상 클래스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; 인터페이스는 클래스들이 구현해야 하는 메서드의 공통 명세를 정의하는 추상 자료형입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;추상 클래스는 하나 이상의 추상 메서드를 포함하는 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;인터페이스와 추상 클래스는 공통점이 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;인터페이스의 모든 메서드(default와 static을 제외한)는 추상 메서드라고 볼 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;추상 메서드는 구현 / 상속을 하는 하위 객체가 반드시 오버라이딩을 해야 한다는 특징을 가지고 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;가장 큰 차이점으로는 인터페이스는 상태를 가지고 있을 수 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그에 비해 추상 클래스는 상태를 가지고 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한, 인터페이스는 다중 상속이 가능하고 추상 클래스는 다중 상속이 불가능하다는 특징이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 특징을 바탕으로 인터페이스와 추상 클래스의 사용 시기를 말씀드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;인터페이스는 다중 상속 가능, 상태를 가지고 있을 수 없고 하위 클래스가 반드시 구현을 해야 한다는 특징을 가지고 있습니다. 그로 인해 관련 없는 클래스들이 공통 동작을 구현해야 할 때 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;1800&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zoqVV/btsKr4bNcgv/rQhPJIFMhLKnT6ot0kWmk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zoqVV/btsKr4bNcgv/rQhPJIFMhLKnT6ot0kWmk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zoqVV/btsKr4bNcgv/rQhPJIFMhLKnT6ot0kWmk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzoqVV%2FbtsKr4bNcgv%2FrQhPJIFMhLKnT6ot0kWmk0%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;1800&quot; height=&quot;290&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에 보이는 인터페이스는 Matcher라는 인터페이스이며, 주요한 기능으로는 match 즉, 비교를 하는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;1638&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hwo1M/btsKq95ZpuJ/9MppvVcb5qOiet0el9kDJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hwo1M/btsKq95ZpuJ/9MppvVcb5qOiet0el9kDJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hwo1M/btsKq95ZpuJ/9MppvVcb5qOiet0el9kDJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHwo1M%2FbtsKq95ZpuJ%2F9MppvVcb5qOiet0el9kDJK%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;1638&quot; height=&quot;716&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Match 인터페이스를 보면 관련이 없지만 비교라는 기능을 하는 클래스들이 구현한 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그에 비해 추상 클래스는 상태를 가질 수 있고, 일반 메서드를 가질 수 있다는 특징이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그로 인해 코드 재사용의 장점이 있고, 변수를 공유하기 때문에 주로 관련된 클래스에서 사용할 때 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. instanceof&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상속과 다형성을 알았다면 여러분은 instanceof를 이해하실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 instanceof는 객체 타입을 검사하는 연산자입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;형 변환이 가능하다면 true, 불가능하다면 false를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하기의 코드는 블로그에서 발췌한 코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730357818623&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Parent{}
class Child extends Parent{}

public class InstanceofTest {

    public static void main(String[] args){

        Parent parent = new Parent();
        Child child = new Child();

        System.out.println( parent instanceof Parent );  // true
        System.out.println( child instanceof Parent );   // true
        System.out.println( parent instanceof Child );   // false
        System.out.println( child instanceof Child );   // true
    }

}
출처: https://mine-it-record.tistory.com/120 [나만의 기록들:티스토리]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;부모 클래스 parent = (자식 클래스) child 가 불가능 하기 때문에 3번째의 경우는 false가 반환됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;그에 비해 자식 클래스는 부모 클래스로 형 변환이 가능하기 때문에 true가 반환됩니다. instanceof 키워드의 단점은 추상화를 설명한 뒤 설명하겠습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 instanceof도 단점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하기의 코드는 instanceof를 통해 객체의 타입을 확인한 뒤, 구현을 한 코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730360170675&quot; class=&quot;pgsql&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public void makeAnimalSound(Animal animal) {
	if (animal instanceof Dog) {
		System.out.println(&quot;멍멍&quot;);
	} else if (animal instanceof Cat) {
		System.out.println(&quot;야옹&quot;);
	} else if (animal instanceof Bird) {
		System.out.println(&quot;짹짹&quot;);
	} else {
		System.out.println(&quot;알 수 없는 동물입니다.&quot;);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 강아지, 고양이, 새의 울음소리를 직접 구현한 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 구현을 하려면 코드의 내부 구조에 대해 알아야 하고 이는 캡슐화가 보장되지 않는다는 단점이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한 새로운 동물이 추가가 되면 instanceof 관련 코드를 고쳐야 하므로 유지보수성도 떨어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 추상화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;추상화는 객체의 공통적인 특성을 추출해서 인터페이스나 추상 클래스로 정의하는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730359558856&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[동물 (Animal)]
     ^
     |
     |
 +-----+-----+
 |     |     |
[개] [고양이] [새]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하기의 코드와 같이 강아지, 고양이, 새의 공통적인 특성을 추출하나 뒤, 추상 클래스로 정의하는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730359569089&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class Animal {
    public abstract void makeSound();
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println(&quot;멍멍!&quot;);
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println(&quot;야옹!&quot;);
    }
}

public class Bird extends Animal {
    @Override
    public void makeSound() {
        System.out.println(&quot;짹짹!&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;추상화를 통해 코드의 재사용성을 높일 수 있으며, 프로그램의 구조를 더 명확하게 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 프로그램의 구조를 더 명확하게 만들 수 있다는 것은 강아지, 고양이, 새의 내부 구현을 몰라도 Animal이라는 클래스를 통해 각각의 클래스를 사용할 수 있는 것을 의미합니다. (다형성과 관련이 있습니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 플랫폼 독립성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;자바는 한 번 작성하면 운영체제와 하드웨어 상관 없이 JVM이 설치되어 있는 공간에서 실행 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. JVM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JVM (Java Virtual Machine)은 자바 애플리케이션을 실행하기 위한 가상 머신입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JVM은 크게 바이트 코드 실행, 메모리 관리등의 일을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;820&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJH3bF/btsKrAv5InX/00UkyoXPoz4KCWGD7fch60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJH3bF/btsKrAv5InX/00UkyoXPoz4KCWGD7fch60/img.png&quot; data-alt=&quot;출처 : https://steady-coding.tistory.com/305&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJH3bF/btsKrAv5InX/00UkyoXPoz4KCWGD7fch60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJH3bF%2FbtsKrAv5InX%2F00UkyoXPoz4KCWGD7fch60%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;820&quot; height=&quot;614&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://steady-coding.tistory.com/305&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1 - 1 실행.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;개발자가 자바 코드를 작성하면 자바 컴파일러가 자바 소스코드를 바이트코드로 변환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 JVM의 클래스로더가 컴파일된 바이트코드를 메모리에 로드합니다. 그 후, 바이트 코드를 실행합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 바이트코드를 실행하는 방법에는 두 가지 방법이 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인터프리터 : 바이트 코드를 한 줄씩 읽어 즉시 실행합니다.&lt;/li&gt;
&lt;li&gt;JIT 컴파일러 : 자주 호출되는 바이트코드를 기계어로 변환하여 성능을 향상시킵니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1 - 2. 메모리, 주소를 곁들인..&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JVM은 효율적인 메모리 관리를 위해 여러 영역으로 나눠 메모리를 관리합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Method Area : 모든 스레드가 공유하는 메모리 영역입니다. 클래스, 인터페이스, 필드, Static 변수 등의 바이트코드를 보관합니다.&lt;/li&gt;
&lt;li&gt;Heap Area : 모든 스레드가 공유하며 new 키워드로 생성된 객체와 배열이 생성되는 영역입니다.&lt;/li&gt;
&lt;li&gt;Stack Area : 메서드를 호출할 때 해당 프레임이 생기고 그 지역 변수, 매개 변수 데이터 값이 저장&lt;/li&gt;
&lt;li&gt;PC register : 스레드가 시작될 때 생성된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 메모리 구조를 바탕으로, 자바에서는 객체의 실제 메모리 주소를 직접 다루지 않고 객체에 대한 참조를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 특징은 객체의 비교를 통해 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;객체를 비교하는 방식에는 equals와 == 방식이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;== 의 경우 기본 타입의 경우 값 자체를 비교하고, 참조 타입인 경우 객체의 메모리 주소를 비교합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;equals의 경우 기본적으로 객체의 메모리 주소를 비교합니다. (Object에 정의된 메서드입니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730378818390&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String s1 = new String(&quot;Test&quot;);
String s2 = new String(&quot;Test&quot;);

System.out.println(s1 == s2);			// false
System.out.println(s1.equals(s2));		// true;
출처: https://mangkyu.tistory.com/101 [MangKyu's Diary:티스토리]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 코드를 보면 문자열 객체를 생성한 것을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 두 객체 모두 문자열 값은 &quot;Test&quot;로 동일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그런데 == 로 비교한 것을 보면 false가 나오고 equals로 비교한 것을 보면 true인 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이는 동일성과 동등성이라는 단어를 사용해 설명할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 동일성이란 객체가 완전히 같은 것을 의미합니다. 즉, 객체의 주소 값까지 같아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;동등성은 객체가 가지고 있는 상태 또는 변수의 값이 같은 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다시 == 와 equals로 돌아와 설명하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;s1과 s2 객체는 new 연산자를 통해 생성이 되었습니다. 그로 인해 힙 메모리의 다른 위치에 할당이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;분명 위에서 equals를 설명할 때 객체의 주소를 비교한다고 했는데 true가 반환된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    return (anObject instanceof String aString)
            &amp;amp;&amp;amp; (!COMPACT_STRINGS || this.coder == aString.coder)
            &amp;amp;&amp;amp; StringLatin1.equals(value, aString.value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이는 String 클래스에서 equals를 오버라이드 했기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실제로 위 코드는 String 클래스에서 재정의 한 equals 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;코드를 보면 만약 같은 객체(메모리까지)면 true를 반환하고, 아닌 경우 String으로 캐스팅을 해서 인코딩 방식과 실제 내용을 비교하는 것을 볼 수 있습니다. 그러므로 s1과 s2가 다른 위치에 저장된 객체여도 실제 내용이 같으므로 true가 반환된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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-size=&quot;size18&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;equals를 재정의 할 때 hashCode도 재정의 해야한다고 하던데?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선, equals를 재정의 할 때 hashCode도 재정의 해야하는 이유부터 말하자면, 자바의 규칙때문에 변경을 해야 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;If two objects are equal according to the&amp;nbsp;equals(Object)&amp;nbsp;method, then calling the&amp;nbsp;hashCode&amp;nbsp;method on each of the two objects must produce the same integer result.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;두 개체가 equals(Object) 메서드에 따라 같으면, 두 개체 각각에 대해 해시코드 메서드를 호출하면 동일한 정수 결과가 생성되어야 합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730385635034&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Object (Java Platform SE 8 )&quot; data-og-description=&quot;Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup. The general contract of fi&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Object (Java Platform SE 8 )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup. The general contract of fi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러면 왜 자바에서 이러한 규칙을 강제했을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 HashCode는 객체를 식별하는 고유한 정수값입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;해시 코드는 주소값으로 만든 고유한 숫자값이라고 생각하시면 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;hashCode 메서드는 객체의 메모리 주소를 기반으로 Hash Code를 생성합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;equals와 hashCode는 둘 다 동일성을 비교한다는 공통점이 있지만, hashCode의 반환 값은 정수이고, equals는 boolean을 반환한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 equals를 동등성을 비교하는 로직으로 재정의하고 hashCode를 재정의하지 않으면 HashMap, HashSet 등의 컬렉션에서 오작동을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;HashMap, HashSet은 객체를 저장할 때 객체의 hashCode를 호출하여 해시 값을 얻습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 이 해시값을 기준으로 특정 버킷에 저장합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;조회를 할 때는 찾고자 하는 객체의 hashCode를 호출하고, 해당 해시 값에 해당하는 버킷을 찾습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후, 버킷 내의 객체들과 equals를 사용해 비교합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 equals를 재정의 하지 않고 hashCode를 재정의 하지 않으면 객체를 찾지 못 하거나, 동일한 객체가 여러개 저장될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그 외 장점 및 특징&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Primitive type과 Reference type&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Primitive Type을 직독직해하면 원시 타입이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;원시적으로 값을 변수에 저장했다고 생각하시면 쉽습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;byte ,short, boolean, char, int, double, float등이 바로 원시타입입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Reference Type은 객체를 참조하는 타입입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;변수에 객체의 주소를 저장한다고 생각하시면 쉽습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pjDQv/btsKtDL3JqG/qU9kICRVEvNTKwLC3WvcZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pjDQv/btsKtDL3JqG/qU9kICRVEvNTKwLC3WvcZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pjDQv/btsKtDL3JqG/qU9kICRVEvNTKwLC3WvcZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpjDQv%2FbtsKtDL3JqG%2FqU9kICRVEvNTKwLC3WvcZ0%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;782&quot; height=&quot;88&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 원시 타입의 변수와 참조 타입의 변수를 만들어서 값을 넣어본 결과 하기의 사진과 같이 원시 타입의 경우 값이 조회되고, 참조 타입의 경우 Integer@762 즉, 객체의 참조를 통해 값을 가르키고 있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp04va/btsKrBJimSo/hbKzddcGQxkqWxUfVOEQA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp04va/btsKrBJimSo/hbKzddcGQxkqWxUfVOEQA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp04va/btsKrBJimSo/hbKzddcGQxkqWxUfVOEQA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp04va%2FbtsKrBJimSo%2FhbKzddcGQxkqWxUfVOEQA1%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;482&quot; height=&quot;144&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;144&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;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아 그러면 원시 변수를 제외한 객체들은 참조를 통해 값을 호출하니까 Java는 Call By Reference?&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;size18&quot;&gt;여기까지 읽고 가시면 Java는 Call by Reference라고 생각하신 채 가실 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;어디가서 이 글 읽고 Call by Reference라고 배웠다고 하지 마세요... &lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 간단하게 Call by Value와 Call by Reference에 대해 설명하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Call by Value와 Call by Reference는 함수나 메서드에 인자를 전달하는 두 가지 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Call by Value는 값에 의해 호출이 되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, 값을 복사하여 처리하는 것입니다. 그러므로 함수 내에서 매개 변수 값을 변경해도 원본 변수에는 영향을 주지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 Call by Reference의 경우 함수 호출 시 인자의 메모리 주소가 전달이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러므로 함수 내에서 매개변수를 통해 원본 데이터를 직접 조작할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러면 다음의 코드 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730437851699&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {
	static class Dog {
		String name;
		Dog(String name) { this.name = name; }
	}

	static void changeDog(Dog d) {
		d.name = &quot;새로운 이름&quot;; // 원본 객체 변경됨
	}

	public static void main(String[] args) {
		Dog myDog = new Dog(&quot;맥스&quot;);
		changeDog(myDog);
		System.out.println(myDog.name); // &quot;새로운 이름&quot; 출력
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;마지막에 출력 함수를 통해 myDog라는 객체의 name 필드를 출력해본 결과 새로운 이름이라는 값이 출력 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;분명 Java는 Call by Value이고, 특성 상 값을 복사하여 처리하기 때문에 깊은 복사와 같이 원본 변수에는 영향을 주면 안됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러나 실제로 확인을 해보면 값이 변경이 됐는데 그 이유는 Java에서 객체를 인자로 전달할 때는 객체의 주소값을 전달하기 때문에 메서드에서 원본 객체의 값을 변경할 수 있는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7VUIm/btsKtMvkHAL/NTNgo45slX3bV1TMksOAR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7VUIm/btsKtMvkHAL/NTNgo45slX3bV1TMksOAR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7VUIm/btsKtMvkHAL/NTNgo45slX3bV1TMksOAR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7VUIm%2FbtsKtMvkHAL%2FNTNgo45slX3bV1TMksOAR0%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;1010&quot; height=&quot;140&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;디버깅을 통해 d 객체가 전달될 때 주소 값이 전달된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. static, final&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;static은 클래스가 로드될 때 메모리에 할당 됩니다. 그리고 클래스가 종료될 때까지 유지 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;주로 클래스간 공유되는 속성이나 메서드를 정의할 때 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;final은 변수, 메서드, 클래스에 선언될 수 있으며 선언된 대상을 불변으로 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;주로 변하면 안 되는 값에 final 키워드를 붙입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;메서드에 final을 붙이면 오버라이딩이 불가능 하고, 클래스에 붙이면 상속이 불가능합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;리터럴과 상수의 차이&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 리터럴은 고정된 값이라는 정의를 가지고 있고 상수는 변하지 않는 값이라는 정의를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사실 고정된 값 = 변하지 않는 값이라고 생각하실 수 있는데 리터럴과 상수는 큰 차이가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 상수는 보통의 경우 static final로 정의를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하기의 코드와 같이 숫자뿐만 아니라 변하면 안 되는 값을 static final 키워드를 붙여서 만든 것을 상수라고 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730439991498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static final double PI = 3.141592;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그에 비해 리터럴은 생각보다 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다시 이 코드를 보면 PI라는 상수에 3.141592라는 값을 할당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때 &quot;3.141592&quot;라는 값이 바로 리터럴입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;변수에 할당되거나 연산에 사용될 수 있는 데이터라고 생각하시면 됩니다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1730440097130&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static final double PI = 3.141592;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한 Java에서는 메인 메서드가 static으로 정의되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;메인 메서드는 프로그램이 시작되면 가장 먼저 호출되는 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그로 인해 객체 생성 없이 메인 메서드를 호출해야 하므로 static으로 호출해야 됩니다.&lt;/p&gt;</description>
      <category>JAVA/개념 및 정리</category>
      <author>코딩하는_대학생</author>
      <guid isPermaLink="true">https://solution-is-here.tistory.com/227</guid>
      <comments>https://solution-is-here.tistory.com/227#entry227comment</comments>
      <pubDate>Thu, 31 Oct 2024 14:21:00 +0900</pubDate>
    </item>
    <item>
      <title>TestContainers에 Singleton 적용하기</title>
      <link>https://solution-is-here.tistory.com/226</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;TestContainers 란?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;TestContainers는 Docker 컨테이너를 활용하여 실제 서비스와의 통합 테스트를 쉽게 수행할 수 있도록 지원하는 테스트 라이브러리입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://testcontainers.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://testcontainers.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723705431678&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Testcontainers&quot; data-og-description=&quot;Testcontainers is an opensource framework for providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.&quot; data-og-host=&quot;testcontainers.com&quot; data-og-source-url=&quot;https://testcontainers.com/&quot; data-og-url=&quot;https://testcontainers.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bvuLHK/hyWOfeXN7Y/xaITsVXN5fRGEWfjUMzoKK/img.png?width=1200&amp;amp;height=577&amp;amp;face=0_0_1200_577&quot;&gt;&lt;a href=&quot;https://testcontainers.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://testcontainers.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bvuLHK/hyWOfeXN7Y/xaITsVXN5fRGEWfjUMzoKK/img.png?width=1200&amp;amp;height=577&amp;amp;face=0_0_1200_577');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Testcontainers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Testcontainers is an opensource framework for providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;testcontainers.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 스프링 사용자들이 Test를 할 때 인메모리 데이터베이스(H2)를 사용하는데 개발, 프로덕션 단계에서는 H2가 아닌 PostgreSQL, MySQL을 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 문제는 없을 수 있으나 테스트의 신뢰성이 저하되는 건 부정할 수 없는 사실입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;실제로 SQL 호환성 및 데이터베이스 기능에서 몇 가지 차이점이 있습니다.&amp;nbsp;&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;이러한 단점을 극복하기 위해 Test Containers를 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TestContainers는 다음과 같은 장점을 가지고 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;일관성 : 테스트 컨테이너를 사용하면 개발, 테스트, 프로덕션 단계 전반에 걸쳐 일관된 환경을 보장할 수 있습니다.&lt;/li&gt;
&lt;li&gt;격리 : 각 테스트는 자체 격리된 컨테이너에서 실행될 수 있어 테스트 간의 간섭을 방지합니다.&lt;/li&gt;
&lt;li&gt;효율성 : 테스트 컨테이너는 빠르게 부팅되어 테스트 환경 설정에 소요되는 시간을 크게 줄입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 다양한 모듈을 제공하는 것도 TestContainers의 큰 장점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하기의 링크를 보면 69개의 모듈을 제공하는 것을 알 수 있습니다. (MySQL, Redis, PostgreSQL, Google Cloud 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://testcontainers.com/modules/?language=java&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://testcontainers.com/modules/?language=java&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723706114196&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Modules&quot; data-og-description=&quot;Testcontainers modules are preconfigured implementations of various dependencies that make writing your tests even easier!&quot; data-og-host=&quot;testcontainers.com&quot; data-og-source-url=&quot;https://testcontainers.com/modules/?language=java&quot; data-og-url=&quot;https://testcontainers.com/modules/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dbvCxo/hyWOfTCLwK/Kg1NAdw5KqOUAIDQ1PCW2k/img.png?width=1200&amp;amp;height=577&amp;amp;face=0_0_1200_577&quot;&gt;&lt;a href=&quot;https://testcontainers.com/modules/?language=java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://testcontainers.com/modules/?language=java&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dbvCxo/hyWOfTCLwK/Kg1NAdw5KqOUAIDQ1PCW2k/img.png?width=1200&amp;amp;height=577&amp;amp;face=0_0_1200_577');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Modules&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Testcontainers modules are preconfigured implementations of various dependencies that make writing your tests even easier!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;testcontainers.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TestContainers의 단점?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게만 본다면 TestContainers은 테스트에서 꼭 필요한 프레임워크처럼 보일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 잘 알아보지 않고 사용한다면 모든 테스트 클래스에서 Docker에 해당 모듈의 이미지를 빌드해 많은 시간이 걸릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 통해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 Container를 정의하고 매우 간단한 테스트가 있는 클래스 6개를 만들겠습니다.&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;1572&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DSftA/btsI5b5oJaL/xg3oRhWz9McZpr6QCosmM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DSftA/btsI5b5oJaL/xg3oRhWz9McZpr6QCosmM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DSftA/btsI5b5oJaL/xg3oRhWz9McZpr6QCosmM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDSftA%2FbtsI5b5oJaL%2Fxg3oRhWz9McZpr6QCosmM0%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;1572&quot; height=&quot;1168&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;1168&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;338&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kG98c/btsI5Xd9mlS/yGz2nubfXd7wq85L2skoeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kG98c/btsI5Xd9mlS/yGz2nubfXd7wq85L2skoeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kG98c/btsI5Xd9mlS/yGz2nubfXd7wq85L2skoeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkG98c%2FbtsI5Xd9mlS%2FyGz2nubfXd7wq85L2skoeK%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;338&quot; height=&quot;298&quot; data-origin-width=&quot;338&quot; data-origin-height=&quot;298&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;그런 뒤 테스트를 전부 실행시켜 보면 52초가 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJCsR/btsI4zSUiTB/i3XxpxJLf6curXHpITuNZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJCsR/btsI4zSUiTB/i3XxpxJLf6curXHpITuNZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJCsR/btsI4zSUiTB/i3XxpxJLf6curXHpITuNZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJCsR%2FbtsI4zSUiTB%2Fi3XxpxJLf6curXHpITuNZ0%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;394&quot; height=&quot;58&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 단위테스트 또는 인메모리 데이터베이스로 진행하면 5초 이내로 실행될 매우 간단한 테스트인데 오래 걸리는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 StopWatch를 통해 어디에서 시간이 오래 걸리는지 파악해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 @Container 어노테이션은 삭제하겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Container 어노테이션은 테스트 컨테이너를 자동으로 관리하는데, 시간을 측정하려면 수동으로 관리를 해야 합니다. (start, stop)&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;static StopWatch stopWatch = new StopWatch();

static MySQLContainer mySQLContainer = new MySQLContainer&amp;lt;&amp;gt;(&quot;mysql:8.0.31&quot;);

@BeforeAll
static void beforeAll() {
    stopWatch.start(&quot;MySQL Container start&quot;);
    mySQLContainer.start();
    stopWatch.stop();

    System.out.println(&quot;MySQLContainer start time: &quot; + stopWatch.getTotalTimeMillis() + &quot; ms&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;560&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxeJPG/btsI6wAoVJQ/7LPtl6em2fLqWnYLiyYk00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxeJPG/btsI6wAoVJQ/7LPtl6em2fLqWnYLiyYk00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxeJPG/btsI6wAoVJQ/7LPtl6em2fLqWnYLiyYk00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxeJPG%2FbtsI6wAoVJQ%2F7LPtl6em2fLqWnYLiyYk00%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;560&quot; height=&quot;60&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;60&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;시작을 하는데 8,000ms 즉 8초가 걸리는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 Test Container의 start 메서드를 통해 docker에 컨테이너를 생성 및 시작하기 때문에 오래 걸리는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@AfterAll
static void afterAll() {
    stopWatch.start(&quot;MySQLContainer Stop&quot;);
    mySQLContainer.stop();
    stopWatch.stop();
    System.out.println(&quot;MySQLContainer stop time: &quot; + stopWatch.getTotalTimeMillis() + &quot; ms&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;524&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eiT03I/btsI4qBUX9h/YjyGVWdpoMmq2ObXau0fIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eiT03I/btsI4qBUX9h/YjyGVWdpoMmq2ObXau0fIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eiT03I/btsI4qBUX9h/YjyGVWdpoMmq2ObXau0fIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeiT03I%2FbtsI4qBUX9h%2FYjyGVWdpoMmq2ObXau0fIk%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;524&quot; height=&quot;54&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;54&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;stop은 컨테이너를 종료하기 때문에 start에 비해 시간이 적게 걸립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 테스트로 돌아와서 왜 52초가 걸렸는지 살펴보겠습니다.&lt;/p&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;6개의 MySQLContainer start time&lt;/b&gt; 기록이 있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 &lt;b&gt;7초, 7초, 7초, 7초, 7초, 7초&lt;/b&gt;가 걸렸습니다. 이를 합산하면 &lt;b&gt;42초&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;총테스트를 실행시키는데 52초가 걸렸다고 하면 10초를 제외한 42초 동안 컨테이너를 Docker에 빌드 및 실행한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(테스트 실행, 컨테이너 종료를 제외하면 거의 대부분을 TestContainers 실행에 사용했다 하더라도 과언이 아닙니다)&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;@Container 어노테이션을 적용해도 동일하게 52초가 나옵니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1723711863525&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MySQLContainer start time: 6976 ms

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.2)

2024-08-15T17:50:06.022+09:00  INFO 97342 --- [demo] [    Test worker] test.example.MySQLContainer1             : Starting MySQLContainer1 using Java 17.0.11 with PID 97342 (started by yongjunhong in /Users/yongjunhong/Desktop/FixtureMonkey Test)
2024-08-15T17:50:06.022+09:00  INFO 97342 --- [demo] [    Test worker] test.example.MySQLContainer1             : No active profile set, falling back to 1 default profile: &quot;default&quot;
2024-08-15T17:50:06.055+09:00  INFO 97342 --- [demo] [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode
2024-08-15T17:50:06.055+09:00  INFO 97342 --- [demo] [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2024-08-15T17:50:06.059+09:00  INFO 97342 --- [demo] [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 3 ms. Found 1 JPA repository interface.
2024-08-15T17:50:06.080+09:00  INFO 97342 --- [demo] [    Test worker] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2024-08-15T17:50:06.081+09:00  INFO 97342 --- [demo] [    Test worker] o.h.c.internal.RegionFactoryInitiator    : HHH000026: Second-level cache disabled
2024-08-15T17:50:06.086+09:00  INFO 97342 --- [demo] [    Test worker] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2024-08-15T17:50:06.086+09:00  INFO 97342 --- [demo] [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Starting...
2024-08-15T17:50:06.093+09:00  INFO 97342 --- [demo] [    Test worker] com.zaxxer.hikari.pool.HikariPool        : HikariPool-2 - Added connection org.testcontainers.jdbc.ConnectionWrapper@32b273c1
2024-08-15T17:50:06.093+09:00  INFO 97342 --- [demo] [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Start completed.
2024-08-15T17:50:06.093+09:00  WARN 97342 --- [demo] [    Test worker] org.hibernate.orm.deprecation            : HHH90000025: MySQL8Dialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
2024-08-15T17:50:06.093+09:00  WARN 97342 --- [demo] [    Test worker] org.hibernate.orm.deprecation            : HHH90000026: MySQL8Dialect has been deprecated; use org.hibernate.dialect.MySQLDialect instead
2024-08-15T17:50:06.109+09:00  INFO 97342 --- [demo] [    Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2024-08-15T17:50:06.117+09:00  INFO 97342 --- [demo] [    Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-08-15T17:50:06.134+09:00  INFO 97342 --- [demo] [    Test worker] test.example.MySQLContainer1             : Started MySQLContainer1 in 0.127 seconds (process running for 16.249)
2024-08-15T17:50:06.164+09:00 DEBUG 97342 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
2024-08-15T17:50:06.333+09:00  INFO 97342 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer2]: MySQLContainer2 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T17:50:06.337+09:00  INFO 97342 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer2
2024-08-15T17:50:06.338+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Creating container for image: mysql:8.0.31
2024-08-15T17:50:06.379+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 is starting: 4c99d15dc8353bdb5834872babd3d1a055aa7a3f36089cd4ea3c6180d8d9082e
2024-08-15T17:50:06.519+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Waiting for database connection to become available at jdbc:mysql://localhost:55923/test using query 'SELECT 1'
2024-08-15T17:50:13.254+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 started in PT6.91563S
2024-08-15T17:50:13.254+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container is started (JDBC URL: jdbc:mysql://localhost:55923/test)
MySQLContainer start time: 6916 ms
2024-08-15T17:50:13.264+09:00 DEBUG 97342 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
&amp;gt; Task :test
2024-08-15T17:50:13.404+09:00  INFO 97342 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer3]: MySQLContainer3 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T17:50:13.415+09:00  INFO 97342 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer3
2024-08-15T17:50:13.417+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Creating container for image: mysql:8.0.31
2024-08-15T17:50:13.456+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 is starting: 58f2df24907f19396e6960ddf44f03d17348470045c512fb7b7da9ceefc1498b
2024-08-15T17:50:13.576+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Waiting for database connection to become available at jdbc:mysql://localhost:55999/test using query 'SELECT 1'
2024-08-15T17:50:20.343+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 started in PT6.926603S
2024-08-15T17:50:20.343+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container is started (JDBC URL: jdbc:mysql://localhost:55999/test)
MySQLContainer start time: 6927 ms
2024-08-15T17:50:20.349+09:00 DEBUG 97342 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
2024-08-15T17:50:20.490+09:00  INFO 97342 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer4]: MySQLContainer4 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T17:50:20.496+09:00  INFO 97342 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer4
2024-08-15T17:50:20.497+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Creating container for image: mysql:8.0.31
2024-08-15T17:50:20.530+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 is starting: 4a01749481cafb6fc1c9479f38530e13ebf1950837ecf814302e44ff4dba819e
2024-08-15T17:50:20.694+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Waiting for database connection to become available at jdbc:mysql://localhost:56072/test using query 'SELECT 1'
2024-08-15T17:50:27.259+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 started in PT6.761506S
2024-08-15T17:50:27.259+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container is started (JDBC URL: jdbc:mysql://localhost:56072/test)
MySQLContainer start time: 6762 ms
2024-08-15T17:50:27.265+09:00 DEBUG 97342 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
2024-08-15T17:50:27.413+09:00  INFO 97342 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer5]: MySQLContainer5 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T17:50:27.420+09:00  INFO 97342 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer5
2024-08-15T17:50:27.422+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Creating container for image: mysql:8.0.31
2024-08-15T17:50:27.455+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 is starting: 6ac14ad2b9c91f3ef8b327464422dc1eff526194151d64c6eb00c4588a1ac282
2024-08-15T17:50:27.568+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Waiting for database connection to become available at jdbc:mysql://localhost:56146/test using query 'SELECT 1'
2024-08-15T17:50:34.555+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 started in PT7.133707S
2024-08-15T17:50:34.556+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container is started (JDBC URL: jdbc:mysql://localhost:56146/test)
MySQLContainer start time: 7135 ms
2024-08-15T17:50:34.569+09:00 DEBUG 97342 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
2024-08-15T17:50:34.733+09:00  INFO 97342 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer6]: MySQLContainer6 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T17:50:34.738+09:00  INFO 97342 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer6
2024-08-15T17:50:34.740+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Creating container for image: mysql:8.0.31
2024-08-15T17:50:34.773+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 is starting: 6a1ad36e06437c2d0a974d8328c3e6a50b152479ce004690e8fa492119bb637c
2024-08-15T17:50:35.001+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Waiting for database connection to become available at jdbc:mysql://localhost:56227/test using query 'SELECT 1'
2024-08-15T17:50:41.833+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 started in PT7.092695S
2024-08-15T17:50:41.833+09:00  INFO 97342 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container is started (JDBC URL: jdbc:mysql://localhost:56227/test)
MySQLContainer start time: 7093 ms&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;시간이 점점 줄어드는 이유로는 Docker Image 캐싱 등이 있을 수 있습니다.&lt;br /&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;하나의 클래스에서 컨테이너를 생성할 때마다 6~7초가 걸린다면 이는 너무나 큰 단점이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;싱글톤을 적용한 TestContainers&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 적용할 수 있는 개념이 싱글톤입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤 패턴은 디자인 패턴 중 하나로, 특정 클래스의 인스턴스가 하나만 존재하도록 보장하는 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TestContainers에 싱글톤을 적용한다면 여러 개의 클래스에서 하나의 테스트 컨테이너로 테스트를 실행하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서는 다음과 같은 방법을 통해 진행하는 것을 추천하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;1278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb7liE/btsI4PVLt2I/Dx8vgRm4WrukcMeqSnLXVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb7liE/btsI4PVLt2I/Dx8vgRm4WrukcMeqSnLXVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb7liE/btsI4PVLt2I/Dx8vgRm4WrukcMeqSnLXVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb7liE%2FbtsI4PVLt2I%2FDx8vgRm4WrukcMeqSnLXVK%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;1396&quot; height=&quot;1278&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;1278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에서는 기본 클래스가 로드될 때 한 번만 실행되는 싱글톤 컨테이너에 대해 설명하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 변수와 메서드는 프로그램이 시작될 때, 즉 클래스가 메모리에 로드될 때 초기화 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 JVM이 프로그램을 시작하면서 필요한 클래스 정보를 메모리에 로드하는 시점에서 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 자연스레 정적 메서드 안에서 선언한 MY_SQL_CONTAINER은 클래스가 로드될 때 초기화 되는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 변수는 모든 인스턴스에서 공유하는 특징으로 인해 사용자는 추가적인 설정 없이 데이터베이스 환경을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 클래스에 적용시켜 본 결과 52초에서 13초로 39초의 시간이 줄었습니다. &lt;b&gt;(75% 성능 개선)&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;컨테이너가 시작하는 것이 7초 종료되는 것을 0.2초로 잡았을 때 컨테이너가 한 번만 생성되므로 5번을 줄일 수 있습니다.&lt;br /&gt;* 원래 6번 생성이 되었는데 한 번으로 줄였기 때문에 5번을 줄인 것입니다.&lt;br /&gt;&lt;br /&gt;그러면 (7 + 0.2) * 5 = 36이므로 실제로 절약한 시간과 비슷함을 알 수 있습니다.&amp;nbsp;&amp;nbsp;&lt;b&gt;&lt;b&gt;(스프링 실행 시간 제외)&lt;/b&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bh3fS/btsI5SYgfIZ/ZjybwkFNehraMpSTrcjfw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bh3fS/btsI5SYgfIZ/ZjybwkFNehraMpSTrcjfw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bh3fS/btsI5SYgfIZ/ZjybwkFNehraMpSTrcjfw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBh3fS%2FbtsI5SYgfIZ%2FZjybwkFNehraMpSTrcjfw1%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;380&quot; height=&quot;62&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;62&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그에서도 한 번의 컨테이너가 실행된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723719048589&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2024-08-15T19:44:55.820+09:00  INFO 26094 --- [demo] [    Test worker] org.testcontainers.DockerClientFactory   : ✔︎ Docker server version should be at least 1.6.0
2024-08-15T19:44:55.822+09:00  INFO 26094 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Creating container for image: mysql:8.0.31
2024-08-15T19:44:55.859+09:00  INFO 26094 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 is starting: 97f1272f60cb61983591b6039b6fcc492dd3c23ac2726532462626d3a6bb751c
2024-08-15T19:44:55.989+09:00  INFO 26094 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Waiting for database connection to become available at jdbc:mysql://localhost:60570/test using query 'SELECT 1'
2024-08-15T19:45:02.797+09:00  INFO 26094 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container mysql:8.0.31 started in PT6.975015S
2024-08-15T19:45:02.797+09:00  INFO 26094 --- [demo] [    Test worker] tc.mysql:8.0.31                          : Container is started (JDBC URL: jdbc:mysql://localhost:60570/test)
2024-08-15T19:45:02.805+09:00  INFO 26094 --- [demo] [    Test worker] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.testcontainers.jdbc.ConnectionWrapper@7dfca9e6
2024-08-15T19:45:02.806+09:00  INFO 26094 --- [demo] [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2024-08-15T19:45:02.824+09:00  WARN 26094 --- [demo] [    Test worker] org.hibernate.orm.deprecation            : HHH90000025: MySQL8Dialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
2024-08-15T19:45:02.824+09:00  WARN 26094 --- [demo] [    Test worker] org.hibernate.orm.deprecation            : HHH90000026: MySQL8Dialect has been deprecated; use org.hibernate.dialect.MySQLDialect instead
2024-08-15T19:45:03.172+09:00  INFO 26094 --- [demo] [    Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2024-08-15T19:45:03.195+09:00 DEBUG 26094 --- [demo] [    Test worker] org.hibernate.SQL                        : create table product (id integer not null auto_increment, name varchar(255), price integer, primary key (id)) engine=InnoDB
2024-08-15T19:45:03.205+09:00  INFO 26094 --- [demo] [    Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-08-15T19:45:03.501+09:00  INFO 26094 --- [demo] [    Test worker] test.ApplicationTests                    : Started ApplicationTests in 8.999 seconds (process running for 9.528)
2024-08-15T19:45:03.825+09:00  INFO 26094 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer1]: MySQLContainer1 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T19:45:03.836+09:00  INFO 26094 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer1

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.2)

2024-08-15T19:45:03.852+09:00  INFO 26094 --- [demo] [    Test worker] test.example.MySQLContainer1             : Starting MySQLContainer1 using Java 17.0.11 with PID 26094 (started by yongjunhong in /Users/yongjunhong/Desktop/FixtureMonkey Test)
2024-08-15T19:45:03.852+09:00  INFO 26094 --- [demo] [    Test worker] test.example.MySQLContainer1             : No active profile set, falling back to 1 default profile: &quot;default&quot;
2024-08-15T19:45:03.885+09:00  INFO 26094 --- [demo] [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode
2024-08-15T19:45:03.885+09:00  INFO 26094 --- [demo] [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2024-08-15T19:45:03.889+09:00  INFO 26094 --- [demo] [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 3 ms. Found 1 JPA repository interface.
2024-08-15T19:45:03.907+09:00  INFO 26094 --- [demo] [    Test worker] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2024-08-15T19:45:03.908+09:00  INFO 26094 --- [demo] [    Test worker] o.h.c.internal.RegionFactoryInitiator    : HHH000026: Second-level cache disabled
2024-08-15T19:45:03.912+09:00  INFO 26094 --- [demo] [    Test worker] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2024-08-15T19:45:03.913+09:00  INFO 26094 --- [demo] [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Starting...
2024-08-15T19:45:03.925+09:00  INFO 26094 --- [demo] [    Test worker] com.zaxxer.hikari.pool.HikariPool        : HikariPool-2 - Added connection org.testcontainers.jdbc.ConnectionWrapper@577e2b91
2024-08-15T19:45:03.925+09:00  INFO 26094 --- [demo] [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Start completed.
2024-08-15T19:45:03.926+09:00  WARN 26094 --- [demo] [    Test worker] org.hibernate.orm.deprecation            : HHH90000025: MySQL8Dialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
2024-08-15T19:45:03.926+09:00  WARN 26094 --- [demo] [    Test worker] org.hibernate.orm.deprecation            : HHH90000026: MySQL8Dialect has been deprecated; use org.hibernate.dialect.MySQLDialect instead
2024-08-15T19:45:03.941+09:00  INFO 26094 --- [demo] [    Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2024-08-15T19:45:03.951+09:00  INFO 26094 --- [demo] [    Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-08-15T19:45:03.969+09:00  INFO 26094 --- [demo] [    Test worker] test.example.MySQLContainer1             : Started MySQLContainer1 in 0.13 seconds (process running for 9.997)
2024-08-15T19:45:03.989+09:00 DEBUG 26094 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
2024-08-15T19:45:04.015+09:00  INFO 26094 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer2]: MySQLContainer2 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T19:45:04.019+09:00  INFO 26094 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer2
2024-08-15T19:45:04.023+09:00 DEBUG 26094 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
2024-08-15T19:45:04.030+09:00  INFO 26094 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer3]: MySQLContainer3 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T19:45:04.033+09:00  INFO 26094 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer3
2024-08-15T19:45:04.037+09:00 DEBUG 26094 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
2024-08-15T19:45:04.042+09:00  INFO 26094 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer4]: MySQLContainer4 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T19:45:04.047+09:00  INFO 26094 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer4
2024-08-15T19:45:04.050+09:00 DEBUG 26094 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
2024-08-15T19:45:04.055+09:00  INFO 26094 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer5]: MySQLContainer5 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T19:45:04.058+09:00  INFO 26094 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer5
2024-08-15T19:45:04.062+09:00 DEBUG 26094 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
2024-08-15T19:45:04.068+09:00  INFO 26094 --- [demo] [    Test worker] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.example.MySQLContainer6]: MySQLContainer6 does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2024-08-15T19:45:04.071+09:00  INFO 26094 --- [demo] [    Test worker] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.Application for test class test.example.MySQLContainer6
2024-08-15T19:45:04.073+09:00 DEBUG 26094 --- [demo] [    Test worker] org.hibernate.SQL                        : insert into product (name,price) values (?,?)
Hibernate: insert into product (name,price) values (?,?)
2024-08-15T19:45:04.086+09:00  INFO 26094 --- [demo] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-08-15T19:45:04.086+09:00  INFO 26094 --- [demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-08-15T19:45:04.087+09:00  INFO 26094 --- [demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2024-08-15T19:45:04.088+09:00  INFO 26094 --- [demo] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-08-15T19:45:04.088+09:00  INFO 26094 --- [demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Shutdown initiated...
2024-08-15T19:45:04.209+09:00  INFO 26094 --- [demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Shutdown completed.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에서는 추상 클래스를 통해 상속으로 사용할 수 있다고 했는데 부모 클래스를 상속하는 자식 클래스에서 부모 클래스에 있는 TestContainers 필드를 사용하지 않는다면 추상 클래스로 정의하지 않아도 됩니다.&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;style3&quot;&gt;필자는 MySQLContainer을 생성하는 클래스는 상속할 이유가 없다고 생각해 final 클래스로 정의했습니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.testcontainers.containers.MySQLContainer;

final class MySqlContainerProvider {

    private static final MySQLContainer MY_SQL_CONTAINER;
    private static final String IMAGE_VERSION = &quot;mysql:8.0.31&quot;;


    private MySqlContainerProvider() {
    }

    static {
       MY_SQL_CONTAINER = new MySQLContainer(IMAGE_VERSION);
       MY_SQL_CONTAINER.start();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;유의할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 컨테이너로 전체 테스트를 진행하기 때문에 데이터 롤백을 잘 신경 쓰셔야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Transaction&lt;/b&gt;이 그 방법이 될 수 있고, &lt;b&gt;@AfterEach, @BeforeEach&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;그리고 모든 모듈이 MySQL Container와 같이 정적 메서드로 간단히 호출할 수 있는 건 아닙니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Redis의 경우 System의 properties를 변경해야 되고, localstack은 S3 관련 Bean들을 대체해야 돼서 @Bean으로 등록해야 할 수도 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 모듈의 특징을 잘 알아보고 하셔야 됩니다. 감사합니다.&lt;/p&gt;</description>
      <category>Backend/Spring</category>
      <category>Testcontainers</category>
      <category>testcontainers singleton</category>
      <category>테스트 컨테이너</category>
      <category>테스트 컨테이너 싱글톤</category>
      <author>코딩하는_대학생</author>
      <guid isPermaLink="true">https://solution-is-here.tistory.com/226</guid>
      <comments>https://solution-is-here.tistory.com/226#entry226comment</comments>
      <pubDate>Thu, 15 Aug 2024 17:53:13 +0900</pubDate>
    </item>
  </channel>
</rss>