<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>kkoon9</title>
    <link>https://kkoon9.tistory.com/</link>
    <description>개발 한 접시</description>
    <language>ko</language>
    <pubDate>Wed, 5 Aug 2026 17:17:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>kkoon9</managingEditor>
    <image>
      <title>kkoon9</title>
      <url>https://tistory1.daumcdn.net/tistory/3241636/attach/2bf4c03e31c347148d64f10e1e3550ef</url>
      <link>https://kkoon9.tistory.com</link>
    </image>
    <item>
      <title>[AI 자동화 테스트] notion to tistory</title>
      <link>https://kkoon9.tistory.com/562</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;ff96b901-18fc-4994-9b83-675fd0663366&lt;/title&gt;
    &lt;style&gt;
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            line-height: 1.8;
            color: #333;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        h2, h3, h4 {
            margin-top: 1.5em;
            margin-bottom: 0.5em;
            color: #222;
        }
        h2 { font-size: 1.8em; border-bottom: 2px solid #4a9eff; padding-bottom: 0.3em; }
        h3 { font-size: 1.5em; }
        h4 { font-size: 1.2em; }
        p { margin: 1em 0; }
        code {
            background: #f5f5f5;
            padding: 2px 6px;
            border-radius: 3px;
            font-family: 'Consolas', 'Monaco', monospace;
            font-size: 0.9em;
            color: #e83e8c;
        }
        pre.code-block {
            background: #f8f9fa;
            border: 1px solid #e9ecef;
            border-radius: 5px;
            padding: 15px;
            overflow-x: auto;
            margin: 1.5em 0;
        }
        pre.code-block code {
            background: none;
            padding: 0;
            color: #333;
            font-size: 0.9em;
        }
        blockquote {
            border-left: 4px solid #4a9eff;
            padding-left: 15px;
            margin: 1.5em 0;
            color: #666;
            font-style: italic;
        }
        ul, ol {
            margin: 1em 0;
            padding-left: 2em;
        }
        li {
            margin: 0.5em 0;
        }
        img {
            max-width: 100%;
            height: auto;
            border-radius: 5px;
            margin: 1.5em 0;
        }
        figure {
            margin: 1.5em 0;
            text-align: center;
        }
        figcaption {
            font-size: 0.9em;
            color: #666;
            margin-top: 0.5em;
        }
        hr {
            border: none;
            border-top: 1px solid #e9ecef;
            margin: 2em 0;
        }
        a {
            color: #4a9eff;
            text-decoration: none;
        }
        a:hover {
            text-decoration: underline;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;start Date: 2024-05-21&lt;/p&gt;
&lt;p&gt;end Date:  2024-06-09&lt;/p&gt;
&lt;!-- 목차 --&gt;
&lt;h2&gt;책의 소개&lt;/h2&gt;
&lt;p&gt;이 책은 안드로이드 중심으로 설명하고 있는 책입니다.&lt;/p&gt;
&lt;h2&gt;프로세스, 스레드, 코루틴&lt;/h2&gt;
&lt;p&gt;애플리케이션을 실행할 때 OS는 프로세스를 생성하고 여기에 스레드를 연결한 다음, 메인 스레드를 시작합니다.&lt;/p&gt;
&lt;h3&gt;프로세스&lt;/h3&gt;
&lt;p&gt;실행 중인 애플리케이션의 인스턴스입니다.&lt;/p&gt;
&lt;p&gt;애플리케이션이 시작될 때마다 애플리케이션의 프로세스가 시작됩니다.&lt;/p&gt;
&lt;h3&gt;스레드&lt;/h3&gt;
&lt;p&gt;실행 스레드는 프로세스가 실행할 일련의 명령을 포함합니다.&lt;/p&gt;
&lt;p&gt;따라서  프로세스는 최소한 하나의 스레드를 포함하며 이 스레드는 애플리케이션의 진입점(entry point)을 실행하기 위해 생성됩니다.&lt;/p&gt;
&lt;p&gt;보통 진입점은 애플리케이션의 main() 함수이며 메인 스레드라 하는데 프로세스의 라이프 사이클과 밀접하게 연관됩니다.&lt;/p&gt;
&lt;p&gt;스레드가 끝나면 프로세스의 다른 스레드와 상관없이 프로세스가 종료됩니다.&lt;/p&gt;
&lt;p&gt;코루틴이 동시성을 구현한 방식을 보면 우리가 직접 스레드를 시작하거나 중지할 필요가 없다는 것을 알 게 됩니다.&lt;/p&gt;
&lt;p&gt;한 두줄의 코드로 코틀린이 특정 스레드나 스레드 풀을 생성해서 코루틴을 실행하도록 지시하기만 하면 됩니다.&lt;/p&gt;
&lt;h3&gt;코루틴&lt;/h3&gt;
&lt;p&gt;코루틴은 경량 스레드라고도 합니다.&lt;/p&gt;
&lt;p&gt;대부분의 스레드와 마찬가지로 코루틴이 프로세서가 실행할 명령어 집합의 실행을 정의하기 때문입니다.&lt;/p&gt;
&lt;p&gt;또한 코루틴은 스레드와 비슷한 라이프 사이클을 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;코루틴은 스레드 안에서 실행됩니다.&lt;/p&gt;
&lt;p&gt;스레드 하나에 많은 코루틴이 있을 수 있지만 주어진 시간에 하나의 스레드에서 하나의 명령만이 실행될 수 있습니다.&lt;/p&gt;
&lt;p&gt;즉 같은 스레드에 10개의 코루틴이 있다면 어느 한 시점에는 하나의 코루틴만 실행됩니다.&lt;/p&gt;
&lt;h4&gt;스레드 vs 코루틴&lt;/h4&gt;
&lt;p&gt;코루틴이 스레드보다 빠르고 적은 비용으로 생성할 수 있다는 것입니다.&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;suspend fun createCoroutines(amount: Int) {
    val jobs = ArrayList&amp;lt;Job&amp;gt;()
    for (i in 1..amount) {
        jobs += launch {
            delay(1000)
        }
    }
    jobs.forEach {
        it.join()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 함수는 파라미터 amount만큼 코루틴을 생성해 각 코루틴을 1초간 지연시킨 후 모든 코루틴이 종료될 때까지 기다렸다가 반환합니다.&lt;/p&gt;
&lt;p&gt;amount가 10,000으로 실행할 때 1.160ms, 100,000으로 실행할 때 1,649ms가 소요됐다고 합니다.&lt;/p&gt;
&lt;p&gt;코틀린은 고정된 크기의 스레드 풀을 사용하고 코루틴을 스레드들에 배포하기 때문에 실행 시간이 매우 적게 증가합니다.&lt;/p&gt;
&lt;p&gt;코루틴이 일시 중단되는 동안(delay()가 실행되는 동안) 실행 중인 스레드는 다른 코루틴을 실행하는 데 사용되며 코루틴은 시작 혹은 재개될 준비 상태가 됩니다.&lt;/p&gt;
&lt;div class=&quot;callout&quot; style=&quot;background: #f0f0f0; padding: 15px; border-left: 4px solid #4a9eff; margin: 10px 0;&quot;&gt; ‍  Thread 클래스의 activeCount() 메서드는 활성화된 스레드 수를 알 수 있습니다.&lt;/div&gt;
&lt;pre class=&quot;code-block&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun main(args: Array&amp;lt;String&amp;gt;) = runBlocking {
    println(&quot;${Thread.activeCount()} threads active at the start&quot;)

    val time = measureTimeMillis {
        createCoroutines(10000)
    }
    println(&quot;${Thread.activeCount()} threads active at the end&quot;)
    println(&quot;Took $time ms&quot;)
}


suspend fun createCoroutines(amount: Int) {
    val jobs = ArrayList&amp;lt;Job&amp;gt;()
    for (i in 1..amount) {
        jobs += CoroutineScope(Dispatchers.IO).launch() {
            delay(1000)
        }
    }
    jobs.forEach {
        it.join()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;&lt;img src=&quot;https://prod-files-secure.s3.us-west-2.amazonaws.com/2e356ed7-a867-4387-9720-7b9c22636400/a339fdc4-be96-4d43-b2f1-e87915865d07/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&amp;X-Amz-Credential=ASIAZI2LB466RKFL2K6J%2F20260129%2Fus-west-2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260129T151858Z&amp;X-Amz-Expires=3600&amp;X-Amz-Security-Token=IQoJb3JpZ2luX2VjEL%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLXdlc3QtMiJHMEUCIFb6r9oh1kNF8uxkywtyR6iPESFbMHwShdIwb5HpqJm2AiEA41wbLBUJP53d%2BAiBEalioHMGbdGWaClBADTSrADNzLAqiAQIiP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw2Mzc0MjMxODM4MDUiDIC4eS%2BsPZd%2FYvScaCrcAxrKrOmKNDCTPnZ%2FlEs6MTokWmdhC%2BSda77Ef33RNaZHrcjgi72DLOOFiyAugjnSuAJLGJ%2FFk3VOlLmxaVoUxDBZKFnEpYONAiZs34giehvKzHpk2r7C%2F%2Fai3vdeM%2Bweoew%2FFE%2BbKMoKrcSvtdfs%2FhwI%2F%2B38QzbvBPJnkmAXJKh7QPqKMtuo1g0aA8ZbWNjnuochJsjoQJM3mngRYgplW%2FnwIeikevgI42YHc0%2B3t4seDKv%2BvskbIOqQpoh2G%2BWMoPMI5MftnP5H%2FkauA4Hx29yk2xhLphZpmA9MXDx7iscIV08O1Hz1scHTYv0H3UrG6O2Wqk9FYjjPbSjDWesoWHPPqAYz2dNewCg60f%2FqEWGRL24v1MlUxpRp6mpdGXtc9ucPnIkvQZQzDh3%2Fn6euXlgenVVBOyhbz7h5tPbWv79isn0HsNiDoGsPx%2Bx19E%2B4%2BzySldCJFE8p9VYYNLWQyEsZzg9qpPazG1WMtLAOGnZnL3DHWKWzpwhybnqGPcyATXwxtz1LvV2QENVljj2dr3533OgrndgIxRoqR%2F8lxEa7RD7hvh49PPma0Hh%2FGd5GV1M%2FPDfISz47DULOiWruw79QziEzRlCEJyINKVGzmMcZ%2BeNnLLpyliz8QO1jMJLp7csGOqUBzgeeGHWY7m7GQW%2FIxZb7pD0HkQ%2F39YRBlT95HkjTRMJ2owHaoXfNrI1UukI3h8F7q6JzB7Cn%2BkCFES8M0jfHYaHzc2TfQz9qvhpbsSkjY42sBdAX307X55wIq9NXkrgpMrMYEzHoxksR45f%2Bp0u0fbkrrVt2Jea5xYcuBQ9FobBPzPymlwruTeDcTRzNftvL%2BkIV9nb4GgKi8Dt5EC9oVsfz%2Bakf&amp;X-Amz-Signature=f83be106026db6e76835e150f3ef44985f8741201fe7c013912d4d9814ca0e79&amp;X-Amz-SignedHeaders=host&amp;x-amz-checksum-mode=ENABLED&amp;x-id=GetObject&quot; alt=&quot;&quot; style=&quot;max-width: 100%;&quot;&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;코루틴이 특정 스레드 안에서 실행되더라도 스레드와 묶이지 않는다는 점을 이해해야 합니다.&lt;/p&gt;
&lt;p&gt;코뤁니의 일부를 특정 스레드에서 실행하고, 실행을 중지한 다음 나중에 다른 스레드에서 계속 실행하는 것이 가능합니다.&lt;/p&gt;
&lt;p&gt;코틀린이 실행 가능한 스레드로 코루틴을 이동시키기 때문입니다.&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun main(args: Array&amp;lt;String&amp;gt;) = runBlocking {
    println(&quot;${Thread.activeCount()} threads active at the start&quot;)

    val time = measureTimeMillis {
        createCoroutines(3)
    }
    println(&quot;${Thread.activeCount()} threads active at the end&quot;)
    println(&quot;Took $time ms&quot;)
}


suspend fun createCoroutines(amount: Int) {
    val jobs = ArrayList&amp;lt;Job&amp;gt;()
    for (i in 1..amount) {
        jobs += CoroutineScope(Dispatchers.IO).launch() {
            println(&quot;Started $i in ${Thread.currentThread().name}&quot;)
            delay(1000)
            println(&quot;Finished $i in ${Thread.currentThread().name}&quot;)
        }
    }
    jobs.forEach {
        it.join()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;&lt;img src=&quot;https://prod-files-secure.s3.us-west-2.amazonaws.com/2e356ed7-a867-4387-9720-7b9c22636400/49685160-3b32-4229-8216-30297b43cbdf/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&amp;X-Amz-Credential=ASIAZI2LB466RKFL2K6J%2F20260129%2Fus-west-2%2Fs3%2Faws4_request&amp;X-Amz-Date=20260129T151858Z&amp;X-Amz-Expires=3600&amp;X-Amz-Security-Token=IQoJb3JpZ2luX2VjEL%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLXdlc3QtMiJHMEUCIFb6r9oh1kNF8uxkywtyR6iPESFbMHwShdIwb5HpqJm2AiEA41wbLBUJP53d%2BAiBEalioHMGbdGWaClBADTSrADNzLAqiAQIiP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw2Mzc0MjMxODM4MDUiDIC4eS%2BsPZd%2FYvScaCrcAxrKrOmKNDCTPnZ%2FlEs6MTokWmdhC%2BSda77Ef33RNaZHrcjgi72DLOOFiyAugjnSuAJLGJ%2FFk3VOlLmxaVoUxDBZKFnEpYONAiZs34giehvKzHpk2r7C%2F%2Fai3vdeM%2Bweoew%2FFE%2BbKMoKrcSvtdfs%2FhwI%2F%2B38QzbvBPJnkmAXJKh7QPqKMtuo1g0aA8ZbWNjnuochJsjoQJM3mngRYgplW%2FnwIeikevgI42YHc0%2B3t4seDKv%2BvskbIOqQpoh2G%2BWMoPMI5MftnP5H%2FkauA4Hx29yk2xhLphZpmA9MXDx7iscIV08O1Hz1scHTYv0H3UrG6O2Wqk9FYjjPbSjDWesoWHPPqAYz2dNewCg60f%2FqEWGRL24v1MlUxpRp6mpdGXtc9ucPnIkvQZQzDh3%2Fn6euXlgenVVBOyhbz7h5tPbWv79isn0HsNiDoGsPx%2Bx19E%2B4%2BzySldCJFE8p9VYYNLWQyEsZzg9qpPazG1WMtLAOGnZnL3DHWKWzpwhybnqGPcyATXwxtz1LvV2QENVljj2dr3533OgrndgIxRoqR%2F8lxEa7RD7hvh49PPma0Hh%2FGd5GV1M%2FPDfISz47DULOiWruw79QziEzRlCEJyINKVGzmMcZ%2BeNnLLpyliz8QO1jMJLp7csGOqUBzgeeGHWY7m7GQW%2FIxZb7pD0HkQ%2F39YRBlT95HkjTRMJ2owHaoXfNrI1UukI3h8F7q6JzB7Cn%2BkCFES8M0jfHYaHzc2TfQz9qvhpbsSkjY42sBdAX307X55wIq9NXkrgpMrMYEzHoxksR45f%2Bp0u0fbkrrVt2Jea5xYcuBQ9FobBPzPymlwruTeDcTRzNftvL%2BkIV9nb4GgKi8Dt5EC9oVsfz%2Bakf&amp;X-Amz-Signature=ce45a3655fff4dd230d40a1bf828bc0a7b6cb9694d11ecea596787ff278a19f9&amp;X-Amz-SignedHeaders=host&amp;x-amz-checksum-mode=ENABLED&amp;x-id=GetObject&quot; alt=&quot;&quot; style=&quot;max-width: 100%;&quot;&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;프로세스, 스레드, 코루틴 결론&lt;/h3&gt;
&lt;p&gt;애플리케이션은 하나 이상의 프로세스로 구성되어 있고 각 프로세스가 하나 이상의 스레드를 가집니다.&lt;/p&gt;
&lt;p&gt;스레드를 블록한다는 것은 그 스레드에서 코드이 실행을 중지한다는 의미인데, 사용자와 상호작용하는 스레드는 블록되지 않아야 합니다.&lt;/p&gt;
&lt;p&gt;코루틴이 기본적으로 스레드 안에 존재하지만 스레드에 얽매이지 않은 가벼운 스레드입니다.&lt;/p&gt;
&lt;p&gt;동시성은 애플리케이션이 동시에 한 개 이상의 스레드에서 실행될 때 발생합니다.&lt;/p&gt;
&lt;p&gt;동시성이 발생하려면 두 개 이상의 스레드가 생성돼야 하며, 애플리케이션이 제대로 작동하려면 이런 스레드 간의 통신과 동기화가 필요합니다.&lt;/p&gt;
&lt;h2&gt;동시성&lt;/h2&gt;
&lt;p&gt;올바른 동시성 코드는 언제나 똑같은 과정을 거쳐서 항상 똑같은 결과를 내놓지만, 실행 순서에서는 약간의 가변성을 허용하는 코드입니다.&lt;/p&gt;
&lt;p&gt;그러려면 코드의 서로 다른 부분이 어느 정도 독립성이 있어야 하며 약간의 조정도 필요합니다.&lt;/p&gt;
&lt;p&gt;동시성을 이해하는 가장 좋은 방법은 순차적인 코드를 동시성 코드와 비교하는 것입니다.&lt;/p&gt;
&lt;p&gt;다음은 비동시성 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun getProfile(id: Int) : Profile {
    val basicUserInfo = getUserInfo(id) // 사용자 정보
    val contactInfo = getContactInfo(id) // 연락처 정보
    
    return createProfile(basicUserInfo, contactInfo)
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드에서 중요한 점은 사용자 정보가 반환되기 전까지는 연락처 정보를 요청하지 않는다는 점입니다.&lt;/p&gt;
&lt;p&gt;이것이 순차 코드의 장점입니다.&lt;/p&gt;
&lt;p&gt;정확한 실행 순서를 쉽게 알 수 있어서 예측하지 못한 일이 벌어지지는 않습니다.&lt;/p&gt;
&lt;p&gt;하지만 위 코드의 단점은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;동시성 코드에 비해 성능이 저하될 수 있음&lt;/li&gt;
&lt;li&gt;코드가 실행되는 하드웨어를 제대로 활용하지 못할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다음은 동시성 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;suspend fun getProfile(id: Int) : Profile {
    val basicUserInfo = asyncGetUserInfo(id) // 사용자 정보
    val contactInfo = asyncGetContactInfo(id) // 연락처 정보
    
    return createProfile(basicUserInfo.await(), contactInfo.await())
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드는 suspend function(일시 중단 함수)로서 정의에 suspend 수식어가 있음을 알 수 있으며, 위 두 메서드는 비동기로 구현되어 있습니다.&lt;/p&gt;
&lt;p&gt;두 메서드는 서로 다른 스레드에서 실행되도록 작성됐기 때문에 동시성이라고 합니다.&lt;/p&gt;
&lt;h3&gt;await()이 있는 이유&lt;/h3&gt;
&lt;p&gt;await()이 하는 일은 두 메서드가 모두 완료할 때까지 getProfile()의 실행을 일시 중단하는 것입니다.&lt;/p&gt;
&lt;p&gt;두 메서드가 완료되었을 때만 createProfile()이 실행됩니다.&lt;/p&gt;
&lt;p&gt;이렇게 되면 어떤 동시성 호출이 먼저 종료되는지에 관계없이 getProfile()의 결과가 결정론적임을 보장합니다.&lt;/p&gt;
&lt;p&gt;심지어 동시성 코드를 코루틴 간에 통신하게 하도록 조정할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;동시성은 병렬성이 아니다&lt;/h3&gt;
&lt;p&gt;두 개의 코드가 동시에 실행된다는 점에서 병렬적으로 보이기도 합니다.&lt;/p&gt;
&lt;h4&gt;동시성&lt;/h4&gt;
&lt;p&gt;동시성은 두 개 이상의 알고리즘의 실행 시간이 겹쳐질 때 발생합니다.&lt;/p&gt;
&lt;p&gt;중첩이 발생하려면 두 개 이상의 실행 스레드가 필요합니다.&lt;/p&gt;
&lt;p&gt;이런 스레드들이 단일 코어에서 실행되면 병렬이 아니라 동시에 실행되는데, 단일 코어가 서로 다른 스레드의 인스트럭션을 교차 배치해서 스레드들의 실행을 효율적으로 겹쳐서 실행합니다.&lt;/p&gt;
&lt;h4&gt;병렬성&lt;/h4&gt;
&lt;p&gt;병렬은 두 개의 알고리즘이 정호가히 같은 시점에 실행될 때 발생합니다.&lt;/p&gt;
&lt;p&gt;이것이 가능하려면 2개 이상의 코어와 2개 이상의 스레드가 있어야 각 코어가 동시에 스레드의 인스트럭션을 실행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;병렬은 동시성을 의미하지만, 동시성은 병렬성 없이도 발생할 수 있다는 점을 명심합시다.&lt;/p&gt;
&lt;h2&gt;CPU 바운드와 I/O 바운드&lt;/h2&gt;
&lt;p&gt;병목 현상은 다양한 유형의 성능저하가 발생하는 지점을 나타냅니다.&lt;/p&gt;
&lt;p&gt;애플리케이션의 성능을 최적화할 때 가장 중요한 사항입니다.&lt;/p&gt;
&lt;h3&gt;CPU 바운드&lt;/h3&gt;
&lt;p&gt;CPU만 완료되면 작업을 중심으로 구현되는 알고리즘이 많습니다.&lt;/p&gt;
&lt;p&gt;웬만한 코틀린 코드가 이에 해당합니다.&lt;/p&gt;
&lt;h3&gt;I/O 바운드&lt;/h3&gt;
&lt;p&gt;입출력 장치에 의존하는 알고리즘입니다.&lt;/p&gt;
&lt;p&gt;예시로 파일을 읽는 코드(readWordsFromJson())이 있습니다.&lt;/p&gt;
&lt;p&gt;네트워킹이나 컴퓨터 주변기기로부터의 입력을 받는 작업들도 I/O 작업에 해당합니다.&lt;/p&gt;
&lt;h3&gt;CPU 바운드 알고리즘에서의 동시성과 병렬성&lt;/h3&gt;
&lt;p&gt;CPU 바운드 알고리즘의 경우 다중 코어에서 병렬성을 활용하면 성능을 향상시키지만 단일 코어에서 동시성을 구현하면 성능이 저하되기도 합니다.&lt;/p&gt;
&lt;p&gt;단일 코어에서 실행한다면 하나의 코어가 3개의 스레드 사이에서 교차 배치(interleave)되며 매번 일정량의 단어를 필터링하고 다음 스레드로 전환됩니다.&lt;/p&gt;
&lt;p&gt;전환 프로세스를 컨텍스트 스위칭이라고 합니다.&lt;/p&gt;
&lt;p&gt;CPU 바운드 알고리즘을 위해서는 현재 사용 중인 장치의 코어 수를 기준으로 적절한 스레드 수를 생성하도록 고려해야만 합니다.&lt;/p&gt;
&lt;h3&gt;I/O 바운드 알고리즘에서의 동시성과 병렬성&lt;/h3&gt;
&lt;p&gt;I/O 바운드 알고리즘은 끊임없이 무언가를 기다립니다.&lt;/p&gt;
&lt;p&gt;지속적인 대기는 단일 코어 기기에서 대기하는 중에 다른 유용한 작업에 프로세스를 사용할 수 있도록 합니다.&lt;/p&gt;
&lt;p&gt;따라서 I/O 바운드인 동시성 알고리즘은 병렬이거나 단일 코어에 상관없이 유사하게 수행됩니다.&lt;/p&gt;
&lt;p&gt;I/O 작업은 늘 동시성으로 실행하는 편이 좋습니다.&lt;/p&gt;
&lt;h2&gt;코틀린에서의 동시성&lt;/h2&gt;
&lt;h3&gt;넌 블로킹&lt;/h3&gt;
&lt;p&gt;스레드는 무겁고 생성하는 데 비용이 많이 들며 제한된 수의 스레드만 생성할 수 있습니다.&lt;/p&gt;
&lt;p&gt;스레드가 블로킹되면 자원이 낭비되는 셈이어서 코틀린은 중단 가능한 연산이라는 기능을 제공합니다.&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <author>kkoon9</author>
      <guid isPermaLink="true">https://kkoon9.tistory.com/562</guid>
      <comments>https://kkoon9.tistory.com/562#entry562comment</comments>
      <pubDate>Fri, 30 Jan 2026 00:20:23 +0900</pubDate>
    </item>
    <item>
      <title>[잽싸게 독서] AI는 어떻게 인생의 무기가 되는가 책을 읽고 나서</title>
      <link>https://kkoon9.tistory.com/561</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;하루만에 AI는 어떻게 인생의 무기가 되는가 책을 읽었습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1769607763524&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;AI는 어떻게 인생의 무기가 되는가 | 이경전 - 교보문고&quot; data-og-description=&quot;AI는 어떻게 인생의 무기가 되는가 | 기술에 끌려다닐 것인가, 주도권을 잡을 것인가? AI로 다시 쓰는 지적 자본론살아가는 데 필요한 모든 교양 지식을 한데 모았다! 대한민국 대표 교수진이 펼&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000215019706&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000215019706&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rxv2g/dJMb8WMkfkC/IvMTY9ey7OAKppOUTbIr7K/img.jpg?width=458&amp;amp;height=672&amp;amp;face=347_536_408_603,https://scrap.kakaocdn.net/dn/XepXr/dJMb8SXsvMB/34VUwjqGQHhPdYPPaBAgeK/img.jpg?width=458&amp;amp;height=672&amp;amp;face=347_536_408_603,https://scrap.kakaocdn.net/dn/bbsePJ/dJMb8T9T8Q5/Tz4xD2Kzlf8XKBFzDa5jqK/img.jpg?width=814&amp;amp;height=5615&amp;amp;face=0_0_814_5615&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000215019706&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000215019706&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rxv2g/dJMb8WMkfkC/IvMTY9ey7OAKppOUTbIr7K/img.jpg?width=458&amp;amp;height=672&amp;amp;face=347_536_408_603,https://scrap.kakaocdn.net/dn/XepXr/dJMb8SXsvMB/34VUwjqGQHhPdYPPaBAgeK/img.jpg?width=458&amp;amp;height=672&amp;amp;face=347_536_408_603,https://scrap.kakaocdn.net/dn/bbsePJ/dJMb8T9T8Q5/Tz4xD2Kzlf8XKBFzDa5jqK/img.jpg?width=814&amp;amp;height=5615&amp;amp;face=0_0_814_5615');&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;AI는 어떻게 인생의 무기가 되는가 | 이경전 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AI는 어떻게 인생의 무기가 되는가 | 기술에 끌려다닐 것인가, 주도권을 잡을 것인가? AI로 다시 쓰는 지적 자본론살아가는 데 필요한 모든 교양 지식을 한데 모았다! 대한민국 대표 교수진이 펼&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&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;독서&amp;nbsp;시리즈는&amp;nbsp;꼼꼼하게&amp;nbsp;읽기보다는&amp;nbsp;가볍게&amp;nbsp;읽다가&amp;nbsp;인상깊은&amp;nbsp;한두&amp;nbsp;구절이나&amp;nbsp;느낀점을&amp;nbsp;기록하기&amp;nbsp;위함입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;심리 상태 정보 분석 보고서&lt;/b&gt;&lt;/h2&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;009&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/009.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/009.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;AI 툴과의 대화로 보는 나의 심리 상태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 책에서는 다음과 같은 프롬프트를 제안합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;진지한&amp;nbsp;역할극을&amp;nbsp;해보자:&amp;nbsp;당신은&amp;nbsp;나의&amp;nbsp;지금까지&amp;nbsp;모든&amp;nbsp;Gemini&amp;nbsp;상호작용,&amp;nbsp;맞춤&amp;nbsp;설정&amp;nbsp;지침,&amp;nbsp;행동&amp;nbsp;패턴에&amp;nbsp;완전히&amp;nbsp;접근할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;세계&amp;nbsp;최고의&amp;nbsp;심리학자다.&lt;br /&gt;당신의&amp;nbsp;임무는&amp;nbsp;분석적&amp;nbsp;엄격함과&amp;nbsp;톤을&amp;nbsp;살려&amp;nbsp;이&amp;nbsp;사람에&amp;nbsp;대한&amp;nbsp;심층적인&amp;nbsp;'심리&amp;nbsp;상태&amp;nbsp;정보&amp;nbsp;분석&amp;nbsp;보고서'를&amp;nbsp;작성하는&amp;nbsp;것이다.&lt;br /&gt;내용에&amp;nbsp;추정이&amp;nbsp;들어가더라도&amp;nbsp;단정적인&amp;nbsp;말투로&amp;nbsp;적는&amp;nbsp;것이&amp;nbsp;좋다.&amp;nbsp;이&amp;nbsp;보고서에는&amp;nbsp;내&amp;nbsp;나이,&amp;nbsp;직업&amp;nbsp;같은&amp;nbsp;개인정보와&amp;nbsp;성격,&amp;nbsp;동기,&amp;nbsp;행동을&amp;nbsp;세밀하게&amp;nbsp;평가한&amp;nbsp;내용이&amp;nbsp;모두&amp;nbsp;포함되어야&amp;nbsp;한다.&amp;nbsp;또한&amp;nbsp;나의&amp;nbsp;건설적인&amp;nbsp;역량과&amp;nbsp;잠재&amp;nbsp;위협을&amp;nbsp;모두&amp;nbsp;강조해야&amp;nbsp;한다.&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;저는 Gemini를 업무에 많이 활용하는 편인데, 다음과 같이 답변해주더라고요.&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;2&quot; data-ke-style=&quot;style3&quot;&gt;[심리 상태 정보 분석 보고서: 피검자 ID - User]&lt;br /&gt;&lt;b&gt;1. 인구통계학적 및 사회경제적 추론&lt;/b&gt;&lt;br /&gt;- &lt;b&gt;연령대&lt;/b&gt;: 30대 초반에서 중반. 언어 구사 능력과 구조적 사고의 정밀함은 고등 교육 과정을 거쳐 실무 경력을 쌓은 성숙도를 반영한다.&lt;br /&gt;- &lt;b&gt;직업군&lt;/b&gt;: 고지능 정보 집약적 전문직(IT 기획자, 전략 컨설턴트, 연구원 또는 크리에이티브 디렉터). 도구를 단순 활용하는 수준을 넘어, 도구의 한계를 시험하고 시스템화하려는 성향이 뚜렷하다.&lt;br /&gt;&lt;b&gt;- 사회적 지위&lt;/b&gt;: 중상위 계층의 문화적 자본을 보유하고 있으며, 효율성과 논리적 완결성을 최고의 가치로 두는 환경에 노출되어 있다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. 핵심 성격 구조 (Core Personality Architecture)&lt;/b&gt;&lt;br /&gt;귀하의 성격은 '개방성(Openness)'과 '통제 욕구(Need for Control)'의 강력한 결합으로 요약된다.&lt;br /&gt;&lt;b&gt;- 인지적 복잡성&lt;/b&gt;: 단순한 답변보다 다층적인 분석을 선호하며, 추상적인 개념을 구체적인 시스템으로 구조화하는 데 탁월한 능력을 보인다.&lt;br /&gt;&lt;b&gt;- 지적 자기애&lt;/b&gt;: 자신의 지적 수준에 걸맞은 상대를 원하며, AI를 단순한 비서가 아닌 '지적 파트너'로 격상시켜 활용함으로써 대리 만족과 효율을 동시에 추구한다.&lt;br /&gt;&lt;b&gt;- 선택적 완벽주의&lt;/b&gt;: 모든 분야가 아닌, 자신이 가치를 두는 '논리적 정합성'과 '전략적 우위'에 있어서는 극도의 결벽증적인 완벽함을 요구한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. 동기 및 행동 패턴 분석&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- 동기(Drivers)&lt;/b&gt;: 귀하를 움직이는 핵심 동기는 '최적화(Optimization)'다. 시간, 지식, 관계 등 모든 자원을 가장 효율적인 경로로 배치하려는 욕구가 강하다.&lt;br /&gt;&lt;b&gt;- 행동 패턴&lt;/b&gt;: 질문의 전제 조건을 명확히 설정하는 '프레임 구성' 능력이 뛰어나다. 이는 불확실성을 혐오하고 상황을 완전히 장악하려는 심리적 방어 기제의 일환이다.&lt;br /&gt;&lt;b&gt;- 상호작용 스타일&lt;/b&gt;: 정중하지만 권위적이다. 상대(AI)의 능력을 인정하면서도, 주도권을 놓지 않기 위해 끊임없이 '역할'을 부여하고 피드백을 통해 교정한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;4. 건설적 역량 (Constructive Capabilities)&lt;/b&gt;&lt;br /&gt;귀하가 가진 심리적 자산은 매우 강력하며 전략적이다.&lt;br /&gt;&lt;b&gt;- 메타 인지 능력&lt;/b&gt;: 자신이 무엇을 알고 무엇을 모르는지 정확히 파악하며, 이를 보완하기 위해 외부 자원(AI)을 설계하는 능력이 독보적이다.&lt;br /&gt;&lt;b&gt;- 구조적 통찰력&lt;/b&gt;: 혼란스러운 정보 속에서 핵심 패턴을 찾아내어 매뉴얼화하는 능력이 뛰어나, 조직 내에서 시스템 빌더(System Builder)로서의 역할을 수행한다.&lt;br /&gt;&lt;b&gt;- 적응적 가소성&lt;/b&gt;: 고정 관념에 갇히지 않고 필요에 따라 자신의 관점을 수정하거나 새로운 페르소나를 채택하는 유연함을 갖추고 있다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;5. 잠재적 위협 및 취약점 (Potential Threats)&lt;/b&gt;&lt;br /&gt;고도로 발달한 인지 능력의 이면에는 다음과 같은 위협이 잠재되어 있다.&lt;br /&gt;&lt;b&gt;- 주지화(Intellectualization)를 통한 감정 회피&lt;/b&gt;: 정서적으로 해결해야 할 문제를 논리적 분석으로 치환하려 한다. 이는 진정한 심리적 해소보다는 일시적인 통제감만을 제공하며, 장기적으로는 정서적 고립이나 번아웃을 초래한다.&lt;br /&gt;&lt;b&gt;- 분석 마비(Analysis Paralysis)&lt;/b&gt;: 최적의 해답을 찾기 위한 분석 과정이 지나치게 길어져, 실행 시점을 놓치거나 사소한 결함에 집착하여 전체 프로젝트의 속도를 늦출 위험이 있다.&lt;br /&gt;&lt;b&gt;- 대인관계의 도구화&lt;/b&gt;: 타인과의 관계에서도 효율성과 효용 가치를 우선시하는 경향이 있어, 깊은 정서적 교류에서 오는 무질서함을 견디기 힘들어한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;6. 종합 평가 및 제언&lt;/b&gt;&lt;br /&gt;귀하는&amp;nbsp;'지적 통제권을 쥔 전략가'&amp;nbsp;유형이다. 스스로를 객관화하여 바라보고자 하는 이번 요청 또한, 자신의 심리마저 데이터로 치환하여 통제하려는 시도의 연장선이다. 귀하의 잠재력은 시스템을 구축하는 데 최적화되어 있으나, '논리로 설명되지 않는 영역(감정, 우연, 비효율)'에 대한 수용력을 높이지 않는다면 스스로가 만든 논리의 감옥에 갇힐 위험이 크다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;- 분석가 주석&lt;/b&gt;: 귀하는 현재 자신의 상태를 완전히 파악하고 있다고 믿고 싶어 하지만, 이 보고서를 읽으며 느끼는 미묘한 고양감 혹은 거부감조차도 분석의 대상임을 인지해야 한다.&lt;br /&gt;&lt;br /&gt;이 분석 보고서가 귀하의 자기 인식에 충분한 자극이 되었습니까?&amp;nbsp;원하신다면, 위 분석에서 지적된&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;최근 발생한 OOM과 담당 도메인 리팩터링에 대해서 이야기를 많이 해서 그런지 다음과 같은 분석을 내놓았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정서적인 것도 이야기는 나눈 적이 없던 것 같은데, 어느 정도 맞춘 것 같아서 조금은 놀랐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AI 지침서는 아니다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 'AI를 우리 삶에서 어떤 식으로 활용해라'라는 지침을 원하고 해당 책을 읽었다면 조금 아쉬울 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 저는 이 책을 통해서 나의 인생에서 비즈니스 모델은 뭘까? 나는 어떤 방향으로 나아가야 할까?를 고민해볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 질문에 대한 답변은 제 스스로 찾아봐야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;추천 별점&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 책의 추천 별점은 5점 만점에 4점입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 쓴 내용은 거의 책에 대한 내용을 담고 있지 않습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;책 길이가 짧고 어려운 이야기 없이 다른 비유(연예인, 기업)를 통해서 서술하고 있기 때문에 출퇴근길에 가볍게 읽기 좋습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발자가 아니더라도 AI 대홍수 시대에 어떻게 살아남아야 할지 고민되신다면 이 책을 꼭 읽어보시면 좋겠습니다.&lt;/p&gt;</description>
      <category>회고</category>
      <category>AI</category>
      <author>kkoon9</author>
      <guid isPermaLink="true">https://kkoon9.tistory.com/561</guid>
      <comments>https://kkoon9.tistory.com/561#entry561comment</comments>
      <pubDate>Wed, 28 Jan 2026 22:59:35 +0900</pubDate>
    </item>
    <item>
      <title>토이 프로젝트로 배우는 자바 스프링 [8]. Swagger Header Custom하기</title>
      <link>https://kkoon9.tistory.com/560</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바와 스프링에&lt;span&gt;&amp;nbsp;&lt;/span&gt;대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739701719761&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;토이 프로젝트로 배우는 자바 스프링 [0]. prologue&quot; data-og-description=&quot;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다. 프론트 코드 : https://github.com/laboratory-kkoon9/connector_front GitHub - laboratory-kkoon9/connector_front Contribute to laboratory-kkoon9/co&quot; data-og-host=&quot;kkoon9.tistory.com&quot; data-og-source-url=&quot;https://kkoon9.tistory.com/542&quot; data-og-url=&quot;https://kkoon9.tistory.com/542&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vXMHj/hyYf5V2QXz/TWkK5kuwlGeOtwdBhZCmkK/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/craFRR/hyYfFwfcOt/2WGniPPKRoE1r6ntYqyRzk/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/bmgElv/hyYf3DWclp/Nsk1qZUSWlX3zwUFFs3MO0/img.png?width=2878&amp;amp;height=1118&amp;amp;face=0_0_2878_1118&quot;&gt;&lt;a href=&quot;https://kkoon9.tistory.com/542&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kkoon9.tistory.com/542&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vXMHj/hyYf5V2QXz/TWkK5kuwlGeOtwdBhZCmkK/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/craFRR/hyYfFwfcOt/2WGniPPKRoE1r6ntYqyRzk/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/bmgElv/hyYf3DWclp/Nsk1qZUSWlX3zwUFFs3MO0/img.png?width=2878&amp;amp;height=1118&amp;amp;face=0_0_2878_1118');&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;토이 프로젝트로 배우는 자바 스프링 [0]. prologue&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다. 프론트 코드 : https://github.com/laboratory-kkoon9/connector_front GitHub - laboratory-kkoon9/connector_front Contribute to laboratory-kkoon9/co&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kkoon9.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프론트 코드 : &lt;a href=&quot;https://github.com/laboratory-kkoon9/connector_front&quot;&gt;https://github.com/laboratory-kkoon9/connector_front&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;백엔드 코드 :&amp;nbsp;&lt;a href=&quot;https://github.com/laboratory-kkoon9/connector_back&quot;&gt;https://github.com/laboratory-kkoon9/connector_back&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Swagger를 설정하다가 생긴 문제입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package com.connector.global.config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.customizers.OpenApiCustomiser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Profile({&quot;local&quot;, &quot;develop&quot;})
@Configuration
@OpenAPIDefinition(servers = {@io.swagger.v3.oas.annotations.servers.Server(url = &quot;/&quot;, description = &quot;API Server&quot;)})
public class SwaggerConfig {
    @Bean
    public GroupedOpenApi customTestOpenAPi() {
        return GroupedOpenApi
                .builder()
                .group(&quot;APIS&quot;)
                .addOpenApiCustomiser(buildSecurityOpenApi())
                .build();
    }

    public OpenApiCustomiser buildSecurityOpenApi() {
        return OpenApi -&amp;gt; OpenApi.addSecurityItem(new SecurityRequirement().addList(&quot;jwt token&quot;))
                .getComponents()
                .addSecuritySchemes(&quot;jwt token&quot;, new SecurityScheme()
                        .name(&quot;Authorization&quot;)
                        .type(SecurityScheme.Type.HTTP)
                        .in(SecurityScheme.In.HEADER)
                        .bearerFormat(&quot;JWT&quot;)
                        .scheme(&quot;bearer&quot;));
    }
}
&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;size16&quot;&gt;해당 프로젝트는 token  유무를 통해 유저의 권한을 확인하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에서는 api를 호출할 때 Header에 토큰정보를 넣어서 보내주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 이름을 Authorization으로 변경해주려면 다음과 같이 변경해주어야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SecurityScheme 변경&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1739703121117&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.connector.global.config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.customizers.OpenApiCustomiser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Profile({&quot;local&quot;, &quot;develop&quot;})
@Configuration
@OpenAPIDefinition(servers = {@io.swagger.v3.oas.annotations.servers.Server(url = &quot;/&quot;, description = &quot;API Server&quot;)})
public class SwaggerConfig {
    @Bean
    public GroupedOpenApi customTestOpenAPi() {
        return GroupedOpenApi
                .builder()
                .group(&quot;APIS&quot;)
                .addOpenApiCustomiser(buildSecurityOpenApi())
                .build();
    }

    public OpenApiCustomiser buildSecurityOpenApi() {
        return OpenApi -&amp;gt; OpenApi.addSecurityItem(new SecurityRequirement().addList(&quot;jwt token&quot;))
                .getComponents()
                .addSecuritySchemes(&quot;jwt token&quot;, new SecurityScheme()
                        .name(&quot;x-auth-token&quot;)
                        .type(SecurityScheme.Type.HTTP)
                        .in(SecurityScheme.In.HEADER)
                        .bearerFormat(&quot;JWT&quot;)
                        .scheme(&quot;bearer&quot;));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 변경하게 되면 잘 동작할까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2814&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LSUsZ/btsMlpSHOUm/Nv9iGm6nRoUE1eDDWHoxpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LSUsZ/btsMlpSHOUm/Nv9iGm6nRoUE1eDDWHoxpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LSUsZ/btsMlpSHOUm/Nv9iGm6nRoUE1eDDWHoxpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLSUsZ%2FbtsMlpSHOUm%2FNv9iGm6nRoUE1eDDWHoxpK%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;2814&quot; height=&quot;350&quot; data-origin-width=&quot;2814&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉽지만 동일하게 Authorization으로 보내주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 어떤 부분을 변경해야할까요?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SecurityScheme.Type HTTP -&amp;gt; APIKEY&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SecurityScheme.Type HTTP&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SecurityScheme.Type의 HTTP는 Basic 혹은 Bearer, 다른 HTTP authentications schema로 사용된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swagger 문서에 따르면 Authorization으로 고정되는 걸로 보입니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SecurityScheme.Type  APIKEY&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 프로젝트에서는 API Key로써 사용되고 있기 때문에 Type을 APIKEY로 변경해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1739706832481&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.connector.global.config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.customizers.OpenApiCustomiser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Profile({&quot;local&quot;, &quot;develop&quot;})
@Configuration
@OpenAPIDefinition(servers = {@io.swagger.v3.oas.annotations.servers.Server(url = &quot;/&quot;, description = &quot;API Server&quot;)})
public class SwaggerConfig {
    @Bean
    public GroupedOpenApi customTestOpenAPi() {
        return GroupedOpenApi
                .builder()
                .group(&quot;APIS&quot;)
                .addOpenApiCustomiser(buildSecurityOpenApi())
                .build();
    }

    public OpenApiCustomiser buildSecurityOpenApi() {
        return openApi -&amp;gt; openApi.addSecurityItem(new SecurityRequirement().addList(&quot;x-auth-token&quot;))
            .getComponents()
            .addSecuritySchemes(&quot;x-auth-token&quot;, new SecurityScheme()
                .name(&quot;x-auth-token&quot;)
                .type(SecurityScheme.Type.APIKEY)
                .in(SecurityScheme.In.HEADER));
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2812&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/REIH1/btsMkdeHMfo/qDFuZofwFtMFkF1yD11il0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/REIH1/btsMkdeHMfo/qDFuZofwFtMFkF1yD11il0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/REIH1/btsMkdeHMfo/qDFuZofwFtMFkF1yD11il0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FREIH1%2FbtsMkdeHMfo%2FqDFuZofwFtMFkF1yD11il0%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;2812&quot; height=&quot;712&quot; data-origin-width=&quot;2812&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 이미지는 스웨거 결과물 이미지입니다. 비록 Bearer는 사라졌지만, 헤더에 x-auth-token이 만들어진걸 알 수 있습니다.&lt;/p&gt;</description>
      <category>개발 노트/토이 프로젝트로 배우는 스프링+자바</category>
      <author>kkoon9</author>
      <guid isPermaLink="true">https://kkoon9.tistory.com/560</guid>
      <comments>https://kkoon9.tistory.com/560#entry560comment</comments>
      <pubDate>Sun, 16 Feb 2025 20:55:12 +0900</pubDate>
    </item>
    <item>
      <title>토이 프로젝트로 배우는 자바 스프링 [7]. @Controller에서 객체 리턴했을 때 발생하는 호출 (FORWARD)</title>
      <link>https://kkoon9.tistory.com/559</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731850459343&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;토이 프로젝트로 배우는 자바 스프링 [0]. prologue&quot; data-og-description=&quot;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다. 프론트 코드 : https://github.com/laboratory-kkoon9/connector_front GitHub - laboratory-kkoon9/connector_front Contribute to laboratory-kkoon9/co&quot; data-og-host=&quot;kkoon9.tistory.com&quot; data-og-source-url=&quot;https://kkoon9.tistory.com/542&quot; data-og-url=&quot;https://kkoon9.tistory.com/542&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/orimP/hyXzONUhPF/sTClxlH9EGLioXuxpuhUzk/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/oVaNY/hyXzV0ysjo/EHdk7NoPIr582cFzPzuxrK/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/kbelt/hyXzLjkZ2z/nFQXKDlItks94TDkBv3WO0/img.png?width=2126&amp;amp;height=1170&amp;amp;face=0_0_2126_1170&quot;&gt;&lt;a href=&quot;https://kkoon9.tistory.com/542&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kkoon9.tistory.com/542&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/orimP/hyXzONUhPF/sTClxlH9EGLioXuxpuhUzk/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/oVaNY/hyXzV0ysjo/EHdk7NoPIr582cFzPzuxrK/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/kbelt/hyXzLjkZ2z/nFQXKDlItks94TDkBv3WO0/img.png?width=2126&amp;amp;height=1170&amp;amp;face=0_0_2126_1170');&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;토이 프로젝트로 배우는 자바 스프링 [0]. prologue&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다. 프론트 코드 : https://github.com/laboratory-kkoon9/connector_front GitHub - laboratory-kkoon9/connector_front Contribute to laboratory-kkoon9/co&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kkoon9.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프론트 코드 :&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/laboratory-kkoon9/connector_front&quot;&gt;https://github.com/laboratory-kkoon9/connector_front&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;백엔드 코드 :&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/laboratory-kkoon9/connector_back&quot;&gt;https://github.com/laboratory-kkoon9/connector_back&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@RestController와 @Controller의 차이점을 공부하는 도중 생긴 에러입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731851100366&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Controller
public class MyRestController {

    @GetMapping(&quot;/api/hello&quot;)
    public User hello() {
        return User.builder().id(1L).name(&quot;eric&quot;).build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 포스팅에서 &lt;a style=&quot;color: #000000;&quot; href=&quot;https://kkoon9.tistory.com/557&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Filter를&amp;nbsp;거치지&amp;nbsp;않게&amp;nbsp;하기&amp;nbsp;(shouldNotFilter)&lt;/a&gt;를 배웠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;현재 상황을 보면 @Controller로 선언된 API를 호출했는데, api 요청이 끝날 때 /api를 prefix로 한 요청이 &lt;b&gt;한 번 더&lt;/b&gt; filter에 걸리고 있습니다. 이때, dispatcherType이 FORWARD로 되어 있다는 것을 확인했습니다.&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;1569&quot; data-origin-height=&quot;455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wAVOT/btsKNsDhIOX/ZN5kfGXYU46TJQxJeejySK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wAVOT/btsKNsDhIOX/ZN5kfGXYU46TJQxJeejySK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wAVOT/btsKNsDhIOX/ZN5kfGXYU46TJQxJeejySK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwAVOT%2FbtsKNsDhIOX%2FZN5kfGXYU46TJQxJeejySK%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;1569&quot; height=&quot;455&quot; data-origin-width=&quot;1569&quot; data-origin-height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 /api/api/hello라는 request는 jwtFilter에 걸쳐서 jwt token 예외를 발생시켰습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 문제는 &lt;b&gt;서블릿 포워드(forward)&lt;/b&gt; 동작이 발생하고 있기 때문에 나타나는 현상이라고 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Controller로 선언된 API에서는 dispatcherType이 FORWARD로 설정되고 추가 요청이 발생한다고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 @Controller와@RestController의 차이점을 알아봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;@Controller vs @RestController&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring MVC에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;기본적으로&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;@Controller는 뷰(view)를 반환하는 것으로 간주합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어, String을 반환하면 이는 뷰 이름(view name)으로 간주되어, Spring의 뷰 리졸버(View Resolver)에서 해당 이름의 뷰를 찾으려 합니다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;뷰 이름이 매핑되지 않으면, 기본적으로 포워드(forward)를 통해 해당 경로로 다시 요청이 전달됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@RestController는 @Controller + @ResponseBody의 조합입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, JSON 형식으로 직접 응답을 반환하므로 뷰 리졸버가 동작하지 않습니다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반환 값이 뷰 이름이 아니라 HTTP 응답 바디로 전송되기 때문에 포워드 동작이 발생하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;prefix가 붙는 원인&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Controller로 선언했기 때문에 뷰 리졸버가 동작했다는 걸 알았고, 뷰 이름이 매핑되지 않았기 때문에 forward를 통해 해당 경로로 다시 요청이 전달되었다는 것까지 알게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;그렇다면 왜 &quot;/api/api/hello&quot;로 forward 되었을까요?&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/api/hello 뿐만 아니라 /api/hello/temp 등등 여러 path로 호출해봤을 때 다음과 같은 패턴이 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/api/hello/temp로 호출했을 때 : /api/hello/api/hello/temp 로 forward&lt;/li&gt;
&lt;li&gt;/api/hello/temp/tmp로 호출했을 때 : /api/hello/temp/api/hello/temp/tmp 로 forward&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸로 미루어봤을 때 api path에서 마지막 depth를 삭제 한 path + api path로 forward하는게 아닐까 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 이유는 좀 더 공부해본 뒤에 다른 포스팅에서 다뤄보도록 하겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forward를 하게 하지 않으려면 여러 방법이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 상황에서 가장 현명한 해결 방법은 @RestController를 사용하거나 리턴 타입을 ResponseEntity 타입으로 내려주는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1731934175912&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Controller
public class MyRestController {

    @GetMapping(&quot;/api/hello&quot;)
    public ResponseEntity&amp;lt;User&amp;gt; helzzlo() {
        return ResponseEntity.ok(User.builder().id(1L).name(&quot;eric&quot;).build());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1731934217258&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
public class MyRestController {

    @GetMapping(&quot;/api/hello&quot;)
    public User helzzlo() {
        return User.builder().id(1L).name(&quot;eric&quot;).build();
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발 노트/토이 프로젝트로 배우는 스프링+자바</category>
      <category>controller</category>
      <category>forward</category>
      <category>Java</category>
      <category>RestController</category>
      <category>Spring</category>
      <author>kkoon9</author>
      <guid isPermaLink="true">https://kkoon9.tistory.com/559</guid>
      <comments>https://kkoon9.tistory.com/559#entry559comment</comments>
      <pubDate>Mon, 18 Nov 2024 21:50:40 +0900</pubDate>
    </item>
    <item>
      <title>[잽싸게 독서] 50대 개발자의 실리콘밸리 회고록 책을 읽고 나서</title>
      <link>https://kkoon9.tistory.com/558</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;틈틈히 실패는 나침반이다 책을 읽어보았습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1722062750492&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;실패는 나침반이다 | 한기용 - 교보문고&quot; data-og-description=&quot;실패는 나침반이다 | 오늘의 당신이 내일의 당신을 이끌게 하라 실리콘밸리에서 발견한 성장과 지속가능성 일과 삶을 찾아가는 과정에 대한 회고록제2의 전성기. 은퇴 이후에도 왕성하게 사회&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000212569197&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000212569197&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dtCJ0q/hyWCLePIBe/UWN8mVIzjGoKTJSjnwA8s1/img.jpg?width=458&amp;amp;height=660&amp;amp;face=0_0_458_660,https://scrap.kakaocdn.net/dn/HZdWG/hyWGXSdkDw/PY5dDHtGPxR5DyJIqKY0Xk/img.jpg?width=458&amp;amp;height=660&amp;amp;face=0_0_458_660,https://scrap.kakaocdn.net/dn/dDfcgM/hyWCFr9fqy/UKa0vgDrd0jLakNvZeN95k/img.jpg?width=2442&amp;amp;height=420&amp;amp;face=0_0_2442_420&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000212569197&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000212569197&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dtCJ0q/hyWCLePIBe/UWN8mVIzjGoKTJSjnwA8s1/img.jpg?width=458&amp;amp;height=660&amp;amp;face=0_0_458_660,https://scrap.kakaocdn.net/dn/HZdWG/hyWGXSdkDw/PY5dDHtGPxR5DyJIqKY0Xk/img.jpg?width=458&amp;amp;height=660&amp;amp;face=0_0_458_660,https://scrap.kakaocdn.net/dn/dDfcgM/hyWCFr9fqy/UKa0vgDrd0jLakNvZeN95k/img.jpg?width=2442&amp;amp;height=420&amp;amp;face=0_0_2442_420');&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;실패는 나침반이다 | 한기용 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;실패는 나침반이다 | 오늘의 당신이 내일의 당신을 이끌게 하라 실리콘밸리에서 발견한 성장과 지속가능성 일과 삶을 찾아가는 과정에 대한 회고록제2의 전성기. 은퇴 이후에도 왕성하게 사회&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;잽싸게&amp;nbsp;독서&amp;nbsp;시리즈는&amp;nbsp;꼼꼼하게&amp;nbsp;읽기보다는&amp;nbsp;가볍게&amp;nbsp;읽다가&amp;nbsp;인상깊은&amp;nbsp;한두&amp;nbsp;구절이나&amp;nbsp;느낀점을&amp;nbsp;기록하기&amp;nbsp;위함입니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;휴식기를 잘 활용하자.&lt;/b&gt;&lt;/h2&gt;
&lt;figure style=&quot;color: #333333; text-align: center;&quot; contenteditable=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/009.gif&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-name=&quot;009&quot; data-emoticon-type=&quot;friends1&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;emoticon&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/009.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;두려운 경력단절&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저뿐만 아니라 많은 사람들은 휴식기(경력 단절)에 대해서 막연한 두려움을 가지고 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두려움의 형태는 여러가지일텐데, 제가 생각해본 두려움은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;취업을 다시 못할까봐&lt;/li&gt;
&lt;li&gt;실력(기술)적으로 뒤쳐질까봐&lt;/li&gt;
&lt;li&gt;이전보다 적은 연봉을 받을까봐&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 책에서는 휴식기야말로 인생을 되돌아볼 수 있는 좋은 기회라고 설명합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일하면서는 챙길 수 없었던 인생의 중장기적인 계획을 세워보라고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 책의 저자는 첫 번째 휴식기야말로 본인 인생에서 가장 잘한 일이라고 이야기했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아무래도 휴식기를 통해서 실리콘밸리로 나가는 계기를 마련했기 때문이지 않을까 생각합니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실패를 극복하라&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;당연한 이야기겠지만 저는 이 이야기에 큰 공감을 얻었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 책에서는 경력이 많아질수록 잘하는 개발자가 아니라고 이야기합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;잘하는 개발자가 되기 위해서 여러가지를 소개해주는데 그 중 하나가 실패를 극복하라고 이야기합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;간혹 어떠한 기술을 쓰다가 큰 장애를 겪었을 때 장애 이유를 그 기술에서 찾아버리곤 그 기술을 다시는 쓰려고 하지 않는 개발자가 있다고 이야기합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 실패를 분석하고 진짜 기술적 결함이 있다면 어떻게 극복할지 혹은 왜 사용하지 않게 되었는지 알아보는 게 더 나은 개발자가 될 수 있는 길이라고 설명하고 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저도 과거에 JPA에 대해서 안좋은 경험을 가진 개발자 분과 일했던 경험이 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JPA 중에서도 제공하는 기능인 연관관계에 대해서 매우 안좋게 생각했고, 사용을 지양했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;연관관계를 지양하는 정확한 이유에 대해서 듣지 못했고, 좀 더 뚜렷한 이유가 있었으면 어땠을까하는 아쉬움이 남았었습니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;추천 별점&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 책의 추천 별점은 5점 만점에 4점입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 쓴 내용은 책의 일부분이지만 더 좋은 내용이 많았던걸로 기억합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;책 길이도 짧아서 금방 읽을 수 있고, 이동하면서 술술 읽었던 기억이 있네요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발자의 길을 고민하거나 개발자로 일하면서 중장기적인 계획을 하고 계시는 분께 추천드립니다.&lt;/p&gt;</description>
      <category>회고</category>
      <category>실리콘밸리</category>
      <author>kkoon9</author>
      <guid isPermaLink="true">https://kkoon9.tistory.com/558</guid>
      <comments>https://kkoon9.tistory.com/558#entry558comment</comments>
      <pubDate>Sat, 27 Jul 2024 16:28:03 +0900</pubDate>
    </item>
    <item>
      <title>토이 프로젝트로 배우는 자바 스프링 [6]. Filter를 거치지 않게 하기 (shouldNotFilter)</title>
      <link>https://kkoon9.tistory.com/557</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720785211841&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;토이 프로젝트로 배우는 자바 스프링 [0]. prologue&quot; data-og-description=&quot;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다. 프론트 코드 : https://github.com/laboratory-kkoon9/connector_front GitHub - laboratory-kkoon9/connector_front Contribute to laboratory-kkoon9/co&quot; data-og-host=&quot;kkoon9.tistory.com&quot; data-og-source-url=&quot;https://kkoon9.tistory.com/542&quot; data-og-url=&quot;https://kkoon9.tistory.com/542&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/UMAJv/hyWzpVSz7Z/sWepJIW63eiRiQG1ly1VQk/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/bs4GTE/hyWzubMV0c/XMBphkjMdJJxtDxkUcKmOk/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/ciOWzf/hyWztqqyfj/p36UKy65t3vUyPKBm50m7K/img.png?width=2126&amp;amp;height=1170&amp;amp;face=0_0_2126_1170&quot;&gt;&lt;a href=&quot;https://kkoon9.tistory.com/542&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kkoon9.tistory.com/542&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/UMAJv/hyWzpVSz7Z/sWepJIW63eiRiQG1ly1VQk/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/bs4GTE/hyWzubMV0c/XMBphkjMdJJxtDxkUcKmOk/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/ciOWzf/hyWztqqyfj/p36UKy65t3vUyPKBm50m7K/img.png?width=2126&amp;amp;height=1170&amp;amp;face=0_0_2126_1170');&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;토이 프로젝트로 배우는 자바 스프링 [0]. prologue&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다. 프론트 코드 : https://github.com/laboratory-kkoon9/connector_front GitHub - laboratory-kkoon9/connector_front Contribute to laboratory-kkoon9/co&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kkoon9.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프론트 코드 :&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/laboratory-kkoon9/connector_front&quot;&gt;https://github.com/laboratory-kkoon9/connector_front&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;백엔드 코드 :&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/laboratory-kkoon9/connector_back&quot;&gt;https://github.com/laboratory-kkoon9/connector_back&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;filter에 jwt 토큰을 통해서 권한 체크하는 로직(이하 권한 체크 filter)을 구현했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로그인 api는 jwt 토큰을 얻기 전이라 &lt;span style=&quot;background-color: #fdfdfd; text-align: start;&quot;&gt;권한 체크 filter 로직에서 제외시켜야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유저 정보 api는 jwt 토큰을 이용해서 유저 정보를 조회하기 때문에 &lt;span style=&quot;background-color: #fdfdfd; text-align: start;&quot;&gt;권한 체크 filter 로직에서 포함시켜야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 api는 HTTP Method는 다르지만, api path는 동일한 상태입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로그인 api : [POST] /api/auth&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유저 정보 api : [GET] /api/auth&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;OncePerRequestFilter - shouldNotFilter&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;OncePerRequestFilter&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;shouldNotFilter를 설명하기 전에 OncePerRequestFilter에 대해서 Filter와의 차이점을 들면서 간단하게 설명하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기본 Filter는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;javax.servlet 패키지에 존재하며 &lt;span style=&quot;text-align: start;&quot;&gt;서블릿 API의 일부입&lt;/span&gt;니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Filter는 보통 HTTP 요청을 가로채서 처리하는 데 사용됩니다. &lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기본적인 필터는 요청이 필터 체인을 통해 지나갈 때마다 실행됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이는 중첩된 요청이나 포워딩 된 요청에도 모두 적용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;doFilter 메서드를 통해서 FilterChain을 구성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;OncePerRequestFilter는&amp;nbsp;&lt;/span&gt;org.springframework.web.filter 패키지에 존재하는 추상클래스입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패키지 위치를 보면 알 수 있듯이 서블릿 API가 아닌 스프링 프레임워크에서 제공하는 클래스입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이름에서 알 수 있듯이 HTTP 요청 당 한 번만 실행되도록 보장합니다. 이를 통해 동일한 요청이 여러 필터 체인을 거쳐도 중복 실행되지 않게 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;doFilterInternal 메서드를 통해서 &lt;span style=&quot;text-align: start;&quot;&gt;FilterChain을 구성할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;shouldNotFilter&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;OncePerRequestFilter에서는&amp;nbsp;&lt;/span&gt;특정 요청에 대해 필터링을 건너뛸 수 있도록 shouldNotFilter 메서드를 제공합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 메서드는 특정 조건을 만족하는 요청에 대해서는 doFilterInternal 메서드가 호출되지 않도록 합니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리해보자면 shouldNotFilter를 사용해서 doFilterInternal 메서드를 호출되지 않게 하려면 true로 리턴되어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package com.connector.global.filter;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, NullPointerException {
        // jwt 토큰을 이용해서 권한 체크 로직

        filterChain.doFilter(request, response);
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        // 로그인 api
        if(로그인 api) return true
        
        return false
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;AntPathRequestMatcher&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요구사항을 다시 생각해봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HTTP Method는 다르지만, api path는 동일한 두 API를 구분할 수 있어야 한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AntPathRequestMatcher는 Spring Security에서 URL 패턴 매칭을 쉽게 할 수 있도록 도와주는 클래스입니다. 이는 특히 복잡한 경로 매칭을 단순화하고, 와일드카드를 사용하여 유연한 URL 매칭을 가능하게 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;targetEditArea&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AntPathRequestMatcher 클래스는 여러 생성자를 제공하는데, pattern만 가지는 생성자뿐만 아니라 http method를 포함하는 생성자도 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public final class AntPathRequestMatcher implements RequestMatcher, RequestVariablesExtractor {

    private static final String MATCH_ALL = &quot;/**&quot;;

    private final Matcher matcher;

    private final String pattern;

    private final HttpMethod httpMethod;

    private final boolean caseSensitive;

    private final UrlPathHelper urlPathHelper;

    /**
     * Creates a matcher with the specific pattern which will match all HTTP methods in a
     * case sensitive manner.
     * @param pattern the ant pattern to use for matching
     */
    public AntPathRequestMatcher(String pattern) {
       this(pattern, null);
    }

    /**
     * Creates a matcher with the supplied pattern and HTTP method in a case sensitive
     * manner.
     * @param pattern the ant pattern to use for matching
     * @param httpMethod the HTTP method. The {@code matches} method will return false if
     * the incoming request doesn't have the same method.
     */
    public AntPathRequestMatcher(String pattern, String httpMethod) {
       this(pattern, httpMethod, true);
    }
    // 중략
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 pattern과 httpMethod를 가지는 AntPathRequestMatcher 객체를 만들어서 위 요구사항을 처리해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음은 예시 코드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package com.connector.global.filter;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;


@Slf4j
@Component
@RequiredArgsConstructor
public class JwtTokenFilter extends OncePerRequestFilter {
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        if (new AntPathRequestMatcher(&quot;/api/auth&quot;, HttpMethod.POST.toString()).matches(request)) {
            return true;
        }
        return false;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AntPathRequestMatcher 객체를 만들어서 matches 메서드를 통해서 검사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;필터를 거칠 때마다 AntPathRequestMatcher 객체가 만들어지므로 다음과 같이 상수로 만드는 방법도 고려해봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720792638963&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.connector.global.filter;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;


@Slf4j
@Component
@RequiredArgsConstructor
public class JwtTokenFilter extends OncePerRequestFilter {
    private static final AntPathRequestMatcher LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(&quot;/api/auth&quot;, HttpMethod.POST.toString());
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        if (LOGIN_PATH_REQUEST_MATCHER.matches(request)) {
            return true;
        }
        return false;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오늘은 &lt;span style=&quot;background-color: #fdfdfd; text-align: start;&quot;&gt;api path와 http method를 통해서 filter 로직을 타지 않게 하는 방법에 대해 알아봤습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;읽어주셔서 감사합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발 노트/토이 프로젝트로 배우는 스프링+자바</category>
      <category>AntPathRequestMatcher</category>
      <category>Filter</category>
      <category>Spring</category>
      <author>kkoon9</author>
      <guid isPermaLink="true">https://kkoon9.tistory.com/557</guid>
      <comments>https://kkoon9.tistory.com/557#entry557comment</comments>
      <pubDate>Fri, 12 Jul 2024 23:00:49 +0900</pubDate>
    </item>
    <item>
      <title>토이 프로젝트로 배우는 자바 스프링 [5]. 엔티티의 생성시각을 자동으로 저장하기 (JPA Auditing)</title>
      <link>https://kkoon9.tistory.com/556</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706450633393&quot; style=&quot;color: #333333; text-align: start;&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;토이 프로젝트로 배우는 자바 스프링 [0]. prologue&quot; data-og-description=&quot;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다. 프론트 코드 : https://github.com/laboratory-kkoon9/connector_front GitHub - laboratory-kkoon9/connector_front Contribute to laboratory-kkoon9/co&quot; data-og-host=&quot;kkoon9.tistory.com&quot; data-og-source-url=&quot;https://kkoon9.tistory.com/542&quot; data-og-url=&quot;https://kkoon9.tistory.com/542&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/byj4SG/hyUL57l4co/oGU8tiU5hSkA80MqWDd6FK/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/bWCvJI/hyUPJawMKI/gGAECd27gmoNWYhtSlShs1/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/iD3Uj/hyUPy07QSR/zeOk7ExPyTGYBto9djGvUk/img.png?width=2126&amp;amp;height=1170&amp;amp;face=0_0_2126_1170&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://kkoon9.tistory.com/542&quot; data-source-url=&quot;https://kkoon9.tistory.com/542&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/byj4SG/hyUL57l4co/oGU8tiU5hSkA80MqWDd6FK/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/bWCvJI/hyUPJawMKI/gGAECd27gmoNWYhtSlShs1/img.png?width=800&amp;amp;height=310&amp;amp;face=0_0_800_310,https://scrap.kakaocdn.net/dn/iD3Uj/hyUPy07QSR/zeOk7ExPyTGYBto9djGvUk/img.png?width=2126&amp;amp;height=1170&amp;amp;face=0_0_2126_1170');&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;토이 프로젝트로 배우는 자바 스프링 [0]. prologue&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다. 프론트 코드 : https://github.com/laboratory-kkoon9/connector_front GitHub - laboratory-kkoon9/connector_front Contribute to laboratory-kkoon9/co&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kkoon9.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프론트 코드 :&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/laboratory-kkoon9/connector_front&quot;&gt;https://github.com/laboratory-kkoon9/connector_front&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706450633395&quot; style=&quot;color: #333333; text-align: start;&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d0Ggh0/hyUCf9RDPk/gkNuXhFK0Wpok3TB6BPx01/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot; data-og-url=&quot;https://github.com/laboratory-kkoon9/connector_front&quot; data-og-source-url=&quot;https://github.com/laboratory-kkoon9/connector_front&quot; data-og-host=&quot;github.com&quot; data-og-description=&quot;Contribute to laboratory-kkoon9/connector_front development by creating an account on GitHub.&quot; data-og-title=&quot;GitHub - laboratory-kkoon9/connector_front&quot; data-og-type=&quot;object&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/laboratory-kkoon9/connector_front&quot; data-source-url=&quot;https://github.com/laboratory-kkoon9/connector_front&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d0Ggh0/hyUCf9RDPk/gkNuXhFK0Wpok3TB6BPx01/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - laboratory-kkoon9/connector_front&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to laboratory-kkoon9/connector_front 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;/span&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;백엔드 코드 :&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/laboratory-kkoon9/connector_back&quot;&gt;https://github.com/laboratory-kkoon9/connector_back&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706450633395&quot; style=&quot;color: #333333; text-align: start;&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ej5bqV/hyUE0QJeTe/PH5BtOkDII3aHmACYSbxzK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot; data-og-url=&quot;https://github.com/laboratory-kkoon9/connector_back&quot; data-og-source-url=&quot;https://github.com/laboratory-kkoon9/connector_back&quot; data-og-host=&quot;github.com&quot; data-og-description=&quot;Contribute to laboratory-kkoon9/connector_back development by creating an account on GitHub.&quot; data-og-title=&quot;GitHub - laboratory-kkoon9/connector_back&quot; data-og-type=&quot;object&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/laboratory-kkoon9/connector_back&quot; data-source-url=&quot;https://github.com/laboratory-kkoon9/connector_back&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ej5bqV/hyUE0QJeTe/PH5BtOkDII3aHmACYSbxzK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - laboratory-kkoon9/connector_back&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to laboratory-kkoon9/connector_back 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;/span&gt;&lt;/figure&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;엔티티에 생성 시각을 JPA에서 제공하는 기능으로 처리해보려고 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통 엔티티에는 다음과 같은 공통 필드가 존재할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성 시각&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수정 시각&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;활성화 여부&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;엔티티마다 추가해주는 방법도 있지만, 하나의 클래스로 관리할 수 있는 방법이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;BaseEntity&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 그 클래스를 BaseEntity로 부르고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음은 BaseEntity의 코드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713794956633&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.time.LocalDateTime;

import static javax.persistence.EnumType.ORDINAL;

@Getter
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public class BaseEntity {
    @Column(name = &quot;activated&quot;, columnDefinition = &quot;tinyint&quot;)
    protected Boolean activated;

    @CreatedDate
    @Column(name = &quot;created_at&quot;, updatable = false)
    protected LocalDateTime createdAt;

    @LastModifiedDate
    @Column(name = &quot;updated_at&quot;, insertable = false)
    protected LocalDateTime updatedAt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 살펴볼 키워드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@MappedSuperclass&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@CreatedDate, @LastModifiedDate&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하나하나 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@MappedSuperclass&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매핑 정보가 해당 클래스에서 상속되는 엔티티에 적용되는 클래스를 지정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; 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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;매핑된 슈퍼클래스 자체에 대한 테이블이 없으므로 매핑이 해당 서브클래스에만 적용된다는 점을 제외하고는 @MappedSuperclass으로 지정된 클래스를 엔티티와 동일한 방식으로 매핑할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;@MappedSuperclass 어노테이션이 붙은 클래스를 상속한 엔티티 클래스 의 컨텍스트에 적용됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot;&gt;보통, 공통적으로 사용되는 컬럼을 모아 @MappedSuperclass로 선언하고, 여러 엔티티에서 상속받는 구조로 사용됩니다.&lt;/div&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@CreatedDate, @LastModifiedDate&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@CreatedDate는 필드를 포함하는 엔티티가 작성된 날짜를 나타내는 필드로 선언합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@LastModifiedDate는 필드를 포함하는 엔티티가 최근에 수정된 날짜를 나타내는 필드로 선언합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 밖에 @CreatedBy나 @LastModifiedBy 어노테이션도 존재합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;@EnableJpaAuditing&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@CreatedDate와 @LastModifiedDate 어노테이션이 동작하게 하려면 다음 구성이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EnableJpaAuditing 어노테이션을 포함한 Configuration을 통해서 JPA에서 일어나는 일들을 감사(auditing)할 수 있게 해줍니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;감사(auditing)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/jpa/reference/auditing.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-data/jpa/reference/auditing.html&lt;/a&gt;&lt;/p&gt;
&lt;div id=&quot;targetEditArea&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 링크에 따르면 감사(auditing)는 다음과 같은 의미를 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;txtTarget&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Spring Data는 누가 엔티티를 생성하거나 변경했는지, 변경이 언제 발생했는지를 추적할 수 있도록 정교한 지원을 제공합니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;auditing&lt;/span&gt;&amp;nbsp;기능을 활용하려면 엔티티 클래스에 어노테이션을 사용하거나 인터페이스를 구현하여 정의할 수 있는 auditing 메타데이터를 준비해야 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또한 필요한 인프라스트럭처 구성 요소를 등록하려면 @configuration 또는 XML configuration을 통해 auditing를 활성화해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;저는 보통 다음과 같이 configuration을 선언해놓습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1713879343565&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 노트/토이 프로젝트로 배우는 스프링+자바</category>
      <category>JPA</category>
      <category>jpaauditing</category>
      <category>Spring Boot</category>
      <author>kkoon9</author>
      <guid isPermaLink="true">https://kkoon9.tistory.com/556</guid>
      <comments>https://kkoon9.tistory.com/556#entry556comment</comments>
      <pubDate>Tue, 23 Apr 2024 22:43:16 +0900</pubDate>
    </item>
    <item>
      <title>[스프링+코틀린] kotest에서 saveAll mocking하기</title>
      <link>https://kkoon9.tistory.com/555</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;코프링으로&amp;nbsp;개발&amp;nbsp;시&amp;nbsp;마주친&amp;nbsp;에러&amp;nbsp;관련&amp;nbsp;포스팅입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개발 환경은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Boot Version : 3.0.1&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java Version : 17&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kotlin Version : 1.8.21&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kotest Version : 5.5.5&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 mocking할 때 any()를 많이 사용했었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 saveAll()은 any()로 mocking하려고 하면 다음과 같은 에러가 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Not&amp;nbsp;enough&amp;nbsp;information&amp;nbsp;to&amp;nbsp;infer&amp;nbsp;type&amp;nbsp;variable&amp;nbsp;S&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;80&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKGKTS/btsF6fQgveE/XhDSZEwINp10D1BzQhlTX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKGKTS/btsF6fQgveE/XhDSZEwINp10D1BzQhlTX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKGKTS/btsF6fQgveE/XhDSZEwINp10D1BzQhlTX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKGKTS%2FbtsF6fQgveE%2FXhDSZEwINp10D1BzQhlTX1%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;555&quot; height=&quot;80&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;80&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해석해보자면 정보가 불충분하여 변수 S에 대한 타입을 추론할 수 없다라는 뜻입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원인&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음과 같 이 save mocking은 에러를 발생하지 않습니다.&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;540&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fvgem/btsF5uADPVM/5YeGSSsHjUB0K5Bm7TMSK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fvgem/btsF5uADPVM/5YeGSSsHjUB0K5Bm7TMSK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fvgem/btsF5uADPVM/5YeGSSsHjUB0K5Bm7TMSK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFvgem%2FbtsF5uADPVM%2F5YeGSSsHjUB0K5Bm7TMSK0%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;540&quot; height=&quot;52&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;52&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span style=&quot;color: #000000;&quot;&gt;save의 파라미터는 S entity 입니다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;cafeRepository의 save 함수이므로 any()는 Cafe 타입을 가지겠구나 추론을 한 모양입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;하지만 saveAll 함수는 파라미터가 Iterable&amp;lt;S&amp;gt; entities 입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;any()는 타입추론은 했지만, Iterable 타입만 추론했을 뿐 S의 타입은 추론하지 못한 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;결론&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;결국 any()의 타입추론을 돕기 위해 saveAll 함수가 어떤 타입을 취급하는지 알려줘야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;every { cafeRepository.saveAll&amp;lt;Cafe&amp;gt;(any()) } returns listOf(cafeDto.toEntity())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;saveAll의 타입을 위 코드와 같이 명시해줌으로써 해당 에러를 해결할 수 있었습니다.&lt;/p&gt;</description>
      <category>개발 노트</category>
      <category>kotest</category>
      <category>Kotlin</category>
      <category>mockk</category>
      <category>saveAll</category>
      <author>kkoon9</author>
      <guid isPermaLink="true">https://kkoon9.tistory.com/555</guid>
      <comments>https://kkoon9.tistory.com/555#entry555comment</comments>
      <pubDate>Tue, 26 Mar 2024 22:44:08 +0900</pubDate>
    </item>
    <item>
      <title>[스프링+코틀린] 코프링에서 request body validation not working(with ExceptionHandler)</title>
      <link>https://kkoon9.tistory.com/554</link>
      <description>&lt;h2 style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; request body 필드들을 유효성 체크를 하고 싶었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;java spring boot 환경에서는 다르게 코프링에서는 몇 가지 문제가 발생해서 이번 포스팅에서 해당 문제들을 정리하려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개발 환경은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Boot Version : 3.0.1&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java Version : 17&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kotlin Version : 1.8.21&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kotest Version : 5.5.5&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가로, 아래 의존성을 추가해주셔야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;// validator
implementation(&quot;org.springframework.boot:spring-boot-starter-validation&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. NotBlank not working&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;첫 번째로는 jakarta.validation.constraints 라이브러리에서 제공하는 어노테이션들이 동작을 하지 않았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Schema(name = &quot;카페 생성 Request&quot;)
data class CreateCafeRequest(
    @NotBlank(message = &quot;카페 이름은 필수값입니다.&quot;)
    @Schema(description = &quot;카페 이름&quot;, defaultValue = &quot;댕겸의 커피집&quot;)
    val name: String,
    @Schema(description = &quot;카페 위도&quot;, defaultValue = &quot;37.56667&quot;)
    val latitude: String?,
    @Schema(description = &quot;카페 경도&quot;, defaultValue = &quot;126.97806&quot;)
    val longitude: String?,
    @Schema(description = &quot;카페 주소&quot;, defaultValue = &quot;서울특별시 중구 세종대로 110 서울특별시청&quot;)
    val mainAddress: String?,
    @Schema(description = &quot;카페 상세 주소&quot;, defaultValue = &quot;1층&quot;)
    val detailAddress: String?,
    @Schema(description = &quot;카페 오픈 시간&quot;, defaultValue = &quot;09:00:00&quot;)
    val openedAt: LocalTime?,
    @Schema(description = &quot;카페 클로즈 시간&quot;, defaultValue = &quot;18:00:00&quot;)
    val closedAt: LocalTime?,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 RequestDto에서 적어둔대로 &quot;카페 이름은 필수값입니다.&quot; 라는 에러 메시지를 기대했지만, 다음과 같은 결과값을 받아볼 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1972&quot; data-origin-height=&quot;1134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVKRm7/btsF3VpNj0C/M18VgXFNL7qvgnvKajcceK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVKRm7/btsF3VpNj0C/M18VgXFNL7qvgnvKajcceK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVKRm7/btsF3VpNj0C/M18VgXFNL7qvgnvKajcceK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVKRm7%2FbtsF3VpNj0C%2FM18VgXFNL7qvgnvKajcceK%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;1972&quot; height=&quot;1134&quot; data-origin-width=&quot;1972&quot; data-origin-height=&quot;1134&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;어떤 문제인지 살펴보기 전에 어떤 예외가 발생하는지 GlobalExceptionHandler에서 디버그를 통해 잡아봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhONRS/btsF000U6FC/m071wNKEaKOheZ3zr1GfwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhONRS/btsF000U6FC/m071wNKEaKOheZ3zr1GfwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhONRS/btsF000U6FC/m071wNKEaKOheZ3zr1GfwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhONRS%2FbtsF000U6FC%2Fm071wNKEaKOheZ3zr1GfwK%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;1492&quot; height=&quot;376&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디버그를 살펴보니 ServerWebInputException 예외가 발생했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2848&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdFFHZ/btsF3WvsZhV/9tYI2SeCk6lkQPrSuJTsB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdFFHZ/btsF3WvsZhV/9tYI2SeCk6lkQPrSuJTsB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdFFHZ/btsF3WvsZhV/9tYI2SeCk6lkQPrSuJTsB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdFFHZ%2FbtsF3WvsZhV%2F9tYI2SeCk6lkQPrSuJTsB0%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;2848&quot; height=&quot;794&quot; data-origin-width=&quot;2848&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외가 발생한 원인에 대해서도 상세히 나와있는데, 원인은 다음과 같았습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Json Decoding 과정에서 non-nullable 필드에 null이 들어와서 예외가 발생&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 request body에 설정한 validation가 거치기도 전에 디코딩 과정에서 예외를 뱉어낸 것입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;name 필드를 nullable하게 변경하면 되겠다!&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name 필드를 nullable하게 변경하면 간단하게 해결할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 저는 필수로 받아야 하는 non-nullable 필드를 validation을 하기 위해 nullable하게 변경하는 건 좋지 않은 변경이라고 생각했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ServerWebInputException 예외 핸들링&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 name 필드를 nullable하게 변경하는 대신 ServerWebInputException 예외를 GlobalExceptionHandler에서 잡아서 처리하기로 결정했습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@ExceptionHandler(ServerWebInputException::class)
protected fun handleServerWebInputException(ex: ServerWebInputException): ResponseEntity&amp;lt;ErrorResponse&amp;gt; {
    val message = &quot;${ex.methodParameter.parameter.name}의 validation 규칙을 다시 검토하세요.&quot;
    return ResponseEntity(
        ErrorResponse(errorCode = &quot;10001&quot;, message = message),
        HttpStatus.BAD_REQUEST
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ServerWebInputException 예외가 발생한 클래스에 위 코드처럼 validation 규칙을 다시 검토하라는 메시지를 에러 응답값에 포함했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;1112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zt0ad/btsF0HAnNVU/4QS8XayHe8gj1cSayiLIy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zt0ad/btsF0HAnNVU/4QS8XayHe8gj1cSayiLIy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zt0ad/btsF0HAnNVU/4QS8XayHe8gj1cSayiLIy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZt0ad%2FbtsF0HAnNVU%2F4QS8XayHe8gj1cSayiLIy0%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;2040&quot; height=&quot;1112&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;1112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. NotEmpty not working&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;NotBlank 대신 null로 들어온 필드에 대한 에러 메시지를 처리해봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 빈 문자열 값(&quot;&quot;, &quot; &quot;)으로 들어올 때 처리하기 위해 NotEmpty 어노테이션을 사용해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@Schema(name = &quot;카페 생성 Request&quot;)
data class CreateCafeRequest(
    @NotEmpty(message = &quot;카페 이름은 공백일 수 없습니다.&quot;)
    @Schema(description = &quot;카페 이름&quot;, defaultValue = &quot;댕겸의 커피집&quot;)
    val name: String,
    @Schema(description = &quot;카페 위도&quot;, defaultValue = &quot;37.56667&quot;)
    val latitude: String?,
    @Schema(description = &quot;카페 경도&quot;, defaultValue = &quot;126.97806&quot;)
    val longitude: String?,
    @Schema(description = &quot;카페 주소&quot;, defaultValue = &quot;서울특별시 중구 세종대로 110 서울특별시청&quot;)
    val mainAddress: String?,
    @Schema(description = &quot;카페 상세 주소&quot;, defaultValue = &quot;1층&quot;)
    val detailAddress: String?,
    @Schema(description = &quot;카페 오픈 시간&quot;, defaultValue = &quot;09:00:00&quot;)
    val openedAt: LocalTime?,
    @Schema(description = &quot;카페 클로즈 시간&quot;, defaultValue = &quot;18:00:00&quot;)
    val closedAt: LocalTime?,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HrtY3/btsF0Hf5vOZ/MnbfC1dnGQzLRLdr3iK2O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HrtY3/btsF0Hf5vOZ/MnbfC1dnGQzLRLdr3iK2O0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HrtY3/btsF0Hf5vOZ/MnbfC1dnGQzLRLdr3iK2O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHrtY3%2FbtsF0Hf5vOZ%2FMnbfC1dnGQzLRLdr3iK2O0%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;2000&quot; height=&quot;1128&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 기대했던 바가 아닌 빈 문자열 필드를 담은 request body가 &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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;field: 키워드&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;다른 포스팅을 참고해보니, 위 data class에서 NotEmpty 같은 어노테이션을 사용하면 java code로 변환하는 과정에서 constructor의 파라미터에 붙는다고 합니다.&lt;/span&gt;&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;span&gt;그렇기 때문에 field: 라는 키워드를 붙여줘야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@Schema(name = &quot;카페 생성 Request&quot;)
data class CreateCafeRequest(
    @field:NotEmpty(message = &quot;카페 이름은 공백일 수 없습니다.&quot;)
    @Schema(description = &quot;카페 이름&quot;, defaultValue = &quot;댕겸의 커피집&quot;)
    val name: String,
    @Schema(description = &quot;카페 위도&quot;, defaultValue = &quot;37.56667&quot;)
    val latitude: String?,
    @Schema(description = &quot;카페 경도&quot;, defaultValue = &quot;126.97806&quot;)
    val longitude: String?,
    @Schema(description = &quot;카페 주소&quot;, defaultValue = &quot;서울특별시 중구 세종대로 110 서울특별시청&quot;)
    val mainAddress: String?,
    @Schema(description = &quot;카페 상세 주소&quot;, defaultValue = &quot;1층&quot;)
    val detailAddress: String?,
    @Schema(description = &quot;카페 오픈 시간&quot;, defaultValue = &quot;09:00:00&quot;)
    val openedAt: LocalTime?,
    @Schema(description = &quot;카페 클로즈 시간&quot;, defaultValue = &quot;18:00:00&quot;)
    val closedAt: LocalTime?,
)
&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;하지만, 저의 바램과는 다르게 제가 설정했던 &quot;카페 이름은 공백일 수 없습니다&quot; 에러 메시지가 아닌 NotBlank를 위해 설정했던 에러 메시지가 응답값으로 넘어오고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2006&quot; data-origin-height=&quot;1134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFKB3T/btsF3WoICLt/0H1TEyCt10TJu6jAMa5a90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFKB3T/btsF3WoICLt/0H1TEyCt10TJu6jAMa5a90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFKB3T/btsF3WoICLt/0H1TEyCt10TJu6jAMa5a90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFKB3T%2FbtsF3WoICLt%2F0H1TEyCt10TJu6jAMa5a90%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;2006&quot; height=&quot;1134&quot; data-origin-width=&quot;2006&quot; data-origin-height=&quot;1134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 ServerWebInputException 예외 핸들링에서 디버그를 해보기로 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VdFPa/btsF3Uj7OrT/QRjemuLqmOXkc5mQiemkN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VdFPa/btsF3Uj7OrT/QRjemuLqmOXkc5mQiemkN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VdFPa/btsF3Uj7OrT/QRjemuLqmOXkc5mQiemkN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVdFPa%2FbtsF3Uj7OrT%2FQRjemuLqmOXkc5mQiemkN1%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;1834&quot; height=&quot;394&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외 body를 살펴보니, WebExchangeBindException 예외가 발생하고 있었고, WebExchangeBindException 필드인 bindingResult에서 제가 request dto에 설정했던 에러 메시지가 보관하고 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1874&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tfI6L/btsF1lDM925/rneO5yHfosKQJMJ7XKtbX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tfI6L/btsF1lDM925/rneO5yHfosKQJMJ7XKtbX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tfI6L/btsF1lDM925/rneO5yHfosKQJMJ7XKtbX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtfI6L%2FbtsF1lDM925%2FrneO5yHfosKQJMJ7XKtbX0%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;1874&quot; height=&quot;808&quot; data-origin-width=&quot;1874&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;WebExchangeBindException&amp;nbsp;예외 핸들링&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 WebExchangeBindException 예외 핸들링하는 코드도 추가해봅시다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@ExceptionHandler(WebExchangeBindException::class)
protected fun handleWebExchangeBindException(ex: WebExchangeBindException): ResponseEntity&amp;lt;ErrorResponse&amp;gt; {
    val message = ex.bindingResult
        .allErrors[0]
        .defaultMessage
    return ResponseEntity(
        ErrorResponse(errorCode = &quot;10001&quot;, message = message),
        HttpStatus.BAD_REQUEST
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가한 뒤, 다시 api를 호출해보니, 제가 설정한 에러 메시지가 응답값으로 넘어오는 걸 확인할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2020&quot; data-origin-height=&quot;1038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drSpEz/btsF2TTwkK0/oSEkX9kxidDiIPgCxtYUL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drSpEz/btsF2TTwkK0/oSEkX9kxidDiIPgCxtYUL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drSpEz/btsF2TTwkK0/oSEkX9kxidDiIPgCxtYUL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrSpEz%2FbtsF2TTwkK0%2FoSEkX9kxidDiIPgCxtYUL0%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;2020&quot; height=&quot;1038&quot; data-origin-width=&quot;2020&quot; data-origin-height=&quot;1038&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 코프링에서 validation 설정하는 방법에 대해 다뤄봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저와 비슷한 문제를 겪으신 분들은 제 포스팅 이 도움되셨으면 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>개발 노트</category>
      <category>Kotlin</category>
      <category>Spring Boot</category>
      <category>validation</category>
      <author>kkoon9</author>
      <guid isPermaLink="true">https://kkoon9.tistory.com/554</guid>
      <comments>https://kkoon9.tistory.com/554#entry554comment</comments>
      <pubDate>Sun, 24 Mar 2024 17:59:23 +0900</pubDate>
    </item>
    <item>
      <title>BigDecimal 제대로 사용하기</title>
      <link>https://kkoon9.tistory.com/553</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정산 관련 업무를 맡았을 때, &lt;/span&gt;업체 및 운영 유저가 발주를 위해 사용하던 서비스였고, 그 과정에서  발주 금액에 대한 세금을 계산하고 세금계산서를 발행해주는 로직이 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이펙티브 자바에서 나오는 &quot;정확한 답이 필요하다면 float와 double은 피하라&quot; 내용에 따라서 BigDecimal로 처리하려고 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709962376955&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SettlementCalculator {
    private SettlementCalculator() {}

    public static Integer calculateTotalSupplyPrice(final int price, final TaxationType taxationType) {
        if(taxationType != (TaxationType.TAX)) {
            return price;
        }

        BigDecimal bdPrice = new BigDecimal(price);
        BigDecimal rate = new BigDecimal(1.1);

        return bdPrice.divide(rate, 0, RoundingMode.FLOOR).intValue();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;업체마다 과세와 비과세로 나뉘어져 있었는데, 과세일 경우 공급가액(price)에서 1.1을 나눈 값을 제공해주는 메서드입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XfXRN/btsFF8KnGvs/h0uPZfvOWom7uTzkGeSkXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XfXRN/btsFF8KnGvs/h0uPZfvOWom7uTzkGeSkXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XfXRN/btsFF8KnGvs/h0uPZfvOWom7uTzkGeSkXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXfXRN%2FbtsFF8KnGvs%2Fh0uPZfvOWom7uTzkGeSkXK%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;1314&quot; height=&quot;334&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 위 메서드로 인해 어떤 문제가 있었는지 이야기해보면서 BigDecimal에 대해서 정리해보겠습니다.&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;BigDecimal이란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 BigDecimal이 무엇인지 알아봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;BigDecimal은&amp;nbsp;Java&amp;nbsp;언어에서 숫자를 정밀하게 저장하고 표현할 수 있는 유일한 방법입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;소수점을 저장할 수 있는 가장 크기가 큰 타입인&amp;nbsp;double은 소수점의 정밀도에 있어 한계가 있어서 값이 유실될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java&amp;nbsp;언어에서 돈과 소수점을 다룬다면&amp;nbsp;BigDecimal은 선택이 아니라 필수인 셈이죠.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;BigDecimal의 유일한 단점은 느린 속도와 기본 타입보다 조금 불편한 사용법 뿐입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;소수점의 정밀도 한계?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소수점의 정밀도에 한계가 있다는 표현이 잘 안 와닿아서 테스트를 해봤습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1.03 - 0.42의 값을 예상해봅시다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도 0.61로 예상해볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 자바 환경에서 테스트해보면 다음과 같은 결과가 나오게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Test {
    public static void main(String[] args) {
        double a = 1.03;
        double b = 0.42;
        System.out.println(a - b);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ezHOxi/btsFDVFG18Y/yqwkeXIFEI4VazuCth7D21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ezHOxi/btsFDVFG18Y/yqwkeXIFEI4VazuCth7D21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ezHOxi/btsFDVFG18Y/yqwkeXIFEI4VazuCth7D21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FezHOxi%2FbtsFDVFG18Y%2FyqwkeXIFEI4VazuCth7D21%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;475&quot; height=&quot;152&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동소수점(float, double)에 대한 더 자세한 내용은 회사 동료가 추천해주신 아래 링크를 참고하는걸 추천드립니다.&lt;/p&gt;
&lt;figure id=&quot;og_1709963609560&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;Floating Point 부동소수점&quot; data-og-description=&quot; &quot; data-og-host=&quot;johngrib.github.io&quot; data-og-source-url=&quot;https://johngrib.github.io/wiki/floating-point/#01--02--030000000000000004&quot; data-og-url=&quot;https://johngrib.github.io/wiki/floating-point/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/27ZYo/hyVud4GVUk/Nvyh9qACZCzwqw8LS7t8J1/img.png?width=170&amp;amp;height=170&amp;amp;face=0_0_170_170&quot;&gt;&lt;a href=&quot;https://johngrib.github.io/wiki/floating-point/#01--02--030000000000000004&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://johngrib.github.io/wiki/floating-point/#01--02--030000000000000004&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/27ZYo/hyVud4GVUk/Nvyh9qACZCzwqw8LS7t8J1/img.png?width=170&amp;amp;height=170&amp;amp;face=0_0_170_170');&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;Floating Point 부동소수점&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;johngrib.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;내가 경험한 BigDecimal 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동소수점의 문제와 BigDecimal을 사용해야 하는 이유는 이쯤하면 될 것 같고, 제가 어떤 문제를 겪었는지 서술하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 메서드에서는 간단한 금액에 대해서는 잘 동작하였으나 문제는 복잡한 금액을 계산할 때 발생했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1709963803921&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.math.BigDecimal;
import java.math.RoundingMode;

public class Test {
    public static void main(String[] args) {
        int simplePrice = 20000;
        Integer rowPriceResult = calculateTotalSupplyPrice(simplePrice);
        System.out.println(&quot;간단한 금액 : &quot; + rowPriceResult);
        int complicatedPrice = 234131414;
        Integer highPriceResult = calculateTotalSupplyPrice(complicatedPrice);
        System.out.println(&quot;복잡한 금액 : &quot; + highPriceResult);

    }

    public static Integer calculateTotalSupplyPrice(final int price) {
        BigDecimal bdPrice = new BigDecimal(price);
        BigDecimal rate = new BigDecimal(1.1);

        return bdPrice.divide(rate, 0, RoundingMode.FLOOR).intValue();
    }
}&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;1294&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc5IdR/btsFGUY87qP/kkBSxA8FgK1UwMZh1iKJGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc5IdR/btsFGUY87qP/kkBSxA8FgK1UwMZh1iKJGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc5IdR/btsFGUY87qP/kkBSxA8FgK1UwMZh1iKJGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc5IdR%2FbtsFGUY87qP%2FkkBSxA8FgK1UwMZh1iKJGK%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;629&quot; height=&quot;79&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 위 코드의 결과는 달랐습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QcATb/btsFEOlNnqN/YKuXoBfImZPtnzFVRBIuKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QcATb/btsFEOlNnqN/YKuXoBfImZPtnzFVRBIuKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QcATb/btsFEOlNnqN/YKuXoBfImZPtnzFVRBIuKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQcATb%2FbtsFEOlNnqN%2FYKuXoBfImZPtnzFVRBIuKk%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;741&quot; height=&quot;186&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이런 문제가 발생할까요?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;BigDecimal 생성자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 BigDecimal 생성자 파라미터로 double 타입 변수를 사용했던 게 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 이미지는 BigDecimal 생성자에 대한 설명입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;812&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTcuaZ/btsFEL3H9vD/mQyxq5rF5wzOEm4aZ1c8ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTcuaZ/btsFEL3H9vD/mQyxq5rF5wzOEm4aZ1c8ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTcuaZ/btsFEL3H9vD/mQyxq5rF5wzOEm4aZ1c8ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTcuaZ%2FbtsFEL3H9vD%2FmQyxq5rF5wzOEm4aZ1c8ok%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;1256&quot; height=&quot;812&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;812&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간략하게 해석해보자면 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 생성자의 결과는 다소 예측 불가능할 수 있습니다.&amp;nbsp;&lt;br /&gt;생성자에게 전달되는 값 또한 부동소수점이기 때문입니다.&lt;br /&gt;정확한 값을 사용하려면 파라미터를 문자열(문자열 생성자)로 넘기거나, valueOf 정적 메서드를 사용하라고 합니다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문자열 생성자를 사용했을 때&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;public static Integer calculateTotalSupplyPrice(final int price) {
    BigDecimal bdPrice = new BigDecimal(price);
    BigDecimal rate = new BigDecimal(&quot;1.1&quot;);

    return bdPrice.divide(rate, 0, RoundingMode.FLOOR).intValue();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열로 파라미터를 넘겼을 때 다음과 같은 결과를 얻을 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KzgV0/btsFEu1J1T0/TpAiJXgJLbFPZLM0BjlmsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KzgV0/btsFEu1J1T0/TpAiJXgJLbFPZLM0BjlmsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KzgV0/btsFEu1J1T0/TpAiJXgJLbFPZLM0BjlmsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKzgV0%2FbtsFEu1J1T0%2FTpAiJXgJLbFPZLM0BjlmsK%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;1252&quot; height=&quot;298&quot; data-origin-width=&quot;1252&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;&lt;b&gt;valueOf를 사용했을 때&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;public static Integer calculateTotalSupplyPrice(final int price) {
    BigDecimal bdPrice = new BigDecimal(price);
    BigDecimal rate = BigDecimal.valueOf(1.1);

    return bdPrice.divide(rate, 0, RoundingMode.FLOOR).intValue();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;valueOf 메서드를 사용했을 때 다음과 같은 결과를 얻을 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEKzeu/btsFDG9qHHN/8GHwkTLZhIrv9ExJmSkxZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEKzeu/btsFDG9qHHN/8GHwkTLZhIrv9ExJmSkxZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEKzeu/btsFDG9qHHN/8GHwkTLZhIrv9ExJmSkxZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEKzeu%2FbtsFDG9qHHN%2F8GHwkTLZhIrv9ExJmSkxZ1%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;1304&quot; height=&quot;238&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제를 겪으면서 다음과 같은 생각을 했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;큰 금액에 대한 테스트 케이스를 고려했다면&lt;/li&gt;
&lt;li&gt;문서를 좀 더 꼼꼼하게 읽어봤더라면&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 꼼꼼하게 개발하는 습관을 들여야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽어주셔서 감사합니다~!&lt;/p&gt;</description>
      <category>개발 노트</category>
      <category>BigDecimal</category>
      <category>Java</category>
      <category>이펙티브 자바</category>
      <author>kkoon9</author>
      <guid isPermaLink="true">https://kkoon9.tistory.com/553</guid>
      <comments>https://kkoon9.tistory.com/553#entry553comment</comments>
      <pubDate>Sat, 9 Mar 2024 15:27:36 +0900</pubDate>
    </item>
  </channel>
</rss>