<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>treecode</title>
    <link>https://treecode.tistory.com/</link>
    <description>웹 개발자 꿈나무</description>
    <language>ko</language>
    <pubDate>Tue, 7 Jul 2026 11:48:24 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>tree code</managingEditor>
    <image>
      <title>treecode</title>
      <url>https://tistory1.daumcdn.net/tistory/4351193/attach/f6814640e19b4c1cb8c2fb65de91c04a</url>
      <link>https://treecode.tistory.com</link>
    </image>
    <item>
      <title>[스프링] Json 데이터 역직렬화 (ObjectMapper @JsonDeserializer)</title>
      <link>https://treecode.tistory.com/124</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도 기반 프로젝트를 진행하면서 RestClient를 사용해서 카카오맵 API를 통해 리버스 지오코딩을 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지오코딩(geocoding)은 주소를 좌표로 변환하는 작업, 리버스 지오코딩은 반대로 좌표를 주소로 변환하는 것을 말한다.&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오맵 API는 기본적으로 Json 형식으로 응답을 하는데 응답받은 데이터를 사용하기 위해 역직렬화하여 객체로 변환하는 작업이 필요하며 이를 위해 JsonDeserializer를 사용해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 스프링에서 HTTP RequestBody, ResponseBody의 데이터를 직렬화, 역직렬화하는 기능은 메시지 컨버터가 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 공식 문서 내용을 보면 RestClient를 builder()로 생성할 수 있고, 메시지 컨버터를 포함한 여러 설정을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 제공하는 메시지 컨버터 인터페이스 HttpMessageConverter는 클라이언트 사이드(ex. RestClient)와 서버 사이드(ex. RestController)에서 HTTP 요청, 응답의 body를 읽고 쓰는 역할을 하며 다양한 구현체들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;1394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XFOoF/btsG4A0mmRP/trI5JPJ1u8Si9Bw4lWL061/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XFOoF/btsG4A0mmRP/trI5JPJ1u8Si9Bw4lWL061/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XFOoF/btsG4A0mmRP/trI5JPJ1u8Si9Bw4lWL061/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXFOoF%2FbtsG4A0mmRP%2FtrI5JPJ1u8Si9Bw4lWL061%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;1758&quot; height=&quot;1394&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;1394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestClient는 기본적으로 등록되어있는  메시지 컨버터를 사용하고 추가로 확장할 수도 있다. 등록된 메시지 컨버터 중에 canRead()를 통해 변환이 가능한 컨버터를 찾아서 처리를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MappingJackson2MessageConverter는 기본적으로 내장된 컨버터로 json 데이터를 변환하는 작업을 해주며 내부적으로 ObjectMapper(Jackson 라이브러리)를 사용한다. ObjectMapper도 canDeserialize()를 통해 deserialize가 가능한지를 체크한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1514&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEIobB/btsG4iFw32b/d8mwixBl3QQeBViQV6Axg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEIobB/btsG4iFw32b/d8mwixBl3QQeBViQV6Axg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEIobB/btsG4iFw32b/d8mwixBl3QQeBViQV6Axg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEIobB%2FbtsG4iFw32b%2Fd8mwixBl3QQeBViQV6Axg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;407&quot; data-origin-width=&quot;1514&quot; data-origin-height=&quot;930&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오맵 API의 Json 응답 예시를 보면 SnakeCase 표기법으로 되어 있는데 기본적으로는 CamelCase 표기법으로 읽기 때문에ObjectMapper에 별도로 setPropertyNamingStrategy()로 설정을 해줘야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJvUz/btsG5jRst4c/NgNIhs2NJYWriyV0grI5Q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJvUz/btsG5jRst4c/NgNIhs2NJYWriyV0grI5Q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJvUz/btsG5jRst4c/NgNIhs2NJYWriyV0grI5Q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJvUz%2FbtsG5jRst4c%2FNgNIhs2NJYWriyV0grI5Q0%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;584&quot; height=&quot;373&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Jackson 라이브러리는 이러한 설정을&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;간편하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;할 수 있도록 @JsonNaming과 같은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&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;ObjectMapepr는 직렬화시 Getter가 있어야 하고 역직렬화 시 기본 생성자와 Setter가 있어야 하는 등의 조건이 있는데(필수는 아니지만)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Jackson 2.12 버전부터 Record 타입을 지원해 주면서 편하게 불변으로 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/29nCM/btsG4ierfgA/1kFKvObSvi3vkivCKroA11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/29nCM/btsG4ierfgA/1kFKvObSvi3vkivCKroA11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/29nCM/btsG4ierfgA/1kFKvObSvi3vkivCKroA11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F29nCM%2FbtsG4ierfgA%2F1kFKvObSvi3vkivCKroA11%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;641&quot; height=&quot;186&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 key-value에 맞춰서 클래스를 생성해 주면 되는데 depth가 깊어지면 복잡해지기 때문에 JsonDeserializer를 구현해서 직접 역직렬화를 해줄 수 있다. JsonDeserializer를 extends하고 deserialize()을 Override해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K4T32/btsG3prHj4d/fGdsQfkk5WjkJrmUVydGi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K4T32/btsG3prHj4d/fGdsQfkk5WjkJrmUVydGi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K4T32/btsG3prHj4d/fGdsQfkk5WjkJrmUVydGi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK4T32%2FbtsG3prHj4d%2FfGdsQfkk5WjkJrmUVydGi0%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;1838&quot; height=&quot;578&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jsonParser.getCode()을 통해 ObjectCodec(ObjectMapper 부모 클래스)의 readTree()를 호출하면 트리 구조의 JsonNode를 받을 수 있다. (해당 노드는 Json 데이터를 파싱한 것이다)&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;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/utOo0/btsG5D98qD2/hWjzGEnPBBzI5eijwLm9Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/utOo0/btsG5D98qD2/hWjzGEnPBBzI5eijwLm9Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/utOo0/btsG5D98qD2/hWjzGEnPBBzI5eijwLm9Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FutOo0%2FbtsG5D98qD2%2FhWjzGEnPBBzI5eijwLm9Tk%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;819&quot; height=&quot;259&quot; data-origin-width=&quot;1294&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_children은 Json 각 객체({}) 내부 key-value를 LinkedHashMap으로 관리하기 때문에 순서를 보장해 주며 같은 depth에 같은 key가 여러 개면 마지막 key만 담긴다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czYHRW/btsG3s9Ndcz/1qzLk4DYWckkEgXQX6qrh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czYHRW/btsG3s9Ndcz/1qzLk4DYWckkEgXQX6qrh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czYHRW/btsG3s9Ndcz/1qzLk4DYWckkEgXQX6qrh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczYHRW%2FbtsG3s9Ndcz%2F1qzLk4DYWckkEgXQX6qrh0%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;1734&quot; height=&quot;742&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eTx1CK/btsG3yhSAOP/AYuKQX41ZK19KGKEpKneFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eTx1CK/btsG3yhSAOP/AYuKQX41ZK19KGKEpKneFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eTx1CK/btsG3yhSAOP/AYuKQX41ZK19KGKEpKneFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeTx1CK%2FbtsG3yhSAOP%2FAYuKQX41ZK19KGKEpKneFK%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;1306&quot; height=&quot;498&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;findValue()는 특정 key의 value를 찾아서 가장 먼저 나오는 결과를 반환해 주는 메서드로 결과가 없으면 null을 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1688&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buTuLb/btsG53tVBxx/KVvL0HRkjfnc8wJ6U8dP6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buTuLb/btsG53tVBxx/KVvL0HRkjfnc8wJ6U8dP6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buTuLb/btsG53tVBxx/KVvL0HRkjfnc8wJ6U8dP6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuTuLb%2FbtsG53tVBxx%2FKVvL0HRkjfnc8wJ6U8dP6k%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;1688&quot; height=&quot;766&quot; data-origin-width=&quot;1688&quot; data-origin-height=&quot;766&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위 _children의 entry를 반복문으로 도는데 각각의 entry를 재귀 호출하기 때문에 depth 우선이 아닌 객체({}) 기준으로 가장 먼저 나오는 key의 value를 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8X0wa/btsG5G6PE14/MI4Ouv2CbzXMYyewC36DxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8X0wa/btsG5G6PE14/MI4Ouv2CbzXMYyewC36DxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8X0wa/btsG5G6PE14/MI4Ouv2CbzXMYyewC36DxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8X0wa%2FbtsG5G6PE14%2FMI4Ouv2CbzXMYyewC36DxK%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;455&quot; height=&quot;291&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 경우에는 가장 상단에 있는 값이 필요해서 findValue()를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 위에서 구현한 JsonDeserializer를 필요한 클래스에 애노테이션으로 선언해 주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QR28v/btsG6azFUUH/qD9MBtV8aBuOViwauOAZe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QR28v/btsG6azFUUH/qD9MBtV8aBuOViwauOAZe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QR28v/btsG6azFUUH/qD9MBtV8aBuOViwauOAZe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQR28v%2FbtsG6azFUUH%2FqD9MBtV8aBuOViwauOAZe1%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;1740&quot; height=&quot;420&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 @JsonDeserialize 애노테이션을 선언해주면 MappingJackson2HttpMessageConverter에서 내부적으로 @JsonDeserialize 애노테이션을 찾아서 적용을 해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2028&quot; data-origin-height=&quot;1122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bixHDG/btsG5EgVj7U/xmk6h7vVuR7iaHIKb7zlak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bixHDG/btsG5EgVj7U/xmk6h7vVuR7iaHIKb7zlak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bixHDG/btsG5EgVj7U/xmk6h7vVuR7iaHIKb7zlak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbixHDG%2FbtsG5EgVj7U%2Fxmk6h7vVuR7iaHIKb7zlak%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;2028&quot; height=&quot;1122&quot; data-origin-width=&quot;2028&quot; data-origin-height=&quot;1122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[참고]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1714547154009&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;Jackson 라이브러리 기본기능 정리 - json 직렬화와 역직렬화&quot; data-og-description=&quot;Jackson Annotation 예제 본 글은 다음 원문을 번역 및 수정한 글입니다. https://www.baeldung.com/jackson-annotations 전체 코드는 다음 레파지토리에 있습니다. 필요하다면 클론 받으셔서 test 패키지에 있는 Jack&quot; data-og-host=&quot;pjh3749.tistory.com&quot; data-og-source-url=&quot;https://pjh3749.tistory.com/281&quot; data-og-url=&quot;https://pjh3749.tistory.com/281&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bfwtIv/hyVVGLSpx5/wAsgCiu4kIs0g9nXfCkVuK/img.png?width=800&amp;amp;height=476&amp;amp;face=0_0_800_476,https://scrap.kakaocdn.net/dn/biGXYD/hyVVL0JGGN/jJ0lVKlKXeGZdGXKux4It0/img.png?width=800&amp;amp;height=476&amp;amp;face=0_0_800_476,https://scrap.kakaocdn.net/dn/LfOig/hyVZnjBThV/41HDUl6zan0SGdHRmWcltk/img.png?width=1752&amp;amp;height=1044&amp;amp;face=0_0_1752_1044&quot;&gt;&lt;a href=&quot;https://pjh3749.tistory.com/281&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pjh3749.tistory.com/281&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bfwtIv/hyVVGLSpx5/wAsgCiu4kIs0g9nXfCkVuK/img.png?width=800&amp;amp;height=476&amp;amp;face=0_0_800_476,https://scrap.kakaocdn.net/dn/biGXYD/hyVVL0JGGN/jJ0lVKlKXeGZdGXKux4It0/img.png?width=800&amp;amp;height=476&amp;amp;face=0_0_800_476,https://scrap.kakaocdn.net/dn/LfOig/hyVZnjBThV/41HDUl6zan0SGdHRmWcltk/img.png?width=1752&amp;amp;height=1044&amp;amp;face=0_0_1752_1044');&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;Jackson 라이브러리 기본기능 정리 - json 직렬화와 역직렬화&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jackson Annotation 예제 본 글은 다음 원문을 번역 및 수정한 글입니다. https://www.baeldung.com/jackson-annotations 전체 코드는 다음 레파지토리에 있습니다. 필요하다면 클론 받으셔서 test 패키지에 있는 Jack&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pjh3749.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1714547100621&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Jackson Release 2.12&quot; data-og-description=&quot;Main Portal page for the Jackson project. Contribute to FasterXML/jackson development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.12&quot; data-og-url=&quot;https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.12&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bx2TUc/hyVVEUQouF/zY25nUlSVlPASze8J3dcfk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.12&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bx2TUc/hyVVEUQouF/zY25nUlSVlPASze8J3dcfk/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;Jackson Release 2.12&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Main Portal page for the Jackson project. Contribute to FasterXML/jackson development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1714547076464&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;Jackson Deserializer 코드 분석해보기 | 카카오페이 기술 블로그&quot; data-og-description=&quot;Json 공백 문자 제거 및 파싱을 개발하면서 Jackson Deserializer 코드를 분석해봅니다.&quot; data-og-host=&quot;tech.kakaopay.com&quot; data-og-source-url=&quot;https://tech.kakaopay.com/post/martin-dev-honey-tip-1/#jackson%EC%97%90%EC%84%9C-%EC%88%AB%EC%9E%90%EC%9D%98-%ED%8C%8C%EC%8B%B1-%EC%9D%98%EB%AC%B8%EC%9D%98-%EC%8B%9C%EC%9E%91&quot; data-og-url=&quot;https://tech.kakaopay.com/post/martin-dev-honey-tip-1/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ca9LQI/hyVZmdWO5w/fgwoAl8KLMKzo2IowtYEX1/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319,https://scrap.kakaocdn.net/dn/bH0xbc/hyVZqgk3kE/ZoKkUr193vYSQgyVtDD6qk/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319&quot;&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/martin-dev-honey-tip-1/#jackson%EC%97%90%EC%84%9C-%EC%88%AB%EC%9E%90%EC%9D%98-%ED%8C%8C%EC%8B%B1-%EC%9D%98%EB%AC%B8%EC%9D%98-%EC%8B%9C%EC%9E%91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakaopay.com/post/martin-dev-honey-tip-1/#jackson%EC%97%90%EC%84%9C-%EC%88%AB%EC%9E%90%EC%9D%98-%ED%8C%8C%EC%8B%B1-%EC%9D%98%EB%AC%B8%EC%9D%98-%EC%8B%9C%EC%9E%91&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ca9LQI/hyVZmdWO5w/fgwoAl8KLMKzo2IowtYEX1/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319,https://scrap.kakaocdn.net/dn/bH0xbc/hyVZqgk3kE/ZoKkUr193vYSQgyVtDD6qk/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319');&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;Jackson Deserializer 코드 분석해보기 | 카카오페이 기술 블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Json 공백 문자 제거 및 파싱을 개발하면서 Jackson Deserializer 코드를 분석해봅니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.kakaopay.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>jackson</category>
      <category>jsondeserializer</category>
      <category>ObjectMapper</category>
      <author>tree code</author>
      <guid isPermaLink="true">https://treecode.tistory.com/124</guid>
      <comments>https://treecode.tistory.com/124#entry124comment</comments>
      <pubDate>Wed, 1 May 2024 16:06:06 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 좋아요 개수 동시성 문제 해결 (Optimistic Lock, Spring AOP)</title>
      <link>https://treecode.tistory.com/123</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요 기능의 동시성 문제를 낙관적 락(Optimistic Lock)과 Retry를 통해 해결하고 개선하는 과정을 정리해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 진행 중인 프로젝트에는 방명록(Post)에 좋아요를 누를 수 있고 Post 목록 조회 시 좋아요 개수를 같이 보여주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Post 목록 조회 시마다 방명록 좋아요 테이블을 join해서 좋아요 개수를 계산하는 것은 비효율적이라고 판단해서 반정규화로 Post에 별도의 likeCount 칼럼을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여러 사용자가 동시에 좋아요를 누를 경우 likeCount를 update 하면서 lost update 문제가 발생할 수 있다. 예를 들어 방명록의 좋아요가 5개에서 두 유저가 좋아요를 누르면 7개가 되어야 한다. 그런데 두 유저가 동시에 좋아요를 누르고 각각 조회 시점에 좋아요 개수가 5개로 보이면 둘 다 6으로 업데이트를 하게 된다. (업데이트를 어떻게 하냐에 따라 다르지만..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위해 낙관적 락(Optimistic Lock), 비관적 락(Pessimistic Lock)을 사용해 볼 수 있다.&lt;/p&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;비관적 락은 충돌이 많이 발생한다고 가정하고 DB에서 락을 거는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에서 락을 거는 방식은 레코드를 SELECT로 읽으면서 FOR SHARE, FOR UPDATE를 사용하면 직접 잠금을 걸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(select .. from .. where .. for share)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FOR SHARE는 읽기 잠금(공유 잠금, Shared lock)으로 다른 세션에서 해당 레코드를 읽는 것은 가능하고 변경할 수 없게 한다.&lt;/li&gt;
&lt;li&gt;FOR UPDATE는 쓰기 잠금(배타 잠금, Exclusive lock)으로 다른 트랜잭션에서 그 레코드를 변경하는 것뿐만 아니라 읽기(FOR SHARE 절을 사용하는 SELECT 쿼리)도 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 InnoDB 스토리지 엔진의 경우 MVCC(Multi Version Concurrency Control)를 통해 잠금 없는 읽기(Non Locking Consistent Read)를 지원하기 때문에 단순 SELECT문은 아무런 대기 없이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비관적 락은 높은 데이터 무결성을 보장하고 충돌이 많이 발생하는 경우 낙관적 락보다 효율적일 수 있지만 일반적으로 DB에 락을 걸기 때문에 동시 처리 성능이 떨어지고 데드락 위험이 있다. 좋아요 기능에 비관적 락을 거는 것은 성능면에서도 적절하지 않은 것 같고 현재 상황에서 충돌이 거의 발생하지 않는다고 판단해서 낙관적 락을 사용하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;낙관적 락은 애플리케이션에서 락을 거는 방식으로 JPA는 @Version 애노테이션으로 version 칼럼을 통해 버전을 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;version 자료형으로는 Integer(int), Long(long) , Short(short) , timestamp가 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long likeCount;

    @Version
    private Integer version;
}&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;방명록 좋아요 로직은 아래와 같이 작성했다. 방명록의 likeCount는 JPA의 변경 감지(dirty checking)으로 update 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Transactional
public void likePost(Long postId, Long userId) {
    final User user = findLoginUser(userId);
    final Post post = findPost(postId);
    if (postQueryRepository.existsPostLike(post, user)) {
        throw new ApiException(PostErrorCode.ALREADY_LIKE);
    }
    final PostLike postLike = post.likeFrom(user);
    postLikeRepository.save(postLike);
}&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;락 모드는 LockModeType로 설정할 수 있는데 version이 있는 Entity는 락 옵션을 적용하지 않아도 기본적으로 낙관적 락을 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;NONE&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조회한 엔티티를 수정할 때 다른 트랜잭션에 의해 변경(삭제)되지 않아야 한다. 엔티티를 수정할 때 version을 체크하면서 version을 증가시킨다. 만약 version 값이 다르면 예외가 발생한다.&lt;/li&gt;
&lt;li&gt;update에 성공한 thread(pool-4-thread-2)은 추가 쿼리가 발생하지 않았다. 반면 update에 실패한 thread(pool-4-thread-1)은 select 쿼리를 한번 더 날리는 것으로 봐서 updated row가 없으면 select 쿼리로 조회해보고 예외를 던지는 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2114&quot; data-origin-height=&quot;1298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crP5Cb/btsFWsuNs1n/gGKtC34bPvk2Yyn3qjXnvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crP5Cb/btsFWsuNs1n/gGKtC34bPvk2Yyn3qjXnvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crP5Cb/btsFWsuNs1n/gGKtC34bPvk2Yyn3qjXnvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrP5Cb%2FbtsFWsuNs1n%2FgGKtC34bPvk2Yyn3qjXnvk%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;627&quot; height=&quot;385&quot; data-origin-width=&quot;2114&quot; data-origin-height=&quot;1298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OPTIMISTIC&lt;/b&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;트랜잭션을 커밋할 때 update와 상관없이 version을 조회해서 현재 엔티티의 버전과 같은지 체크한다.&lt;/li&gt;
&lt;li&gt;만약 version이 바뀌어 update에 실패하면 NONE인 경우랑 마찬가지로 version 조회 대신 엔티티를 조회해 보고 예외를 던진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2572&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwVT32/btsFUXPQmEC/dA6XLtvVTzPQ5xHY966500/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwVT32/btsFUXPQmEC/dA6XLtvVTzPQ5xHY966500/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwVT32/btsFUXPQmEC/dA6XLtvVTzPQ5xHY966500/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwVT32%2FbtsFUXPQmEC%2FdA6XLtvVTzPQ5xHY966500%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;726&quot; height=&quot;135&quot; data-origin-width=&quot;2572&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OPTIMISTIC_FORCE_INCREMENT&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티를 수정하지 않아도 version을 증가시키고, 엔티티를 수정하면 version이 2번 나타날 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optimistic Lock 예외 상황을 테스트해보았다. CountDownLatch를 사용해서 첫 번째 Thread를 작업 중간에 wait 시키고, 두번째 Thread가 version을 update 시킨다. 그러면 첫번째 Thread는 바뀐 version으로 인해 update에 실패하고 예외가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;var waitingLatch = new CountDownLatch(1);
// 낙관적 락 version이 업데이트가 될때까지 대기
var waitWorker = CompletableFuture.runAsync(() -&amp;gt; waitPostLikeAndNoRetry(post, likeFailUser, waitingLatch));
// 낙관적 락 version을 업데이트하고 countDown
CompletableFuture.runAsync(() -&amp;gt; postService.likePost(post.getId(), likeSuccessUser.getId()))
    .thenRun(waitingLatch::countDown);
// 두번째 작업의 완료로 인해 version이 변경 되어 OptimisticLockingFailureException 예외 발생
assertThatThrownBy(waitWorker::join)
    .extracting(&quot;cause&quot;)
    .isInstanceOf(OptimisticLockingFailureException.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hibernate에서 발생한 StaleObjectStateException 예외를 스프링에서 OptimisticLockingFailureException으로 감싸서 던진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KT65l/btsFWLgAggj/nMMcZvWMwzSWHXCRIxHPxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KT65l/btsFWLgAggj/nMMcZvWMwzSWHXCRIxHPxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KT65l/btsFWLgAggj/nMMcZvWMwzSWHXCRIxHPxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKT65l%2FbtsFWLgAggj%2FnMMcZvWMwzSWHXCRIxHPxK%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;822&quot; height=&quot;257&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optimistic Lock 예외를 클라이언트한테 에러로 응답해 주면 클라이언트는 매우 불편할 것이다. 그래서 Optimistic 예외 발생 시 재시도를 위해 스프링 AOP로 Retry를 추가해 줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AOP란?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 로직은 크게 핵심 기능과 부가 기능으로 나눌 수 있는데 부가 기능은 핵심 기능을 보조하기 위해 제공되는 기능이다.&lt;/li&gt;
&lt;li&gt;여기서 핵심 기능은 좋아요 기능이고 부가 기능은 재시도 기능이 되겠다. 좋아요를 처리하는 로직 외에 예외를 try-catch 해서 재시도하는 로직이 들어가면 가독성도 떨어지고 다른 기능에서도 재시도가 필요한 경우 거의 동일한 로직을 사용한다.&lt;/li&gt;
&lt;li&gt;AOP(Aspect-Oriented Programming)은 관점 지향 프로그래밍으로, Aspect는 부가 기능과 해당 부과 기능을 어디에 적용할지 선택하는 기능을 하나의 모듈로 만든 것이며 핵심 기능에서 부가 기능을 분리하기 위해 나온 것이다.&lt;/li&gt;
&lt;li&gt;애플리케이션을 바라보는 관점을 하나하나의 기능에서 횡단 관심사(cross-cutting-concerns) 관점으로 보면 여러 모듈에서 공통적으로 사용되는 부가 기능들이 있을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tv1Mg/btsFWjq7srt/TxCegeKWLbx4xwEiTPxly0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tv1Mg/btsFWjq7srt/TxCegeKWLbx4xwEiTPxly0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tv1Mg/btsFWjq7srt/TxCegeKWLbx4xwEiTPxly0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftv1Mg%2FbtsFWjq7srt%2FTxCegeKWLbx4xwEiTPxly0%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;1610&quot; height=&quot;696&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 AOP는 프록시를 통해 AOP를 적용하는데 메서드에만 적용할 수 있는 등의 한계와 주의점들이 있지만 비교적 간단하고 유연하게 AOP를 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AOP 간단 용어&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;조인 포인트(Join point)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추상적인 개념으로 AOP를 적용할 수 있는 지점&lt;/li&gt;
&lt;li&gt;스프링 AOP는 프록시 방식의 한계로 조인 포인트가 메소드 지점으로 제한된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O6nLb/btsFUeq0egy/xR7HPF5XaEx8VUaYEKkzv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O6nLb/btsFUeq0egy/xR7HPF5XaEx8VUaYEKkzv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O6nLb/btsFUeq0egy/xR7HPF5XaEx8VUaYEKkzv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO6nLb%2FbtsFUeq0egy%2FxR7HPF5XaEx8VUaYEKkzv1%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;670&quot; height=&quot;190&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;포인트컷(Pointcut)&lt;/b&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;주로 AspectJ 표현식, 애노테이션으로 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어드바이스(Advice)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시가 호출하는 부가 기능, 즉 프록시 로직&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;애스펙트(Aspect)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Advice + Pointcut를 모듈화 한 것&lt;/li&gt;
&lt;li&gt;여러 Advice와 Pointcut이 함께 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어드바이저(Advisor)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 Advice와 Pointcut으로 구성&lt;/li&gt;
&lt;li&gt;스프링 AOP에서만 사용되는 용어&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 AOP 라이브러리를 추가하면 &lt;b&gt;AnnotationAwareAspectJAutoProxyCreator&lt;/b&gt; 라는 빈 후처리기(BeanPostProcessor)가 스프링 빈에 자동으로 등록된다. (BeanPostProcessor는 스프링이 객체를 생성하여 Bean 저장소에 등록하기 전에 조작하기 위해 사용한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AnnotationAwareAspectJAutoProxyCreator는 스프링 빈으로 등록된 Advisor들을 자동으로 찾아서 프록시가 필요한 곳에 자동으로 프록시를 적용, @AspectJ 와 관련된 AOP 기능도 자동으로 찾아서 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링이 bean 대상이 되는 객체를 생성해서 AnnotationAwareAspectJAutoProxyCreator에 전달하면 스프링 컨테이너에서 모든 Advisor를 조회해서 Pointcut을 통해 프록시 적용 대상인지를 판단하고 프록시 적용 대상이라면 프록시를 생성하고 반환해서 프록시를 스프링 빈으로 등록한다. @Aspect 애노테이션을 찾아서 Advisor로 변환해서 저장하는 기능도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BeanFactoryAspectJAdvisorBuilder&lt;/b&gt;는 내부 저장소에 @Aspect 정보를 기반으로 생성된 Advisor를 캐시 한다. 캐시에 어드바이저가 이미 만들어져 있는 경우 캐시에 저장된 어드바이저 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Advice 종류&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@Around&lt;/b&gt; : 메서드 호출 전후 수행, JoinPoint 실행 여부 선택, 반환 값 변환, 예외 변환 등이 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Before&lt;/b&gt;: JoinPoint 실행 이전에 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@AfterReturning&lt;/b&gt; : JoinPoint 정상 완료 후 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@AfterThrowing&lt;/b&gt; : 메서드가 예외를 던지는 경우 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@After&lt;/b&gt; : JoinPoint가 정상 또는 예외에 관계없이 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retry 로직은 다음과 같이 추가했다. Advisor는 기본적으로 순서를 보장하지 않아서 필요시 Order 애노테이션으로 순서를 적용해줘야 한다. (클래스 단위로 적용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;@Transactional은 @EnableTransactionManagement를 보면 Order가 가장 낮은 순위로 되어있다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1904&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvvRUs/btsFWl3yv1r/15Mqwa9VK48yiCXImRvUVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvvRUs/btsFWl3yv1r/15Mqwa9VK48yiCXImRvUVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvvRUs/btsFWl3yv1r/15Mqwa9VK48yiCXImRvUVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvvRUs%2FbtsFWl3yv1r%2F15Mqwa9VK48yiCXImRvUVK%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;1904&quot; height=&quot;842&quot; data-origin-width=&quot;1904&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Order를 지정해주지 않으면 Transactional이 Retry보다 먼저 실행되는 경우 변경 감지가 커밋 시점에 발생하기 때문에 변경 감지 update 쿼리가 나가기 전에 retry 로직이 성공으로 return 되고OptimisticLockingFaulureExceptuon을 catch 할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retry를 통해 정상적으로 좋아요가 처리되는 테스트를 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;//given
final ExecutorService executorService = Executors.newFixedThreadPool(postLikeCount);
final CountDownLatch completeLatch = new CountDownLatch(postLikeCount);
//when
users.forEach(user -&amp;gt; executorService.submit(() -&amp;gt; {
    postService.likePost(post.getId(), user.getId());
    completeLatch.countDown();
}));
completeLatch.await();
//then
assertThat(postRepository.findById(post.getId()))
    .isPresent().get()
    .satisfies(updatedPost -&amp;gt; assertThat(updatedPost.getLikeCount()).isEqualTo(postLikeCount));&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;추가로 낙관적 락을 사용할 때 외래키가 있으면 데드락(DeadLock)으로 인해 CannotAcquireLockException 예외가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;show engine innodb status;&lt;/code&gt; 로 데드락 체크를 해보면 아래와 같다.(최대한 생략시킨 결과)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;------------------------
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 1068786, ACTIVE 0 sec starting index read

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 36000 page no 4 n bits 72
index PRIMARY of table `photospot`.`post`
trx id 1068786 lock mode S locks rec but not gap

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 36000 page no 4 n bits 72
index PRIMARY of table `photospot`.`post` trx id 1068786
lock_mode X locks rec but not gap waiting

*** (2) TRANSACTION:
TRANSACTION 1068787, ACTIVE 0 sec starting index read

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 36000 page no 4 n bits 72
index PRIMARY of table `photospot`.`post`
trx id 1068787 lock mode S locks rec but not gap

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 36000 page no 4 n bits 72
index PRIMARY of table `photospot`.`post` trx id 1068787
lock_mode X locks rec but not gap waiting

*** WE ROLL BACK TRANSACTION (2)&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;(참고로 InnoDB는 인덱스의 레코드에 대해 락을 건다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 1, 2가 post 테이블의 primary index에 대한 S-Lock을 점유하고 있는 상태에서 X-lock을 대기하면서 결국 하나의 트랜잭션이 롤백되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostLike 테이블은 Post의 PK를 FK로 가지고 있다. 그래서 좋아요를 insert 할 때 FK 제약 조건을 체크하기 위해 S-Lock을 건다. (S-Lock은 여러 트랜잭션에서 걸 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 Post의 좋아요 개수를 Update 할 때 X-Lock을 획득하는데 트랜잭션 1,2가 각각 S-Lock을 점유하고 있어서 서로 X-Lock을 획득하지 못하고 대기한다. (X-Lock은 읽기 잠금도 제한하기 때문에 이미 읽기 잠금이 걸려있으면 대기한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostLike의 FK를 제거하니 CannotAcquireLockException 예외가 발생하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 update는 한 번에 하나의 트랜잭션만 레코드를 변경하기 때문에 Atomic 하게 동작한다. 그래서 이런 단순 count를 증가시키는 쿼리는 직접 likeCount = likeCount + 1로 update 하면 동시성 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;UPDATE post SET like_count = like_count + 1 where id = 1;&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;JPA의 변경 감지 기능에 너무 의존하다 보니 이런 부분을 놓친 것 같다. 그래도 낙관적 락 사용하고 테스트해보면서 배운 게 많아서 나름 만족스럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[참고]&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710850349164&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;트랜잭션 동시성 문제 해결- Optimistic Lock과 AOP활용&quot; data-og-description=&quot;0. 배경 서로 다른 API요청이 거의 동시에 들어올 경우 동시성 문제가 발생할 수 있는 기능이 있어서 이를 해결하기 위한 부분을 고민하게 되었습니다. 0-1. 기능 설명 해당 프로젝트는 사람들을 &quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@xogml951/API동시성-문제-해결-기록-Optimistic-Lock과-AOP활용&quot; data-og-url=&quot;https://velog.io/@xogml951/API동시성-문제-해결-기록-Optimistic-Lock과-AOP활용&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DrHGT/hyVAyN1TJj/8Cfq80Z2WJ3lom7TqftEX1/img.png?width=1332&amp;amp;height=598&amp;amp;face=0_0_1332_598,https://scrap.kakaocdn.net/dn/uT5xD/hyVAFsNM1X/6yBddI6sxbaSxWxLRcpTqk/img.png?width=1332&amp;amp;height=598&amp;amp;face=0_0_1332_598,https://scrap.kakaocdn.net/dn/mQp4V/hyVAIiJ5FP/itqAAAoXGZZdlaqL9DvL00/img.png?width=1332&amp;amp;height=598&amp;amp;face=0_0_1332_598&quot;&gt;&lt;a href=&quot;https://velog.io/@xogml951/API동시성-문제-해결-기록-Optimistic-Lock과-AOP활용&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@xogml951/API동시성-문제-해결-기록-Optimistic-Lock과-AOP활용&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DrHGT/hyVAyN1TJj/8Cfq80Z2WJ3lom7TqftEX1/img.png?width=1332&amp;amp;height=598&amp;amp;face=0_0_1332_598,https://scrap.kakaocdn.net/dn/uT5xD/hyVAFsNM1X/6yBddI6sxbaSxWxLRcpTqk/img.png?width=1332&amp;amp;height=598&amp;amp;face=0_0_1332_598,https://scrap.kakaocdn.net/dn/mQp4V/hyVAIiJ5FP/itqAAAoXGZZdlaqL9DvL00/img.png?width=1332&amp;amp;height=598&amp;amp;face=0_0_1332_598');&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;트랜잭션 동시성 문제 해결- Optimistic Lock과 AOP활용&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;0. 배경 서로 다른 API요청이 거의 동시에 들어올 경우 동시성 문제가 발생할 수 있는 기능이 있어서 이를 해결하기 위한 부분을 고민하게 되었습니다. 0-1. 기능 설명 해당 프로젝트는 사람들을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1710850320896&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;좋아요 개수 조회 최적화하기&quot; data-og-description=&quot;속닥속닥 프로젝트(https://github.com/woowacourse-teams/2022-sokdak&amp;hellip;&quot; data-og-host=&quot;tecoble.techcourse.co.kr&quot; data-og-source-url=&quot;https://tecoble.techcourse.co.kr/post/2022-10-10-like-count/&quot; data-og-url=&quot;https://tecoble.techcourse.co.kr/post/2022-10-10-like-count/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zccsw/hyVAMMevjT/yDU6EMMWJKWAhYiyane5M1/img.jpg?width=600&amp;amp;height=425&amp;amp;face=0_0_600_425,https://scrap.kakaocdn.net/dn/dA4eX2/hyVALT4hc6/p4RDtVqHXyVxwWPqTmp9T0/img.jpg?width=600&amp;amp;height=425&amp;amp;face=0_0_600_425,https://scrap.kakaocdn.net/dn/RwuDU/hyVDFdB5FG/zg0qdfKUJ3KsCYvY04qpQK/img.png?width=1088&amp;amp;height=666&amp;amp;face=0_0_1088_666&quot;&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2022-10-10-like-count/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tecoble.techcourse.co.kr/post/2022-10-10-like-count/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zccsw/hyVAMMevjT/yDU6EMMWJKWAhYiyane5M1/img.jpg?width=600&amp;amp;height=425&amp;amp;face=0_0_600_425,https://scrap.kakaocdn.net/dn/dA4eX2/hyVALT4hc6/p4RDtVqHXyVxwWPqTmp9T0/img.jpg?width=600&amp;amp;height=425&amp;amp;face=0_0_600_425,https://scrap.kakaocdn.net/dn/RwuDU/hyVDFdB5FG/zg0qdfKUJ3KsCYvY04qpQK/img.png?width=1088&amp;amp;height=666&amp;amp;face=0_0_1088_666');&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;속닥속닥 프로젝트(https://github.com/woowacourse-teams/2022-sokdak&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tecoble.techcourse.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1710850308095&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;Optimistic Lock in JPA&quot; data-og-description=&quot;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&quot; data-og-host=&quot;junhyunny.github.io&quot; data-og-source-url=&quot;https://junhyunny.github.io/spring-boot/jpa/junit/jpa-optimistic-lock/&quot; data-og-url=&quot;https://junhyunny.github.io/spring-boot/jpa/junit/jpa-optimistic-lock/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://junhyunny.github.io/spring-boot/jpa/junit/jpa-optimistic-lock/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://junhyunny.github.io/spring-boot/jpa/junit/jpa-optimistic-lock/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Optimistic Lock in JPA&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;junhyunny.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1710850297190&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;MySQL 낙관적 락과 데드락(dead lock) With JPA Hibernate&quot; data-og-description=&quot;프로젝트에서 모임 가입 기능을 구현하면서, 동시성 문제와 데드락까지 경험한 내용 그리고 어떻게 해결하였는지 고민과정과 해결방법을 정리하려고 합니다. 프로젝트 버전 SpringBoot 2.7.8 MySQL 8.&quot; data-og-host=&quot;0soo.tistory.com&quot; data-og-source-url=&quot;https://0soo.tistory.com/214&quot; data-og-url=&quot;https://0soo.tistory.com/214&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nSqXH/hyVAAdVVIv/TENluBok1MMFrXbSsozqnK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/lzXxH/hyVAOXDD5l/DQiI9KFxdQ1DrIZsGeU7W0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/kJtgQ/hyVDCnEwL2/7pakfsnwPBtnMqTugAMOnK/img.png?width=2052&amp;amp;height=1970&amp;amp;face=0_0_2052_1970&quot;&gt;&lt;a href=&quot;https://0soo.tistory.com/214&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://0soo.tistory.com/214&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nSqXH/hyVAAdVVIv/TENluBok1MMFrXbSsozqnK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/lzXxH/hyVAOXDD5l/DQiI9KFxdQ1DrIZsGeU7W0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/kJtgQ/hyVDCnEwL2/7pakfsnwPBtnMqTugAMOnK/img.png?width=2052&amp;amp;height=1970&amp;amp;face=0_0_2052_1970');&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;MySQL 낙관적 락과 데드락(dead lock) With JPA Hibernate&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트에서 모임 가입 기능을 구현하면서, 동시성 문제와 데드락까지 경험한 내용 그리고 어떻게 해결하였는지 고민과정과 해결방법을 정리하려고 합니다. 프로젝트 버전 SpringBoot 2.7.8 MySQL 8.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;0soo.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1710850235185&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;스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기   수강 &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://inf.run/rQEkx&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/z4UEp/hyVDwAYr7h/YOSWkVHaudEF4tuEgK6F00/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/cf15DW/hyVDGXTxGW/IoSTO4E2skR7Q1l54KFU4k/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/s3Vhm/hyVAJaSnXf/2Vg6fLkVnwsSSgV53bpwF1/img.png?width=1200&amp;amp;height=628&amp;amp;face=731_255_826_360&quot;&gt;&lt;a href=&quot;https://inf.run/rQEkx&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inf.run/rQEkx&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/z4UEp/hyVDwAYr7h/YOSWkVHaudEF4tuEgK6F00/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/cf15DW/hyVDGXTxGW/IoSTO4E2skR7Q1l54KFU4k/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/s3Vhm/hyVAJaSnXf/2Vg6fLkVnwsSSgV53bpwF1/img.png?width=1200&amp;amp;height=628&amp;amp;face=731_255_826_360');&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;스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기   수강&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1710850259757&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;자바 ORM 표준 JPA 프로그래밍 | 김영한 - 교보문고&quot; data-og-description=&quot;자바 ORM 표준 JPA 프로그래밍 | 자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA &quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000000935744&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000000935744&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ubRli/hyVDwgFHwF/oxxcoVpA85Yf2nJQx6VsTk/img.jpg?width=458&amp;amp;height=609&amp;amp;face=0_0_458_609,https://scrap.kakaocdn.net/dn/b6ZzIh/hyVAyHi4o4/XgaOI2DiMqrjMTIzkfYef0/img.jpg?width=458&amp;amp;height=609&amp;amp;face=0_0_458_609,https://scrap.kakaocdn.net/dn/bczN0Y/hyVDv9TZIG/Yvk7hIcQUKoobKYPCQnck0/img.png?width=335&amp;amp;height=335&amp;amp;face=0_0_335_335&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000000935744&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000000935744&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ubRli/hyVDwgFHwF/oxxcoVpA85Yf2nJQx6VsTk/img.jpg?width=458&amp;amp;height=609&amp;amp;face=0_0_458_609,https://scrap.kakaocdn.net/dn/b6ZzIh/hyVAyHi4o4/XgaOI2DiMqrjMTIzkfYef0/img.jpg?width=458&amp;amp;height=609&amp;amp;face=0_0_458_609,https://scrap.kakaocdn.net/dn/bczN0Y/hyVDv9TZIG/Yvk7hIcQUKoobKYPCQnck0/img.png?width=335&amp;amp;height=335&amp;amp;face=0_0_335_335');&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;자바 ORM 표준 JPA 프로그래밍 | 김영한 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바 ORM 표준 JPA 프로그래밍 | 자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA&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;&lt;b&gt;RealMySQL8.0&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>AOP</category>
      <category>MySQL</category>
      <category>optimistic</category>
      <category>Spring</category>
      <category>낙관적 락</category>
      <category>동시성</category>
      <author>tree code</author>
      <guid isPermaLink="true">https://treecode.tistory.com/123</guid>
      <comments>https://treecode.tistory.com/123#entry123comment</comments>
      <pubDate>Tue, 19 Mar 2024 21:09:23 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] AWS SSM Parameter Store 환경 변수 관리 (SpringBoot 3.2)</title>
      <link>https://treecode.tistory.com/122</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 변수를 서버내에 하드 코딩하지 않고 외부에서 관리하면서 안전하게 사용할 수 있는 방법을 찾다가 AWS에서 key, value 기반으로 관리할 수 있는 SSM Parameter Store를 찾았다. 현재 프로젝트 규모나 관리할 환경 변수가 많지는 않아서 적절하다고 생각되었다. 비용도 standard는 무료로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;1056&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KugOr/btsD4TvLtfV/oAtU1ScU4rQmvwg4TzqVaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KugOr/btsD4TvLtfV/oAtU1ScU4rQmvwg4TzqVaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KugOr/btsD4TvLtfV/oAtU1ScU4rQmvwg4TzqVaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKugOr%2FbtsD4TvLtfV%2FoAtU1ScU4rQmvwg4TzqVaK%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;1886&quot; height=&quot;1056&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;1056&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 AWS 웹 콘솔 Parameter Store에서 key, value를 추가하면 되는데 민감한 데이터의 경우 KMS로 암호화해서 보관할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층 구조로 프로필, 용도에 따라 prefix(ex. &quot;/config/photosot_dev&quot;)를 추가하고 뒤에 환경 변수를 추가해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIk4ZR/btsD8zwyGW5/PxCAzx2iwNP7ch9Xw1bih1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIk4ZR/btsD8zwyGW5/PxCAzx2iwNP7ch9Xw1bih1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIk4ZR/btsD8zwyGW5/PxCAzx2iwNP7ch9Xw1bih1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIk4ZR%2FbtsD8zwyGW5%2FPxCAzx2iwNP7ch9Xw1bih1%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;581&quot; height=&quot;580&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Spring Boot 설정을 보면 먼저 지속적으로 버전 관리가 되고 있는 awspring.io를 사용했고 spring boot 3 버전대에서는 설정이 바뀐 부분이 좀 있는데 공식 문서에 잘 나와있고 블로그에도 정리해주신 분이 있어서 편하게 설정할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1706586251032&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;Spring Cloud AWS&quot; data-og-description=&quot;Secrets Manager helps to protect secrets needed to access your applications, services, and IT resources. The service enables you to easily rotate, manage, and retrieve database credentials, API keys, and other secrets throughout their lifecycle. Spring Clo&quot; data-og-host=&quot;docs.awspring.io&quot; data-og-source-url=&quot;https://docs.awspring.io/spring-cloud-aws/docs/3.1.0/reference/html/index.html#getting-started&quot; data-og-url=&quot;https://docs.awspring.io/spring-cloud-aws/docs/3.1.0/reference/html/index.html#getting-started&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.awspring.io/spring-cloud-aws/docs/3.1.0/reference/html/index.html#getting-started&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.awspring.io/spring-cloud-aws/docs/3.1.0/reference/html/index.html#getting-started&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Cloud AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Secrets Manager helps to protect secrets needed to access your applications, services, and IT resources. The service enables you to easily rotate, manage, and retrieve database credentials, API keys, and other secrets throughout their lifecycle. Spring Clo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.awspring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle에서 Spring Cloud AWS BOM(Bill of Materials)을 사용해서 의존성 관리를 간편하게 할 수 있다. platform() dependencies를 추가하고 하위 의존성은 버전 없이 spring-cloud-aws-starter-xx 만 입력해주면 된다. (-s3, -parameter-store 등)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Of7o8/btsEaZBt1dE/zqXjDLzbSzZZs1bGZ3rhk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Of7o8/btsEaZBt1dE/zqXjDLzbSzZZs1bGZ3rhk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Of7o8/btsEaZBt1dE/zqXjDLzbSzZZs1bGZ3rhk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOf7o8%2FbtsEaZBt1dE%2FzqXjDLzbSzZZs1bGZ3rhk1%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;1992&quot; height=&quot;1020&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;1020&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ysti0/btsD6w7Mx92/zob02KizGfCXewSP3VubV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ysti0/btsD6w7Mx92/zob02KizGfCXewSP3VubV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ysti0/btsD6w7Mx92/zob02KizGfCXewSP3VubV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fysti0%2FbtsD6w7Mx92%2Fzob02KizGfCXewSP3VubV1%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;1472&quot; height=&quot;126&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application.properties, application.yml 설정 파일에 Parameter Store의 key prefix를 아래와 같이 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;spring.config.import에&lt;span&gt;&amp;nbsp;맞춰서 추가해준다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NdjPR/btsD7npxWXl/iitgZhL6tmYG0fBKuOcDRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NdjPR/btsD7npxWXl/iitgZhL6tmYG0fBKuOcDRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NdjPR/btsD7npxWXl/iitgZhL6tmYG0fBKuOcDRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNdjPR%2FbtsD7npxWXl%2FiitgZhL6tmYG0fBKuOcDRk%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;1976&quot; height=&quot;790&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prefix를 추가해서 자동으로 바인딩 시킬 수도 있는데 오히려 헷갈리는 것 같아서 prefix 부분은 직접 명시해줬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;1178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/de6Q8w/btsD4ubuQlt/XyaXzMHVAaHsKK9ZhFuYTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/de6Q8w/btsD4ubuQlt/XyaXzMHVAaHsKK9ZhFuYTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/de6Q8w/btsD4ubuQlt/XyaXzMHVAaHsKK9ZhFuYTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fde6Q8w%2FbtsD4ubuQlt%2FXyaXzMHVAaHsKK9ZhFuYTK%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;1992&quot; height=&quot;1178&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;1178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPBlbB/btsEa4ivgZG/YUk6ytkQWQlDTwCiI5bA01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPBlbB/btsEa4ivgZG/YUk6ytkQWQlDTwCiI5bA01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPBlbB/btsEa4ivgZG/YUk6ytkQWQlDTwCiI5bA01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPBlbB%2FbtsEa4ivgZG%2FYUk6ytkQWQlDTwCiI5bA01%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;916&quot; height=&quot;534&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 EC2에서 Parameter Store에 값을 가져올 수 있도록 EC2 인스턴스에 IAM Role로 AmazonSSMReadOnlyAccess 를 추가해준다. (Systems Manager의 기능이므로 SSM Access를 추가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 터미널에서 aws configure list를 입력하면 IAM-Role로 발급 받은 access, secret key를 볼 수 있다. 스프링 부트를 실행하면 해당 key를 이용해서 Parameter Store에서 값을 가져와서 환경 변수를 주입해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzdQzN/btsEbCTDYMZ/KZ0qaW2yLOKEKKb4hc9PN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzdQzN/btsEbCTDYMZ/KZ0qaW2yLOKEKKb4hc9PN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzdQzN/btsEbCTDYMZ/KZ0qaW2yLOKEKKb4hc9PN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzdQzN%2FbtsEbCTDYMZ%2FKZ0qaW2yLOKEKKb4hc9PN0%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;958&quot; height=&quot;218&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우 로컬 default 프로필에서 S3 업로드를 할때 IAM User를 생성해서 access, secret key를 환경 변수로 사용하고 있었는데 default 프로필이다보니 dev 프로필에서도 적용이 되어서 문제가 생겼다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2732&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F7GZ4/btsEdEXNCel/lO3FAMaijIwkKt5CWYcKv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F7GZ4/btsEdEXNCel/lO3FAMaijIwkKt5CWYcKv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F7GZ4/btsEdEXNCel/lO3FAMaijIwkKt5CWYcKv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF7GZ4%2FbtsEdEXNCel%2FlO3FAMaijIwkKt5CWYcKv1%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;2732&quot; height=&quot;422&quot; data-origin-width=&quot;2732&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 dev 프로필에서는 instance-profile을 true로 설정하고 access, secret key를 빈 값으로 덮어씌웠다. instance-profile은 EC2 인스턴스의 key를 사용하겠다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9CPML/btsEaXcU1RH/GhcrMWY0WGbIHv1hrcCkok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9CPML/btsEaXcU1RH/GhcrMWY0WGbIHv1hrcCkok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9CPML/btsEaXcU1RH/GhcrMWY0WGbIHv1hrcCkok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9CPML%2FbtsEaXcU1RH%2FGhcrMWY0WGbIHv1hrcCkok%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;814&quot; height=&quot;300&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CredentialsProperties를 보면 access,secret key가 nullable하고 instanceProfile 기본값은 false로 되어있다. access, secret key가 있으면 해당 키를 사용하고 없는 경우 ec2 인스턴스의 키를 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3206&quot; data-origin-height=&quot;1314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYw0iC/btsD4uv7xhj/4nsvnj6KkWohBKxTQXSTsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYw0iC/btsD4uv7xhj/4nsvnj6KkWohBKxTQXSTsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYw0iC/btsD4uv7xhj/4nsvnj6KkWohBKxTQXSTsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYw0iC%2FbtsD4uv7xhj%2F4nsvnj6KkWohBKxTQXSTsK%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;3206&quot; height=&quot;1314&quot; data-origin-width=&quot;3206&quot; data-origin-height=&quot;1314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[참고]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1706591415904&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;[Spring] - Spring Boot 2.4 이상 환경에서 AWS Parameter Store 적용하기&quot; data-og-description=&quot;회사 소스코드 application.yml 설정파일을 보면 데이터베이스 접속정보, api키값 등 보안에 민감한 데이터가 있어서 이러한 데이터를 외부에서 직접 주입 할 수 있도록 구성하려고 이것 저것 알아보&quot; data-og-host=&quot;kim-jong-hyun.tistory.com&quot; data-og-source-url=&quot;https://kim-jong-hyun.tistory.com/m/120&quot; data-og-url=&quot;https://kim-jong-hyun.tistory.com/120&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hjKL6/hyVb2BvLWg/mfgKwc1gq9aPiA08SpSmd0/img.jpg?width=485&amp;amp;height=325&amp;amp;face=0_0_485_325,https://scrap.kakaocdn.net/dn/be1pbW/hyVca0Cvmq/gpckXczFkSBCbFMb1MlvS1/img.jpg?width=485&amp;amp;height=325&amp;amp;face=0_0_485_325,https://scrap.kakaocdn.net/dn/ElKDm/hyVccxlwph/kAnInvCYeogxOhQ5yGeZgk/img.jpg?width=1492&amp;amp;height=582&amp;amp;face=0_0_1492_582&quot;&gt;&lt;a href=&quot;https://kim-jong-hyun.tistory.com/m/120&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kim-jong-hyun.tistory.com/m/120&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hjKL6/hyVb2BvLWg/mfgKwc1gq9aPiA08SpSmd0/img.jpg?width=485&amp;amp;height=325&amp;amp;face=0_0_485_325,https://scrap.kakaocdn.net/dn/be1pbW/hyVca0Cvmq/gpckXczFkSBCbFMb1MlvS1/img.jpg?width=485&amp;amp;height=325&amp;amp;face=0_0_485_325,https://scrap.kakaocdn.net/dn/ElKDm/hyVccxlwph/kAnInvCYeogxOhQ5yGeZgk/img.jpg?width=1492&amp;amp;height=582&amp;amp;face=0_0_1492_582');&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;[Spring] - Spring Boot 2.4 이상 환경에서 AWS Parameter Store 적용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;회사 소스코드 application.yml 설정파일을 보면 데이터베이스 접속정보, api키값 등 보안에 민감한 데이터가 있어서 이러한 데이터를 외부에서 직접 주입 할 수 있도록 구성하려고 이것 저것 알아보&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kim-jong-hyun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1706591435269&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;spring boot 3.2에서 aws parameter store 적용하기&quot; data-og-description=&quot;최근에 사이드 프로젝트를 하나 진행하고 있다.배포도 생각중이라 DB 정보를 어떻게 관리해야 하나 고민을 하던 와중에 우연치 않게 aws parameter store에 대해서 듣게 되었다.가격도 공짜에 사이드 &quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@as9587/spring-boot-3.2%EC%97%90%EC%84%9C-aws-parameter-store-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0&quot; data-og-url=&quot;https://velog.io/@as9587/spring-boot-3.2에서-aws-parameter-store-적용하기&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xvqet/hyVcfU8Xzf/1aLBKKE5ilEY2hyabbcrRk/img.png?width=577&amp;amp;height=386&amp;amp;face=0_0_577_386,https://scrap.kakaocdn.net/dn/bQugcA/hyVcafdRj3/Rqe2Bqk4llNzSsqP8eklk0/img.png?width=577&amp;amp;height=386&amp;amp;face=0_0_577_386,https://scrap.kakaocdn.net/dn/cGtW2W/hyVb0p8fLN/4faRUMT2QkWgi6UMkH7ObK/img.png?width=629&amp;amp;height=797&amp;amp;face=0_0_629_797&quot;&gt;&lt;a href=&quot;https://velog.io/@as9587/spring-boot-3.2%EC%97%90%EC%84%9C-aws-parameter-store-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@as9587/spring-boot-3.2%EC%97%90%EC%84%9C-aws-parameter-store-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xvqet/hyVcfU8Xzf/1aLBKKE5ilEY2hyabbcrRk/img.png?width=577&amp;amp;height=386&amp;amp;face=0_0_577_386,https://scrap.kakaocdn.net/dn/bQugcA/hyVcafdRj3/Rqe2Bqk4llNzSsqP8eklk0/img.png?width=577&amp;amp;height=386&amp;amp;face=0_0_577_386,https://scrap.kakaocdn.net/dn/cGtW2W/hyVb0p8fLN/4faRUMT2QkWgi6UMkH7ObK/img.png?width=629&amp;amp;height=797&amp;amp;face=0_0_629_797');&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;spring boot 3.2에서 aws parameter store 적용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최근에 사이드 프로젝트를 하나 진행하고 있다.배포도 생각중이라 DB 정보를 어떻게 관리해야 하나 고민을 하던 와중에 우연치 않게 aws parameter store에 대해서 듣게 되었다.가격도 공짜에 사이드&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1706591482124&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;SpringBoot &amp;amp; AWS S3 연동하기&quot; data-og-description=&quot;안녕하세요? 이번 시간엔 SpringBoot &amp;amp; AWS S3 연동하기 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와&quot; data-og-host=&quot;jojoldu.tistory.com&quot; data-og-source-url=&quot;https://jojoldu.tistory.com/300&quot; data-og-url=&quot;https://jojoldu.tistory.com/300&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yIt3l/hyVb4zizr6/bURwfdIAfDgMXL0lYxc4EK/img.png?width=800&amp;amp;height=232&amp;amp;face=0_0_800_232,https://scrap.kakaocdn.net/dn/GLp7E/hyVcdbXFsE/x3nYpQoTCigxgHinRbnWtK/img.png?width=800&amp;amp;height=232&amp;amp;face=0_0_800_232,https://scrap.kakaocdn.net/dn/QqcHh/hyVb0p8gcN/6noRKgizoyRSIiBUn57BkK/img.png?width=3192&amp;amp;height=1030&amp;amp;face=0_0_3192_1030&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/300&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jojoldu.tistory.com/300&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yIt3l/hyVb4zizr6/bURwfdIAfDgMXL0lYxc4EK/img.png?width=800&amp;amp;height=232&amp;amp;face=0_0_800_232,https://scrap.kakaocdn.net/dn/GLp7E/hyVcdbXFsE/x3nYpQoTCigxgHinRbnWtK/img.png?width=800&amp;amp;height=232&amp;amp;face=0_0_800_232,https://scrap.kakaocdn.net/dn/QqcHh/hyVb0p8gcN/6noRKgizoyRSIiBUn57BkK/img.png?width=3192&amp;amp;height=1030&amp;amp;face=0_0_3192_1030');&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;SpringBoot &amp;amp; AWS S3 연동하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요? 이번 시간엔 SpringBoot &amp;amp; AWS S3 연동하기 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jojoldu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1706591501964&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;AWS Instance Profile doesn't work with Spring Cloud AWS&quot; data-og-description=&quot;I have a small Spring Boot app, using Spring Cloud AWS (1.0.0.RELEASE) to access SQS queue. It is beeing deployed on an EC2 instance with Instance Profile set. It appears that AWS side of things is&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/30571728/aws-instance-profile-doesnt-work-with-spring-cloud-aws&quot; data-og-url=&quot;https://stackoverflow.com/questions/30571728/aws-instance-profile-doesnt-work-with-spring-cloud-aws&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJiHu0/hyVb0RcEUm/kCl01gZ6qDbJfu7tGEZmp0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/30571728/aws-instance-profile-doesnt-work-with-spring-cloud-aws&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/30571728/aws-instance-profile-doesnt-work-with-spring-cloud-aws&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJiHu0/hyVb0RcEUm/kCl01gZ6qDbJfu7tGEZmp0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;AWS Instance Profile doesn't work with Spring Cloud AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I have a small Spring Boot app, using Spring Cloud AWS (1.0.0.RELEASE) to access SQS queue. It is beeing deployed on an EC2 instance with Instance Profile set. It appears that AWS side of things is&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Parameter store</category>
      <category>springboot3.2</category>
      <author>tree code</author>
      <guid isPermaLink="true">https://treecode.tistory.com/122</guid>
      <comments>https://treecode.tistory.com/122#entry122comment</comments>
      <pubDate>Tue, 30 Jan 2024 14:12:43 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot 3.2] RestClientTest에서 여러개의 RestClient, RestTemplate 사용시 주의점 (MockRestServiceServer)</title>
      <link>https://treecode.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;외부 API 테스트를 위해 RestClientTest 사용시 일반적으로 MockRestServiceServer를 @Autowired로 주입 받아서 사용하는데 RestTemplate 또는 RestClient가 2개 이상인 경우 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Itkzc/btsDxjtYEMo/5xqMgk0iWBjcqO618lkPm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Itkzc/btsDxjtYEMo/5xqMgk0iWBjcqO618lkPm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Itkzc/btsDxjtYEMo/5xqMgk0iWBjcqO618lkPm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FItkzc%2FbtsDxjtYEMo%2F5xqMgk0iWBjcqO618lkPm0%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;1860&quot; height=&quot;138&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니 관련 이슈가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;If you have two&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;RestTemplate&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;beans or two&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;RestClient&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;beans in your application, you can't use an auto-wired&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;MockRestServiceServer&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;in your test&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1705378757462&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;MockRestServiceServerAutoConfiguration  does not support RestTemplate and RestClient together &amp;middot; Issue #38820 &amp;middot; spring-projects&quot; data-og-description=&quot;It should be possible to have together RestTemplate and RestClient in application and test them independently. Unfortunately, currently if RestTemplate is used, RestClient cannot be tested: org.spr...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/spring-projects/spring-boot/issues/38820&quot; data-og-url=&quot;https://github.com/spring-projects/spring-boot/issues/38820&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Bopys/hyU5L0Ghgr/UekXVac8VJdeoYbisyoeb0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/issues/38820&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/spring-projects/spring-boot/issues/38820&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Bopys/hyU5L0Ghgr/UekXVac8VJdeoYbisyoeb0/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;MockRestServiceServerAutoConfiguration does not support RestTemplate and RestClient together &amp;middot; Issue #38820 &amp;middot; spring-projects&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;It should be possible to have together RestTemplate and RestClient in application and test them independently. Unfortunately, currently if RestTemplate is used, RestClient cannot be tested: org.spr...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1f2328;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;고칠 예정이라고 하는데 일단 질문자의 샘플에 대한 예시는 이런식으로 사용하라고 나와있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705378820402&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Test
    void test(@Autowired RestClient restClient, @Autowired MockServerRestClientCustomizer customizer) {

        // given
        customizer.getServer().expect(requestTo(&quot;/&quot;)).andRespond(withSuccess());

        // when then
        restClient.get()
                .uri(&quot;/&quot;)
                .retrieve();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6f8fa; color: #1f2328; text-align: start;&quot;&gt;@RestClientTest의 메타 애노테이션중 @AutoConfigureMockRestServiceServer를 보면 여러 Clients를 쓰는 경우 MockServerRestXXXCustomizer를 주입 받아서 getServer()를 사용하거나 MockRestServiceServer에 직접 bind 하라고 나와있다.&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;1530&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXa5hN/btsDAb9Nm79/2RpR3UlbjrEslWJmJ2V500/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXa5hN/btsDAb9Nm79/2RpR3UlbjrEslWJmJ2V500/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXa5hN/btsDAb9Nm79/2RpR3UlbjrEslWJmJ2V500/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXa5hN%2FbtsDAb9Nm79%2F2RpR3UlbjrEslWJmJ2V500%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;1530&quot; height=&quot;806&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6f8fa; color: #1f2328; text-align: start;&quot;&gt;나는 HttpInterface를 쓰고 있어서 아래와 같이 직접 bind()해주는 방식으로 수정했다. MockRestServiceServer에 먼저 bindTo()를 해준 뒤에 HttpClient 프록시를 Bean으로 등록하는 부분에 restClientBuilder를 넣어줘야한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6f8fa; color: #1f2328; text-align: start;&quot;&gt;Build the MockRestServiceServer and set up the underlying RestTemplate with a ClientHttpRequestFactory that creates mock requests.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IF2Na/btsDuSX9A7Q/htv4rBzfvue1Q6pB52zoN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IF2Na/btsDuSX9A7Q/htv4rBzfvue1Q6pB52zoN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IF2Na/btsDuSX9A7Q/htv4rBzfvue1Q6pB52zoN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIF2Na%2FbtsDuSX9A7Q%2Fhtv4rBzfvue1Q6pB52zoN0%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;1808&quot; height=&quot;902&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Spring</category>
      <category>MockRestServiceServer</category>
      <category>RestClient</category>
      <category>RestClientTest</category>
      <author>tree code</author>
      <guid isPermaLink="true">https://treecode.tistory.com/121</guid>
      <comments>https://treecode.tistory.com/121#entry121comment</comments>
      <pubDate>Tue, 16 Jan 2024 13:47:27 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] 단일값 IN vs equals 차이점</title>
      <link>https://treecode.tistory.com/120</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt; 단일 비교를 하는 경우 IN()절로 작성한 SQL을 재사용해도 좋을지 궁금해서 찾아봤는데 괜찮아보이는 글이 보였다. 대체로 차이가 없다는 의견이지만 공식 문서는 없는 것 같아서 간단하게 테스트를 해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1705377474436&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;Performance differences between equal (=) and IN with one literal value&quot; data-og-description=&quot;How does SQL engines differ when we use equal sign and IN operator have same value? Does execution time changes? 1st one using equality check operator WHERE column_value = 'All' 2nd one using IN&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/37828398/performance-differences-between-equal-and-in-with-one-literal-value&quot; data-og-url=&quot;https://stackoverflow.com/questions/37828398/performance-differences-between-equal-and-in-with-one-literal-value&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cd02YY/hyU5Q1YEDQ/RyNgc6mSYkOGkr5k8EuVX1/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/37828398/performance-differences-between-equal-and-in-with-one-literal-value&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/37828398/performance-differences-between-equal-and-in-with-one-literal-value&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cd02YY/hyU5Q1YEDQ/RyNgc6mSYkOGkr5k8EuVX1/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;Performance differences between equal (=) and IN with one literal value&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How does SQL engines differ when we use equal sign and IN operator have same value? Does execution time changes? 1st one using equality check operator WHERE column_value = 'All' 2nd one using IN&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spot_id IN(1), spot_id = 1 모두 동등 비교를 하고 실행 계획 type도 ref로 나왔다. ref는 동등 조건으로 검색할 때의 접근 방법을 나타낸다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4waz3/btsDsYD3CD3/BSoxBDhWOKj8WBkD2xJM70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4waz3/btsDsYD3CD3/BSoxBDhWOKj8WBkD2xJM70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4waz3/btsDsYD3CD3/BSoxBDhWOKj8WBkD2xJM70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4waz3%2FbtsDsYD3CD3%2FBSoxBDhWOKj8WBkD2xJM70%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;1312&quot; height=&quot;64&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IN 절에 여러 id가 들어가는 경우에는 id = 1 or id = 2.. 형태로 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JdOaH/btsDrJNXQoj/TnLYvkZ8tZ89WzSEWmhmR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JdOaH/btsDrJNXQoj/TnLYvkZ8tZ89WzSEWmhmR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JdOaH/btsDrJNXQoj/TnLYvkZ8tZ89WzSEWmhmR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJdOaH%2FbtsDrJNXQoj%2FTnLYvkZ8tZ89WzSEWmhmR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1862&quot; height=&quot;70&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>꿀팁</category>
      <category>equals</category>
      <category>in</category>
      <category>MySQL</category>
      <author>tree code</author>
      <guid isPermaLink="true">https://treecode.tistory.com/120</guid>
      <comments>https://treecode.tistory.com/120#entry120comment</comments>
      <pubDate>Tue, 16 Jan 2024 13:10:53 +0900</pubDate>
    </item>
    <item>
      <title>[Real MySQL] 실행 계획</title>
      <link>https://treecode.tistory.com/119</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 안전하게 저장, 관리하고 효율적으로 조회하려면 옵티마이저가 어떻게 동작할지, 쿼리의 실행 계획에 대해 알아야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획은 쿼리를 실행할때 EXPLAIN 키워드를 붙여서 실행하면 된다. 실행 계획도 그때그때 찾아보는 것이 좋을 것 같은데 자주 보이고 알면 좋은 부분만 정리해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획 결과는 기본적으로 테이블(json, tree 같은 포맷도 있다)로 여러 정보를 알려준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3172&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mIN93/btsDqNu8E2D/yt9Z3gMPGo4k4S1OEUKRSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mIN93/btsDqNu8E2D/yt9Z3gMPGo4k4S1OEUKRSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mIN93/btsDqNu8E2D/yt9Z3gMPGo4k4S1OEUKRSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmIN93%2FbtsDqNu8E2D%2Fyt9Z3gMPGo4k4S1OEUKRSk%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;3172&quot; height=&quot;248&quot; data-origin-width=&quot;3172&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. id&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 단위 select 쿼리(select 키워드 단위로 구분한 것)의 식별자로 테이블을 조인하면 조인되는 테이블의 개수만큼 실행 계획 레코드가 생기지만 같은 id 값을 가진다. (id 값이 같으면 조인된 경우)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- id가 테이블의 접근 순서를 의미하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. select_type&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 단위 select 쿼리가&lt;b&gt; 어떤 타입의 쿼리인지&lt;/b&gt; 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;서브쿼리를 사용하지 않는 단순 쿼리를 나타내는 SIMPLE, 서브 쿼리를 가지는 select 쿼리의 가장 바깥쪽에 있는 쿼리를 나타내는 PRIMARY 등의 키워드가 있다.&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;b&gt;DERIVED&lt;/b&gt;는 단위 SELECT 쿼리의 실행 결과로 메모리나 디스크에 임시 테이블(파생 테이블)을 생성하는 것을 의미하는데 주로 from절 서브쿼리를 사용하는 경우를 나타낸다. (성능상 주의깊게 볼 부분)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. type&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- &lt;/b&gt;MySQL 서버가 각 &lt;b&gt;테이블을 어떤 방식으로 읽을지&lt;/b&gt;를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 주로 쓰이는 타입으로는 &lt;b&gt;all &amp;lt; index &amp;lt; range &amp;lt; index_subquery &amp;lt; unique_subquery &amp;lt; ref &amp;lt; eq_ref &amp;lt; const&lt;/b&gt; 의 순서대로 빠른 순서를 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;const(Unique index scan)&lt;/b&gt;는 where절로 PK 또는 유니크 키로 1건만 조회하는 쿼리로 많이 사용하는 방식이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;eq_ref&lt;/b&gt;는 join에서 처음 읽은 테이블의 컬럼값을, 그 다음 읽어야 할 테이블의 PK나 유니크 키 컬럼의 검색 조건에 사용할 때 나타낸다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;ref&lt;/b&gt;는 인덱스 종류와 관계없이 동등 조건으로 검색할 때를 나타낸다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;index_subquery, unique_subquery&lt;/b&gt;는 where 조건절에서 사용되는 in(subquery) 형태의 쿼리를 나타낸다. MySQL8.0 버전은 in subquery 형태의 세미 조인의 최적화된 기능이 많이 도입 되어서 실제 테스트 해보니 ref로 실행 된다거나 하는 경우가 많았다. (책에서도 최적화 옵션을 비활성화 하고 테스트를 하고 있다)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;range&lt;/b&gt;는 인덱스 레인지 스캔 형태의 접근 방법으로 인덱스를 범위로 검색하는 경우 (&amp;lt;, &amp;gt;, IS NULL, BETWEEN, IN, LIKE)등의 연산자로 검색시 사용된다. 속도는 생각보다 낮지만 range로 조회만 하더라도 어느정도 최적의 성능이 보장된다고 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;index&lt;/b&gt;는 인덱스를 처음부터 끝까지 읽는 인덱스 풀 스캔방식이다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;all&lt;/b&gt;은 풀 테이블 스캔 방식으로 InnoDB는 풀 스캔시 대량의 디스크 I/O를 유발하는 작업을 위해 한번에 많은 페이지를 읽어 들이는 기능(Read AHead)을 제공한다. 대용량 레코드 처리시 잘못 튜닝된 인덱스 쿼리보다는 풀 테이블 스캔이 더 나은 경우도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. keys&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실행 계획에서 사용하는 인덱스를 의미하며 쿼리를 튜닝할 때는 key 컬럼에 의도한 인덱스가 표시되는지 확인하는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. rows&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 얼마나 많은 레코드를 읽고 체크해야 하는지 예측한 레코드 건수를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스토리지 엔진이 가지고 있는 통계 정보를 참조해서 MySQL 옵티마이저가 산출해 낸 예상값이라서 정확하지는 않다.&lt;/p&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;&lt;b&gt;6. filtered&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7.&amp;nbsp;extra&lt;/b&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;b&gt;Using where&lt;/b&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;MySQL 엔진에서 스토리지 엔진으로부터 받은 데이터를 별도의 가공을 해서 필터링 작업을 처리한 경우에 나타난다. (검색 조건에 따라 스토리지 엔진에서 필터링을 할 수도, MySQL 엔진에서 필터링을 할 수도 있다.)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Using index(커버링 인덱스)&lt;/b&gt;는 인덱스만 읽어서 쿼리를 처리할 수 있는 경우 나타난다. 인덱스를 이용해 처리하는 쿼리에서 가장 큰 부하는 인덱스 검색에서 일치하는 키 값들의 레코드를 읽기 위해 데이터 파일을 검색하는 작업이다. InnoDB의 세컨더리 인덱스는 데이터 레코드를 찾아가기 위한 논리 주소로 사용하기 위해 PK를 같이 저장하는데 이는 추가 컬럼을 하나 더 가지는 인덱스 효과도 얻을 수 있다. 하지만 너무 과도하게 인덱스 컬럼이 많아지면 메모리 낭비가 심해지고 레코드 저장, 변경 작업이 매우 느려질 수 있어서 주의해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;b&gt;Using index condition&lt;/b&gt;는 (MySQL 5.6 버전부터) 인덱스를 범위 제한 조건으로 사용하지 못한다고 하더라도 인덱스에 포함된 컬럼의 조건이 있다면 모두 같이 모아서 스토리지 엔진으로 전달하는 것을 말한다. 만약 스토리지 엔진에서 범위 제한 조건으로 10000건을 조회해서 MySQL엔진으로 전달했는데 MySQL 엔진에서 체크 조건으로 1건만 필터링이 된다면 매우 비효율적이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Using filesort&lt;/b&gt;는 order&amp;nbsp; by를 처리하기 위해 적절한 인덱스를 이용하지 못해서 조회된 레코드를 메모리 버퍼에 복사해 다시 한번 정렬하는 경우를 말한다. (성능상 주의해야 될 부분)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;explain analyze&lt;/b&gt;로 검색하면 쿼리의 실행 계획과 단계별 소요된 시간 정보를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 들여쓰기가 같은 레벨에서는 상단 라인이 먼저 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 들여쓰기가 다른 레벨에서는 가장 안쪽 라인이 먼저 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- actual time=0.007..0.009 : 첫번째 레코드를 가져오는 데 걸린 평균 시간(ms), 두번째는 마지막 레코드를 가져오는 데 걸린 평균 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- rows 처리한 레코드 건수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- loops 반복 횟수&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2184&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pCfOm/btsDnCBGUg4/O0QkwbzZtActqyfDobxtgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pCfOm/btsDnCBGUg4/O0QkwbzZtActqyfDobxtgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pCfOm/btsDnCBGUg4/O0QkwbzZtActqyfDobxtgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpCfOm%2FbtsDnCBGUg4%2FO0QkwbzZtActqyfDobxtgk%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;2184&quot; height=&quot;496&quot; data-origin-width=&quot;2184&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획과 더불어 쿼리 시간을 측정하는 경우 주의할 부분이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 서버는 운영체제의 파일 시스템 관련 기능(시스템 콜)을 이용해 데이터 파일을 읽어온다. 그런데 일반적으로 대부분의 운영체제는 한 번 읽은 데이터는 운영체제가 관리하는 별도의 캐시 영역에 보관해 뒀다가 다시 해당 데이터가 요청되면 디스크를 읽지 않고 캐시의 내용을 바로 MySQL 서버로 반환한다. InnoDB 스토리지 엔진은 일반적으로 파일 시스템의 캐시나 버퍼를 거치지 않는 Direct I/O를 사용하므로 운영체제의 캐시가 큰 영향을 미치지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 MySQL 서버에서도 데이터 파일의 내용을 페이지 단위로 캐시하는 기능을 제공한다. &lt;b&gt;InnoDB 스토리지 엔진이 관리하는 캐시를 버퍼 풀&lt;/b&gt;이라고 하는데 이는 &lt;b&gt;인덱스 페이지는 물론이고 데이터 페이지까지 캐시하며, 쓰기 작업을 위한 버퍼링 작업까지 겸해서 처리&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 서버가 한번 시작되면 InnoDB의 버퍼 풀을 강제로 삭제할 수 있는 방법이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 버퍼 풀은 MySQL 서버가 종료될 때 자동으로 덤프됐다가 다시 시작될 때 자동으로 적재된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SET GLObAL innodb_buffer_pool_dump_at_shutdown=OFF&lt;/li&gt;
&lt;li&gt;SET GLObAL innodb_buffer_pool_load_at_startup=OFF&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실제 쿼리의 성능 테스트를 &lt;b&gt;MySQL 서버의 상태가 워밍업된 상태(캐시나 버퍼등의 데이터가 준비된 상태)로 할지 아니면 콜드 상태(캐시나 버퍼가 모두 초기화된 상태)에서 진행할지도 고려&lt;/b&gt;해야 한다. 일반적으로 쿼리의 성능 테스트는 워밍업된 상태를 가정하고 테스트한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL의 버퍼 풀은 크기가 제한적이라서 쿼리에서 필요로 하는 데이터나 인덱스 페이지보다 크기가 작으면 플러시 작업과 캐시 작업이 반복해서 발생하므로 쿼리를 6~7번 정도 반복해서 실행한 후, 처음 한두 번의 결과는 버리고 나머지 결과의 평균값을 기준으로 비교하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[참고]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RealMySQL 8.0&lt;/b&gt;&lt;/p&gt;</description>
      <category>데이터베이스</category>
      <category>MySQL</category>
      <category>realmysql</category>
      <category>실행 계획</category>
      <category>옵티마이저</category>
      <author>tree code</author>
      <guid isPermaLink="true">https://treecode.tistory.com/119</guid>
      <comments>https://treecode.tistory.com/119#entry119comment</comments>
      <pubDate>Mon, 15 Jan 2024 15:52:02 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] CodeDeploy 사용시 주의점</title>
      <link>https://treecode.tistory.com/118</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. CodeDeploy Agent 설치를 하고 이후에 EC2에 CodeDeploy 관련 Role을 추가해주는 경우 CodeDeploy Agent Restart를 해줘야 적용이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1704875115317&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;AWS Systems Manager를 사용하여 CodeDeploy 에이전트 설치 - AWS CodeDeploy&quot; data-og-description=&quot;이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ssm.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ssm.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ssm.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ssm.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AWS Systems Manager를 사용하여 CodeDeploy 에이전트 설치 - AWS CodeDeploy&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ApplicationStop 단계에서 실행되는 스크립트가 수정이 되더라도 적용이 안 될 수가 있다. 아래 경로를 찾아서 제거해줘도 되지만 aws deploy create-deployment시 --ignore-application-stop-failures 옵션을 추가해주는 것이 확실하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2102&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2o729/btsDh87wgNT/wnKRwYIma0aIDYgGP6naz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2o729/btsDh87wgNT/wnKRwYIma0aIDYgGP6naz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2o729/btsDh87wgNT/wnKRwYIma0aIDYgGP6naz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2o729%2FbtsDh87wgNT%2FwnKRwYIma0aIDYgGP6naz0%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;2102&quot; height=&quot;662&quot; data-origin-width=&quot;2102&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uillO/btsDlmi75mI/X1HDScaAJONsaVxoEHnFAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uillO/btsDlmi75mI/X1HDScaAJONsaVxoEHnFAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uillO/btsDlmi75mI/X1HDScaAJONsaVxoEHnFAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuillO%2FbtsDlmi75mI%2FX1HDScaAJONsaVxoEHnFAK%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;266&quot; height=&quot;566&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1704875134347&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b9Um7d/hyU2m7YEGO/hT66hnn18GhFRZU97SUYzk/img.png?width=800&amp;amp;height=96&amp;amp;face=0_0_800_96,https://scrap.kakaocdn.net/dn/v2kC2/hyU2rOZKgu/mtkx23UVevISQKcLZlUtk0/img.png?width=800&amp;amp;height=96&amp;amp;face=0_0_800_96,https://scrap.kakaocdn.net/dn/dLNinL/hyU2f148uT/9HbSXkgNZ4nafEJ2oodFgk/img.png?width=2074&amp;amp;height=672&amp;amp;face=0_0_2074_672&quot; data-og-url=&quot;https://jaykos96.tistory.com/46&quot; data-og-source-url=&quot;https://jaykos96.tistory.com/46&quot; data-og-host=&quot;jaykos96.tistory.com&quot; data-og-description=&quot;Gitlab에서 CodeDeploy를 이용해 EC2 개발서버에 배포하던중 최초 성공 이후, 그 다음 배포에서 계속 CodeDeploy의 ApplicationStop에서 에러가 지속적으로 발생하였습니다.. Appspec.yml에 ApplicationStop에 해당하&quot; data-og-title=&quot;CodeDeploy에서 ApplicationStop 에러날때&quot; data-og-type=&quot;article&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a href=&quot;https://jaykos96.tistory.com/46&quot; data-source-url=&quot;https://jaykos96.tistory.com/46&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b9Um7d/hyU2m7YEGO/hT66hnn18GhFRZU97SUYzk/img.png?width=800&amp;amp;height=96&amp;amp;face=0_0_800_96,https://scrap.kakaocdn.net/dn/v2kC2/hyU2rOZKgu/mtkx23UVevISQKcLZlUtk0/img.png?width=800&amp;amp;height=96&amp;amp;face=0_0_800_96,https://scrap.kakaocdn.net/dn/dLNinL/hyU2f148uT/9HbSXkgNZ4nafEJ2oodFgk/img.png?width=2074&amp;amp;height=672&amp;amp;face=0_0_2074_672');&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;CodeDeploy에서 ApplicationStop 에러날때&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Gitlab에서 CodeDeploy를 이용해 EC2 개발서버에 배포하던중 최초 성공 이후, 그 다음 배포에서 계속 CodeDeploy의 ApplicationStop에서 에러가 지속적으로 발생하였습니다.. Appspec.yml에 ApplicationStop에 해당하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jaykos96.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. AppSpec의 hook에 추가한 스크립트가 실행 되는 경로는 Code Deploy Agent가 설치된 경로이다. docker compose를 사용하면 경로를 체크한다. (cd $PATH로 이동해서 실행 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kSnBE/btsDfP12F5V/bN9HjS4TVsjdrkgco9XkT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kSnBE/btsDfP12F5V/bN9HjS4TVsjdrkgco9XkT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kSnBE/btsDfP12F5V/bN9HjS4TVsjdrkgco9XkT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkSnBE%2FbtsDfP12F5V%2FbN9HjS4TVsjdrkgco9XkT1%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;1470&quot; height=&quot;206&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1704876905057&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;AWS Code Deploy를 통한 배포 자동화 - DRAMA&amp;amp;COMPANY&quot; data-og-description=&quot;서버 배포는 단순하고 반복작업이지만 절차가 적지 않아 실수를 할 가능성이 높습니다. 또 한번의 실수는 커다란 시스템 장애로 이루어질 수 있기 때문에 많은 분들에게 배포란 꽤나 부담스럽&quot; data-og-host=&quot;blog.dramancompany.com&quot; data-og-source-url=&quot;https://blog.dramancompany.com/2017/04/aws-code-deploy%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94/&quot; data-og-url=&quot;https://blog.dramancompany.com/2017/04/aws-code-deploy를-통한-배포-자동화/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/irDYq/hyU2nZ6sT4/yrH7tsE5G1k7iqbNxH8IG1/img.png?width=784&amp;amp;height=420&amp;amp;face=0_0_784_420,https://scrap.kakaocdn.net/dn/YziAx/hyU2qvMq53/Y3gZcXnaeWFRle3raAsQ60/img.png?width=2186&amp;amp;height=1010&amp;amp;face=0_0_2186_1010,https://scrap.kakaocdn.net/dn/bDt0ca/hyU2nTmc0o/bMF03ZesKgH9MKkjv32JkK/img.png?width=2440&amp;amp;height=556&amp;amp;face=0_0_2440_556&quot;&gt;&lt;a href=&quot;https://blog.dramancompany.com/2017/04/aws-code-deploy%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.dramancompany.com/2017/04/aws-code-deploy%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/irDYq/hyU2nZ6sT4/yrH7tsE5G1k7iqbNxH8IG1/img.png?width=784&amp;amp;height=420&amp;amp;face=0_0_784_420,https://scrap.kakaocdn.net/dn/YziAx/hyU2qvMq53/Y3gZcXnaeWFRle3raAsQ60/img.png?width=2186&amp;amp;height=1010&amp;amp;face=0_0_2186_1010,https://scrap.kakaocdn.net/dn/bDt0ca/hyU2nTmc0o/bMF03ZesKgH9MKkjv32JkK/img.png?width=2440&amp;amp;height=556&amp;amp;face=0_0_2440_556');&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;AWS Code Deploy를 통한 배포 자동화 - DRAMA&amp;amp;COMPANY&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;서버 배포는 단순하고 반복작업이지만 절차가 적지 않아 실수를 할 가능성이 높습니다. 또 한번의 실수는 커다란 시스템 장애로 이루어질 수 있기 때문에 많은 분들에게 배포란 꽤나 부담스럽&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.dramancompany.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>꿀팁</category>
      <category>codedeploy</category>
      <author>tree code</author>
      <guid isPermaLink="true">https://treecode.tistory.com/118</guid>
      <comments>https://treecode.tistory.com/118#entry118comment</comments>
      <pubDate>Wed, 10 Jan 2024 18:05:55 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Multipart 전송 시 Json 데이터 같이 보낼 경우 주의점</title>
      <link>https://treecode.tistory.com/117</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 이미지 업로드를 할때 Json 데이터를 Multipart로 같이 보내는 경우 Content-Type을 application/json으로 명시해줘주지 않으면&amp;nbsp;application/octet-stream not supported 예외가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;multipart/form-data를 받을 경우 @RequestPart 애노테이션을 사용하는데 애노테이션의 주석을 보면 method argument가 String or MultipartFile이 아닌 경우 HttpMessageConverter에서 각 request part의 content-type을 체크한다고 나와있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MXZFO/btsC35iw88S/xXR8oS5lHrdl0KkKemqbj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MXZFO/btsC35iw88S/xXR8oS5lHrdl0KkKemqbj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MXZFO/btsC35iw88S/xXR8oS5lHrdl0KkKemqbj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMXZFO%2FbtsC35iw88S%2FxXR8oS5lHrdl0KkKemqbj0%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;1632&quot; height=&quot;798&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RequestPart 애노테이션이 붙은 파라미터를 처리하는 RequestPartMethodArgumentResolver는 AbstracMessageConverterMethodArgumentResolver를 extends 하고 있다. 그래서 resolveMultipartArgument()로 Multipart를 처리하고 Multipart가 아닌 경우에는 AbstracMessageConverterMethodArgumentResolver의 readWithMessageConverters()에서 messageConvert로 revolve 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXkVa/btsC1nxcnrD/nTg0xcJk4ZHbQvJcwCmKC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXkVa/btsC1nxcnrD/nTg0xcJk4ZHbQvJcwCmKC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXkVa/btsC1nxcnrD/nTg0xcJk4ZHbQvJcwCmKC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXkVa%2FbtsC1nxcnrD%2FnTg0xcJk4ZHbQvJcwCmKC1%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;1926&quot; height=&quot;454&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Content-Type이 없는 경우 default로 application/octet-stream가 설정이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkS5px/btsCWM5ESyw/XfsANwRzmoEAOnVmmV9H6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkS5px/btsCWM5ESyw/XfsANwRzmoEAOnVmmV9H6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkS5px/btsCWM5ESyw/XfsANwRzmoEAOnVmmV9H6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkS5px%2FbtsCWM5ESyw%2FXfsANwRzmoEAOnVmmV9H6k%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;1408&quot; height=&quot;878&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application/octet-stream는 보통 unknown binary file에 대해 default로 사용이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1Jjgb/btsCZ8Afkwz/mbNLrke4NnM9XrkCIRDHOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1Jjgb/btsCZ8Afkwz/mbNLrke4NnM9XrkCIRDHOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1Jjgb/btsCZ8Afkwz/mbNLrke4NnM9XrkCIRDHOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1Jjgb%2FbtsCZ8Afkwz%2FmbNLrke4NnM9XrkCIRDHOK%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;1604&quot; height=&quot;454&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 등록된 MessageConverter를 돌면서 convert가 가능한지 체크를 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bULaHV/btsC4sxNNTd/PzfOvavWONCY3wTgeod5uK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bULaHV/btsC4sxNNTd/PzfOvavWONCY3wTgeod5uK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bULaHV/btsC4sxNNTd/PzfOvavWONCY3wTgeod5uK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbULaHV%2FbtsC4sxNNTd%2FPzfOvavWONCY3wTgeod5uK%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;1842&quot; height=&quot;318&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ByteArrayHttpMessageConverter가 application/octet-stream을 처리하지만 Controller에서는 byte[]가 아닌 json을 mapping하려고 하기 때문에 supports()에서 false를 반환한다.&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;1438&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLpMf6/btsC0XFE0rk/9oWeFO8IgSE5efFzLDl5v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLpMf6/btsC0XFE0rk/9oWeFO8IgSE5efFzLDl5v1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLpMf6/btsC0XFE0rk/9oWeFO8IgSE5efFzLDl5v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLpMf6%2FbtsC0XFE0rk%2F9oWeFO8IgSE5efFzLDl5v1%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;1438&quot; height=&quot;238&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rpnKb/btsC32F9BQt/23B1FKnaRQACMnHjNwTD40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rpnKb/btsC32F9BQt/23B1FKnaRQACMnHjNwTD40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rpnKb/btsC32F9BQt/23B1FKnaRQACMnHjNwTD40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrpnKb%2FbtsC32F9BQt%2F23B1FKnaRQACMnHjNwTD40%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;990&quot; height=&quot;274&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 json을 mapping해주기 위해 AbstractJackson2HttpMessageConverter를 extends해서 application/octet-stream 타입을 받아서 변환하도록 해줄 수도 있지만 request 전송시 content-type을 명시해주면 해결이 된다.&lt;/p&gt;</description>
      <category>Spring</category>
      <category>@RequestPart</category>
      <category>Multipart</category>
      <category>Spring</category>
      <author>tree code</author>
      <guid isPermaLink="true">https://treecode.tistory.com/117</guid>
      <comments>https://treecode.tistory.com/117#entry117comment</comments>
      <pubDate>Thu, 4 Jan 2024 17:43:48 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Rest Docs] Enum, 예외 코드 문서화를 위한 Custom Snippet 생성(AbstractFieldsSnippet)</title>
      <link>https://treecode.tistory.com/116</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Rest Docs에서 Error Code나 Enum의 문서를 보여주기 위해서는 Custom Snippet을 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 템플릿도 수정해서 사용할 수 있는데 일반적으로 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;optional, default, constraints 같은 column을 추가해서 사용한다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2016&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgbFOX/btsCP2FMqdz/NxOkpG40Cnd37Rsoc54VH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgbFOX/btsCP2FMqdz/NxOkpG40Cnd37Rsoc54VH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgbFOX/btsCP2FMqdz/NxOkpG40Cnd37Rsoc54VH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgbFOX%2FbtsCP2FMqdz%2FNxOkpG40Cnd37Rsoc54VH0%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;2016&quot; height=&quot;1200&quot; data-origin-width=&quot;2016&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Error Code나 Enum을 나타내기 위한 템플릿은 column이 아예 달라서 새로 추가하는 것이 좋다. restdocs-core의 default snippet 템플릿이 있는 경로대로 테스트의 resources 하위에 test/resources/org/.. 디렉토리를 생성하고 템플릿을 추가하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;1640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crDSsF/btsCDf8d2VV/PrpF521DXnEmnQ50xe7pVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crDSsF/btsCDf8d2VV/PrpF521DXnEmnQ50xe7pVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crDSsF/btsCDf8d2VV/PrpF521DXnEmnQ50xe7pVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrDSsF%2FbtsCDf8d2VV%2FPrpF521DXnEmnQ50xe7pVk%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;1530&quot; height=&quot;1640&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;1640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDn7EP/btsCF7PBANB/Q1s1gDI1kLBLmum2zIrywK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDn7EP/btsCF7PBANB/Q1s1gDI1kLBLmum2zIrywK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDn7EP/btsCF7PBANB/Q1s1gDI1kLBLmum2zIrywK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDn7EP%2FbtsCF7PBANB%2FQ1s1gDI1kLBLmum2zIrywK%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;1260&quot; height=&quot;878&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 error code를 문서화하기 위해 errorcde-response-fields.snippet을 생성했는데&amp;nbsp; xxx-response-fields처럼 네이밍을 지키면서 추가하면 된다. 그 다음 Custom Snippet을 적용하기 위해 AbstractFieldsSnippet를 상속받는 클래스를 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cH8yp0/btsCODe7Vgc/Hz0b7rcVi2sz3BsHm3YdHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cH8yp0/btsCODe7Vgc/Hz0b7rcVi2sz3BsHm3YdHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cH8yp0/btsCODe7Vgc/Hz0b7rcVi2sz3BsHm3YdHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcH8yp0%2FbtsCODe7Vgc%2FHz0b7rcVi2sz3BsHm3YdHk%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;1570&quot; height=&quot;1036&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 메서드인 getContentType(), getContent()를 구현해줘야 하는데 실수로 그냥 지나쳤다가 문서가 제대로 생성 되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getContent()로 body를 꺼내서 veriftContent()를 할 때 length가 0이면 SnippetException body is empty 예외가 발생한다. ㅠㅠ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c07Jhs/btsCJjhRBuP/kMd5NOZnkc6uMH1dGigvck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c07Jhs/btsCJjhRBuP/kMd5NOZnkc6uMH1dGigvck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c07Jhs/btsCJjhRBuP/kMd5NOZnkc6uMH1dGigvck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc07Jhs%2FbtsCJjhRBuP%2FkMd5NOZnkc6uMH1dGigvck%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;1866&quot; height=&quot;628&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1874&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmJCeR/btsCQ4XCi8L/djBGKOQVJTzPU2eaQMfAQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmJCeR/btsCQ4XCi8L/djBGKOQVJTzPU2eaQMfAQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmJCeR/btsCQ4XCi8L/djBGKOQVJTzPU2eaQMfAQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmJCeR%2FbtsCQ4XCi8L%2FdjBGKOQVJTzPU2eaQMfAQ1%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;330&quot; data-origin-width=&quot;1874&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;1550&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG0oIl/btsCN78zA8d/TC6WOBRJolQaXf6YRbcrL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG0oIl/btsCN78zA8d/TC6WOBRJolQaXf6YRbcrL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG0oIl/btsCN78zA8d/TC6WOBRJolQaXf6YRbcrL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG0oIl%2FbtsCN78zA8d%2FTC6WOBRJolQaXf6YRbcrL0%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;1550&quot; height=&quot;558&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 custom snippet, CustomResponseFieldSnippet 클래스를 추가했으면 test 디렉토리 내부에 Enum 값들을 반환해 주는 Controller를 생성하고 해당 Controller에서 문서화하려는 Enum 값들을 반환해 준다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 Rest Docs용으로 생성한 Controller에 요청을 보내면 Enum 값들을 Map으로 반환해 줘서 key는 문서의 row로 사용하고 value는 column으로 사용한다. (column에서 보여줄 status, code, message 등을 DTO로 생성한다,)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;1220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjXYfD/btsCN62UrLR/YT6KgycR6lZ0RByxCZb0PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjXYfD/btsCN62UrLR/YT6KgycR6lZ0RByxCZb0PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjXYfD/btsCN62UrLR/YT6KgycR6lZ0RByxCZb0PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjXYfD%2FbtsCN62UrLR%2FYT6KgycR6lZ0RByxCZb0PK%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;1652&quot; height=&quot;1220&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;1220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래와 같이 response가 생성이 되는데 key를 fieldWithPath()의 path로 지정하고 type, code, status, message는 attributes에 값을 넣어주면 column으로 보여줄 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddA2zj/btsCKpbb41p/waCD5NRMt6P8hahg0KU7Y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddA2zj/btsCKpbb41p/waCD5NRMt6P8hahg0KU7Y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddA2zj/btsCKpbb41p/waCD5NRMt6P8hahg0KU7Y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddA2zj%2FbtsCKpbb41p%2FwaCD5NRMt6P8hahg0KU7Y1%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;826&quot; height=&quot;722&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 responseFIelds(fieldWithPath()...)의 형태로 추가를 하면 response-fields.snippet을 찾아서 문서가 생성이 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/800Zq/btsCKPOqKYo/X9LV33xzezWPKKVuvJLfxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/800Zq/btsCKPOqKYo/X9LV33xzezWPKKVuvJLfxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/800Zq/btsCKPOqKYo/X9LV33xzezWPKKVuvJLfxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F800Zq%2FbtsCKPOqKYo%2FX9LV33xzezWPKKVuvJLfxk%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;1652&quot; height=&quot;272&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이번에는 custom으로 생성을 하기 때문에 아까 생성해 놓은 CustomResponseFieldSnippet의 생성자를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ignoreUndocumentedFields를 true로 줘서 문서화하지 않은 필드는 무시하도록 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biztoN/btsCRhJo4nZ/sS9uOUdgVxG342pNKqJ7O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biztoN/btsCRhJo4nZ/sS9uOUdgVxG342pNKqJ7O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biztoN/btsCRhJo4nZ/sS9uOUdgVxG342pNKqJ7O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiztoN%2FbtsCRhJo4nZ%2FsS9uOUdgVxG342pNKqJ7O1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1396&quot; height=&quot;556&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Rest Docs 테스트 코드를 작성하면 되는데 나는 document()의 공통 부분을 restDocsTemplate() 메서드로 빼두었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ury7e/btsCMw1ScVi/iFVXffPtm3o5r3IX3J9uk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ury7e/btsCMw1ScVi/iFVXffPtm3o5r3IX3J9uk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ury7e/btsCMw1ScVi/iFVXffPtm3o5r3IX3J9uk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fury7e%2FbtsCMw1ScVi%2FiFVXffPtm3o5r3IX3J9uk0%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;1734&quot; height=&quot;336&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createErrorCodeFieldDescriptors()에서 Map&amp;lt;String, ErrorCodeResponse&amp;gt;를 받아서 FieldDescriptor를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;path에는 Map의 key가 들어가고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;attributes에는 Map의 value인 ErrorCodeResponse의 type, code, status, message 필드들을 추가해 주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2112&quot; data-origin-height=&quot;1262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo4ttr/btsCLp2YKVn/SCDkikSI4NL3UhXowzTH80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo4ttr/btsCLp2YKVn/SCDkikSI4NL3UhXowzTH80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo4ttr/btsCLp2YKVn/SCDkikSI4NL3UhXowzTH80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo4ttr%2FbtsCLp2YKVn%2FSCDkikSI4NL3UhXowzTH80%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;2112&quot; height=&quot;1262&quot; data-origin-width=&quot;2112&quot; data-origin-height=&quot;1262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4UZkK/btsCQ4i1QC2/8cChywzxdDL5q9kwr8ivzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4UZkK/btsCQ4i1QC2/8cChywzxdDL5q9kwr8ivzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4UZkK/btsCQ4i1QC2/8cChywzxdDL5q9kwr8ivzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4UZkK%2FbtsCQ4i1QC2%2F8cChywzxdDL5q9kwr8ivzk%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;1440&quot; height=&quot;824&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Error Code를 테이블 형태로 보여줄 수 있다. ErrorCode를 하나의 Enum 클래스에서 관리한다면 자동으로 Error Code 동기화가 돼서 좋은 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2068&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by3kkX/btsCN11Ferq/72gaaxiQfuSciUE8d81nLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by3kkX/btsCN11Ferq/72gaaxiQfuSciUE8d81nLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by3kkX/btsCN11Ferq/72gaaxiQfuSciUE8d81nLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby3kkX%2FbtsCN11Ferq%2F72gaaxiQfuSciUE8d81nLk%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;2068&quot; height=&quot;998&quot; data-origin-width=&quot;2068&quot; data-origin-height=&quot;998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[참고]&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703749512942&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;RestDocs - Custom Error Code Enum 문서화&quot; data-og-description=&quot;RestDocs - Custom Error Code Enum 문서화 Enum으로 Custom ErrorCode 관리시 Enum 문서 자동화 방법 SpringBoot 버전 2.7.8 restdocs 버전 2.0.7 개요 데브코스에서 프로젝트를 하다 고민이 생겼습니다. 프론트엔드와의 &quot; data-og-host=&quot;0soo.tistory.com&quot; data-og-source-url=&quot;https://0soo.tistory.com/210&quot; data-og-url=&quot;https://0soo.tistory.com/210&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bb1yxD/hyUTHj8Gsz/MVi7h4d5dAPlBeF7EREqP0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/7gLPn/hyUTDWjL2a/WfphwFi6WcfLXmjzMOcvWk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bs4PIa/hyUTAk1iCA/UryQLTDQO5IWqsGygQiRYk/img.png?width=1090&amp;amp;height=854&amp;amp;face=0_0_1090_854&quot;&gt;&lt;a href=&quot;https://0soo.tistory.com/210&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://0soo.tistory.com/210&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bb1yxD/hyUTHj8Gsz/MVi7h4d5dAPlBeF7EREqP0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/7gLPn/hyUTDWjL2a/WfphwFi6WcfLXmjzMOcvWk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bs4PIa/hyUTAk1iCA/UryQLTDQO5IWqsGygQiRYk/img.png?width=1090&amp;amp;height=854&amp;amp;face=0_0_1090_854');&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;RestDocs - Custom Error Code Enum 문서화&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;RestDocs - Custom Error Code Enum 문서화 Enum으로 Custom ErrorCode 관리시 Enum 문서 자동화 방법 SpringBoot 버전 2.7.8 restdocs 버전 2.0.7 개요 데브코스에서 프로젝트를 하다 고민이 생겼습니다. 프론트엔드와의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;0soo.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1703749508331&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;Spring Rest Docs 적용 | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} 안녕하세요? 우아한형제들에서 정산시스템을 개발하고 있는 이호진입니다. 지금부터 정산시스템 API 문서를 wiki 에서 Spring Rest Docs 로 전환한 이야기를 해보려고 합니다. 1. 전환하는 &quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/2597/&quot; data-og-url=&quot;https://techblog.woowahan.com/2597/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkOtW5/hyUTCC7r4E/TN8uVkKPOlb6Mj1nxQ4KFk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bVwiZ7/hyUTDhKwEN/WUfkKlGquV02sLim6jhPg0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/EKSux/hyUTGS4229/nO3Ii5CIBlqkeOpDupLTk0/img.png?width=1980&amp;amp;height=912&amp;amp;face=0_0_1980_912&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2597/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/2597/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkOtW5/hyUTCC7r4E/TN8uVkKPOlb6Mj1nxQ4KFk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bVwiZ7/hyUTDhKwEN/WUfkKlGquV02sLim6jhPg0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/EKSux/hyUTGS4229/nO3Ii5CIBlqkeOpDupLTk0/img.png?width=1980&amp;amp;height=912&amp;amp;face=0_0_1980_912');&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;Spring Rest Docs 적용 | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 안녕하세요? 우아한형제들에서 정산시스템을 개발하고 있는 이호진입니다. 지금부터 정산시스템 API 문서를 wiki 에서 Spring Rest Docs 로 전환한 이야기를 해보려고 합니다. 1. 전환하는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1703749476489&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;Spring REST Docs 적용 및 최적화 하기&quot; data-og-description=&quot;Java, JPA, Spring을 주로 다루고 공유합니다.&quot; data-og-host=&quot;backtony.github.io&quot; data-og-source-url=&quot;https://backtony.github.io/spring/2021-10-15-spring-test-3/#enum-코드-문서화&quot; data-og-url=&quot;https://backtony.github.io/spring/2021-10-15-spring-test-3/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qC5UF/hyUTxhw5iR/PFJcFlpgnuMoHtpliVYijK/img.png?width=433&amp;amp;height=266&amp;amp;face=0_0_433_266,https://scrap.kakaocdn.net/dn/cZ1Hb2/hyUTw3ZPwm/3Yl3K3MiV4DQNxl7PPuK5k/img.png?width=433&amp;amp;height=266&amp;amp;face=0_0_433_266,https://scrap.kakaocdn.net/dn/UjbIC/hyUTKgQnug/9ZDW2OlF1sqilgnkaWB2g0/img.png?width=1268&amp;amp;height=789&amp;amp;face=0_0_1268_789&quot;&gt;&lt;a href=&quot;https://backtony.github.io/spring/2021-10-15-spring-test-3/#enum-코드-문서화&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://backtony.github.io/spring/2021-10-15-spring-test-3/#enum-코드-문서화&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qC5UF/hyUTxhw5iR/PFJcFlpgnuMoHtpliVYijK/img.png?width=433&amp;amp;height=266&amp;amp;face=0_0_433_266,https://scrap.kakaocdn.net/dn/cZ1Hb2/hyUTw3ZPwm/3Yl3K3MiV4DQNxl7PPuK5k/img.png?width=433&amp;amp;height=266&amp;amp;face=0_0_433_266,https://scrap.kakaocdn.net/dn/UjbIC/hyUTKgQnug/9ZDW2OlF1sqilgnkaWB2g0/img.png?width=1268&amp;amp;height=789&amp;amp;face=0_0_1268_789');&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;Spring REST Docs 적용 및 최적화 하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Java, JPA, Spring을 주로 다루고 공유합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;backtony.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>AbstractFieldsSnippet</category>
      <category>spring rest docs</category>
      <author>tree code</author>
      <guid isPermaLink="true">https://treecode.tistory.com/116</guid>
      <comments>https://treecode.tistory.com/116#entry116comment</comments>
      <pubDate>Thu, 28 Dec 2023 16:46:00 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot 3.2] RestClient, HttpInterface 사용법 (RestClientTest 및 HttpComponents 라이브러리 주의)</title>
      <link>https://treecode.tistory.com/115</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 외부 API 호출을 위해 RestTemplate, WebClient을 제공하는데 스프링 부트 3.2 버전부터 정식으로 RestClient가 추가되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RestClient&amp;nbsp;- synchronous client with a fluent API.&lt;/li&gt;
&lt;li&gt;WebClient&amp;nbsp;- non-blocking, reactive client with fluent API.&lt;/li&gt;
&lt;li&gt;RestTemplate&amp;nbsp;- synchronous client with template method API.&lt;/li&gt;
&lt;li&gt;HTTP Interface&amp;nbsp;- annotated interface with generated, dynamic proxy implementation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebClient, HttpInterface를 사용하면 직관적이고 깔끔하게 구현할 수 있지만 webflux 의존성을 추가해야 되는 단점이 있다. RestClient는 새로운 동기식 HTTP 클라이언트로 WebClient와 유사한 API로 사용할 수 있다. 이전에는 HttpInterface를 사용하려면&amp;nbsp; WebClient를 사용했어야 했는데 스프링 6.1부터 HttpExchangeAdapter의 구현체로 RestClientAdapter와 RestTemplateAdapter 둘 다 사용할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 카카오맵 API를 사용하면서 RestClient, HttpInterface를 사용했는데 RestClient는 create() 혹은 builder를 통해 생성할 수 있고 builder를 사용하면 추가로 baseUrl, header, statusHandler 등 여러 설정을 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMAMZY/btsB6jXD1VB/TkFlKdbQ3kkM8Y9oaVi1n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMAMZY/btsB6jXD1VB/TkFlKdbQ3kkM8Y9oaVi1n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMAMZY/btsB6jXD1VB/TkFlKdbQ3kkM8Y9oaVi1n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMAMZY%2FbtsB6jXD1VB%2FTkFlKdbQ3kkM8Y9oaVi1n1%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;1844&quot; height=&quot;434&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build() 내부를 보면 설정이 없는 경우 여러 초기화 작업을 해준다. RestTemplate, RestClient는 내부적으로 HTTP 통신을 위해 Apache HttpComponents를 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1904&quot; data-origin-height=&quot;920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMIedj/btsCgrsxbJQ/Mg6XcAt4ZeRrFkBkLzVLS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMIedj/btsCgrsxbJQ/Mg6XcAt4ZeRrFkBkLzVLS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMIedj/btsCgrsxbJQ/Mg6XcAt4ZeRrFkBkLzVLS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMIedj%2FbtsCgrsxbJQ%2FMg6XcAt4ZeRrFkBkLzVLS0%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;1904&quot; height=&quot;920&quot; data-origin-width=&quot;1904&quot; data-origin-height=&quot;920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestClient 설정시 ClientRequestFactory를 추가할 수 있는데 ClientHttpRequestFactory는 HttpClient, ConnectionManager 등의 설정을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestClient.Builder에서 HttpComponents5 라이브러리를 직접 추가하지 않으면 SimpleClientHttpRequestFactory가 default로 들어간다. SimpleClientHttpRequestFactory를 그대로 사용하면 운영 환경에서 많은 문제가 생길 수 있어서 HttpComponentsClientHttpRequestFactory를 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(원래 HttpComponentsClientHttpRequestFactory가 default인데 라이브러리를 따로 추가하지 않은 경우 발생하는 문제이다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnNjv0/btsCg0aom9u/5nxdYAeWEWnNOJE3Uk2Zk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnNjv0/btsCg0aom9u/5nxdYAeWEWnNOJE3Uk2Zk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnNjv0/btsCg0aom9u/5nxdYAeWEWnNOJE3Uk2Zk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnNjv0%2FbtsCg0aom9u%2F5nxdYAeWEWnNOJE3Uk2Zk0%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;1458&quot; height=&quot;156&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpComponentsClientHttpRequestFactory를 생성할 수가 없어서 주석을 보니 스프링 부트3 버전대에서 HttpComponents 5.1 이상 라이브러리를 추가로 필요로 한다고 나와있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjC66x/btsB7VolDXO/ymqrIrcTIRspxSa9KSqXak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjC66x/btsB7VolDXO/ymqrIrcTIRspxSa9KSqXak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjC66x/btsB7VolDXO/ymqrIrcTIRspxSa9KSqXak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjC66x%2FbtsB7VolDXO%2FymqrIrcTIRspxSa9KSqXak%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;2048&quot; height=&quot;438&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpComponents 5.2.x 버전을 추가하고 다시 보면 default가 HttpComponentsClientHttpRequestFactory로 바뀌고 HttpComponentsClientHttpRequestFactory 또한 직접 생성해서 설정할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;하지만..! 스프링 부트 3.2 기준으로 HttpComponents 5.2.x 버전을 추가하면 HttpInterface Proxy 생성이 안 되는 문제가 있어서 최신 버전인 5.3으로 추가해야 한다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpComponentsClientHttpRequestFactory 내부에는 httpClient의 default ConnectionManager로 PoolingHttpClientConnectionManager가 주입이 되어 있다. PoolingHttpClientConnectionManager는 커넥션 풀을 사용하여 커넥션 관련 설정을 custom 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2012&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vqhLD/btsB7V20z57/VCOsk1S67SrtmbPWp3T6v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vqhLD/btsB7V20z57/VCOsk1S67SrtmbPWp3T6v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vqhLD/btsB7V20z57/VCOsk1S67SrtmbPWp3T6v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvqhLD%2FbtsB7V20z57%2FVCOsk1S67SrtmbPWp3T6v0%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;2012&quot; height=&quot;912&quot; data-origin-width=&quot;2012&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpComponentsClientHttpRequestFactory는 상황에 따라 타임 아웃, 커넥션 풀 등의 설정을 통해 변경할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfOaKq/btsCg5bHPCm/2yiHuTNHdv8jZ06PHpLHT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfOaKq/btsCg5bHPCm/2yiHuTNHdv8jZ06PHpLHT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfOaKq/btsCg5bHPCm/2yiHuTNHdv8jZ06PHpLHT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfOaKq%2FbtsCg5bHPCm%2F2yiHuTNHdv8jZ06PHpLHT1%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;1810&quot; height=&quot;632&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpInterface는 아래와 같이 인터페이스, 애노테이션 기반으로 정의를 해서 직관적으로 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/86cdR/btsCguiuf9p/lvtBa4Qq77KSIzEsxDjhpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/86cdR/btsCguiuf9p/lvtBa4Qq77KSIzEsxDjhpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/86cdR/btsCguiuf9p/lvtBa4Qq77KSIzEsxDjhpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F86cdR%2FbtsCguiuf9p%2FlvtBa4Qq77KSIzEsxDjhpk%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;1720&quot; height=&quot;574&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 직접 해당 인터페이스의 프록시 구현체를 만들어서 Bean으로 등록해주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y9Hv2/btsB7j35Og0/rhO8sXmnDozT0c9dO0ckbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y9Hv2/btsB7j35Og0/rhO8sXmnDozT0c9dO0ckbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y9Hv2/btsB7j35Og0/rhO8sXmnDozT0c9dO0ckbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy9Hv2%2FbtsB7j35Og0%2FrhO8sXmnDozT0c9dO0ckbk%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;1990&quot; height=&quot;628&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정한 외부 API 호출을 테스트하려면 @RestClientTest를 활용할 수 있다. 주의점은 RestTemplate, RestClient를 주입받는 Bean에서 파라미터로 RestTemplateBuilder or RestClient.Builder를 사용해야 한다. 그래서 위에 HttpInterface를 Bean으로 등록할 때 RestClient.Builder를 파라미터로 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccszXd/btsCau4WMjr/3ilg864fF34De0gxgooNj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccszXd/btsCau4WMjr/3ilg864fF34De0gxgooNj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccszXd/btsCau4WMjr/3ilg864fF34De0gxgooNj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccszXd%2FbtsCau4WMjr%2F3ilg864fF34De0gxgooNj1%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;2132&quot; height=&quot;500&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestClientTest는 최소한의 Context로 테스트를 실행하기 때문에 테스트할 HttpInterface를 value에 추가해 주었다. MockRestServiceServer는 외부 API를 테스트하기 위한 Mock 서버이다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6xLiu/btsCgYwS1Eg/IxkwY63eVUeVQxM5DRAFe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6xLiu/btsCgYwS1Eg/IxkwY63eVUeVQxM5DRAFe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6xLiu/btsCgYwS1Eg/IxkwY63eVUeVQxM5DRAFe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6xLiu%2FbtsCgYwS1Eg%2FIxkwY63eVUeVQxM5DRAFe0%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;1448&quot; height=&quot;554&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 간단하게 예상 Response 등을 설정해 주고 mockServer에 설정을 해준 뒤에 예상 경로로 요청이 가는지, 바인딩이 제대로 되는지 등의 테스트를 검증한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2YMox/btsB6L7CzxL/xmKJV08aMlzTJg2Kw5iknK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2YMox/btsB6L7CzxL/xmKJV08aMlzTJg2Kw5iknK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2YMox/btsB6L7CzxL/xmKJV08aMlzTJg2Kw5iknK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2YMox%2FbtsB6L7CzxL%2FxmKJV08aMlzTJg2Kw5iknK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1540&quot; height=&quot;796&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lYOY5/btsCgw1HEY5/EzbnkZeKfndrnHXcf1AbZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lYOY5/btsCgw1HEY5/EzbnkZeKfndrnHXcf1AbZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lYOY5/btsCgw1HEY5/EzbnkZeKfndrnHXcf1AbZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlYOY5%2FbtsCgw1HEY5%2FEzbnkZeKfndrnHXcf1AbZK%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;1214&quot; height=&quot;448&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestClient는 custom 메시지 컨버터를 추가할 수 있지만 default로 등록된 컨버터 중 MappingJackson2HttpMessageConverter가 json 응답을 객체에 바인딩해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[참고]&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1702891687016&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;Spring Boot에서 외부 API 테스트하기&quot; data-og-description=&quot;안녕하세요? 이번 시간엔 Spring Boot의 @RestClientTest 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. 1. 문제 상황 예를 들어 외부 API를 &quot; data-og-host=&quot;jojoldu.tistory.com&quot; data-og-source-url=&quot;https://jojoldu.tistory.com/341&quot; data-og-url=&quot;https://jojoldu.tistory.com/341&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b3IeVc/hyUPMdMMeM/6IaUGzMKKao3YN7QS7H1fk/img.png?width=800&amp;amp;height=326&amp;amp;face=0_0_800_326,https://scrap.kakaocdn.net/dn/ASReE/hyULVi9MeS/0w7WgAE85MWcFTbb3RraGK/img.png?width=800&amp;amp;height=326&amp;amp;face=0_0_800_326,https://scrap.kakaocdn.net/dn/zR9dj/hyUPHKjysQ/zpsTZqwutRk3cwTK374pOK/img.png?width=2710&amp;amp;height=1286&amp;amp;face=0_0_2710_1286&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/341&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jojoldu.tistory.com/341&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b3IeVc/hyUPMdMMeM/6IaUGzMKKao3YN7QS7H1fk/img.png?width=800&amp;amp;height=326&amp;amp;face=0_0_800_326,https://scrap.kakaocdn.net/dn/ASReE/hyULVi9MeS/0w7WgAE85MWcFTbb3RraGK/img.png?width=800&amp;amp;height=326&amp;amp;face=0_0_800_326,https://scrap.kakaocdn.net/dn/zR9dj/hyUPHKjysQ/zpsTZqwutRk3cwTK374pOK/img.png?width=2710&amp;amp;height=1286&amp;amp;face=0_0_2710_1286');&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;Spring Boot에서 외부 API 테스트하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요? 이번 시간엔 Spring Boot의 @RestClientTest 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. 1. 문제 상황 예를 들어 외부 API를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jojoldu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1702891729102&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;RestTemplate 사용시 주의사항 - Incheol's TECH BLOG&quot; data-og-description=&quot;ResponseEntity response = restTemplate.exchange(&amp;quot; &amp;quot;, HttpMethod.GET, requestEntity, String.class);&quot; data-og-host=&quot;incheol-jung.gitbook.io&quot; data-og-source-url=&quot;https://incheol-jung.gitbook.io/docs/q-and-a/spring/resttemplate&quot; data-og-url=&quot;https://incheol-jung.gitbook.io/docs/q-and-a/spring/resttemplate&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b5WDH0/hyUPJOS4Wk/NtZKIJG0KOvK1YpVpZKVT0/img.png?width=2000&amp;amp;height=2349&amp;amp;face=0_0_2000_2349,https://scrap.kakaocdn.net/dn/XLBxG/hyUPyfxxE3/rRMS6of2ZTcDbpM6RTITpk/img.png?width=2000&amp;amp;height=1628&amp;amp;face=0_0_2000_1628,https://scrap.kakaocdn.net/dn/ktC82/hyUPMSnVtk/dKc3H8AZokwZKzWWn6buwK/img.jpg?width=2047&amp;amp;height=1365&amp;amp;face=1269_527_1396_667&quot;&gt;&lt;a href=&quot;https://incheol-jung.gitbook.io/docs/q-and-a/spring/resttemplate&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://incheol-jung.gitbook.io/docs/q-and-a/spring/resttemplate&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b5WDH0/hyUPJOS4Wk/NtZKIJG0KOvK1YpVpZKVT0/img.png?width=2000&amp;amp;height=2349&amp;amp;face=0_0_2000_2349,https://scrap.kakaocdn.net/dn/XLBxG/hyUPyfxxE3/rRMS6of2ZTcDbpM6RTITpk/img.png?width=2000&amp;amp;height=1628&amp;amp;face=0_0_2000_1628,https://scrap.kakaocdn.net/dn/ktC82/hyUPMSnVtk/dKc3H8AZokwZKzWWn6buwK/img.jpg?width=2047&amp;amp;height=1365&amp;amp;face=1269_527_1396_667');&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;RestTemplate 사용시 주의사항 - Incheol's TECH BLOG&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ResponseEntity response = restTemplate.exchange(&quot; &quot;, HttpMethod.GET, requestEntity, String.class);&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;incheol-jung.gitbook.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1702891714969&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;Apache HttpComponent 제대로 사용하기&quot; data-og-description=&quot;Java 어플리케이션에서 일반적으로 http호출을 할때 Apache HttpClient를 사용하며 http client가 버전업을 하여 Apache HttpComponent로 변경된것은 아마 다들 알고있는 사항일것입니다. (아주 옛날에 바꼈으니&quot; data-og-host=&quot;inyl.github.io&quot; data-og-source-url=&quot;https://inyl.github.io/programming/2017/09/14/http_component.html&quot; data-og-url=&quot;https://inyl.github.io/programming/2017/09/14/http_component.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://inyl.github.io/programming/2017/09/14/http_component.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inyl.github.io/programming/2017/09/14/http_component.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Apache HttpComponent 제대로 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Java 어플리케이션에서 일반적으로 http호출을 할때 Apache HttpClient를 사용하며 http client가 버전업을 하여 Apache HttpComponent로 변경된것은 아마 다들 알고있는 사항일것입니다. (아주 옛날에 바꼈으니&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;inyl.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>HttpInterface</category>
      <category>RestClient</category>
      <author>tree code</author>
      <guid isPermaLink="true">https://treecode.tistory.com/115</guid>
      <comments>https://treecode.tistory.com/115#entry115comment</comments>
      <pubDate>Mon, 18 Dec 2023 18:29:15 +0900</pubDate>
    </item>
  </channel>
</rss>