<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>FREESTROKES DEVLOG</title>
    <link>https://freestrokes.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 15 Jul 2026 22:35:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>freestrokes</managingEditor>
    <image>
      <title>FREESTROKES DEVLOG</title>
      <url>https://tistory1.daumcdn.net/tistory/1502977/attach/ba0cf4d039934f39914363a21b160677</url>
      <link>https://freestrokes.tistory.com</link>
    </image>
    <item>
      <title>Bun 이용하여 Monorepo 환경 구성하기</title>
      <link>https://freestrokes.tistory.com/187</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;b&gt;Bun 이용하여 Monorepo 환경 구성하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Bun 런타임의 특징에 대해 알아보고 Monorepo 환경을 구성하는 방법에 대해 정리한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Bun 알아보기&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;figure id=&quot;og_1737971787992&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;Bun &amp;mdash; A fast all-in-one JavaScript runtime&quot; data-og-description=&quot;Bun implements the Web-standard APIs you know and love, including fetch, ReadableStream, Request, Response, WebSocket, and FormData.&quot; data-og-host=&quot;bun.sh&quot; data-og-source-url=&quot;https://bun.sh/&quot; data-og-url=&quot;https://bun.sh/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://bun.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bun.sh/&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;Bun &amp;mdash; A fast all-in-one JavaScript runtime&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Bun implements the Web-standard APIs you know and love, including fetch, ReadableStream, Request, Response, WebSocket, and FormData.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bun.sh&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Bun은 JavaScript 및 TypeScript 기반의 애플리케이션을 위한 툴킷이며 Node.js 및 Deno의 대안으로 개발되었습니다. bundler, transpiler, script runner, task runner 등의 수행이 가능한 런타임 입니다. 다음과 같은 특징을 가지고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Low-level 언어인 Zig로 개발되었고 JavaScriptCore 엔진 기반 최적화를 통해 Node.js 보다 더 빠르게 동작.&lt;/li&gt;
&lt;li&gt;JavaScript 및 TypeScript를 지원하여 .jsx, .tsx, .ts 파일을 대상으로 transpiler 동작.&lt;/li&gt;
&lt;li&gt;ESM(ES Module), CommonJS 및 npm 패키지 호환성 지원.&lt;/li&gt;
&lt;li&gt;fetch, WebSocket, ReadableStream과 같은 웹 표준 API 지원.&lt;/li&gt;
&lt;li&gt;bun:test 이용한 내장 테스트 러너 지원.&lt;/li&gt;
&lt;li&gt;Node.js와의 호환성을 바탕으로 Node 스타일의 모듈 resolution 지원.&lt;/li&gt;
&lt;li&gt;빠른 속도의 SQLite3 클라이언트 제공.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;binary lockfile을 사용하여 json 또는 yaml 기반의 lockfile 보다 더 빠르게 동작.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1.1. Bun 런타임&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Bun은 런타임 이상의 목표를 가지고 JavaScript 및 TypeScript 기반의 앱을 빌드하기 위한 통합 인프라 툴킷이 되는 것을 목표로 하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JavaScript는 프로그래밍 언어 수준의 스펙으로 정의되었고 이를 실행하기 위한 구글의 V8 또는 애플의 JavaScriptCore와 같은 엔진이 개발 되었습니다. 또한 JavaScript를 브라우저와 같은 환경에서 실행하기 위한 런타임이 요구 되었습니다. 브라우저에서는 JavaScript 런타임과 함께 window 객체와 같은 웹 관련 API를 제공하였고 이를 이용하여 웹 페이지에서의 컨텍스트 및 동적인 동작을 구현할 수 있게 되었습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;마찬가지로 JavaScript 런타임 중 하나인 Node.js는 브라우저가 아닌 서버와 같은 환경에서 사용할 수 있도록 만들어졌습니다. 그러나 Node.js에는 fetch, ES Module 등의 JavaScript 표준이 정의되어 있지 않았고 JavaScript의 표준 모듈 시스템이 아닌 CommomJS 기반의 모듈 resolution 알고리즘이 적용되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Bun은 이러한 JavaScript 런타임 및 웹 표준 관점에서의 호환성을 지원하는 것을 목표로 개발 되었습니다. 따라서 ES 모듈, Node.js 모듈, CommonJS에 대한 지원과 함께 Node.js를 대체하는 보다 빠르고 가벼운 툴킷의 역할을 하는 것을 목표로 하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Bun 설치 및 실행하기&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1. Bun 설치하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음 명령어를 이용하여 Bun을 설치해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1737196214225&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# cURL
# for macOS, Linux, and WSL
curl -fsSL https://bun.sh/install | bash 

# to install a specific version
curl -fsSL https://bun.sh/install | bash -s &quot;bun-v1.1.44&quot;

# Homebrew
# for macOS and Linux
brew install oven-sh/bun/bun&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;설치한 Bun을 이용하여 프로젝트를 생성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1737196160147&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bun init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;TypeScript 사용 시 Type Definition 처리를 위한 @types/bun 패키지를 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1737196319783&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bun add -D @types/bun&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.2 Bun HTTP Server 실행하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Bun을 이용하여 scaffolding을 한 이후에 생성된 index.ts 파일을 다음과 같이 수정해줍니다. Bun의 빌트인 API인 Bun.serve를 이용하여 간단한 HTTP Server를 실행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1737284987034&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const server = Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response(&quot;Bun!&quot;);
  },
});

console.log(`Listening on http://localhost:${server.port} ...`);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음과 같이 index.ts 파일을 실행하면 HTTP Server가 실행되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1737285444352&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bun index.ts

Listening on http://localhost:3000 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;1148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKWdKz/btsLSSO7rcn/AGwj14JimUElcWUHCWdnIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKWdKz/btsLSSO7rcn/AGwj14JimUElcWUHCWdnIk/img.png&quot; data-alt=&quot;Bun HTTP Server in Browser&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKWdKz/btsLSSO7rcn/AGwj14JimUElcWUHCWdnIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKWdKz%2FbtsLSSO7rcn%2FAGwj14JimUElcWUHCWdnIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;480&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;1148&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Bun HTTP Server in Browser&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.3. Bun Package Manager&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;Bun은 npm&lt;/span&gt;, &lt;span&gt;yarn&lt;/span&gt;, &lt;span&gt;pnpm &lt;/span&gt;보다 뛰어난 성능을 보여주는 빠르고 효율적인 패키지 매니저를 제공합니다. 또한 의존성 그래프를 이용하여 package.json 파일에 명시된 의존성 문제들을 처리합니다. 각 하위 dependency 관계에 맞는 버전을 찾고 필요한 모듈과 라이브러리를 내려받아 설치합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Bun은 dependency에 대한 install 과정에서 최상단 node_modules 디렉터리를 사용하게 되는데, 같은 dependency를 가진 경우에 대해 심볼릭 링크(Symlinks)를 사용하여 처리합니다. 여러 패키지에서 동일한 dependency를 가진 경우엔 node_modules에 설치된 dependency를 호이스팅하여 중복 install을 방지 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.4. Bun 모듈 시스템&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Bun에서는 ES Module을 기본적으로 지원하기 때문에 별다른 설정 없이 사용할 수 있습니다. 반면에 Node.js에서는 ES Module을 사용하기 위해서는 package.json에 &quot;type&quot;: &quot;module&quot; 설정이 추가로 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Bun Workspaces&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.1. Bun Workspaces 이용하여 Monorepo 환경 구성 하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;bun 프로젝트를 구성한 후에 package.json을 다음과 같이 설정해줍니다. &quot;private&quot;: true 속성은 root 패키지가 npm으로 발행되는 것을 방지 합니다. workspaces 속성에는 관리할 하위 패키지 경로를 작성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738312957296&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;bun-monorepo-examples&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;private&quot;: true,
  &quot;module&quot;: &quot;index.ts&quot;,
  &quot;type&quot;: &quot;module&quot;,
  &quot;workspaces&quot;: [&quot;packages/*&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;workspaces 속성은 경우에 따라 다음과 같이 패키지를 개별적으로 작성해줄 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738313474580&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;workspaces&quot;: [
  &quot;packages/package-a&quot;,
  &quot;packages/package-b&quot;
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음으로 packages 디렉터리 경로에 하위 패키지를 생성하여 다음과 같은 형태로 구성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738313260620&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.
├── README.md
├── bun.lock
├── package.json
├── tsconfig.json
└── packages
    ├── package-a
    │   ├── index.ts
    │   ├── package.json
    │   └── tsconfig.json
    └── package-b
        ├── index.ts
        ├── package.json
        └── tsconfig.json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;package-a의 package.json 파일은 다음과 같이 구성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738314385377&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;package-a&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;main&quot;: &quot;index.ts&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;bun build index.ts --outdir dist&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;package-b의 package.json 파일은 다음과 같이 구성해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738314472736&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;package-b&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;main&quot;: &quot;index.ts&quot;,
  &quot;dependencies&quot;: {
    &quot;package-a&quot;: &quot;workspace:*&quot;
  },
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;bun build index.ts --outdir dist&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;dependencies 속성에 package-a에 대한 의존성을 설정해줍니다. workspace 사이의 의존성을 설정해주려면 &quot;workspace:*&quot; 구문을 사용해줍니다. 위와 같이 설정하면 Bun은 의존성에 대한 버전을 찾아 링크를 설정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;구성이 완료되면 dependency install을 위해 아래와 같이 실행해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738315314752&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bun install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Bun의 Workspace는 Yarn을 이용한 것과 동일하게 Monorepo 환경을 구성할 수 있지만, 아직 특정 패키지에 대한 의존성을 설치할 때 필요한 CLI가 지원되진 않습니다. Yarn에서는 &quot;yarn workspace &amp;lt;workspace_name&amp;gt; &amp;lt;command&amp;gt;&quot; 와 같이 특정 패키지에 대한 CLI를 지원하지만 Bun에서는 아직 지원되지 않습니다. 따라서, Bun에서 특정 패키지의 설치가 필요한 경우엔 다음과 같이 해당 패키지의 디렉터리까지 이동한 후에 패키지 설치를 실행해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738315701128&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd packages/package-a
bun install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또 다른 방법으로는 다음과 같이 --filter 옵션을 사용하여 특정 패키지에 대한 설치를 실행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738316563765&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// package-a 패키지만 설치
bun install --filter &quot;package-a&quot;

// package-b 패키지만 설치
bun install --filter &quot;package-b&quot;

// 모든 패키지 설치
bun install --filter &quot;package-*&quot;

// package-a, package-b 패키지를 각각 설치
bun install --filter &quot;package-a&quot; --filter &quot;package-b&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;이상으로 Bun의 특징과 함께 Workspace를 이용하여 Monorepo 환경을 구성하는 방법에 대해 알아봤습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;※ Reference&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bun.sh, What is Bun? | Bun Docs, &lt;a href=&quot;https://bun.sh/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://bun.sh/docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;www.jeong-min.com, Bun&amp;nbsp;한&amp;nbsp;번&amp;nbsp;써보자,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://www.jeong-min.com/51-bun/&quot;&gt;https://www.jeong-min.com/51-bun/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;helloinyong.tistory.com, Bun&amp;nbsp;1.0&amp;nbsp;릴리즈&amp;nbsp;후,&amp;nbsp;서비스&amp;nbsp;개발에&amp;nbsp;문제&amp;nbsp;없을지&amp;nbsp;리서치&amp;nbsp;해본&amp;nbsp;결과, &lt;a href=&quot;https://helloinyong.tistory.com/353&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://helloinyong.tistory.com/353&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;johnny-developer-story.tistory.com, Bun&amp;nbsp;런타임:&amp;nbsp;Node.js를&amp;nbsp;대체할&amp;nbsp;혁신적인&amp;nbsp;선택&amp;nbsp;(1):&amp;nbsp;Bun&amp;nbsp;런타임의&amp;nbsp;개념과&amp;nbsp;주요&amp;nbsp;특징&amp;nbsp;자세히&amp;nbsp;알아보기, &lt;a href=&quot;https://johnny-developer-story.tistory.com/49&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://johnny-developer-story.tistory.com/49&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;johnny-developer-story.tistory.com, Bun&amp;nbsp;런타임:&amp;nbsp;Node.js를&amp;nbsp;대체할&amp;nbsp;혁신적인&amp;nbsp;선택&amp;nbsp;(3):&amp;nbsp;Bun과&amp;nbsp;Node.js&amp;nbsp;차이점과&amp;nbsp;선택&amp;nbsp;가이드, &lt;a href=&quot;https://johnny-developer-story.tistory.com/51&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://johnny-developer-story.tistory.com/51&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;medium.com/@hong009319, 자바스크립트의 표준 정의 : CommonJS vs ES Modules, &lt;a href=&quot;https://medium.com/@hong009319/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%91%9C%EC%A4%80-%EC%A0%95%EC%9D%98-commonjs-vs-es-modules-306e5f0a74b1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@hong009319/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%91%9C%EC%A4%80-%EC%A0%95%EC%9D%98-commonjs-vs-es-modules-306e5f0a74b1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/React</category>
      <category>architecture</category>
      <category>BUN</category>
      <category>development</category>
      <category>IT</category>
      <category>javascript</category>
      <category>Runtime</category>
      <category>typescript</category>
      <category>vite</category>
      <category>web</category>
      <author>freestrokes</author>
      <guid isPermaLink="true">https://freestrokes.tistory.com/187</guid>
      <comments>https://freestrokes.tistory.com/187#entry187comment</comments>
      <pubDate>Fri, 31 Jan 2025 18:44:19 +0900</pubDate>
    </item>
    <item>
      <title>Vite + React App 생성하기</title>
      <link>https://freestrokes.tistory.com/186</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;b&gt;Vite + React App 생성하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Vite를 이용하여 React 애플리케이션을 생성하고 실행하는 방법에 대해 정리한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1. Vite 알아보기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;figure id=&quot;og_1723288869067&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;Vite&quot; data-og-description=&quot;Next Generation Frontend Tooling&quot; data-og-host=&quot;vitejs.dev&quot; data-og-source-url=&quot;https://vitejs.dev/&quot; data-og-url=&quot;https://vitejs.dev&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hCYvn/hyWOlLV2Kd/hcrnW5bh5ml6xIlb2kqOhK/img.png?width=2600&amp;amp;height=1302&amp;amp;face=0_0_2600_1302,https://scrap.kakaocdn.net/dn/rI8uv/hyWKBv8rR1/IGkhgeYodWpXtZTk1BNKt1/img.png?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640&quot;&gt;&lt;a href=&quot;https://vitejs.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://vitejs.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hCYvn/hyWOlLV2Kd/hcrnW5bh5ml6xIlb2kqOhK/img.png?width=2600&amp;amp;height=1302&amp;amp;face=0_0_2600_1302,https://scrap.kakaocdn.net/dn/rI8uv/hyWKBv8rR1/IGkhgeYodWpXtZTk1BNKt1/img.png?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640');&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;Vite&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Next Generation Frontend Tooling&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;vitejs.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Vite는 빠르고 간결한 모던 웹 프로젝트 개발 경험에 초점을 맞춰 탄생한 빌드 도구입니다. 다음과 같이 주요한 두 가지 특징을 갖고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 시 네이티브 ES Module을 넘어 더욱 다양한 기능을 제공. &lt;br /&gt;ex) HMR (Hot Module Replacement)&lt;/li&gt;
&lt;li&gt;번들링 시, Rollup 기반의 다양한 빌드 커맨드를 사용할 수 있음. &lt;br /&gt;이는 높은 수준으로 최적화된 정적(Static) 리소스들을 배포할 수 있게끔 하며, 미리 정의된 설정(Pre-configured)을 제공.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;또한 Vite는 다음과 같은 템플릿을 지원합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;JavaScript&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;vanilla&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;vanilla-ts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;vue&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;vue-ts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;react&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;react-ts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;preact&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;preact-ts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;lit&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;lit-ts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;svelte&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;svelte-ts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;solid&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;solid-ts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;qwik&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;qwik-ts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.1. Vite를&amp;nbsp;사용해야&amp;nbsp;하는&amp;nbsp;이유&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;기존에 React App을 생성하기 위해선 CRA( Create React App)를 사용하였습니다. CRA는 JavaScript 기반의 Webpack을 사용하여 모듈 번들링을 진행하는데 JavaScript는 기본적으로 interpreted (or just-in-time compiled) 언어이기 때문에 속도가 느려지는 문제가 발생했습니다. 번들링 해야하는 JavaScript 모듈이 많아질수록 병목 현상이 발생하고 생산성에 영향을 주게 되었습니다.&amp;nbsp;이와 같은 문제를 해결하기 위해 Go 언어를 사용해서 Esbuild라는 빌드 툴을 만들었고, 이렇게 만들어진 Esbuild를 기반으로 Vite가 만들어지게 되었습니다. (Esbuild는 Webpack과 같은 기존 번들러와 대비하여 10-100배 빠른 속도를 제공합니다)&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2. Vite 프로젝트 생성하고 실행하기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;Vite 프로젝트 생성을 위해서는 사전에 18+ 또는 20+ 버전의 Node.js가 설치되어 있어야 합니다. 다음 명령어를 실행하여 Vite 프로젝트를 생성해줍니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// npm
npm create vite@latest

// yarn
yarn create vite&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;프로젝트 이름이나 사용할 템플릿을 지정하여 생성하려는 경우엔 다음과 같이 실행해줍니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// TypeScript React 템플릿 생성 (npm)
npm create vite@latest {PROJECT_NAME} -- --template react-ts

// TypeScript React 템플릿 생성 (yarn)
yarn create vite {PROJECT_NAME} --template react-ts

// 프로젝트 이름 없이 현재 경로에 생성 (npm)
npm create vite@latest . -- --template react-ts

// 프로젝트 이름 없이 현재 경로에 생성 (yarn)
yarn create vite . --template react-ts&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;yarn create v1.22.22
[1/4]    Resolving packages...
[2/4]    Fetching packages...
[3/4]    Linking dependencies...
[4/4]    Building fresh packages...
success Installed &quot;create-vite@5.5.2&quot; with binaries:
      - create-vite
      - cva

Scaffolding project in /vite-project/yarn-create-vite...

Done. Now run:

  cd vite-project
  yarn
  yarn dev

✨  Done in 1.63s.&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;프로젝트가 생성되면 패키지 설치 후에 실행해줍니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;cd vite-project
yarn
yarn dev&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;Vite 프로젝트의 기본 포트는 5173 입니다. 브라우저에서 다음과 같이 앱이 실행되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TWFS2/btsI77t7o39/RyT7fSBRo7RK27LgKKA4M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TWFS2/btsI77t7o39/RyT7fSBRo7RK27LgKKA4M0/img.png&quot; data-alt=&quot;Vite Project in Browser&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TWFS2/btsI77t7o39/RyT7fSBRo7RK27LgKKA4M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTWFS2%2FbtsI77t7o39%2FRyT7fSBRo7RK27LgKKA4M0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;497&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Vite Project in Browser&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;이상으로 Vite를 이용하여 React 프로젝트를 생성하고 실행하는 방법에 대해 알아봤습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;※ Reference&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ko.vitesjs.dev, 시작하기 | Vite, &lt;a href=&quot;https://ko.vitejs.dev/guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vitejs.dev/guide/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;velog.io/@codns1223, [React]&amp;nbsp;Vite&amp;nbsp;사용해서&amp;nbsp;React&amp;nbsp;app&amp;nbsp;생성, &lt;a href=&quot;https://velog.io/@codns1223/React-Vite-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-React-app-%EC%83%9D%EC%84%B1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@codns1223/React-Vite-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-React-app-%EC%83%9D%EC%84%B1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;velog.io/@homile, [React]&amp;nbsp;Vite를&amp;nbsp;사용해보자,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://velog.io/@homile/react-vite&quot;&gt;https://velog.io/@homile/react-vite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/React</category>
      <category>development</category>
      <category>frontend</category>
      <category>Internet</category>
      <category>IT</category>
      <category>javascript</category>
      <category>Programming</category>
      <category>react</category>
      <category>typescript</category>
      <category>vite</category>
      <category>web</category>
      <author>freestrokes</author>
      <guid isPermaLink="true">https://freestrokes.tistory.com/186</guid>
      <comments>https://freestrokes.tistory.com/186#entry186comment</comments>
      <pubDate>Sun, 18 Aug 2024 18:56:41 +0900</pubDate>
    </item>
    <item>
      <title>JPA N+1 문제 해결하기</title>
      <link>https://freestrokes.tistory.com/183</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;b&gt;JPA N+1 문제 해결하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;JPA 사용시 발생하는 N+1 문제를 해결하는 방법에 대해 정리한 내용입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1. N+1 문제가 발생하는 이유&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;N+1 문제는 1:N 또는 N:1 연관관계가 설정된 엔티티를 조회했을 때 발생하는 문제입니다. 연관관계가 설정된 엔티티를 한 번에 조회하지 않고 조회된 데이터의 개수(N) 만큼 연관관계의 엔티티에 대해 추가로 조회 쿼리가 실행되는 문제입니다. 조회한 엔티티의 데이터 개수가 10개이면 연관관계의 엔티티를 조회하는 SQL도 10번 실행되어 10번 + 1번 조회하게 됩니다. 이러한 문제는 많은 양의 쿼리가 발생했을 때 성능 저하의 원인이 됩니다.&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&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.1. 즉시 로딩에서 발생하는 &lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;N+1 문제&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉시 로딩이 설정된 연관관계의 엔티티를 조회하는 경우에 N+1 문제가 발생할 수 있습니다. &lt;span style=&quot;text-align: start;&quot;&gt;JPA Repository의 findAll()과 같은 메서드를 사용하여 엔티티를 조회할 때&amp;nbsp;&lt;/span&gt;JPQL이 SQL을 생성하여 동작하게 되는데, 이러한 경우에 JPQL은 즉시 로딩이나 지연 로딩과 같은 Fetch 전략을 무시하고 생성한 SQL을 실행하게 됩니다. 먼저 조회한 엔티티에 연관관계가 설정된 다른 엔티티가 있을 때 SQL이 추가로 생성되고 실행되어 N+1 문제가 발생하게 됩니다.&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&gt;즉시 로딩에서의 N+1 문제를 확인하기 위해 다음과 같이 Board, BoardComment 엔티티의 연관관계를 구성해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697272130705&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board&quot;)
public class Board {

    @OneToMany(mappedBy = &quot;board&quot;, fetch = FetchType.EAGER)
    private List&amp;lt;BoardComment&amp;gt; boardComments;

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1697272170875&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board_comment&quot;)
public class BoardComment {

    @ManyToOne
    @JoinColumn(name = &quot;board_id&quot;)
    private Board board;

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;테스트 코드를 작성하여 구성한 엔티티를 조회해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698679255834&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RunWith(SpringRunner.class)
@SpringBootTest()
public class BoardServiceTests {

    @Autowired
    private BoardRepository boardRepository;

    @Autowired
    private BoardCommentRepository boardCommentRepository;

    @Before
    public void setup() {
        for (int i = 0; i &amp;lt; 5; i++) {
            Board board = Board.builder()
                .title(&quot;board title test&quot;)
                .content(&quot;board content test&quot;)
                .author(&quot;board author test&quot;)
                .build();

            boardRepository.save(board);

            BoardComment boardComment = BoardComment.builder()
                .board(board)
                .content(&quot;board comment test &quot; + i)
                .author(&quot;board comment test &quot; + i)
                .build();

            boardCommentRepository.save(boardComment);
        }
    }

    @After
    public void cleanAll() {
        boardCommentRepository.deleteAll();
        boardRepository.deleteAll();
    }

    @Test
    public void contextLoads() {
        List&amp;lt;Board&amp;gt; boards = boardRepository.findAll();
        assertThat(boards.size(), is(5));
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음과 같이 쿼리가 추가로 실행되어 N+1 문제가 발생하는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697450020326&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hibernate: select board0_.board_id as board_id1_0_, board0_.author as author2_0_, board0_.content as content3_0_, board0_.title as title4_0_ from board board0_
Hibernate: select boardcomme0_.board_id as board_id4_1_0_, boardcomme0_.board_comment_id as board_co1_1_0_, boardcomme0_.board_comment_id as board_co1_1_1_, boardcomme0_.author as author2_1_1_, boardcomme0_.board_id as board_id4_1_1_, boardcomme0_.content as content3_1_1_ from board_comment boardcomme0_ where boardcomme0_.board_id=?
Hibernate: select boardcomme0_.board_id as board_id4_1_0_, boardcomme0_.board_comment_id as board_co1_1_0_, boardcomme0_.board_comment_id as board_co1_1_1_, boardcomme0_.author as author2_1_1_, boardcomme0_.board_id as board_id4_1_1_, boardcomme0_.content as content3_1_1_ from board_comment boardcomme0_ where boardcomme0_.board_id=?
Hibernate: select boardcomme0_.board_id as board_id4_1_0_, boardcomme0_.board_comment_id as board_co1_1_0_, boardcomme0_.board_comment_id as board_co1_1_1_, boardcomme0_.author as author2_1_1_, boardcomme0_.board_id as board_id4_1_1_, boardcomme0_.content as content3_1_1_ from board_comment boardcomme0_ where boardcomme0_.board_id=?
Hibernate: select boardcomme0_.board_id as board_id4_1_0_, boardcomme0_.board_comment_id as board_co1_1_0_, boardcomme0_.board_comment_id as board_co1_1_1_, boardcomme0_.author as author2_1_1_, boardcomme0_.board_id as board_id4_1_1_, boardcomme0_.content as content3_1_1_ from board_comment boardcomme0_ where boardcomme0_.board_id=?
Hibernate: select boardcomme0_.board_id as board_id4_1_0_, boardcomme0_.board_comment_id as board_co1_1_0_, boardcomme0_.board_comment_id as board_co1_1_1_, boardcomme0_.author as author2_1_1_, boardcomme0_.board_id as board_id4_1_1_, boardcomme0_.content as content3_1_1_ from board_comment boardcomme0_ where boardcomme0_.board_id=?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.2. 지연 로딩에서 발생하는&amp;nbsp;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;N+1 문제&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;지연 로딩이 설정된 연관관계의 엔티티를 조회하기 위해 JPQL이 SQL을 생성하여 실행하는 경우엔 N+1 문제가 발생하지 않습니다. 하지만 비즈니스 로직에서 조회한 엔티티와 연관관계가 설정된 엔티티를 탐색할 때 N+1 문제가 발생하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;지연 로딩에서의 N+1 문제를 확인하기 위해 다음과 같이 Board, BoardComment 엔티티의 연관관계를 구성해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697451318953&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board&quot;)
public class Board {

    @OneToMany(mappedBy = &quot;board&quot;)
    private List&amp;lt;BoardComment&amp;gt; boardComments;

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1697451323758&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board_comment&quot;)
public class BoardComment {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;board_id&quot;)
    private Board board;

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;엔티티 조회 후 연관관계의 엔티티 탐색을 위해 다음과 같이 Mock 서비스를 만들어줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698740905268&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class BoardMockService {

    private final BoardRepository boardRepository;

    @Transactional(readOnly = true)
    public void getMockBoards() {
        boardRepository.findAll().forEach(board -&amp;gt; {
            board.getBoardComments().forEach(boardComment -&amp;gt;
                System.out.println(boardComment.getContent())
            );
        });
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;테스트 코드에서는 위에서 작성한 getMockBoards() 메서드를 호출해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698740986208&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@RunWith(SpringRunner.class)
@SpringBootTest()
public class BoardServiceTests {

    @Test
    public void contextLoads() {
        boardMockService.getMockBoards();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;지연 로딩에서는 비즈니스 로직에서 하위 엔티티를 탐색할 때 추가로 쿼리가 실행되는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697451479132&quot; class=&quot;bash&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Hibernate: select board0_.board_id as board_id1_0_, board0_.author as author2_0_, board0_.content as content3_0_, board0_.title as title4_0_ from board board0_
Hibernate: select boardcomme0_.board_id as board_id4_1_0_, boardcomme0_.board_comment_id as board_co1_1_0_, boardcomme0_.board_comment_id as board_co1_1_1_, boardcomme0_.author as author2_1_1_, boardcomme0_.board_id as board_id4_1_1_, boardcomme0_.content as content3_1_1_ from board_comment boardcomme0_ where boardcomme0_.board_id=?
Hibernate: select boardcomme0_.board_id as board_id4_1_0_, boardcomme0_.board_comment_id as board_co1_1_0_, boardcomme0_.board_comment_id as board_co1_1_1_, boardcomme0_.author as author2_1_1_, boardcomme0_.board_id as board_id4_1_1_, boardcomme0_.content as content3_1_1_ from board_comment boardcomme0_ where boardcomme0_.board_id=?
Hibernate: select boardcomme0_.board_id as board_id4_1_0_, boardcomme0_.board_comment_id as board_co1_1_0_, boardcomme0_.board_comment_id as board_co1_1_1_, boardcomme0_.author as author2_1_1_, boardcomme0_.board_id as board_id4_1_1_, boardcomme0_.content as content3_1_1_ from board_comment boardcomme0_ where boardcomme0_.board_id=?
Hibernate: select boardcomme0_.board_id as board_id4_1_0_, boardcomme0_.board_comment_id as board_co1_1_0_, boardcomme0_.board_comment_id as board_co1_1_1_, boardcomme0_.author as author2_1_1_, boardcomme0_.board_id as board_id4_1_1_, boardcomme0_.content as content3_1_1_ from board_comment boardcomme0_ where boardcomme0_.board_id=?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2. N+1 문제 해결하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;2.1. BatchSize 사용하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;하이버네이트의 BatchSize 설정을 사용하여 N+1 문제를 해결할 수 있습니다. N개의 추가 쿼리를 실행하지 않고 설정한 BatchSize 크기 만큼 SQL에서 IN 구문을 사용하여 조회해옵니다. 데이터베이스에서 일반적으로 IN 구문의 최대 개수는 1000개로 되어 있기 때문에 BatchSize 값은 그 이하로 설정해줍니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;application.yml에서 BatchSize를 다음과 같이 설정해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696151582845&quot; class=&quot;bash&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;앞서 작성한 테스트 코드를 실행해보면 N개의 추가 쿼리 대신에 SQL의 IN 구문이 실행되는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698743013792&quot; class=&quot;bash&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hibernate: select board0_.board_id as board_id1_0_, board0_.author as author2_0_, board0_.content as content3_0_, board0_.title as title4_0_ from board board0_
Hibernate: select boardcomme0_.board_id as board_id4_1_1_, boardcomme0_.board_comment_id as board_co1_1_1_, boardcomme0_.board_comment_id as board_co1_1_0_, boardcomme0_.author as author2_1_0_, boardcomme0_.board_id as board_id4_1_0_, boardcomme0_.content as content3_1_0_ from board_comment boardcomme0_ where boardcomme0_.board_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.2. &lt;b&gt;Fetch Join 사용하기&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;JPQL을 이용하여 Fetch Join을 사용하면&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;연관된 엔티티나 컬렉션을 같이 조회 할 수 있습니다. Fetch Join을 이용하면 객체 그래프를 한 번에 조회 할 수 있고 연관관계의 엔티티도 영속성 컨텍스트 관리 대상에 포함되게 됩니다. 일반 조인은 지정된 SELECT 결과만 가져올 수 있지만 패치 조인을 이용하면 연관관계의 엔티티나 컬렉션을 조회할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Fetch Join은 다음과 같은 특징이 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;SQL을 이용하여 연관관계의 엔티티들을 한 번에 조회하며 성능 최적화에 사용.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;JPQL을 이용하기 때문에&amp;nbsp;엔티티에 적용한 Fetch 전략을 무시하고 SQL을 실행.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;SQL에서 Fetch Join 구문은 Inner Join 구문으로 변경되어 실행.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;쿼리 한 번에 모든 데이터를 조회하기 때문에 Paging API(Pageable) 사용 불가.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;SQL에서 JOIN 이후에 WHERE 구문이 필터링 되는데 객체와 DB의 일관성이 유지되지 않아 별칭(alias) 사용 불가.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;데이터 정합성 문제로 &lt;/span&gt;두 개 이상의 엔티티나 컬렉션을 대상으로는 사용 불가.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Fetch Join을 사용하기 위해 다음과 같이 repository에 메서드를 작성해줍니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699516981183&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public interface BoardRepository extends JpaRepository&amp;lt;Board, String&amp;gt; {

    @Query(&quot;SELECT b FROM Board b JOIN FETCH b.boardComments&quot;)
    List&amp;lt;Board&amp;gt; findAllByFetchJoin();

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;테스트 코드에서는 위에서 작성한 findAllByFetchJoin() 메서드를 호출해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699517111763&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RunWith(SpringRunner.class)
@SpringBootTest()
public class BoardServiceTests {

    @Test
    public void contextLoads() {
        List&amp;lt;Board&amp;gt; boards = boardRepository.findAllByFetchJoin();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;테스트 코드를 실행해보면 연관관계의 엔티티를 Inner Join으로 조회해오는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696151582845&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Hibernate: select boardentit0_.board_id as board_id1_0_0_, boardcomme1_.board_comment_id as board_co1_1_1_, boardentit0_.author as author2_0_0_, boardentit0_.content as content3_0_0_, boardentit0_.title as title4_0_0_, boardcomme1_.author as author2_1_1_, boardcomme1_.board_id as board_id4_1_1_, boardcomme1_.content as content3_1_1_, boardcomme1_.board_id as board_id4_1_0__, boardcomme1_.board_comment_id as board_co1_1_0__ from board boardentit0_ inner join board_comment boardcomme1_ on boardentit0_.board_id=boardcomme1_.board_id&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.3. EntityGraph 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;EntityGraph를 사용하면 Fetch Join를 사용한 경우와 마찬가지로 &lt;span style=&quot;text-align: start;&quot;&gt;연관된 엔티티나 컬렉션을 같이 조회 할 수 있습니다. JPQL과 함께 사용하며&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;attributePaths에 쿼리 수행 시 조회할 필드를 지정해줍니다. 지정된 필드는 지연 로딩이 아닌 즉시 로딩으로 조회됩니다. EntityGraph는 Fetch Join과는 다르게 Outer Join이 사용되어 동작합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;EntityGraph를 사용하기 위해 다음과 같이 repository에 메서드를 작성해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699621965748&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Repository
public interface BoardRepository extends JpaRepository&amp;lt;Board, String&amp;gt; {

    @EntityGraph(
        attributePaths = {&quot;boardComments&quot;},
        type = EntityGraph.EntityGraphType.LOAD
    )
    @Query(&quot;SELECT b FROM Board b&quot;)
    List&amp;lt;Board&amp;gt; findAllByEntityGraph();

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;@EntityGraph 어노테이션의 type 프로퍼티는 다음과 같이 2가지 패치 유형을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;EntityGraph.EntityGraphType.FETCH&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;attributePaths에 명시한 엔티티는 EAGER로 패치, 나머지 엔티티는 LAZY로 패치.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;EntityGraph.EntityGraphType.LOAD&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;attributePaths에 명시한 엔티티는 EAGER로 패치,&lt;/span&gt; 나머지 엔티티는 각 엔티티에 명시한 FetchType 또는 기본 FetchType으로 패치. (@OneToMany, @ManyToMany 기본 패치 전략은 LAZY 이고 @OneToOne, &lt;span style=&quot;text-align: left;&quot;&gt;@ManyToOne&lt;/span&gt; 기본 패치 전략은 EAGER)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;테스트 코드에서는 위에서 작성한 findAllByEntityGraph() 메서드를 호출해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699622035005&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@RunWith(SpringRunner.class)
@SpringBootTest()
public class BoardServiceTests {

    @Test
    public void contextLoads() {
        List&amp;lt;Board&amp;gt; boards = boardRepository.findAllByEntityGraph();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;테스트 코드를 실행해보면 연관관계의 엔티티를 Outer Join으로 조회해오는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699620852949&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hibernate: select boardentit0_.board_id as board_id1_0_0_, boardcomme1_.board_comment_id as board_co1_1_1_, boardentit0_.author as author2_0_0_, boardentit0_.content as content3_0_0_, boardentit0_.title as title4_0_0_, boardcomme1_.author as author2_1_1_, boardcomme1_.board_id as board_id4_1_1_, boardcomme1_.content as content3_1_1_, boardcomme1_.board_id as board_id4_1_0__, boardcomme1_.board_comment_id as board_co1_1_0__ from board boardentit0_ left outer join board_comment boardcomme1_ on boardentit0_.board_id=boardcomme1_.board_id&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.4. Fetch Join 및 EntityGraph 사용 시 발생하는 카테시안 곱 문제&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Fetch Join 또는 EntityGraph를 사용한 경우엔 카테시안 곱(Cartesian Product)으로 인하여 중복 문제가 발생할 수 있습니다. &lt;/span&gt;&lt;span&gt;카테시안 곱은 연관관계의 엔티티에 Join 규칙을 사용하지 않아 결합 조건이 주어지지 않았을 때 모든 데이터의 경우의 수를 결합(M * N)하여 반환하는 문제입니다. 즉, 조인 조건이 없는 경우에 대한 두 테이블의 결합 결과를 반환해야 하기 때문에 존재하는 모든 행의 개수를 곱한 결과를 반환하게 됩니다. &lt;/span&gt;&lt;span&gt;카테시안 곱 문제를 해결하기 위해서는 다음과 같은 방법을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.4.1. JPQL에 DISTINCT 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;JPQL을 이용한 쿼리를 사용할 때 다음과 같이 DISTINCT를 사용하여 중복을 제거합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699781425055&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Repository
public interface BoardRepository extends JpaRepository&amp;lt;Board, String&amp;gt; {

    @Query(&quot;SELECT DISTINCT b FROM Board b JOIN FETCH b.boardComments&quot;)
    List&amp;lt;Board&amp;gt; findAllByFetchJoin();
    
    @EntityGraph(
        attributePaths = {&quot;boardComments&quot;},
        type = EntityGraph.EntityGraphType.LOAD
    )
    @Query(&quot;SELECT DISTINCT b FROM Board b&quot;)
    List&amp;lt;Board&amp;gt; findAllByEntityGraph();

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.4.2. 연관관계의 필드 타입에 Set 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Set은 중복을 허용하지 않기 때문에 중복 데이터가 들어가지 않지만 순서가 보장되지 않습니다. 이러한 경우엔 다음과 같이 연관관계의 필드에 LinkedHashSet을 사용하여 중복을 제거하고 순서를 보장 해줄 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699782359348&quot; class=&quot;java&quot; style=&quot;color: #2c3e50; text-align: left;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board&quot;)
public class Board {

    @OneToMany(mappedBy = &quot;board&quot;)
    private Set&amp;lt;BoardComment&amp;gt; boardComments = new LinkedHashSet&amp;lt;&amp;gt;();

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;이상으로 JPA 사용 시 발생하는 N+1 문제를 해결하는 방법에 대해 알아봤습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;※ References&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;김영한 지음, 『자바 ORM 표준 JPA 프로그래밍』, 에이콘 (2015), 13장 웹 애플리케이션과 영속성 관리 (p586 ~ p588)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;김영한 지음, 『자바 ORM 표준 JPA 프로그래밍』, 에이콘 (2015), 15장 고급 주제와 성능 최적화 (p675 ~ p681)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;jojoldu.tistory.com, JPA&amp;nbsp;N+1&amp;nbsp;문제&amp;nbsp;및&amp;nbsp;해결방안, &lt;a href=&quot;https://jojoldu.tistory.com/165&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jojoldu.tistory.com/165&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;dev-coco.tistory.com, [JPA]&amp;nbsp;N+1&amp;nbsp;문제&amp;nbsp;원인&amp;nbsp;및&amp;nbsp;해결방법&amp;nbsp;알아보기, &lt;a href=&quot;https://dev-coco.tistory.com/165&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev-coco.tistory.com/165&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;jh2021.tistory.com, JPA&amp;nbsp;n+1&amp;nbsp;문제는&amp;nbsp;왜&amp;nbsp;생기는걸까?, &lt;a href=&quot;https://jh2021.tistory.com/21&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jh2021.tistory.com/21&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;lucas-owner.tistory.com, 내가&amp;nbsp;보려고&amp;nbsp;만든&amp;nbsp;개발&amp;nbsp;(Tech)&amp;nbsp;blog, &lt;a href=&quot;https://lucas-owner.tistory.com/52&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://lucas-owner.tistory.com/52&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;velog.io/@sweet_sumin, JPA&amp;nbsp;N+1&amp;nbsp;이슈는&amp;nbsp;무엇이고,&amp;nbsp;해결책은&amp;nbsp;무엇인가요?, &lt;a href=&quot;https://velog.io/@sweet_sumin/JPA-N1-%EC%9D%B4%EC%8A%88%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%ED%95%B4%EA%B2%B0%EC%B1%85%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@sweet_sumin/JPA-N1-%EC%9D%B4%EC%8A%88%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%ED%95%B4%EA%B2%B0%EC%B1%85%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;maivve.tistory.com, [Spring]&amp;nbsp;JPA&amp;nbsp;N+1&amp;nbsp;문제에&amp;nbsp;대한&amp;nbsp;고찰.&amp;nbsp;(원인,&amp;nbsp;테스트,&amp;nbsp;해결방법), &lt;a href=&quot;https://maivve.tistory.com/340&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://maivve.tistory.com/340&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;loosie.tistory.com, [JPA]&amp;nbsp;페치&amp;nbsp;조인(Fetch&amp;nbsp;Join)의&amp;nbsp;일관성&amp;nbsp;-&amp;nbsp;대상에&amp;nbsp;별칭&amp;nbsp;사용하면&amp;nbsp;안되는&amp;nbsp;이유, &lt;a href=&quot;https://loosie.tistory.com/750#%ED%8E%98%EC%B9%98_%EC%A1%B0%EC%9D%B8(Fetch_Join)%EC%97%90%EC%84%9C_%EB%8C%80%EC%83%81%EC%97%90%EA%B2%8C_%EB%B3%84%EC%B9%AD%EC%9D%84_%EA%B1%B8%EB%A9%B4_%EC%95%88%EB%90%98%EB%8A%94_%EC%9D%B4%EC%9C%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://loosie.tistory.com/750#%ED%8E%98%EC%B9%98_%EC%A1%B0%EC%9D%B8(Fetch_Join)%EC%97%90%EC%84%9C_%EB%8C%80%EC%83%81%EC%97%90%EA%B2%8C_%EB%B3%84%EC%B9%AD%EC%9D%84_%EA%B1%B8%EB%A9%B4_%EC%95%88%EB%90%98%EB%8A%94_%EC%9D%B4%EC%9C%A0&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Backend/JPA</category>
      <category>backend</category>
      <category>development</category>
      <category>EntityGraph</category>
      <category>IT</category>
      <category>JPA</category>
      <category>N+1</category>
      <category>Programming</category>
      <category>spring</category>
      <category>springboot</category>
      <category>web</category>
      <author>freestrokes</author>
      <guid isPermaLink="true">https://freestrokes.tistory.com/183</guid>
      <comments>https://freestrokes.tistory.com/183#entry183comment</comments>
      <pubDate>Sun, 12 Nov 2023 18:57:48 +0900</pubDate>
    </item>
    <item>
      <title>JPA Infinite Recursion 해결하기</title>
      <link>https://freestrokes.tistory.com/177</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;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;b&gt;JPA Infinite Recursion 해결하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JPA 사용시 발생하는 Infinite Recursion을 해결하는 방법에 대해 정리한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1. Infinite Recursion (nested exception) 문제가 발생하는 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.1. 양방향 연관관계에서 발생하는 Infinite Recursion&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;JPA를 이용하여 양방향 연관관계를 구성한 경우에 발생하는데 컨트롤러에서는 JSON 형태로 값을 출력하기 위해 타입 변환을 필요로 합니다. 이 때 타입 변환이 필요한 엔티티의 필드가 다른 엔티티를 참조하고, 참조한 엔티티의 필드가 기존의 엔티티를 참조하거나 다른 엔티티를 참조하게 되면서 반복적인 재귀가 일어나 Infinite Recursion 문제가 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 Board, BoardComment 엔티티에 양방향 연관관계를 구성하고 조회해보면 Infinite Recursion 문제가 발생하고 이로 인하여 Jackson 라이브러리에서 JSONObject로 직렬화를 할 때 JsonMappingException 예외가 발생하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696408812299&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board&quot;)
public class Board {

    ...

    @OneToMany(
        mappedBy = &quot;board&quot;,
        cascade = {CascadeType.ALL},
        orphanRemoval = true
    )
    private List&amp;lt;BoardComment&amp;gt; boardComments;

    ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1696409723909&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board_comment&quot;)
public class BoardComment {

    ...

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;board_id&quot;)
    private Board board;

    ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1696409961923&quot; class=&quot;bash&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;ERROR 95745 --- [nio-8081-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception 
[Request processing failed; 
nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); 
nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) 
(through reference chain: 
com.freestrokes.domain.Board[&quot;boardComments&quot;]-&amp;gt;org.hibernate.collection.internal.PersistentBag[0]
-&amp;gt;com.freestrokes.domain.BoardComment[&quot;board&quot;]-&amp;gt;com.freestrokes.domain.Board[&quot;boardComments&quot;]
-&amp;gt;org.hibernate.collection.internal.PersistentBag[0]-&amp;gt;com.freestrokes.domain.BoardComment[&quot;board&quot;]) 
... ] with root cause

java.lang.StackOverflowError: null
	at java.base/java.lang.Exception.&amp;lt;init&amp;gt;(Exception.java:85) ~[na:na]
	at java.base/java.io.IOException.&amp;lt;init&amp;gt;(IOException.java:80) ~[na:na]
	at com.fasterxml.jackson.core.JacksonException.&amp;lt;init&amp;gt;(JacksonException.java:26) ~[jackson-core-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.core.JsonProcessingException.&amp;lt;init&amp;gt;(JsonProcessingException.java:25) ~[jackson-core-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.DatabindException.&amp;lt;init&amp;gt;(DatabindException.java:22) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.DatabindException.&amp;lt;init&amp;gt;(DatabindException.java:34) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.JsonMappingException.&amp;lt;init&amp;gt;(JsonMappingException.java:247) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:789) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774) ~[jackson-databind-2.13.5.jar:2.13.5]
    ...&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2. &lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;Infinite Recursion 해결하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.1. @JsonIgnore 사용하기&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;다음과 같이 엔티티에서 참조 대상이 되는 필드에 @JsonIgnore 어노테이션을 사용해줍니다. 이렇게 설정하면 Jackson 라이브러리에서 JSONObject 직렬화를 할 때 해당 필드를 제외하게 됩니다. 이 경우에 해당 필드는 null이 들어가기 때문에 DTO 변환 시 필요한 필드를 조회하여 매핑해줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1674548356785&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board&quot;)
public class Board {

    ...

    @JsonIgnore
    @OneToMany(
        mappedBy = &quot;board&quot;,
        cascade = {CascadeType.ALL},
        orphanRemoval = true
    )
    private List&amp;lt;BoardComment&amp;gt; boardComments;

    ...

}&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;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.2. &lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;@JsonManagedReference, &lt;/span&gt;&lt;/b&gt;@JsonBackReference 사용하기&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;다음과 같이 직렬화를 수행할 필드와 수행하지 않을 필드에 대해 각각 @JsonManagedReference, @JsonBackReference 어노테이션을 설정해줍니다. @OneToMany 필드에 @JsonManagedReference 어노테이션을 설정하여 정상적으로 직렬화를 수행하도록 하고, @ManyToOne 필드에 @JsonBackReference 어노테이션을 설정하여 직렬화 대상에서 제외하도록 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696498534577&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board&quot;)
public class Board {

    ...
    @JsonManagedReference
    @OneToMany(
        mappedBy = &quot;board&quot;,
        cascade = {CascadeType.ALL},
        orphanRemoval = true
    )
    private List&amp;lt;BoardComment&amp;gt; boardComments;

    ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1696498542051&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board_comment&quot;)
public class BoardComment {

    ...
    @JsonBackReference
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;board_id&quot;)
    private Board board;

    ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이상으로 JPA 사용 시 발생하는 Infinite Recursion 예외를 해결하는 방법에 대해 알아봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;※ References&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&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;www.baeldung.com, Jackson&amp;nbsp;&amp;ndash;&amp;nbsp;Bidirectional&amp;nbsp;Relationships, &lt;a href=&quot;https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;imbf.github.io, Jackson&amp;nbsp;Infinite&amp;nbsp;Recursion&amp;nbsp;Issue&amp;nbsp;With&amp;nbsp;JPA&amp;nbsp;Entity, &lt;a href=&quot;https://imbf.github.io/spring/2020/07/20/Jackson-Infinite-Recursion-Issue-With-JPA-Entity.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://imbf.github.io/spring/2020/07/20/Jackson-Infinite-Recursion-Issue-With-JPA-Entity.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;bellog.tistory.com, [Spring]&amp;nbsp;Could&amp;nbsp;not&amp;nbsp;write&amp;nbsp;JSON:&amp;nbsp;Infinite&amp;nbsp;recursion&amp;nbsp;(StackOverflowError)&amp;nbsp;이슈, &lt;a href=&quot;https://bellog.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://bellog.tistory.com/149&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;advenoh.tistory.com, Jackson에서&amp;nbsp;Infinite&amp;nbsp;Recursion&amp;nbsp;이슈&amp;nbsp;해결방법, &lt;a href=&quot;https://advenoh.tistory.com/53&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://advenoh.tistory.com/53&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;kim6394.tistory.com, [JPA]&amp;nbsp;Infinite&amp;nbsp;Recursion, &lt;a href=&quot;https://kim6394.tistory.com/272&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kim6394.tistory.com/272&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;velog.io/@2yeseul, [JPA]&amp;nbsp;Infinite&amp;nbsp;Recursion, &lt;a href=&quot;https://velog.io/@2yeseul/JPA-Infinite-Recursion&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@2yeseul/JPA-Infinite-Recursion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/JPA</category>
      <category>backend</category>
      <category>Boot</category>
      <category>development</category>
      <category>framework</category>
      <category>JPA</category>
      <category>orm</category>
      <category>Programming</category>
      <category>server</category>
      <category>spring</category>
      <category>web</category>
      <author>freestrokes</author>
      <guid isPermaLink="true">https://freestrokes.tistory.com/177</guid>
      <comments>https://freestrokes.tistory.com/177#entry177comment</comments>
      <pubDate>Sun, 12 Nov 2023 18:57:38 +0900</pubDate>
    </item>
    <item>
      <title>JPA LazyInitializationException 해결하기</title>
      <link>https://freestrokes.tistory.com/176</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;&lt;span style=&quot;font-size: 18.6667px; color: #666666;&quot;&gt;&lt;b&gt;JPA LazyInitializationException 해결하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;JPA 사용시 발생하는 LazyInitializationException을 해결하는 방법에 대해 정리한 내용입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1. LazyInitializationException 예외가 발생하는 이유&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.1. 지연 로딩(LAZY LOADING) 으로 인하여 발생하는 &lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;LazyInitializationException&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;JPA를 이용하여 객체의 양방향 연관관계를 구성한 후에 엔티티를 조회했을 때 LazyInitializationException 예외가 발생하는 경우가 있습니다. FetchType.LAZY 로 연관관계가 설정된 필드는 지연 로딩으로 조회 되는데 이 때 해당 필드의 엔티티를 프록시 객체로 조회하게 됩니다. 프록시 객체로 조회된 엔티티는 초기화 되어있지 않기 때문에 해당 엔티티를 조회하려 할 때 LazyInitializationException 예외가 발생하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;지연 로딩의 확인을 위해 다음과 같이 Board 와 BoardComment 클래스를 연관관계를 구성해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696237714504&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board&quot;)
public class Board {

    ...

    @JsonIgnore
    @OneToMany(
        mappedBy = &quot;board&quot;,
        cascade = {CascadeType.ALL},
        orphanRemoval = true
    )
    private List&amp;lt;BoardComment&amp;gt; boardComments;

    ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1696237933942&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@Entity(name = &quot;board_comment&quot;)
public class BoardComment {

    ...

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;board_id&quot;)
    private Board board;

    ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;서비스 레벨에서 Board 엔티티를 조회해보면 연관관계의 BoardComment 엔티티에서 &lt;/span&gt;LazyInitializationException 예외가 발생하는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1696238373975&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@Service
public class BoardService {

    private final BoardRepository boardRepository;

    ...
    
    public List&amp;lt;BoardDto.ResponseDto&amp;gt; getBoards() throws Exception {
        List&amp;lt;BoardDto.ResponseDto&amp;gt; boardsResponseDto = boardRepository.findAll()
            .stream()
            .map(board -&amp;gt; {
                return BoardDto.ResponseDto.builder()
                    .boardId(board.getBoardId())
                    .title(board.getTitle())
                    .content(board.getContent())
                    .author(board.getAuthor())
                    .boardComments(board.getBoardComments())
                    .build();
            })
            .collect(Collectors.toList());

        return boardsResponseDto;

    }

    ...

}&lt;/code&gt;&lt;/pre&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-filename=&quot;스크린샷 2023-10-02 오후 6.28.07.png&quot; data-origin-width=&quot;1932&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXwEo7/btsv8Q0KOFE/8EqgWzRoKaSCIURZc6LcUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXwEo7/btsv8Q0KOFE/8EqgWzRoKaSCIURZc6LcUk/img.png&quot; data-alt=&quot;LazyInitializationException&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXwEo7/btsv8Q0KOFE/8EqgWzRoKaSCIURZc6LcUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXwEo7%2Fbtsv8Q0KOFE%2F8EqgWzRoKaSCIURZc6LcUk%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;1932&quot; height=&quot;288&quot; data-filename=&quot;스크린샷 2023-10-02 오후 6.28.07.png&quot; data-origin-width=&quot;1932&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LazyInitializationException&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.2. &lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;프록시 객체&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;엔티티를 조회할 때 사용하지 않는 연관관계의 엔티티도 매번 함께 조회해온다면 효율적이지 않게 됩니다. 이러한 문제를 해결하기 위해 JPA에서는 지연 로딩을 이용하여 엔티티의 데이터베이스 조회를 지연하는 방법을 제공합니다. 지연 로딩을 사용하여 엔티티를 조회했을 때 실제 엔티티 객체 대신에 Mock Object 역할을 하는 가짜 객체를 사용하게 되는데 이것을 프록시 객체라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.3. &lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;즉시 로딩과 지연 로딩&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;즉시 로딩과 지연 로딩은 연관관계의 엔티티를 조회하기 위해 사용하는 방법입니다. 영속성 컨텍스트에 실제 엔티티를 모두 생성해 놓거나 필요한 시점에 생성하여 사용하는 것에 따른 차이가 있습니다.&lt;/span&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;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;즉시 로딩 (EAGER LOADING)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;@ManyToOne(fetch = FetchType.EAGER) 설정하여 사용.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;엔티티를 조회할 때 연관된 엔티티도 함께 조회.&lt;br /&gt;JPA에서는 즉시 로딩을 최적화하기 위해 연관된 엔티티에 대한 추가 쿼리를 실행하기 보다는 가능하면 조인 쿼리를 사용.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;@ManyToOne, @OneToOne 과 같이 연관된 엔티티가 하나인 경우에 대한 디폴트 패치 전략.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;지연 로딩 (LAZY LOADING)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;(fetch&amp;nbsp;=&amp;nbsp;FetchType.LAZY) 설정하여 사용.&lt;br /&gt;&lt;/span&gt;엔티티를 실제 사용할 때 조회.&lt;br /&gt;연관된 객체의 멤버변수에 프록시 객체를 넣어둠.&lt;br /&gt;실제 데이터가 필요한 시점에 데이터베이스를 조회해서 프록시 객체를 초기화.&lt;br /&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;@OneToMany, @ManyToMany 와 같이 연관된 엔티티가 여러 개인 경우에 대한 디폴트 패치 전략.&lt;/span&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2. &lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;LazyInitializationException 해결하기 (Anti-Pattern)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;LazyInitializationException 예외 해결을 위한 여러가지 방법이 있지만 다음의 방법들은 권장되지 않는 해결 방법입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;2.1. OSIV(Open Session In View) 활성화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Spring의 open-in-view 프로퍼티를 활성화 해줍니다. 기본값은 true 이며 활성화 되었을 땐 응답이 완료되거나 뷰가 렌더링 될 때까지 영속성 컨텍스트를 유지합니다. @Transactional 어노테이션이 사용된 메서드가 종료되어도 컨트롤러 리턴 시점까지 세션을 유지해줍니다. 이 방법은 &lt;span style=&quot;text-align: start;&quot;&gt;데이터베이스 커넥션을 계속 이어서 사용하고&amp;nbsp;&lt;/span&gt;세션의 수명이 길어짐에 따라 성능과 확장성 측면에서 좋지 않기 때문에 권장되지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696314492893&quot; class=&quot;bash&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;spring.jpa.open-in-view: true&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;color: #666666;&quot;&gt;&lt;b&gt;2.2. hibernate.enable_lazy_load_no_trans 활성화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Spring의 hibernate.enable_lazy_load_no_trans 프로퍼티를 활성화 해줍니다. 이 방법은 세션이 종료된 이후에도 다른 세션을 사용하여 데이터를 조회합니다. 여러 개의 지연 로딩이 있는 경우에 대해 각각 새로운 데이터베이스 커넥션을 획득하여 조회하기 때문에 성능 측면에서 좋지 않고 커넥션 풀을 고갈시키는 장애를 유발할 수 있기 때문에 권장되지 않습니다.&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696315667163&quot; class=&quot;bash&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.jpa.properties.hibernate.enable_lazy_load_no_trans: true&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;color: #666666;&quot;&gt;&lt;b&gt;2.3. 지연 로딩을 즉시 로딩으로 바꾸기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;연관관계가 설정된 엔티티에 FetchType.EAGER를 사용하여 즉시 로딩으로 엔티티를 조회하면 연관된 객체가 모두 영속성 컨텍스트에 생성되어 해결이 가능합니다. 하지만 연관된 엔티티가 컬렉션과 같은 경우엔 성능에 문제가 발생할 수 있기 때문에 권장되지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;3. &lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;LazyInitializationException 해결하기 (&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;권장 방법)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;다음의 방법들을 사용하여 LazyInitializationException 예외를 해결하는 것이 권장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;3.1. @Transactional 사용하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;서비스 계층에서 @Transactional 어노테이션을 읽기 전용(&lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;readOnly = true&lt;/span&gt;)으로 사용하여 해결하는 방법입니다. &lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;트랜잭션을 읽기 전용으로 사용하면 Dirty Checking을 하지 않아 성능을 향상시키고 데이터의 의도하지 않은 변경을 방지해줍니다. &lt;/span&gt;&lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;LazyInitializationException 예외는 JPA의 영속성 컨텍스트가&lt;/span&gt; 종료된 후에 연관관계가 설정된 엔티티를 조회하려고 할 때 발생하기 때문에 세션이 유지되도록 트랜잭션을 설정해줍니다. 서비스 계층에서 트랜잭션을 시작하면 Repository까지 해당 트랜잭션이 전파되어 사용됩니다. 따라서 지연 로딩 시점까지 세션을 유지하여 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674548076340&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional(readOnly = true)
public List&amp;lt;BoardDto.ResponseDto&amp;gt; getBoards() throws Exception {
    List&amp;lt;BoardDto.ResponseDto&amp;gt; boardsResponseDto = boardRepository.findAll()
        .stream()
        .map(board -&amp;gt; {
            return BoardDto.ResponseDto.builder()
                .boardId(board.getBoardId())
                .title(board.getTitle())
                .content(board.getContent())
                .author(board.getAuthor())
                .boardComments(board.getBoardComments())
                .build();
        })
        .collect(Collectors.toList());
        
    return boardsResponseDto;
}&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;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.2. Transactional 내부에서 연관된 엔티티 조회하기 (DTO 변환)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;트랜잭션이 설정된 서비스 계층에서 연관관계의 엔티티를 미리 조회하고 트랜잭션 종료 시점에 DTO로 변환하는 방법입니다. 세션이 유지된 상태에서 조회하기 때문에 지연 로딩 조회가 가능합니다. 또한 엔티티를 단일 DTO로 변환하는 것은 즉시 로딩(&lt;span style=&quot;text-align: left;&quot;&gt;FetchType.EAGER&lt;/span&gt;)을 사용하는 것과 동일하기 때문에 경우에 따라 필요한 필드만 DTO로 변환해주는 것이 권장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696319366301&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Transactional(readOnly = true)
public List&amp;lt;BoardDto.ResponseDto&amp;gt; getBoards() throws Exception {
    List&amp;lt;BoardDto.ResponseDto&amp;gt; boardsResponseDto = boardRepository.findAll()
        .stream()
        .map(board -&amp;gt; {
            return BoardDto.ResponseDto.builder()
                .boardId(board.getBoardId())
                .title(board.getTitle())
                .content(board.getContent())
                .author(board.getAuthor())
                .boardComments(
                    board.getBoardComments().stream().map(boardComment -&amp;gt; {
                        return BoardComment.builder()
                            .boardCommentId(boardComment.getBoardCommentId())
                            .board(board)
                            .content(boardComment.getContent())
                            .author(boardComment.getAuthor())
                            .build();
                    }).collect(Collectors.toList())
                )
                .build();
        })
        .collect(Collectors.toList());
        
    return boardsResponseDto;
}&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;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.3. @EntityGraph 사용하기&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;RDB에도 JPA 엔티티와 같이 연관관계가 설정되어 있지 않은 경우엔 LazyInitializationException 예외가 계속 발생할 수 있습니다. &lt;/span&gt;&lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;이러한 경우에 @ManyToOne 관계를 설정해준 엔티티에서 @EntityGraph 어노테이션을 사용하여 연관된 객체를 조회할 수 있습니다. &lt;/span&gt;&lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;@EntityGraph 어노테이션은 즉시 로딩으로 연관관계의 객체를 조회해오고 Left Outer Join으로 읽어오기 때문에 LazyInitializtionException 예외를 해결 할 수 있습니다. 하지만 연관관계의 상위 엔티티를 중복 조회하기 때문에 JPQL 사용 시 distinct 설정을 사용하거나 Set 컬렉션을 사용하는 것이 권장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;@ManyToOne 으로 설정된 BoardComment 객체의 Repository 메서드에 아래와 같이 @EntityGraph 어노테이션을 설정해줍니다. attributePaths 에는 조회할 엔티티명을 작성하고 type 에는 패치 전략을 설정해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696320864665&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public interface BoardCommentRepository extends JpaRepository&amp;lt;BoardComment, String&amp;gt; {

    @EntityGraph(
        attributePaths = {&quot;board&quot;},
        type = EntityGraph.EntityGraphType.FETCH
    )
    Optional&amp;lt;BoardComment&amp;gt; findByBoardCommentId(String boardCommentId);

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;@EntityGraph 어노테이션의 type 프로퍼티는 다음과 같이 2가지 패치 유형을 제공합니다.&lt;/span&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;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;EntityGraph.EntityGraphType.FETCH&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;attributePaths에 명시한 엔티티는 EAGER로 패치하고 나머지 엔티티는 LAZY로 패치.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;EntityGraph.EntityGraphType.LOAD&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;attributePaths에 명시한 엔티티는 EAGER로 패치하고&lt;/span&gt; 나머지 엔티티는 각 엔티티에 명시한 FetchType 또는 디폴트 FetchType으로 패치. (@OneToMany, @ManyToMany 디폴트 패치는 LAZY 이고 @OneToOne, &lt;span style=&quot;text-align: left;&quot;&gt;@ManyToOne&lt;/span&gt; 디폴트 패치는 EAGER)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;서비스 계층에서는 @EntityGraph 어노테이션을 이용하여 조회해온 엔티티를 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696321176354&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public BoardCommentDto.ResponseDto getBoardComment(String boardCommentId) throws Exception {

    BoardComment findBoardComment = boardCommentRepository
        .findByBoardCommentId(boardCommentId).orElseThrow(NoSuchElementException::new);

    ...

    return BoardCommentDto.ResponseDto.builder()
        .boardCommentId(findBoardComment.getBoardCommentId())
        .board(findBoardComment.getBoard())
        .content(findBoardComment.getContent())
        .author(findBoardComment.getAuthor())
        .build();

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 12pt; color: #666666;&quot;&gt;이상으로 JPA 사용 시 발생하는 LazyInitializationException 예외를 해결하는 방법에 대해 알아봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;※ References&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&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;span style=&quot;color: #666666;&quot;&gt;김영한 지음, 『자바 ORM 표준 JPA 프로그래밍』, 에이콘 (2015), 8장 프록시와 연관관계 관리 (p288 ~ p317)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;resilient-923.tistory.com, [Spring&amp;nbsp;/&amp;nbsp;TIL]&amp;nbsp;@Transactional(readOnly=true)&amp;nbsp;가&amp;nbsp;꼭&amp;nbsp;필요한가?, &lt;a href=&quot;https://resilient-923.tistory.com/391&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://resilient-923.tistory.com/391&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;jsonobject.tistory.com, Spring&amp;nbsp;Boot,&amp;nbsp;JPA,&amp;nbsp;LazyInitializationException&amp;nbsp;예외&amp;nbsp;설명&amp;nbsp;및&amp;nbsp;해결책&amp;nbsp;정리,&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://jsonobject.tistory.com/605&quot;&gt;https://jsonobject.tistory.com/605&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;velog.io/@oenomel87, JPA의&amp;nbsp;LazyInitializationException, &lt;a href=&quot;https://velog.io/@oenomel87/JPA-LazyInitializationException&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@oenomel87/JPA-LazyInitializationException&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;programmer-chocho.tistory.com, JPA에서&amp;nbsp;지연로딩(LAZY&amp;nbsp;LOADING)이란, &lt;a href=&quot;https://programmer-chocho.tistory.com/81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://programmer-chocho.tistory.com/81&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;blog.jiniworld.me, [Spring&amp;nbsp;Data&amp;nbsp;JPA&amp;nbsp;Tutorial]&amp;nbsp;10.&amp;nbsp;LazyInitializationException&amp;nbsp;해결하기&amp;nbsp;2.&amp;nbsp;@OneToMany,&amp;nbsp;&lt;a href=&quot;https://blog.jiniworld.me/152#a02-2&quot;&gt;https://blog.jiniworld.me/152#a02-2&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;wonin.tistory.com, @EntityGraph에&amp;nbsp;대해&amp;nbsp;알아보자, &lt;a href=&quot;https://wonin.tistory.com/496&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://wonin.tistory.com/496&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;vladmihalcea.com, The&amp;nbsp;hibernate.enable_lazy_load_no_trans&amp;nbsp;Anti-Pattern, &lt;a href=&quot;https://vladmihalcea.com/the-hibernate-enable_lazy_load_no_trans-anti-pattern/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://vladmihalcea.com/the-hibernate-enable_lazy_load_no_trans-anti-pattern/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;vladmihalcea.com, The&amp;nbsp;best&amp;nbsp;way&amp;nbsp;to&amp;nbsp;map&amp;nbsp;a&amp;nbsp;projection&amp;nbsp;query&amp;nbsp;to&amp;nbsp;a&amp;nbsp;DTO&amp;nbsp;(Data&amp;nbsp;Transfer&amp;nbsp;Object)&amp;nbsp;with&amp;nbsp;JPA&amp;nbsp;and&amp;nbsp;Hibernate, &lt;a href=&quot;https://vladmihalcea.com/the-best-way-to-map-a-projection-query-to-a-dto-with-jpa-and-hibernate/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://vladmihalcea.com/the-best-way-to-map-a-projection-query-to-a-dto-with-jpa-and-hibernate/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/JPA</category>
      <category>backend</category>
      <category>Boot</category>
      <category>development</category>
      <category>Exception</category>
      <category>framework</category>
      <category>JPA</category>
      <category>Programming</category>
      <category>server</category>
      <category>spring</category>
      <category>web</category>
      <author>freestrokes</author>
      <guid isPermaLink="true">https://freestrokes.tistory.com/176</guid>
      <comments>https://freestrokes.tistory.com/176#entry176comment</comments>
      <pubDate>Sun, 12 Nov 2023 18:57:23 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot Properties 클래스 사용하기</title>
      <link>https://freestrokes.tistory.com/173</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;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;b&gt;Spring Boot Properties 클래스 사용하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Spring Boot에서 Properties 설정값들을 클래스에 매핑하여 사용하는 방법에 대해 정리한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1. Properties 클래스 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot에서 설정값을 사용하기 위해서 application.properties 또는 application.yml 파일에 key value 형식의 데이터를 저장하고 다음과 같이 @Value 어노테이션으로 읽어와서 사용하는 경우가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672324678460&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ApplicationProperties {

    @Value(&quot;${property.name}&quot;)
    private String propertyName;
    
    @Value(&quot;${property.age}&quot;)
    private String propertyAge;
    
    @Value(&quot;${property.gender}&quot;)
    private String propertyGender;

    ...

}&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;이와 같이 사용하는 경우엔 클래스마다 필요한 설정값을 선언 해줘야하는 번거로움이 생기는데 클래스에 properties 값들을 매핑해주는 방법을 사용하면 하나의 클래스로 보다 편하게 사용할 수 있습니다.&lt;/p&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;span style=&quot;font-size: 12pt;&quot;&gt;1.1. dependency 추가하기 (gradle)&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;build.gradle 파일에 아래와 같이 @ConfigurationProperties 어노테이션을 사용하기 위한 spring-boot-configuration-processor 의존성을 추가해줍니다. @ConfigurationProperties 어노테이션은 .properties 또는 .yml 파일에서 사용하는 configuration의 자동 완성을 지원해주고 추가한 설정값들을 Bean에 등록해주는 역할을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1672326974149&quot; class=&quot;bash&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    annotationProcessor &quot;org.springframework.boot:spring-boot-configuration-processor&quot;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;font-size: 12pt;&quot;&gt;1.2. yml 설정값 추가하기&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;application.yml 파일에 아래와 같이 설정값을 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1672327425746&quot; class=&quot;bash&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;application:
  endpoint: https://freestrokes.tistory.com/
  user:
    name: freestrokes
    age: 30
    gender: male
  property:
    test:
      list: a,b,c&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.3. properties 클래스 생성하기&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;application.yml 파일의 설정값을 매핑할 ApplicationProperties 클래스를 아래와 같이 생성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1672327556593&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Component
@ConfigurationProperties(prefix = &quot;application&quot;)
public class ApplicationProperties {

    private String endpoint;
    private User user;
    private Property property;

    @Getter
    @Setter
    public static class User {
        private String name;
        private String age;
        private String gender;
    }

    @Getter
    @Setter
    public static class Property {
        private Test test;
    }

    @Getter
    @Setter
    public static class Test {
        private List&amp;lt;String&amp;gt; list;
    }

}&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;다른 클래스에서도 사용이 가능한 Bean 등록을 위해 @Component 어노테이션을 추가해주고, 설정값을 멤버변수에 할당하고 사용하기 위해 lombok의 @Getter, @Setter 어노테이션도 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정값을 멤버변수에 바인딩하기 위해서 @ConfigurationProperties 어노테이션을 추가해줍니다. 함께 사용 가능한 속성값은 다음과 같습니다.&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;prefix&lt;/b&gt;&lt;br /&gt;접두사로 사용되는 key 값&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ignoreInvalidFields&lt;/b&gt;&lt;br /&gt;데이터 타입이 일치하지 않아 유효하지 않은 필드에 대한 무시 여부 (기본값은 false)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ignoreUnknownFields&lt;/b&gt;&lt;br /&gt;Bean 내부에 선언되지 않은 필드에 대한 무시 여부 (기본값은 false)&amp;nbsp;&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;참고로 @ConfigurationProperties 어노테이션의 기능을 활성화하려면 @EnableConfigurationProperties 어노테이션을 추가로 설정해주어야 했습니다. 하지만 Spring Boot 2.2 버전 이후부터는 @ConfigurationProperties 어노테이션만 사용해도 필요한 클래스들을 모두 스캔하여 등록해주기 때문에 생략해주어도 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.4. Properties 클래스 테스트하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;테스트를 위해 아래와 같이 테스트 클래스를 작성해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1672382071574&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.freestrokes.config.ApplicationProperties;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest()
public class ApplicationPropertiesTests {

    @Autowired
    private ApplicationProperties applicationProperties;

    @Test
    public void contextLoads() {
        System.out.println(applicationProperties.getEndpoint());
        System.out.println(applicationProperties.getUser().getName());
        System.out.println(applicationProperties.getUser().getAge());
        System.out.println(applicationProperties.getUser().getGender());
        System.out.println(applicationProperties.getProperty().getTest().getList().toString());
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;실행해보면 다음과 같이 설정값이 매핑되어 있는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYy2lc/btrU1N5mIUW/f3nTvlker6E1j11tliWKK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYy2lc/btrU1N5mIUW/f3nTvlker6E1j11tliWKK0/img.png&quot; data-alt=&quot;Application Properties Test&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYy2lc/btrU1N5mIUW/f3nTvlker6E1j11tliWKK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYy2lc%2FbtrU1N5mIUW%2Ff3nTvlker6E1j11tliWKK0%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;707&quot; height=&quot;435&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;435&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Application Properties Test&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이상으로 Spring Boot에서 설정값들을 Properties 클래스에 매핑하여 사용하는 방법에 대해 알아봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;※ References&lt;/span&gt;&lt;/b&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;kim-jong-hyun.tistory.com, [Spring]&amp;nbsp;-&amp;nbsp;Spring&amp;nbsp;Boot&amp;nbsp;Configuration&amp;nbsp;Properties&amp;nbsp;클래스&amp;nbsp;만들기, &lt;a href=&quot;https://kim-jong-hyun.tistory.com/56&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kim-jong-hyun.tistory.com/56&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;velog.io/@max9106, ,[Spring&amp;nbsp;Boot]&amp;nbsp;외부설정2&amp;nbsp;-&amp;nbsp;@ConfigurationProperties &lt;a href=&quot;https://velog.io/@max9106/Spring-Boot-%EC%99%B8%EB%B6%80%EC%84%A4%EC%A0%95-4xk69h8o50&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@max9106/Spring-Boot-%EC%99%B8%EB%B6%80%EC%84%A4%EC%A0%95-4xk69h8o50&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;www.amitph.com, Using&amp;nbsp;@ConfigurationProperties&amp;nbsp;in&amp;nbsp;Spring&amp;nbsp;Boot, &lt;a href=&quot;https://www.amitph.com/spring-boot-configuration-properties/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.amitph.com/spring-boot-configuration-properties/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;sgc109.github.io, Spring&amp;nbsp;Boot&amp;nbsp;의&amp;nbsp;@ConfigurationProperties, &lt;a href=&quot;https://sgc109.github.io/2020/07/07/spring-boot-configuration-properties/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sgc109.github.io/2020/07/07/spring-boot-configuration-properties/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;brush-up.github.io, [java]SpringBoot&amp;nbsp;의&amp;nbsp;@ConfigurationProperties&amp;nbsp;간략&amp;nbsp;정리, &lt;a href=&quot;https://brush-up.github.io/java/java-ConfigurationProperties/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://brush-up.github.io/java/java-ConfigurationProperties/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;inseok9068.tistory.com, [SpringBoot]&amp;nbsp;properties값&amp;nbsp;클래스로&amp;nbsp;매핑하기, &lt;a href=&quot;https://inseok9068.github.io/springboot/springboot-properties/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://inseok9068.github.io/springboot/springboot-properties/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/Spring Boot</category>
      <category>application</category>
      <category>backend</category>
      <category>Boot</category>
      <category>Class</category>
      <category>development</category>
      <category>Java</category>
      <category>Properties</category>
      <category>server</category>
      <category>spring</category>
      <category>springboot</category>
      <author>freestrokes</author>
      <guid isPermaLink="true">https://freestrokes.tistory.com/173</guid>
      <comments>https://freestrokes.tistory.com/173#entry173comment</comments>
      <pubDate>Fri, 30 Dec 2022 15:40:44 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot JPA 알아보기</title>
      <link>https://freestrokes.tistory.com/174</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;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;b&gt;Spring Boot JPA 알아보기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Spring Boot에서 JPA를 사용하는 방법에 대해 정리한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1. Spring Boot JPA&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.1. JPA (Java Persistence API)&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;JPA는 EJB(Enterprise Java Beans) 3.0 스펙에서 Hibernate를 기반으로 JavaSE, JavaEE를 위한 영속성 관리와 ORM을 위한 기술 표준입니다. 즉, ORM을 사용하기 위한 표준 API 인터페이스를 정의한 것입니다. JPA를 사용하기 위해서는 Hibernate, OpenJPA, EclipseLink, DataNucleus 등과 같이 JPA를 구현한 ORM 프레임워크를 사용해야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jpa1.png&quot; data-origin-width=&quot;1999&quot; data-origin-height=&quot;593&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G3neL/btrP33TfPNN/5Ysh6y0DmhZfHKKkACH941/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G3neL/btrP33TfPNN/5Ysh6y0DmhZfHKKkACH941/img.png&quot; data-alt=&quot;JPA&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G3neL/btrP33TfPNN/5Ysh6y0DmhZfHKKkACH941/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG3neL%2FbtrP33TfPNN%2F5Ysh6y0DmhZfHKKkACH941%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;1999&quot; height=&quot;593&quot; data-filename=&quot;jpa1.png&quot; data-origin-width=&quot;1999&quot; data-origin-height=&quot;593&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JPA&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 Spring Data JPA를 사용하면 JPA를 더 편리하게 사용할 수 있는데 Repository 인터페이스에 정해진 규칙에 따라 메서드를 작성하면 Spring이 구현체를 만들어서 Bean에 등록해줍니다.&lt;/p&gt;
&lt;figure id=&quot;og_1665993928379&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 Data JPA&quot; data-og-description=&quot;Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use dat&quot; data-og-host=&quot;spring.io&quot; data-og-source-url=&quot;https://spring.io/projects/spring-data-jpa&quot; data-og-url=&quot;https://spring.io/projects/spring-data-jpa&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://spring.io/projects/spring-data-jpa&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://spring.io/projects/spring-data-jpa&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 Data JPA&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use dat&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;spring.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;JPA 사용에 따른 장단점은 다음과 같습니다.&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;장점&lt;/b&gt;&lt;br /&gt;객체 지향적인 코드 사용으로 비즈니스 로직에 좀 더 집중 가능.&lt;br /&gt;객체 지향적인 데이터 관리로 일관된 구조 유지 가능.&lt;br /&gt;SQL 대신 객체를 사용하기 때문에 생산성과 재사용성이 높아져서 유지보수가 용이하고 빠른 개발이 가능.&lt;br /&gt;DBMS에 대한 종속성이 줄어들어 객체 자체에만 집중 가능.&lt;br /&gt;테이블에 대한 생성, 수정 과정이 줄어들어 관리가 쉬움.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;&lt;br /&gt;잘 사용하기 위해선 높은 학습 비용을 필요로 함.&lt;br /&gt;통계와 같은 복잡한 쿼리 처리에 불리한 점이 있음.&lt;br /&gt;잘못 사용한 경우엔 성능 문제나 데이터 손실이 발생할 수 있음.&lt;br /&gt;기존 환경이 데이터베이스 중심으로 설계되어 있는 경우엔 사용하기에 어려움이 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.2. ORM (Object-Relational Mapping)&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;ORM은 객체와 RDB(Relational Database) 테이블을 매핑하여 객체 지향적으로 다루는 기술입니다. ORM 프레임워크를 사용하여 객체와 테이블을 매핑하고 SQL 쿼리가 아닌 자바 메서드를 이용한 데이터 조작이 가능합니다. 객체와 테이블 또한 트랜잭션과 같은 데이터베이스 관련 작업들을 좀 더 편리하게 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 MyBatis, iBatis와 같은 SQL Mapper와 ORM의 차이는 다음과 같습니다.&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;ORM&lt;/b&gt;&lt;br /&gt;객체와 테이블을 매핑.&lt;br /&gt;ORM 프레임워크가 SQL을 생성해서 처리.&lt;br /&gt;ex) Hibernate&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SQL Mapper&lt;/b&gt;&lt;br /&gt;객체와 SQL을 매핑.&lt;br /&gt;사용자의 SQL 생성이 필요.&amp;nbsp;&lt;br /&gt;JDBC API 사용에 따른 응답 결과를 객체로 매핑하는 과정을 처리.&lt;br /&gt;ex) MyBatis, iBatis&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.3. Hibernate&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;JPA를 사용하기 위해서 이를 구현한 ORM 프레임워크입니다. javax.persistence.EntityManager와 같은 JPA의 인터페이스를 직접 구현한 라이브러리이며 내부적으로는 JDBC API를 사용합니다.&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;Hibernate의 특징은 다음과 같습니다.&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;SQL을 사용하지 않고 메서드를 이용한 쿼리를 수행하며 객체 중심적 개발이 가능.&lt;/li&gt;
&lt;li&gt;추상화된 데이터 접근 계층을 제공하여 데이터베이스에 종속적이지 않음.&lt;/li&gt;
&lt;li&gt;메서드를 이용한 쿼리 수행의 한계를 보완하기 위해 JPQL, Native Query를 지원.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2. Spring Data JPA 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.1. 디렉터리 구조 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 구성을 위해 아래와 같이 디렉터리 구조를 설정해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666770716881&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;src
└─ main
    └─ java
        └─ com
            └─ freestrokes
                ├─ controller
&amp;nbsp;&amp;nbsp;              │ &amp;nbsp;└─ BoardController.java
                ├─ domain
&amp;nbsp;&amp;nbsp;              │&amp;nbsp; └─ Board.java
                ├─ dto
&amp;nbsp;&amp;nbsp;              │&amp;nbsp;&amp;nbsp;└─ BoardDto.java
                ├─ repositroy
&amp;nbsp;&amp;nbsp;              │&amp;nbsp; └─ BoardRepository.java
                ├─ service
&amp;nbsp;&amp;nbsp;              │&amp;nbsp;&amp;nbsp;└─ BoardService.java
                └─ SpringBootJpaApplication.java&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;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.2. dependency 추가하기 (gradle)&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;build.gradle 파일에 아래와 같이 spring-boot-starter-data-jpa와 spring-boot-starter-jdbc 의존성을 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666110953046&quot; class=&quot;bash&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation &quot;org.springframework.boot:spring-boot-starter-data-jpa&quot;
    implementation &quot;org.springframework.boot:spring-boot-starter-jdbc&quot;
    implementation &quot;junit:junit:4.13.2&quot;
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor &quot;org.springframework.boot:spring-boot-configuration-processor&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.3. application.yml 설정 (MySQL 연동)&amp;nbsp;&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;MySQL 연동을 위해 application.yml 파일을 아래와 같이 작성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666770240303&quot; class=&quot;bash&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  jpa:
    open-in-view: false
    database: mysql
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        use_sql_comments: true
    hibernate:
      ddl-auto: none
      naming:
        physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
        implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
  datasource:
    url: jdbc:mysql://{HOST}:{PORT}/testdb?useUnicode=true&amp;amp;characterEncoding=utf8&amp;amp;useSSL=false
    username: {USERNAME}
    password: {PASSWORD}&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;다음은 yml 설정에 사용한 속성값들에 대한 내용입니다.&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;spring.jpa.open-in-view&lt;/b&gt;&lt;br /&gt;OSIV(Open Session In View) 활성화 옵션.&lt;br /&gt;기본값은 true이며 활성화 됐을 경우엔 응답이 완료되거나 view가 렌더링 될 때까지 영속성 컨텍스트를 유지.&lt;br /&gt;성능과 확장성 면에서 좋지 않기 때문에 false로 설정.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;spring.jpa.database&lt;/b&gt;&lt;br /&gt;JPA 데이터베이스를 설정.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;spring.jpa.database-platform&lt;/b&gt;&lt;br /&gt;JPA 데이터베이스의 플랫폼을 설정.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;spring.jpa.properties.hibernate&lt;/b&gt;&lt;br /&gt;하이버네이트 관련 설정.&lt;br /&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;show_sql&lt;/b&gt;&lt;br /&gt;하이버네이트 SQL 로깅 설정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;format_sql&lt;/b&gt;&lt;br /&gt;하이버네이트 SQL 포맷팅 설정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;use_sql_comments&lt;/b&gt;&lt;br /&gt;하이버네이트 SQL 주석 설정.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;spring.jpa.hibernate.ddl-auto&lt;/b&gt;&lt;br /&gt;데이터베이스 초기화 전략을 설정.&lt;br /&gt;&lt;br /&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;br /&gt;아무 것도 실행하지 않음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;create&lt;/b&gt;&lt;br /&gt;애플리케이션 실행시 기존 테이블을 모두 삭제하고 다시 생성.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;create-drop&lt;/b&gt;&lt;br /&gt;애플리케이션 실행시 기존 테이블을 모두 삭제하고 다시 생성.&lt;br /&gt;애플리케이션 종료시 테이블을 모두 삭제.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;update&lt;/b&gt;&lt;br /&gt;애플리케이션 실행시 변경된 스키마만 반영.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;validate&lt;/b&gt;&lt;br /&gt;엔티티와 테이블 내용이 동일하게 매핑되었는지 확인.&lt;br /&gt;일치하지 않을 경우 애플리케이션을 실행시키지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;spring.jpa.hibernate.naming&lt;/b&gt;&lt;br /&gt;엔티티와 테이블에 대한 네이밍 전략을 설정.&lt;br /&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;physical-strategy&lt;/b&gt;&lt;br /&gt;물리적 명칭 전략.&lt;br /&gt;암시적(논리적) 명칭 전략이 적용된 이후에 적용 됨.&lt;br /&gt;데이터베이스의 공통적인 명칭에 사용하는 것이 권장 됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;implicit-strategy&lt;/b&gt;&lt;br /&gt;암시적(논리적) 명칭 전략.&lt;br /&gt;하이버네이트에서 기본적으로 논리적 명칭을 설정하기 위해 사용.&lt;br /&gt;엔티티에서 @Table, @Column 어노테이션을 사용하여 이름을 설정하지 않은 경우 적용 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;spring.datasource&lt;/b&gt;&lt;br /&gt;데이터베이스 연결 관련 설정.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;url&lt;/b&gt;&lt;br /&gt;데이터베이스 url.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;username&lt;/b&gt;&lt;br /&gt;데이터베이스 계정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;password&lt;/b&gt;&lt;br /&gt;데이터베이스 비밀번호.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;font-size: 12pt;&quot;&gt;2.4. Entity 생성&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;JPA를 사용해서 테이블과 매핑할 엔티티 클래스를 작성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666770372550&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.freestrokes.domain;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@NoArgsConstructor
@Entity(name = &quot;board&quot;)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, unique = true, nullable = false)
    private Long id;

    @Column(name = &quot;title&quot;, length = 500)
    private String title;

    @Column(name = &quot;content&quot;, columnDefinition = &quot;TEXT&quot;)
    private String content;

    @Column(name = &quot;author&quot;, length = 100)
    private String author;

    public void update(Board board) {
        this.title = board.getTitle();
        this.content = board.getContent();
        this.author = board.getAuthor();
    }

    @Builder
    public Board(
        String title,
        String content,
        String author
    ) {
        this.title = title;
        this.content = content;
        this.author = author;
    }

}&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 어노테이션들에 대한 내용입니다.&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;@Entity&lt;/b&gt;&lt;br /&gt;객체와 테이블을 매핑하기 위해 사용.&lt;br /&gt;name 속성을 사용하여 엔티티 이름 지정 가능.&lt;br /&gt;엔티티 이름의 기본값은 camelCase로 작성된 클래스명을 under_score 네이밍에 매칭하여 작성.&lt;br /&gt;파라미터가 없는 기본 생성자가 필요 (public 또는 protected)&lt;br /&gt;final 클래스, inner 클래스, interface, enum에는 사용 불가.&lt;br /&gt;저장할 필드에는 final 사용 불가.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Id&lt;/b&gt;&lt;br /&gt;매핑된 테이블에서 기본키(PK)로 사용할 필드를 지정하기 위해 사용.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@GeneratedValue&lt;/b&gt;&lt;br /&gt;기본키(PK)로 지정된 필드에 대해 생성 규칙을 명명하기 위해 사용.&lt;br /&gt;strategy 속성을 GenerationType.IDENTITY로 지정하면 AUTO_INCREMENT로 적용 됨.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Column&lt;/b&gt;&lt;br /&gt;객체 필드를 테이블 필드에 매핑하기 위해 사용.&lt;br /&gt;name 속성을 사용하여 필드 이름 지정 가능.&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;함께 사용한 lombok 어노테이션에 대한 내용은 다음과 같습니다.&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;@Getter&lt;/b&gt;&lt;br /&gt;필드에 대한 접근자 메서드를 생성.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@NoArgsConstructor&lt;/b&gt;&lt;br /&gt;파라미터가 없는 기본 생성자를 생성.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Builder&lt;/b&gt;&lt;br /&gt;엔티티 클래스에 빌더 패턴을 적용하기 위해 사용.&lt;br /&gt;필드 변경에 따른 유연성과 가독성을 높이고 불변성을 유지하기 위해 사용.&lt;br /&gt;클래스가 아닌 생성자에 사용시 생성자에 포함된 필드에만 적용 됨.&amp;nbsp;&lt;br /&gt;(update 메서드는 c&lt;span&gt;ustom setter로 사용하기 위해 추가)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.5. DTO 생성&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;계층간 데이터 교환을 위해 사용할 DTO 클래스를 작성해줍니다. 용도에 따라 RequestDto, ResponseDto를 나눠서 작성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666775045430&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.freestrokes.dto;

import com.freestrokes.domain.Board;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class BoardDto {

    @Getter
    public static class RequestDto {
        private String title;
        private String content;
        private String author;

        @Builder
        public RequestDto(
            String title,
            String content,
            String author
        ) {
            this.title = title;
            this.content = content;
            this.author = author;
        }

        public Board toEntity() {
            return Board.builder()
                .title(title)
                .content(content)
                .author(author)
                .build();
        }
    }

    @Getter
    public static class ResponseDto {
        private Long id;
        private String title;
        private String content;
        private String author;

        @Builder
        public ResponseDto(
            Long id,
            String title,
            String content,
            String author
        ) {
            this.id = id;
            this.title = title;
            this.content = content;
            this.author = author;
        }

        public Board toEntity() {
            return Board.builder()
                .title(title)
                .content(content)
                .author(author)
                .build();
        }
    }

}&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;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.6. Repository 생성&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;엔티티에 대한 CRUD 메서드를 사용하기 위해 JpaRepository 인터페이스를 작성해줍니다. JpaRepository&amp;lt;Entity 클래스, PK 타입&amp;gt;를 상속 받도록 구현해주면 해당 엔티티의 기본적인 CRUD 메서드가 자동으로 생성됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666770411014&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.freestrokes.repository;

import com.freestrokes.domain.Board;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BoardRepository extends JpaRepository&amp;lt;Board, Long&amp;gt; {
}&lt;/code&gt;&lt;/pre&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;@Repository&lt;/b&gt;&lt;br /&gt;해당 클래스가 Repository 클래스인 것을 명시.&lt;br /&gt;스프링의 component-scan에 의해 스프링 빈(bean)으로 자동 등록 됨.&amp;nbsp;&lt;br /&gt;JPA 예외 발생시 스프링이 추상화한 예외로 변환하여 서비스 계층에 반환.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.7. Controller 생성&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;API 요청을 받을 Controller 클래스를 작성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666770482545&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.freestrokes.controller;

import com.freestrokes.dto.BoardDto;
import com.freestrokes.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequiredArgsConstructor
@RestController
@RequestMapping(&quot;/api/v1&quot;)
public class BoardController {

    private final BoardService boardService;

    @GetMapping(path = &quot;/boards&quot;, produces = &quot;application/json&quot;)
    public ResponseEntity&amp;lt;List&amp;lt;BoardDto.ResponseDto&amp;gt;&amp;gt; getBoards() throws Exception {
        List&amp;lt;BoardDto.ResponseDto&amp;gt; result = boardService.getBoards();
        return new ResponseEntity&amp;lt;List&amp;lt;BoardDto.ResponseDto&amp;gt;&amp;gt;(result, HttpStatus.OK);
    }

    @PostMapping(path = &quot;/boards&quot;, produces = &quot;application/json&quot;)
    public ResponseEntity&amp;lt;BoardDto.ResponseDto&amp;gt; postBoard(@RequestBody BoardDto.RequestDto boardRequestDto) throws Exception {
        BoardDto.ResponseDto result = boardService.postBoard(boardRequestDto);
        return new ResponseEntity&amp;lt;BoardDto.ResponseDto&amp;gt;(result, HttpStatus.OK);
    }

    @PutMapping(path = &quot;/boards/{id}&quot;, produces = &quot;application/json&quot;)
    public ResponseEntity&amp;lt;BoardDto.ResponseDto&amp;gt; putBoard(@PathVariable Long id, @RequestBody BoardDto.RequestDto boardRequestDto) throws Exception {
        BoardDto.ResponseDto result = boardService.putBoard(id, boardRequestDto);
        return new ResponseEntity&amp;lt;BoardDto.ResponseDto&amp;gt;(result, HttpStatus.OK);
    }

    @DeleteMapping(path = &quot;/boards/{id}&quot;, produces = &quot;application/json&quot;)
    public ResponseEntity&amp;lt;?&amp;gt; deleteBoard(@PathVariable Long id) throws Exception {
        boardService.deleteBoard(id);
        return new ResponseEntity&amp;lt;&amp;gt;(&quot;{}&quot;, HttpStatus.OK);
    }

}&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; Controller 클래스에 사용한 어노테이션들에 대한 내용입니다.&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;@RestController&lt;/b&gt;&lt;br /&gt;@Controller 어노테이션에 @ResponseBody 어노테이션이 추가된 형태.&amp;nbsp;&lt;br /&gt;응답 객체를 ResponseEntity로 감싸서 반환.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@RequestMapping&lt;/b&gt;&lt;br /&gt;요청 받을 url과 http 메서드를 설정.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@GetMapping&lt;/b&gt;&lt;br /&gt;GET 요청을 매핑하기 위한 어노테이션.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@PostMapping&lt;/b&gt;&lt;br /&gt;POST 요청을 매핑하기 위한 어노테이션.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@PutMapping&lt;/b&gt;&lt;br /&gt;PUT 요청을 매핑하기 위한 어노테이션.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@DeleteMapping&lt;/b&gt;&lt;br /&gt;DELETE 요청을 매핑하기 위한 어노테이션.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@RequestBody&lt;/b&gt;&lt;br /&gt;요청 받은 데이터를 Java 객체로 변환해주는 어노테이션.&lt;br /&gt;본문의 JSON, XML, Text 등의 데이터가 HttpMessageConverter를 통해 파싱되어 변환 됨.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@PathVariable&lt;/b&gt;&lt;br /&gt;url path에서 동일한 이름의 변수에 대해 파라미터로 처리해주는 어노테이션.&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;함께 사용한 lombok 어노테이션에 대한 내용은 다음과 같습니다.&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;@RequiredArgsConstructor&lt;/b&gt;&lt;br /&gt;final 또는 @NonNull 필드만 파라미터로 받는 생성자를 생성.&lt;br /&gt;@Autowired 어노테이션 없이 의존성을 주입하기 위해 사용.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.8. Service 생성&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;비즈니스 로직을 수행하는 Service 클래스를 작성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666770437108&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.freestrokes.service;

import com.freestrokes.domain.Board;
import com.freestrokes.dto.BoardDto;
import com.freestrokes.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class BoardService {

    private final BoardRepository boardRepository;

    public List&amp;lt;BoardDto.ResponseDto&amp;gt; getBoards() throws Exception {
        List&amp;lt;BoardDto.ResponseDto&amp;gt; boardsResponseDto = boardRepository.findAll()
            .stream()
            .map(item -&amp;gt; {
                return BoardDto.ResponseDto.builder()
                    .id(item.getId())
                    .title(item.getTitle())
                    .content(item.getContent())
                    .author(item.getAuthor())
                    .build();
            })
            .collect(Collectors.toList());

        return boardsResponseDto;
    }

    public BoardDto.ResponseDto postBoard(BoardDto.RequestDto boardRequestDto) throws Exception {
        Board board = boardRepository.save(boardRequestDto.toEntity());

        BoardDto.ResponseDto boardResponseDto = BoardDto.ResponseDto.builder()
            .id(board.getId())
            .title(board.getTitle())
            .content(board.getContent())
            .author(board.getAuthor())
            .build();

        return boardResponseDto;
    }

    public BoardDto.ResponseDto putBoard(Long id, BoardDto.RequestDto boardRequestDto) throws Exception {
        Optional&amp;lt;Board&amp;gt; persistBoard = boardRepository.findById(id);

        if (persistBoard.isPresent()) {
            Board board = Board.builder()
                .title(boardRequestDto.getTitle())
                .content(boardRequestDto.getContent())
                .author(boardRequestDto.getAuthor())
                .build();

            persistBoard.get().update(board);

            boardRepository.save(persistBoard.get());
        } else {
            throw new NoSuchElementException();
        }

        BoardDto.ResponseDto boardResponseDto = BoardDto.ResponseDto.builder()
            .id(persistBoard.get().getId())
            .title(persistBoard.get().getTitle())
            .content(persistBoard.get().getContent())
            .author(persistBoard.get().getAuthor())
            .build();

        return boardResponseDto;

    }

    public void deleteBoard(Long id) throws Exception {
        boardRepository.deleteById(id);
    }

}&lt;/code&gt;&lt;/pre&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;@Service&lt;/b&gt;&lt;br /&gt;해당 클래스가 비즈니스 로직을 포함한 Service 클래스인 것을 명시.&lt;br /&gt;스프링의 component-scan에 의해 스프링 빈(bean)으로 자동 등록 됨.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3. CRUD API 테스트하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;Gradle 빌드 후에 Spring Boot 애플리케이션을 실행하고 Postman을 이용하여 다음과 같이 API 테스트를 해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.1. postBoard (POST)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2510&quot; data-origin-height=&quot;1868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNGfnU/btrQj4TdpO0/GRSKvYasSGv8N30E8nkkBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNGfnU/btrQj4TdpO0/GRSKvYasSGv8N30E8nkkBk/img.png&quot; data-alt=&quot;POST API TEST&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNGfnU/btrQj4TdpO0/GRSKvYasSGv8N30E8nkkBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNGfnU%2FbtrQj4TdpO0%2FGRSKvYasSGv8N30E8nkkBk%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;2510&quot; height=&quot;1868&quot; data-origin-width=&quot;2510&quot; data-origin-height=&quot;1868&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;POST API TEST&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.2. getBoards (GET)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2510&quot; data-origin-height=&quot;1868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egQkyh/btrQlqnlnTs/Hxt7wKGRQWG9yTqCg0GXVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egQkyh/btrQlqnlnTs/Hxt7wKGRQWG9yTqCg0GXVK/img.png&quot; data-alt=&quot;GET API TEST&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egQkyh/btrQlqnlnTs/Hxt7wKGRQWG9yTqCg0GXVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegQkyh%2FbtrQlqnlnTs%2FHxt7wKGRQWG9yTqCg0GXVK%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;2510&quot; height=&quot;1868&quot; data-origin-width=&quot;2510&quot; data-origin-height=&quot;1868&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;GET API TEST&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.3. putBoard (PUT)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2510&quot; data-origin-height=&quot;1868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2vK9O/btrQihepzn2/nea6IW0QYTFl55hQ3Db9KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2vK9O/btrQihepzn2/nea6IW0QYTFl55hQ3Db9KK/img.png&quot; data-alt=&quot;PUT API TEST&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2vK9O/btrQihepzn2/nea6IW0QYTFl55hQ3Db9KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2vK9O%2FbtrQihepzn2%2Fnea6IW0QYTFl55hQ3Db9KK%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;2510&quot; height=&quot;1868&quot; data-origin-width=&quot;2510&quot; data-origin-height=&quot;1868&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PUT API TEST&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.4. deleteBoard (DELETE)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2510&quot; data-origin-height=&quot;1868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVDVOx/btrQjm7AzuJ/kynG2OaMAGz6yA7nckgtAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVDVOx/btrQjm7AzuJ/kynG2OaMAGz6yA7nckgtAk/img.png&quot; data-alt=&quot;DELETE API TEST&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVDVOx/btrQjm7AzuJ/kynG2OaMAGz6yA7nckgtAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVDVOx%2FbtrQjm7AzuJ%2FkynG2OaMAGz6yA7nckgtAk%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;2510&quot; height=&quot;1868&quot; data-origin-width=&quot;2510&quot; data-origin-height=&quot;1868&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DELETE API TEST&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이상으로 Spring Boot에서 JPA를 사용하는 방법에 대해 알아봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;※ References&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&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;spring.io, Spring&amp;nbsp;Data&amp;nbsp;JPA, &lt;a href=&quot;https://spring.io/projects/spring-data-jpa&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://spring.io/projects/spring-data-jpa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;velog.io/@swchoi0329, Spring&amp;nbsp;Boot에서&amp;nbsp;JPA&amp;nbsp;사용하기, &lt;a href=&quot;https://velog.io/@swchoi0329/Spring-Boot%EC%97%90%EC%84%9C-JPA-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@swchoi0329/Spring-Boot%EC%97%90%EC%84%9C-JPA-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;memostack.tistory.com, Spring&amp;nbsp;Boot&amp;nbsp;에서&amp;nbsp;JPA&amp;nbsp;사용하기&amp;nbsp;(MySQL&amp;nbsp;사용), &lt;a href=&quot;https://memostack.tistory.com/155#1.1.%20MySQL%20%EC%84%A4%EC%B9%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://memostack.tistory.com/155#1.1.%20MySQL%20%EC%84%A4%EC%B9%98&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;goddaehee.tistory.com, [스프링부트&amp;nbsp;(7)]&amp;nbsp;Spring&amp;nbsp;Boot&amp;nbsp;JPA(1)&amp;nbsp;-&amp;nbsp;시작&amp;nbsp;및&amp;nbsp;기본&amp;nbsp;설정, &lt;a href=&quot;https://goddaehee.tistory.com/209&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://goddaehee.tistory.com/209&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;김영한 지음, 『자바 ORM 표준 JPA 프로그래밍』, 에이콘 (2015), 1장 JPA 소개 (p29 ~ p62)&lt;/li&gt;
&lt;li&gt;김영한 지음, 『자바 ORM 표준 JPA 프로그래밍』, 에이콘 (2015), 4장 엔티티 매핑 (p121 ~ p153)&lt;/li&gt;
&lt;li&gt;www.daleseo.com, [자바]&amp;nbsp;자주&amp;nbsp;사용되는&amp;nbsp;Lombok&amp;nbsp;어노테이션, &lt;a href=&quot;https://www.daleseo.com/lombok-popular-annotations/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.daleseo.com/lombok-popular-annotations/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;mangkyu.tistory.com, [Java]&amp;nbsp;빌더&amp;nbsp;패턴(Builder&amp;nbsp;Pattern)을&amp;nbsp;사용해야&amp;nbsp;하는&amp;nbsp;이유, &lt;a href=&quot;https://mangkyu.tistory.com/163&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mangkyu.tistory.com/163&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;victorydntmd.tistory.com, [SpringBoot]&amp;nbsp;JPA&amp;nbsp;설정하기&amp;nbsp;(&amp;nbsp;MySQL&amp;nbsp;), &lt;a href=&quot;https://victorydntmd.tistory.com/323&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://victorydntmd.tistory.com/323&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;velog.io/@mumuni, Hibernate5&amp;nbsp;Naming&amp;nbsp;Strategy&amp;nbsp;간단&amp;nbsp;정리, &lt;a href=&quot;https://velog.io/@mumuni/Hibernate5-Naming-Strategy-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@mumuni/Hibernate5-Naming-Strategy-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;nefertirii.github.io, 하이버네이트&amp;nbsp;명명&amp;nbsp;전략(Hibernate&amp;nbsp;Naming&amp;nbsp;Strategies), &lt;a href=&quot;https://nefertirii.github.io/jpa/hibernate-naming-strategies/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://nefertirii.github.io/jpa/hibernate-naming-strategies/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;jaehoney.tistory.com, DTO를&amp;nbsp;Inner&amp;nbsp;static&amp;nbsp;class로&amp;nbsp;간결하게&amp;nbsp;관리하기!&amp;nbsp;(+&amp;nbsp;domain&amp;nbsp;분리), &lt;a href=&quot;https://jaehoney.tistory.com/157&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jaehoney.tistory.com/157&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;velog.io/@hermaeus, 7.JpaRepository&amp;nbsp;인터페이스, &lt;a href=&quot;https://velog.io/@hermaeus/7.JpaRepository-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@hermaeus/7.JpaRepository-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;medium.com/webeveloper, @RequiredArgsConstructor&amp;nbsp;를&amp;nbsp;이용한&amp;nbsp;의존성&amp;nbsp;주입(Dependency&amp;nbsp;Injection), &lt;a href=&quot;https://medium.com/webeveloper/requiredargsconstructor-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-dependency-injection-4f1b0ac33561&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/webeveloper/requiredargsconstructor-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-dependency-injection-4f1b0ac33561&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;mangkyu.tistory.com, [Spring]&amp;nbsp;@Controller와&amp;nbsp;@RestController&amp;nbsp;차이, &lt;a href=&quot;https://mangkyu.tistory.com/49&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mangkyu.tistory.com/49&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/Spring Boot</category>
      <category>backend</category>
      <category>Boot</category>
      <category>development</category>
      <category>framework</category>
      <category>Java</category>
      <category>JPA</category>
      <category>Programming</category>
      <category>server</category>
      <category>spring</category>
      <category>web</category>
      <author>freestrokes</author>
      <guid isPermaLink="true">https://freestrokes.tistory.com/174</guid>
      <comments>https://freestrokes.tistory.com/174#entry174comment</comments>
      <pubDate>Thu, 3 Nov 2022 23:10:01 +0900</pubDate>
    </item>
    <item>
      <title>Spring Booot Test 작성 및 실행하기</title>
      <link>https://freestrokes.tistory.com/110</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;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;b&gt;Spring Boot Test 작성 및 실행하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot Test를 작성하고 실행하는 방법에 대해 정리한 내용입니다.&lt;/p&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;span style=&quot;font-size: 12pt;&quot;&gt;1. Spring Boot Test&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot에서는 애플리케이션을 테스트할 때 도움이 되는 테스트 스타터를 제공합니다. 스타터는 크게 spring-boot-test와 spring-boot-test-autoconfigure 두가지 모듈로 구성됩니다.&lt;/p&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-test는 테스트를 위한 핵심 기능을 지원하고 spring-boot-test-autoconfigure는 테스트를 위한 자동 구성을 지원합니다. 일반적으로 Spring Boot 테스트 모듈과 JUnit, Jupiter, AssertJ, Hamcrest 및 다른 유용한 라이브러리들이 포함되어 있는 spring-boot-starter-test를 이용하여 테스트를 실행합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1662804065790&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;Core Features&quot; data-og-description=&quot;Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, an&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing&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;Core Features&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, an&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2. Spring Boot Test 어노테이션&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot에서는 테스트 목적에 따라 다양한 어노테이션을 제공합니다. 일반적으로 통합 테스트와 단위 테스트로 구분되는데 이에 따라 주로 사용되는 어노테이션을 구분하면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;통합 테스트&lt;/b&gt;&lt;br /&gt;@SpringBootTest&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단위 테스트&lt;/b&gt;&lt;br /&gt;@WebMvcTest&lt;br /&gt;@DataJpaTest&lt;br /&gt;@RestClientTest&lt;br /&gt;@JsonTest&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot의 테스트 어노테이션은 JUnit에 내장된 테스트 메서드를 사용하기 편리하게 가공한 것입니다. 각 어노테이션의 용도에 따라 적합한 상황에 사용해주면 됩니다.&lt;/p&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;span style=&quot;font-size: 12pt;&quot;&gt;2.1. @SpringBootTest&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;@SpringBootTest 어노테이션은 Spring Boot 에서 통합 테스트를 할 때 사용하는 기본적인 어노테이션입니다. Spring Boot 프로젝트를 생성하면 메인 클래스와 함께 기본으로 제공됩니다. 애플리케이션이 실행될 때의 설정을 임의로 바꾸어 테스트를 진행하거나 여러 단위의 테스트를 하나로 통합하여 수행할 때 적합합니다.&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;@SpringBootTest 어노테이션은 실제 구동되는 애플리케이션과 동일하게 context를 로드하여 테스트하기 때문에 원하는 테스트를 모두 수행할 수 있습니다. 하지만 애플리케이션에 설정된 Bean을 모두 로드하는 특징이 있어서 애플리케이션 규모가 커질수록 느려지게 됩니다.&lt;/p&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 프로젝트를 생성할 때 기본으로 제공되는 테스트 클래스입니다. 클래스명은 프로젝트명 뒤에 'Tests'를 붙인 형태로 생성됩니다. 테스트 클래스를 실행하면 애플리케이션의 context를 로드하여 테스트가 실행됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662804885951&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.freestrokes;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootStudyApplicationTests {

	@Test
	public void contextLoads() {
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;테스트 클래스에 사용된 어노테이션에 대한 설명은 다음과 같습니다.&lt;/span&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;@RunWith&lt;/b&gt;&lt;br /&gt;어노테이션에 정의된 Runner 클래스를 사용.&lt;br /&gt;@SpringBootTest 어노테이션을 사용을 위해 필요.&lt;br /&gt;JUnit 실행에 필요한 SpringJUnit4ClassRunner 클래스를 상속 받아 @RunWith(SpringRunner.class) 형태로 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@SpringBootTest&lt;/b&gt;&lt;br /&gt;Spring Boot 애플리케이션을 테스트할 때 필요한 의존성을 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;앞서 알아본 예제에 아래 내용을 추가해서 @SpringBootTest 어노테이션에서 파라미터를 사용하는 방법에 대해 알아보겠습니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;&lt;/span&gt;
&lt;pre id=&quot;code_1662806291297&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.freestrokes;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

@RunWith(SpringRunner.class)
@SpringBootTest(
	value = &quot;value=test&quot;,
	properties = {&quot;property.value=propertyTest&quot;},
	classes = {SpringBootStudyApplication.class},
	webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
public class SpringBootStudyApplicationTests {

	@Value(&quot;${value}&quot;)
	private String value;

	@Value(&quot;${property.value}&quot;)
	private String propertyValue;

	@Test
	public void contextLoads() {
		assertThat(value, is(&quot;test&quot;));
		assertThat(propertyValue, is(&quot;propertyTest&quot;));
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 12pt; letter-spacing: 0px;&quot;&gt;@SpringBootTest 어노테이션의 파라미터로 value와 properties는 함께 사용할 수 없기 때문에 실행할 때는 하나만 사용해줘야 합니다. @SpringBootTest 어노테이션에 사용된 파라미터에 대한 설명은 다음과 같습니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;value&lt;/b&gt;&lt;br /&gt;테스트 실행전에 사용할 프로퍼티를 추가. &lt;br /&gt;기존에 프로퍼티가 있는 경우엔 오버라이드 됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;properties&lt;/b&gt;&lt;br /&gt;테스트 실행전에 {&quot;key=value&quot;} 형식으로 프로퍼티를 추가.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;classes&lt;/b&gt;&lt;br /&gt;테스트 실행시 애플리케이션 context에 로드할 클래스를 지정.&lt;br /&gt;따로 지정하지 않을 경우엔 @SpringBootTest 어노테이션이 사용된 해당 클래스를 로드.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;webEnvironment&lt;/b&gt;&lt;br /&gt;애플리케이션이 실행될 때의 웹 환경을 설정. &lt;br /&gt;기본값은 Mock 서블릿을 로드.&lt;br /&gt;(위의 예제에서는 랜덤 포트값을 로드하도록 설정)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;다음은 @SpringBootTest 어노테이션에 대한 추가적인 내용입니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@SpringBootTest의&amp;nbsp; 검색 알고리즘&amp;nbsp;&lt;/b&gt;&lt;br /&gt;@SpringBootApplication 또는 @SpringBootConfiguration 어노테이션을 검색하여 찾음.&lt;br /&gt;@SpringBootTest에서는 해당 어노테이션 중 하나를 반드시 필요로 함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발, QA, 운영 등의 Profile 환경마다 각각 다른 DataSource를 사용하는 경우&lt;/b&gt; &lt;br /&gt;@ActiveProfiles(&quot;local&quot;) 같은 형식의 프로파일 환경값을 어노테이션으로 추가하여 구분.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Transactional 어노테이션&lt;/b&gt; &lt;br /&gt;추가해주면 테스트를 마친 후에 데이터가 Rollback 됨. &lt;br /&gt;해당 테스트가 서버의 다른 쓰레드에서 실행중인 경우엔 WebEnvironment의 RANDOM_PORT 또는 DEFINED_PORT를 사용하여 테스트를 실행해도 Rollback되지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.2. @WebMvcTest&lt;/span&gt;&lt;/b&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;@WebMvcTest 어노테이션은 웹에서의 요청과 응답에 대한 테스트할 때 사용합니다. Security 또는 Filter까지 자동으로 테스트하며 수동으로 추가/삭제가 가능한 특징이 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;@WebMvcTest 어노테이션을 사용하여 테스트하면 MVC 관련 설정들(@Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver)만 로드하기 때문에 @SpringBootTest 어노테이션을 사용했을 때보다 가볍고 빠르게 테스트를 진행할 수 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;아래와 같이 MVC 구조로 패키지를 만들고 각각의 클래스와 인터페이스를 생성해줍니다.&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1662910968178&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;src
└── main
     └── java
          └── com
               ├── controller
               │    └── BookController.java
               ├── domain
               │    └── Book.java
               └── service
                    └── BookService.java&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;Book.java 클래스는 아래와 같이 작성해줍니다.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1662910968178&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

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

@NoArgsConstructor
@Getter
public class Book {

    private Integer idx;
    private String title;
    private LocalDateTime publishedAt;

    @Builder
    public Book(
        String title,
        LocalDateTime publishedAt
    ) {
        this.title = title;
        this.publishedAt = publishedAt;
    }

}&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;위와 같이 lombok을 이용하여 관련 어노테이션을 사용하기 위해서는 build.gradle에 다음과 같이 설정을 추가해주어야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1662979036727&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'&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;BookService.java 인터페이스는 아래와 같이 작성해줍니다. BookService 인터페이스에서는 Book 타입의 리스트를 넘기는 메서드를 생성해줍니다. BookService 인터페이스는 @Service 어노테이션과 함께 구현되어야 하지만 아래의 BookControllerTest.java에서 @MockBean을 사용하여 실제 객체처럼 동작하도록 합니다.&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1662910968180&quot; class=&quot;routeros&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.springboot.springboot.domain.Book;
import java.util.List;

public interface BookService {
    List&amp;lt;Book&amp;gt; getBookList();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BookController.java 클래스는 아래와 같이 작성해줍니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;BookController에서는 /books url에 대한 GET request로 받아 BookService에서 getBookList()를 호출하여 bookList를 반환하도록 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662910968180&quot; class=&quot;css&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.springboot.springboot.service.BookService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class BookController {

    @Autowired
    private BookService bookService;

    @GetMapping(&quot;/books&quot;)
    public String getBookList(Model model) {
        model.addAttribute(&quot;bookList&quot;, bookService.getBookList());
        return &quot;book&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;마지막으로 테스트를 위해 BookControllerTest.java 클래스를 아래와 같이 작성해줍니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1662911037466&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.freestrokes.controller.BookController;
import com.freestrokes.domain.Book;
import com.freestrokes.service.BookService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDateTime;
import java.util.Collections;

import static org.hamcrest.Matchers.contains;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class)
@WebMvcTest(BookController.class)
public class BookControllerTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private BookService bookService;

    @Test
    public void bookMvcTest() throws Exception {
        Book book = new Book(&quot;Spring Boot Book&quot;, LocalDateTime.now());
        
        given(bookService.getBookList()).willReturn(Collections.singletonList(book));
        
        mvc.perform(
            get(&quot;/books&quot;))
            .andExpect(status().isOk())
            .andExpect(view().name(&quot;book&quot;))
            .andExpect(model().attributeExists(&quot;bookList&quot;))
            .andExpect(model().attribute(&quot;bookList&quot;, contains(book))
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BookControllerTest 클래스에 대한 설명은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@WebMvcTest(BookController.class)&lt;/b&gt;&lt;br /&gt;@WebMvcTest 어노테이션에 사용하기 위한 특정 Controller를 명시.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;org.springframework.test.web.servlet.MockMvc&lt;/b&gt;&lt;br /&gt;Controller 테스트시 모든 의존성을 로드하지 않고 해당 Controller의 관련 Bean만 로드.&lt;br /&gt;HTTP 서버를 실행하지 않고 테스트 가능.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@MockBean&lt;/b&gt;&lt;br /&gt;Controller 내부의 의존성인 BookService를 실제 객체가 아닌 Mock 객체로 대체.&lt;br /&gt;Mock 객체를 만들고 given()을 사용하여 getBookList() 메서드의 실행에 대한 반환값을 미리 정의.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트에 사용한 MockMvc의 메서드에 대한 설명은 다음과 같습니다.&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;andExpect(status.isOk())&lt;/b&gt;&lt;br /&gt;HTTP 상태값 확인 (200)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;b&gt;andExpect(view().name(&quot;book&quot;))&lt;/b&gt;&lt;/b&gt;&lt;br /&gt;view 이름이 book인지 확인.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;andExpect(model().attributeExists(&quot;bookList&quot;))&lt;/b&gt;&lt;br /&gt;model에 bookList 프로퍼티가 존재하는지 확인.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;andExpect(model().attributeExsits(&quot;bookList&quot;, contains(book)))&lt;br /&gt;&lt;/b&gt;model의 bookList 프로퍼티에 book 객체가 포함되어 있는지 확인.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.3. @DataJpaTest&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@DataJpaTest 어노테이션은 JPA 테스트를 할 때 사용합니다. JPA와 관련된 설정만 로드하며 DataSource의 설정이나 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;@DataJpaTest 어노테이션은 기본적으로 In-Memory Embedded Database를 사용하며 @Entity 클래스를 스캔하여 스프링 데이터 JPA 저장소(Spring Data JPA Repositories)를 구성합니다. 또한 EntityManager를 테스트 용도로 대체하기 위해 만들어진 TestEntityManager를 사용하여 persist, flush, find 등의 기본적인 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;JPA 사용을 위해서 build.gradle의 dependencies에 아래의 의존성을 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662965594908&quot; class=&quot;bash&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;compile('org.springframework.boot:spring-boot-starter-data-jpa')&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;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;다음으로 앞서 작성한 Book.java 클래스를 아래와 같이 수정해줍니다. JPA 테스트를 수행할 수 있게 관련 어노테이션을 추가해줍니다.&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1662965619188&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

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

@NoArgsConstructor
@Getter
@Entity
@Table
public class Book {

    @Id
    @GeneratedValue
    private Integer idx;

    @Column
    private String title;

    @Column
    private LocalDateTime publishedAt;

    @Builder
    public Book(
        String title,
        LocalDateTime publishedAt
    ) {
        this.title = title;
        this.publishedAt = publishedAt;
    }

}&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 Repository로 사용할 BookRepository.java 인터페이스도 다음과 같이 작성해줍니다. repository 패키지를 생성해서 하위에 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662966221568&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

public interface BookRepository extends JpaRepository&amp;lt;Book, Integer&amp;gt; {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 12pt; letter-spacing: 0px;&quot;&gt;마지막으로 JPA 관련 설정들을 테스트하기 위한 BookJpaTest.java 클래스를 작성해줍니다. @Test 어노테이션이 매핑된 각각의 메서드를 실행하면&amp;nbsp;JPA 테스트 결과를 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1662966338562&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.freestrokes.domain.Book;
import com.freestrokes.repository.BookRepository;

import org.hamcrest.collection.IsEmptyCollection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;

import java.time.LocalDateTime;
import java.util.List;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;

@RunWith(SpringRunner.class)
@DataJpaTest
public class BookJpaTest {

    private final static String JPA_TEST_TITLE = &quot;Spring Boot Jpa Test&quot;;

    @Autowired
    private TestEntityManager testEntityManager;

    @Autowired
    private BookRepository bookRepository;

    @Test
    public void saveBook() {
        Book book = Book.builder().title(JPA_TEST_TITLE).publishedAt(LocalDateTime.now()).build();
        testEntityManager.persist(book);
        assertThat(bookRepository.getOne(book.getIdx()), is(book));
    }

    @Test
    public void saveAndFindBookList() {
        Book book1 = Book.builder().title(JPA_TEST_TITLE + &quot;1&quot;).publishedAt(LocalDateTime.now()).build();
        testEntityManager.persist(book1);
        Book book2 = Book.builder().title(JPA_TEST_TITLE + &quot;2&quot;).publishedAt(LocalDateTime.now()).build();
        testEntityManager.persist(book2);
        Book book3 = Book.builder().title(JPA_TEST_TITLE + &quot;3&quot;).publishedAt(LocalDateTime.now()).build();
        testEntityManager.persist(book3);

        List&amp;lt;Book&amp;gt; bookList = bookRepository.findAll();
        assertThat(bookList, hasSize(3));
        assertThat(bookList, contains(book1, book2, book3));
    }

    @Test
    public void saveAndDeleteBookList() {
        Book book1 = Book.builder().title(JPA_TEST_TITLE + &quot;1&quot;).publishedAt(LocalDateTime.now()).build();
        testEntityManager.persist(book1);
        Book book2 = Book.builder().title(JPA_TEST_TITLE + &quot;2&quot;).publishedAt(LocalDateTime.now()).build();
        testEntityManager.persist(book2);

        bookRepository.deleteAll();
        assertThat(bookRepository.findAll(), IsEmptyCollection.empty());
    }
}&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;BookJpaTest 클래스에 대한 설명은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;saveBook()&lt;/b&gt;&lt;br /&gt;testEntityManager로 persist() 메서드의 정상 동작을 확인.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;saveAndFindBookList()&lt;/b&gt;&lt;br /&gt;도메인 객체를 저장하고 리스트를 조회하여 개수와 각 객체의 포함 여부를 확인.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;saveAndDeleteBookList()&lt;/b&gt;&lt;br /&gt;도메인 객체를 저장하고 삭제한 뒤에 모두 삭제 되었는지 확인.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.4. @RestClientTest&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;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;@RestClientTest 어노테이션은 REST와 관련된 테스트를 할 때 사용합니다. REST 통신을 테스트하고 데이터 타입으로 사용되는 JSON 타입이 정상적인 response로 반환되는지 테스트할 수 있습니다.&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;아래와 같이 클래스들과 테스트에 사용할 JSON 파일을 생성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662974123090&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;src
├── main
│    └── java
│         └── com
│              ├──controller
│              │   └── BookRestController.java
│              ├──domain
│              │   └── Book.java
│              └──service
│                  └── BookRestService.java
└── test
     ├── java
     └── resources
          └── test.json&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;테스트에 사용할 test.json 파일은 아래와 같이 작성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662975270058&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;idx&quot;: null,
  &quot;title&quot;: &quot;테스트&quot;,
  &quot;publishedAt&quot;: null
}&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;BookRestService.java 클래스는 다음과 같이 작성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662974162262&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.freestrokes.domain.Book;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class BookRestService {

    private final RestTemplate restTemplate;

    public BookRestService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.rootUri(&quot;/rest/test&quot;).build();
    }

    public Book getRestBookList() {
        return this.restTemplate.getForObject(&quot;/rest/test&quot;, Book.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;BookRestService 클래스 대한 설명은 다음과 같습니다.&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;BookRestService(RestTemplateBuilder restTemplateBuilder)&lt;/b&gt;&lt;br /&gt;RestTemplateBuilder는 RestTemplate을 핸들링하는 빌더 객체.&lt;br /&gt;connectionTimeout, readTimeOut과 같은 RestTemplate과 관련된 여러 설정을 제공.&lt;br /&gt;REST 테스트를 할 rootUri를 설정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;getRestBookList()&lt;/b&gt;&lt;br /&gt;RestTemplate의 GET 방식으로 통신하는 getForObject() 메서드를 사용하여 uri에 request를보냄.&lt;br /&gt;response는 Book 객체 타입으로 받아오도록 설정.&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;BookRestController.java 클래스는 다음과 같이 작성해줍니다. @RestController 어노테이션을 사용하여 REST 형식의 데이터를 반환하도록 해줍니다. getRestBooklist() 메서드는 Book 객체를 리턴하지만 @RestController 어노테이션을 사용하면 JSON 형식의 String 데이터로 반환됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662974558649&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.freestrokes.domain.Book;
import com.freestrokes.service.BookRestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookRestController {

    @Autowired
    private BookRestService bookRestService;

    @GetMapping(
        path = &quot;/rest/test&quot;,
        produces = MediaType.APPLICATION_JSON_VALUE
    )
    public Book getRestBookList() {
        return bookRestService.getRestBookList();
    }
    
}&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;마지막으로 테스트를 위해 BookRestTest.java 클래스를 작성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662974755749&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.freestrokes.domain.Book;
import com.freestrokes.service.BookRestService;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.HttpServerErrorException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

@RunWith(SpringRunner.class)
@RestClientTest(BookRestService.class)
public class BookRestTest {

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Autowired
    private BookRestService bookRestService;

    @Autowired
    private MockRestServiceServer mockRestServiceServer;

    @Test
    public void restTest() {
        this.mockRestServiceServer
            .expect(requestTo(&quot;/rest/test&quot;))
            .andRespond(withSuccess(new ClassPathResource(&quot;/test.json&quot;, getClass()), MediaType.APPLICATION_JSON));

        Book book = this.bookRestService.getRestBookList();
        assertThat(book.getTitle()).isEqualTo(&quot;테스트&quot;);
    }

    @Test
    public void restErrorTest() {
        this.mockRestServiceServer
            .expect(requestTo(&quot;/rest/test&quot;))
            .andRespond(withServerError());

        this.thrown.expect(HttpServerErrorException.class);
        this.bookRestService.getRestBookList();
    }

}&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;BookRestTest 클래스 대한 설명은 다음과 같습니다.&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;@RestClientTest(BookRestService.class)&lt;/b&gt;&lt;br /&gt;테스트 대상이 되는 빈 클래스를 주입.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Rule&lt;/b&gt;&lt;br /&gt;지정된 필드값은 하나의 테스트가 끝날 때마다 지정한 값으로 초기화 됨.&lt;br /&gt;@Before, @After 어노테이션에 상관없이 동작.&lt;br /&gt;테스트에서 자체적인 규칙을 정의하여 재사용할 때 유용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MockRestServiceServer&lt;/b&gt;&lt;br /&gt;클라이언트와 서버 사이의 REST 테스트를 위한 객체.&lt;br /&gt;Mock 객체를 사용했던 것처럼 테스트가 이루어지도록 함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;restTest()&lt;/b&gt;&lt;br /&gt;요청에 대한 응답을 테스트하는 메서드.&lt;br /&gt;getRestBookList() 메서드를 호출하여 JSON String으로 반환된 값이 test.json에 작성된 값과 일치하는지 확인.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;restErrorTest()&lt;/b&gt;&lt;br /&gt;요청에 대한 에러를 테스트하는 메서드.&lt;br /&gt;ExcepectedException 객체의 expect() 메서드로 지정한 에러를 발생.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.5. @JsonTest&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;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;@JsonTest 어노테이션은 JSON의 직렬화와 역직렬화를 테스트를 할 때 사용합니다. Gson 또는 Jackson 라이브러리의 테스터를 사용하여 테스트를 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;다음과 같이 BookJsonTest.java 클래스를 작성해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1662979264311&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.freestrokes.domain.Book;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@JsonTest
public class BookJsonTest {

    @Autowired
    private JacksonTester&amp;lt;Book&amp;gt; json;

    @Test
    public void jsonTest() throws Exception {
        Book book = Book.builder()
            .title(&quot;테스트&quot;)
            .build();

        String content = &quot;{\&quot;title\&quot;:\&quot;테스트\&quot;}&quot;;

        assertThat(this.json.parseObject(content).getTitle()).isEqualTo(book.getTitle());
        assertThat(this.json.parseObject(content).getPublishedAt()).isNull();

        assertThat(this.json.write(book)).isEqualToJson(&quot;/test.json&quot;);
        assertThat(this.json.write(book)).hasJsonPathStringValue(&quot;title&quot;);
        assertThat(this.json.write(book)).extractingJsonPathStringValue(&quot;title&quot;).isEqualTo(&quot;테스트&quot;);
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BookRestTest 클래스 대한 설명은 다음과 같습니다.&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;assertThat(this.json.parseObject(content).getTitle()).isEqualTo(book.getTitle())&lt;/b&gt;&lt;br /&gt;content 문자열을 객체로 변환하여 Book 객체의 title과 일치하는지 확인.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;assertThat(this.json.parseObject(content).getPublishedAt()).isNull()&lt;/b&gt;&lt;br /&gt;content 문자열을 객체로 변환하여 publishedAt이 null인지 확인.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;assertThat(this.json.write(book)).isEqualToJson(&quot;/test.json&quot;)&lt;/b&gt;&lt;br /&gt;Book 객체를 문자열로 변환하여 test.json 파일의 내용과 일치하는지 확인.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;assertThat(this.json.write(book)).hasJsonPathStringValue(&quot;title&quot;)&lt;/b&gt;&lt;br /&gt;Book 객체를 문자열로 변환하여 title 값이 있는지 확인.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;assertThat(this.json.write(book)).extractingJsonPathStringValue(&quot;title&quot;).isEqualTo(&quot;테스트&quot;)&lt;/b&gt;&lt;br /&gt;Book 객체를 문자열로 변환하여 title 값이 일치하는지 확인.&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;이상으로 Spring Boot Test를 작성하고 실행하는 방법에 대해 알아봤습니다.&lt;/p&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;※ References&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;김영재 지음, 『처음 배우는 스프링 부트 2』, 한빛미디어 (2019), p73 ~ p.91 CHAPTER 3 스프링 부트 테스트&lt;/li&gt;
&lt;li&gt;gracelove91.tistory.com, gradle&amp;nbsp;사용시&amp;nbsp;lombok&amp;nbsp;cannot&amp;nbsp;find&amp;nbsp;symbol&amp;nbsp;에러, &lt;a href=&quot;https://gracelove91.tistory.com/13&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gracelove91.tistory.com/13&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;goddaehee.tistory.com,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[스프링부트&amp;nbsp;(9)]&amp;nbsp;SpringBoot&amp;nbsp;Test(2)&amp;nbsp;-&amp;nbsp;@SpringBootTest로&amp;nbsp;통합테스트&amp;nbsp;하기,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://goddaehee.tistory.com/211&quot;&gt;https://goddaehee.tistory.com/211&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;goddaehee.tistory.com, [스프링부트 (10)] SpringBoot Test(3) - 단위 테스트(@WebMvcTest, @DataJpaTest, @RestClientTest 등), &lt;a href=&quot;https://goddaehee.tistory.com/212&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://goddaehee.tistory.com/212&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/Spring Boot</category>
      <category>annotation</category>
      <category>booot</category>
      <category>Client</category>
      <category>Database</category>
      <category>Entity</category>
      <category>framework</category>
      <category>inmemory</category>
      <category>Internet</category>
      <category>IT</category>
      <category>JPA</category>
      <category>json</category>
      <category>MVC</category>
      <category>rest</category>
      <category>spring</category>
      <category>Starter</category>
      <category>test</category>
      <category>web</category>
      <author>freestrokes</author>
      <guid isPermaLink="true">https://freestrokes.tistory.com/110</guid>
      <comments>https://freestrokes.tistory.com/110#entry110comment</comments>
      <pubDate>Mon, 12 Sep 2022 19:48:54 +0900</pubDate>
    </item>
    <item>
      <title>Gradle 설치 및 사용하기</title>
      <link>https://freestrokes.tistory.com/108</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;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;b&gt;Gradle 설치 및 사용하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle을 설치하고 사용하는 방법에 대해 정리한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; 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;font-size: 12pt;&quot;&gt;1. Gradle&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 프로젝트의 빌드 도구로는 Maven을 많이 사용하지만 XML 기반의 설정 파일인 pom.xml은 동적인 구성에 제약이 있었습니다. 이러한 Maven의 대안으로 나온 Gradle은 Groovy 기반의 스크립트 언어로 구성되어 있으며 변수 선언 및 조건문(if-else, for) 등을 사용하여 로직 구현이 가능합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1659263291171&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;Gradle Build Tool&quot; data-og-description=&quot;Accelerate developer productivity. Gradle helps teams build, automate and deliver better software, faster.&quot; data-og-host=&quot;gradle.org&quot; data-og-source-url=&quot;https://gradle.org/&quot; data-og-url=&quot;https://gradle.org/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wOmjX/hyPhvaD5ar/Xba6rkHYP5E05HPxVkKw0K/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/l4UN6/hyPgeuPNsM/XrgR3tmgmOyLMvGgeb5zt0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bw2Bb2/hyPhwOaaH4/73WzJN54DwhFiYVQGguaoK/img.png?width=281&amp;amp;height=281&amp;amp;face=0_0_281_281&quot;&gt;&lt;a href=&quot;https://gradle.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gradle.org/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wOmjX/hyPhvaD5ar/Xba6rkHYP5E05HPxVkKw0K/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/l4UN6/hyPgeuPNsM/XrgR3tmgmOyLMvGgeb5zt0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bw2Bb2/hyPhwOaaH4/73WzJN54DwhFiYVQGguaoK/img.png?width=281&amp;amp;height=281&amp;amp;face=0_0_281_281');&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;Gradle Build Tool&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Accelerate developer productivity. Gradle helps teams build, automate and deliver better software, faster.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gradle.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle의 특징은 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; 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;Ant 빌드 도구와 Maven 의존성 라이브러리 관리 기능을 차용&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Maven에 비해 10~100배 정도 빠른 빌드 속도&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Maven의 상속 구조와는 다른 설정 주입(Configuration Injection)을 사용하여 유연한 빌드 환경 구성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Groovy 기반의 스크립트를 사용하여 코딩에 의한 간결하고 동적인 설정 정보 구성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Gradle 설치 없이도 Gradle Wrapper를 이용한 빌드 지원&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Maven, Ivy repository 지원&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멀티 프로젝트 빌드를 지원&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.1. Gradle 설치하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같이 homebrew를 이용하여 gradle을 설치해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1659262868319&quot; class=&quot;bash&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ brew install gradle

Running `brew update --auto-update`...
==&amp;gt; Auto-updated Homebrew!
Updated 2 taps (homebrew/core and homebrew/cask).
==&amp;gt; New Formulae
git-delete-merged-branches        libeatmydata                 mesheryctl                   open62541                         pkcs11-tools
==&amp;gt; New Casks
iqmol                             livebook

You have 10 outdated formulae installed.
You can upgrade them with brew upgrade
or list them with brew outdated.

==&amp;gt; Downloading https://ghcr.io/v2/homebrew/core/openjdk/manifests/18.0.2
######################################################################## 100.0%
==&amp;gt; Downloading https://ghcr.io/v2/homebrew/core/openjdk/blobs/sha256:bbcf2194487d9beeaa1d5e08
==&amp;gt; Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:bbcf2194487d9beeaa1d5e08?se=2022-07-31T10%3A25%3A00Z&amp;amp;sig=33D2JDj4dN2nRiWmKfnpnkqUTXMorwRb4Hx%2FetPHQWs%3D&amp;amp;sp=r&amp;amp;spr=https&amp;amp;sr=b&amp;amp;sv=2019-12-12
######################################################################## 100.0%
==&amp;gt; Downloading https://ghcr.io/v2/homebrew/core/gradle/manifests/7.5
######################################################################## 100.0%
==&amp;gt; Downloading https://ghcr.io/v2/homebrew/core/gradle/blobs/sha256:86b7fe4097ed2d3608afdcad057cc4871e
==&amp;gt; Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:86b7fe4097ed2d3608afdcad057cc4871e?se=2022-07-31T10%3A25%3A00Z&amp;amp;sig=YrAgBI8QReFZCalmX34feAdasJU9FqgXlLqFU8Ex0uc%3D&amp;amp;sp=r&amp;amp;spr=https&amp;amp;sr=b&amp;amp;sv=2019-12-12
######################################################################## 100.0%
==&amp;gt; Installing dependencies for gradle: openjdk
==&amp;gt; Installing gradle dependency: openjdk
==&amp;gt; Pouring openjdk--18.0.2.arm64_monterey.bottle.tar.gz
   /opt/homebrew/Cellar/openjdk/18.0.2: 642 files, 309MB
==&amp;gt; Installing gradle
==&amp;gt; Pouring gradle--7.5.all.bottle.tar.gz
   /opt/homebrew/Cellar/gradle/7.5: 11,340 files, 276.1MB
==&amp;gt; Running `brew cleanup gradle`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none; line-height: 1;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none; line-height: 1;&quot; data-ke-size=&quot;size16&quot;&gt;설치된 버전을 확인해줍니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none; line-height: 1;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659262929384&quot; class=&quot;bash&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gradle --version

------------------------------------------------------------
Gradle 7.5
------------------------------------------------------------

Build time:   2022-07-14 12:48:15 UTC
Revision:     123456789089ad2b0c1472b6fe663e6d654a5103

Kotlin:       1.6.21
Groovy:       3.0.10
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          18.0.2 (Homebrew 18.0.2+0)
OS:           Mac OS X 12.5 aarch64&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.2. Gradle Wrapper&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;Gradle Wrapper는 빌드 관련 설정을 프로젝트에 함께 포함하여 배포할 수 있도록 지원해줍니다. CI(Continuous Integration) 환경에서 빌드할 때 java 또는 gradle의 설치 없이 환경에 종속되지 않고 빌드가 가능하도록 해줍니다. Spring Initializr를 이용하여 프로젝트를 생성하면 Gradle이 자동으로 설치되는데 이것은 Gradle Wrapper를 통해 설치된 것 입니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;설치된 Gradle의 기본 구조는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1659363118357&quot; class=&quot;bash&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;gradle 
├── wrapper
│    ├── gradle-wrapper.jar
│    └── gradle-wrapper.properties
gradlew
gradlew.bat
build.gradle
settings.gradle&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;각 파일들의 용도는 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; 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;gradle/wrapper/gradle-wrapper.jar&lt;/b&gt;&lt;br /&gt;실행 스크립트가 동작할 때 Wrapper에 맞는 환경을 로컬 캐시에 다운로드 받아 task를 실행.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;gradle/wrapper/gradle-wrapper.properties&lt;/b&gt;&lt;br /&gt;Gradle 버전 정보와 같은 설정 정보가 있는 프로퍼티 파일.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;gradlew&lt;/b&gt;&lt;br /&gt;유닉스, 리눅스 및 MacOS용 wrapper 실행 스크립트.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;gradlew.bat&lt;/b&gt;&lt;br /&gt;윈도우용 wrapper 실행 배치 스크립트.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;build.gradle&lt;/b&gt;&lt;br /&gt;빌드에 필요한 모든 설정을 정의하는 파일.&lt;br /&gt;프로젝트 환경 설정, 빌드 방법, 라이브러리 정보 등을 작성하여 빌드 및 프로젝트의 관리 환경을 구성.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;setting.gradle&lt;/b&gt;&lt;br /&gt;프로젝트 구성을 설정할 때 작성하는 파일. &lt;br /&gt;멀티 프로젝트를 구성할 때 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;또한 Gradle Wrapper를 이용하여 다음 명령어를 실행하면 버전을 수정 할 수 있습니다. 실행 이후에 gradle-wrapper.properties 파일의 distributionUrl 값을 확인해보면 수정한 버전을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1659364616067&quot; class=&quot;bash&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gradle wrapper --gradle-version 7.5&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1.3. build.gradle&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;build.gradle 파일은 프로젝트의 환경과 빌드 정보를 설정하는 파일입니다. Groovy 문법의 메서드와 프로퍼티로 설정을 정의할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;다음은 Spring Initializr로 프로젝트 생성시 함께 만들어지는 build.gradle 파일입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1659366039060&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id 'org.springframework.boot' version '2.7.2'
    id 'io.spring.dependency-management' version '1.0.12.RELEASE'
    id 'java'
}

group = 'com.freestrokes'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'junit:junit:4.13.2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    compileOnly 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;각 프로퍼티의 역할은 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; 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;plugins&lt;/b&gt;&lt;br /&gt;프로젝트에서 환경 구성에 필요한 플러그인을 추가.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;group&lt;/b&gt; &lt;br /&gt;프로젝트 groupId&lt;/li&gt;
&lt;li&gt;&lt;b&gt;version&lt;/b&gt; &lt;br /&gt;프로젝트 버전&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sourceCompatibility&lt;/b&gt;&lt;br /&gt;프로젝트 자바 버전&lt;/li&gt;
&lt;li&gt;&lt;b&gt;repositories&lt;/b&gt;&lt;br /&gt;라이브러리를 다운로드 할 저장소.&lt;br /&gt;jcenter 또는 maven 저장소를 사용 가능.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;dependencies&lt;/b&gt;&lt;br /&gt;의존성 라이브러리를 추가.&lt;br /&gt;&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;api, compile&lt;/b&gt;&lt;br /&gt;라이브러리를 추가&lt;br /&gt;라이브러리 수정 시 해당 라이브러리에 의존하는 모든 라이브러리들을 빌드&lt;br /&gt;compile의 경우 Gradle 3.0 부터는 사용하는 것을 권장하지 않음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;implementation&lt;/b&gt;&lt;br /&gt;라이브러리를 추가&lt;br /&gt;라이브러리 수정 시 해당 라이브러리에 직접 의존하는 라이브러리만 빌드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;testImplementation&lt;/b&gt;&lt;br /&gt;테스트에&amp;nbsp;사용하는&amp;nbsp;라이브러리&amp;nbsp;추가&lt;/li&gt;
&lt;li&gt;&lt;b&gt;annotationProcessor&lt;/b&gt;&lt;br /&gt;어노테이션 기반 라이브러리를 컴파일러가 인식할 수 있도록 추가 (Lombok, QueryDSL, ConfigurationProperties 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;compileOnly&lt;/b&gt;&lt;br /&gt;compile 시점에만 필요한 라이브러리를 추가.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;runtimeOnly&lt;/b&gt;&lt;br /&gt;runtime 시점에만 필요한 라이브러리를 추가.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;이상으로 Gradle 설치 및 사용 방법에 대해 알아봤습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ References&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; 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;docs.gradle.org, Gradle User Manual, &lt;a href=&quot;https://docs.gradle.org/current/userguide/userguide.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.gradle.org/current/userguide/userguide.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;kotlinworld.com, Mac에서 Gradle 설치하기, &lt;a href=&quot;https://kotlinworld.com/312&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlinworld.com/312&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;kotlinworld.com, [Gradle]&amp;nbsp;build.gradle의&amp;nbsp;동작원리&amp;nbsp;한&amp;nbsp;번에&amp;nbsp;정리하기, &lt;a href=&quot;https://kotlinworld.com/321&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlinworld.com/321&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;dev-coco.tistory.com,&lt;span&gt;&amp;nbsp;&lt;/span&gt;메이븐(Maven)과&amp;nbsp;그래들(Gradle)의&amp;nbsp;개념&amp;nbsp;및&amp;nbsp;비교,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://dev-coco.tistory.com/65&quot;&gt;https://dev-coco.tistory.com/65&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;jungseob86.tistory.com, Gradle&amp;nbsp;Wrapper란?, &lt;a href=&quot;https://jungseob86.tistory.com/21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jungseob86.tistory.com/21&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;compogetters.tistory.com, Gradle&amp;nbsp;Compile과&amp;nbsp;implementation의&amp;nbsp;차이점, &lt;a href=&quot;https://compogetters.tistory.com/64&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://compogetters.tistory.com/64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;김영재 지음, 『처음 배우는 스프링 부트 2』, 한빛미디어 (2019), p44 ~ p46. CHAPTER 2 스프링 부트 환경 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Build/Gradle</category>
      <category>build</category>
      <category>development</category>
      <category>environment</category>
      <category>Gradle</category>
      <category>install</category>
      <category>Internet</category>
      <category>IT</category>
      <category>Programming</category>
      <category>web</category>
      <category>Wrapper</category>
      <author>freestrokes</author>
      <guid isPermaLink="true">https://freestrokes.tistory.com/108</guid>
      <comments>https://freestrokes.tistory.com/108#entry108comment</comments>
      <pubDate>Tue, 2 Aug 2022 00:31:55 +0900</pubDate>
    </item>
    <item>
      <title>React Query 알아보기</title>
      <link>https://freestrokes.tistory.com/170</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;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;b&gt;React Query 알아보기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;React Query의 기본적인 내용에 대해 알아보고 정리한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1. React Query&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Query는 리액트 애플리케이션에서 서버의 데이터를 조회하거나 캐싱, 업데이트, 에러 처리와 같은 비동기 로직을 지원하는 fetching 라이브러리입니다. Server-side와 Client-side 사이에서 비동기 로직을 보다 쉽게 다루게 해주며 Server State를 관리해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1656383408155&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;React Query&quot; data-og-description=&quot;Hooks for fetching, caching and updating asynchronous data in React&quot; data-og-host=&quot;react-query.tanstack.com&quot; data-og-source-url=&quot;https://react-query.tanstack.com/&quot; data-og-url=&quot;https://react-query.tanstack.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/PEfRD/hyOczZ3PMP/2drgPXwXKsPMXqM8mk6c2k/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/cTcgVu/hyOcCicw4k/02eg25KJzYfkqmbpOkyvFk/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629&quot;&gt;&lt;a href=&quot;https://react-query.tanstack.com/&quot; data-source-url=&quot;https://react-query.tanstack.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/PEfRD/hyOczZ3PMP/2drgPXwXKsPMXqM8mk6c2k/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/cTcgVu/hyOcCicw4k/02eg25KJzYfkqmbpOkyvFk/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629');&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;React Query&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Hooks for fetching, caching and updating asynchronous data in React&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react-query.tanstack.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;React Query에서는 Server State를 다음과 같이 정의합니다.&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;Client가 제어하거나 소유하지 않는 위치에서 원격으로 유지 됨.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;fetching 및 updating을 위한 비동기 API를 필요로 함.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태가 공유되며 사용자 모르게 변경될 수 있음.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주의하지 않으면 애플리케이션이 잠재적으로 &quot;out of date&quot; 상태가 될 수 있음.&lt;/b&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;React Query는 앞서 언급한 Server State의 관리 기능을 포함하여 아래와 같은 특징도 가지고 있습니다.&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;Caching 지원.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동일한 데이터에 대한 중복 요청을 제거하고 한 번만 요청하도록 함.&amp;nbsp;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;out of date&quot; 상태의 데이터를 파악하고 updating 지원.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pagination 및 Lazy Loading 성능 최적화.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Server State의 메모리 관리 및 garbage collection 지원.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;React Hooks와 유사한 인터페이스 제공.&lt;/b&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;React Query에서 Server State를 모델링하는 과정은 다음과 같습니다.&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-filename=&quot;react-query-async-states.jpeg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0hY1V/btrFXYgkiWB/rp8XSWeQMdcS0KmKODimoK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0hY1V/btrFXYgkiWB/rp8XSWeQMdcS0KmKODimoK/img.jpg&quot; data-alt=&quot;React Query - Async states&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0hY1V/btrFXYgkiWB/rp8XSWeQMdcS0KmKODimoK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0hY1V%2FbtrFXYgkiWB%2Frp8XSWeQMdcS0KmKODimoK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;364&quot; data-filename=&quot;react-query-async-states.jpeg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;React Query - Async states&lt;/figcaption&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;Fetching&lt;/b&gt;&lt;br /&gt;초기 상태이며 백엔드와 같은 외부 소스로부터 데이터를 가져오기 위해 동작.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Fresh&lt;/b&gt;&lt;br /&gt;Fetching 이후에 Server-side와 Client-side의 데이터가 동일하게 유지되는 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Stale&lt;/b&gt;&lt;br /&gt;데이터가 오래된 상태이며 Fetching을 통해 Fresh 상태로 유지해줘야 함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Inactive&lt;/b&gt;&lt;br /&gt;애플리케이션에서 사용되지 않는 데이터에 대한 상태.&lt;br /&gt;React Query에서 브라우저 캐시를 관리하는 가비지 컬렉터에 의해 삭제 됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Deleted&lt;/b&gt;&lt;br /&gt;Inactive 상태의 데이터가 캐시에서 삭제된 상태.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2. React Query 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Query 사용을 위한 설정 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.1. React Query 설치&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;다음 명령어를 실행하여 React Query를 설치해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1655018165347&quot; class=&quot;dockerfile&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ yarn add react-query&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;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.2. QueryClientProvider&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;QueryClientProvider는 리액트 애플리케이션에서 비동기 요청을 처리하기위한 Context Provider로 동작하며 하위 컴포넌트에서 QueryClient를 사용할 수 있게 해줍니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;index.tsx 파일에 React Query를 다음과 같이 설정해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1651121418714&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* index.tsx */

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import { QueryClient, QueryClientProvider } from 'react-query';

const queryClient = new QueryClient();

ReactDOM.render(
  &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/QueryClientProvider&amp;gt;,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2.3. QueryCache&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;QueryCache는 React Query를 이용하여 사용된 쿼리의 메타 정보와 상태 등의 데이터를 저장하는 용도로 사용합니다. 또한 onError, onSuccess 콜백을 사용하여 애플리케이션 전역에서 이벤트를 핸들링 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;QueryCache 사용을 위해 index.tsx 파일에서 QueryClient 부분을 아래와 같이 설정해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1655088342563&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* index.tsx */

import { QueryClient, QueryClientProvider, QueryCache } from 'react-query';

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) =&amp;gt; {
      console.log('onError', error);
    },
    onSuccess: (data) =&amp;gt; {
      console.log('onSuccess', data);
    }
  })
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3. React Query API&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Query를 이용한 API 사용 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.1. useQuery&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;useQuery는 서버에서 데이터를 가져오기(get) 위해 사용하는 hook 입니다. unique key, promise 기반의 함수, 옵션 값을 파라미터로 받아서 동작합니다. unique key는 애플리케이션 전역에서 해당 쿼리를 refetching, caching, sharing 하는 용도로 사용되며, 쿼리의 리턴 값으로는 status, data, error와 같은 템플릿을 포함하여 데이터 사용에 필요한 정보가 제공됩니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;다음과 같이 기본적인 쿼리의 상태(status)와 프로퍼티를 사용할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&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;isLoading&lt;/b&gt;&lt;br /&gt;쿼리에 데이터가 없고 &lt;span&gt;fetching 하&lt;/span&gt;는 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;isError&lt;/b&gt;&lt;br /&gt;쿼리에 에러가 발생한 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;isSuccess&lt;/b&gt;&lt;br /&gt;쿼리가 성공적으로 실행되었고 데이터를 사용가능한 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;isIdle&lt;/b&gt;&lt;br /&gt;쿼리를 사용할 수 없는 상태. (disabled)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;error&lt;/b&gt;&lt;br /&gt;쿼리가 isError 상태인 경우 에러 정보 확인을 위해 사용하는 프로퍼티.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;data&lt;/b&gt;&lt;br /&gt;쿼리가 isSucess 상태인 경우 데이터 사용을 위해 사용하는 프로퍼티.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;isFetching&lt;/b&gt;&lt;br /&gt;쿼리의 fetching/refetching 여부에 대한 boolean 값.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;다음 예제를 통해 useQuery의 사용 방법에 대해 알아보겠습니다. useQuery에 사용한 파라미터는 순서대로 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&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;queryKey&lt;/b&gt;&lt;br /&gt;쿼리에 사용할 unique key 값.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;queryFn&lt;/b&gt;&lt;br /&gt;쿼리에 사용할 promise 기반의 비동기 API 함수.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;options&lt;/b&gt;&lt;br /&gt;쿼리에 사용할 옵션 값.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1655632383223&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {
  isSuccess,
  isError,
  isLoading,
  isFetching,
  data,
  error
} = useQuery(
  'getUsers',
  () =&amp;gt; axios.get(`https://jsonplaceholder.typicode.com/users`),
  {
    onSuccess: (data) =&amp;gt; {
      console.log('onSuccess', data);
    },
    onError: (error) =&amp;gt; {
      console.log('onError', error);
    }
  }
);

if (isFetching) {
  console.log('fetching...');
}

if (isLoading) {
  console.log('loading...');
}

if (isError) {
  console.log('error', error);
}

if (isSuccess) {
  console.log('success', data);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;예제를 실행한 결과는 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-26 오후 3.09.49.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/70iV5/btrFJ9QXiEJ/AZ5LI1ZLHdGLCNbUEyn6DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/70iV5/btrFJ9QXiEJ/AZ5LI1ZLHdGLCNbUEyn6DK/img.png&quot; data-alt=&quot;React Query - useQuery&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/70iV5/btrFJ9QXiEJ/AZ5LI1ZLHdGLCNbUEyn6DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F70iV5%2FbtrFJ9QXiEJ%2FAZ5LI1ZLHdGLCNbUEyn6DK%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;581&quot; data-filename=&quot;스크린샷 2022-06-26 오후 3.09.49.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;581&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;React Query - useQuery&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;쿼리의 프로퍼티 중에서 isLoading, isError, isSuccess는 다음과 같이 status 프로퍼티로 대체하여 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1656225509578&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {
  status,
  isFetching,
  data,
  error
} = useQuery(
  'getUsers',
  () =&amp;gt; axios.get(`https://jsonplaceholder.typicode.com/users`)
);

if (isFetching) {
  console.log('fetching...');
}

if (status === 'loading') {
  console.log('loading...');
}

if (status === 'error') {
  console.log('error', error);
}

if (status === 'success') {
  console.log('success', data);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.1.1. useQuery의 Query Key&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;useQuery에서 파라미터로 사용되는 Query Key는 React Query에서 쿼리 캐싱을 관리하기 위한 unique key로 사용됩니다. 문자열이나 배열로 설정이 가능합니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;Query Key를 문자열로 사용하면 내부적으로 배열로 변환됩니다. 일반적인 목록이나 인덱싱 된 리소스, 비계층적(Non-hierarchical) 리소스를 대상으로 유용하게 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1656231222705&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {
  status,
  isFetching,
  data,
  error
} = useQuery(
  'getUsers',
  () =&amp;gt; axios.get(`https://jsonplaceholder.typicode.com/users`)
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;Query Key를 배열로 사용하는 경우엔 unique key로 사용할 문자열과 serializable 객체를 사용해줍니다. 계층적(Hierarchical) 리소스나 중첩된(nested) 리소스를 대상으로 ID 또는 추가적인 파라미터를 전달하여 사용합니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;다음과 같이 쿼리의 unique key와 함께 쿼리 내부에 전달할 파라미터를 작성하여 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1656232062361&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const user = {
  id: 1
};

const {
  status,
  isFetching,
  data,
  error
} = useQuery(
  ['getUser', user.id],
  () =&amp;gt; axios.get(`https://jsonplaceholder.typicode.com/users/${user.id}`)
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;배열을 사용한 Query Key는 해싱에서도 차이가 있는데 &lt;span&gt;배열 요소의 순서가 다르면 다르게 해싱됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1656232551980&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 배열의 순서가 같으면 같은 쿼리로 해싱
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)

// 배열의 순서가 다르면 다른 쿼리로 해싱
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;Query Key에 대한 좀 더 자세한 내용은 아래 링크에서 확인할 수 있습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1656232830240&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;Query Keys&quot; data-og-description=&quot;Query Keys At its core, React Query manages query caching for you based on query keys. Query keys can be as simple as a string, or as complex as an array of many strings and nested objects. As long as the query key is serializable, and unique to the query'&quot; data-og-host=&quot;react-query.tanstack.com&quot; data-og-source-url=&quot;https://react-query.tanstack.com/guides/query-keys&quot; data-og-url=&quot;https://react-query.tanstack.com/guides/query-keys&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bKdbyn/hyOSRTSOS9/k20ccQfbx408i3exOX4fik/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/b25FZt/hyOSHDJqWd/ffpmZJ79FWlnnD7odZ2yW1/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629&quot;&gt;&lt;a href=&quot;https://react-query.tanstack.com/guides/query-keys&quot; data-source-url=&quot;https://react-query.tanstack.com/guides/query-keys&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bKdbyn/hyOSRTSOS9/k20ccQfbx408i3exOX4fik/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/b25FZt/hyOSHDJqWd/ffpmZJ79FWlnnD7odZ2yW1/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629');&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;Query Keys&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Query Keys At its core, React Query manages query caching for you based on query keys. Query keys can be as simple as a string, or as complex as an array of many strings and nested objects. As long as the query key is serializable, and unique to the query'&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react-query.tanstack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.1.2. useQuery 동기적으로 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;useQuery는 기본적으로 비동기로 동작합니다. useQuery에 다음과 같이 enabled 옵션을 false로 사용하면 동기적으로 사용할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;enabled 옵션을 false로 사용하게 되면 컴포넌트가 mount 되거나 window focus 되어도 쿼리가 자동으로 실행되지 않습니다. 또한 queryClient에서 invalidateQueries 또는 refetchQueries 함수를 호출해도 refetching 되지 않습니다. 쿼리는 캐싱되지 않은 idle 상태이며 fetching을 위해서는 refetch 함수를 트리거로 사용해야 합니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 통해 useQuery의 동기적인 사용 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1656225627957&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {
  status,
  refetch,
  isFetching,
  data,
  error
} = useQuery(
  'getUsers',
  () =&amp;gt; axios.get(`https://jsonplaceholder.typicode.com/users`),
  {
    enabled: false,
    onSuccess: (data) =&amp;gt; {
      console.log('onSuccess', data);
    },
    onError: (error) =&amp;gt; {
      console.log('onError', error);
    }
  }
);

useEffect(() =&amp;gt; {
  console.log(status);
}, [status]);

return (
  &amp;lt;&amp;gt;
    &amp;lt;button onClick={() =&amp;gt; refetch()}&amp;gt;[ GET USERS ]&amp;lt;/button&amp;gt;
  &amp;lt;/&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;예제를 실행한 결과는 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MIObz/btrFHF9T5Up/MKCod1creb7A5Kya0uHYO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MIObz/btrFHF9T5Up/MKCod1creb7A5Kya0uHYO1/img.png&quot; data-alt=&quot;React Query - useQuery with disable automatically running&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MIObz/btrFHF9T5Up/MKCod1creb7A5Kya0uHYO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMIObz%2FbtrFHF9T5Up%2FMKCod1creb7A5Kya0uHYO1%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;304&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;React Query - useQuery with disable automatically running&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;이와 관련된 좀 더 자세한 내용은 아래 링크에서 확인할 수 있습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1656232982750&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;Disabling/Pausing Queries&quot; data-og-description=&quot;Subscribe to Bytes Your weekly dose of JavaScript news. Delivered every Monday to over 80,000 devs, for free.&quot; data-og-host=&quot;react-query.tanstack.com&quot; data-og-source-url=&quot;https://react-query.tanstack.com/guides/disabling-queries&quot; data-og-url=&quot;https://react-query.tanstack.com/guides/disabling-queries&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/q9FTH/hyOSJuD6zV/SupMmvsYA4WBKeOoiKEOQ1/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/tw4HM/hyOSK798Hp/MTO78En2yxjl3YpoXRa2iK/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629&quot;&gt;&lt;a href=&quot;https://react-query.tanstack.com/guides/disabling-queries&quot; data-source-url=&quot;https://react-query.tanstack.com/guides/disabling-queries&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/q9FTH/hyOSJuD6zV/SupMmvsYA4WBKeOoiKEOQ1/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/tw4HM/hyOSK798Hp/MTO78En2yxjl3YpoXRa2iK/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629');&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;Disabling/Pausing Queries&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Subscribe to Bytes Your weekly dose of JavaScript news. Delivered every Monday to over 80,000 devs, for free.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react-query.tanstack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.1.3. useQueries를 이용하여 useQuery 여러개 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;useQuery는 기본적으로 비동기로 동작하기 때문에 컴포넌트 내에 useQuery가 여러 개 있다면 순서대로 실행되지 않고 동시에 실행됩니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;이러한 경우에 다음과 같이 useQueries를 이용하면 여러개의 쿼리를 하나로 묶어서 사용할 수 있습니다. 아래 예제를 실행하면 UseQueryResult가 배열로 반환됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1656233910579&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const users = [1, 2, 3, 4, 5];

const results = useQueries(
  users.map(user =&amp;gt; {
    return {
      queryKey: ['getUser', user],
      queryFn: () =&amp;gt; axios.get(`https://jsonplaceholder.typicode.com/users/${user}`)
    }
  })
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.1.4. useQuery 순차적으로 실행하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;앞서 알아본 useQuery의 enabled 옵션을 다음과 같이 사용하면 여러개의 쿼리를 순차적으로 실행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1656834123276&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {
  status: firstQueryStatus,
  refetch: firstQueryRefetch,
  isFetching: firstQueryIsFetching,
  data: firstQueryData,
  error: firstQueryError
} = useQuery(
  ['getUser', 1],
  () =&amp;gt; axios.get(`https://jsonplaceholder.typicode.com/users/1`),
  {
    onSuccess: (data) =&amp;gt; {
      console.log('firstQueryData', data);
    },
  }
);

const {
  status: secondQueryStatus,
  refetch: secondQueryRefetch,
  isFetching: secondQueryIsFetching,
  data: secondQueryData,
  error: secondQueryError
} = useQuery(
  ['getUser', 2],
  () =&amp;gt; axios.get(`https://jsonplaceholder.typicode.com/users/2`),
  {
    enabled: !firstQueryData,
    onSuccess: (data) =&amp;gt; {
      console.log('secondQueryData', data);
    },
  }
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.1.5. useQuery의 유용한 옵션&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;useQuery에는 다양한 옵션을 사용할 수 있는데 이 중에서 몇가지 유용한 옵션에 대해 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&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;enabled&lt;/b&gt;&lt;br /&gt;false로 설정하면 쿼리가 자동으로 실행되지 않음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;retry&lt;/b&gt;&lt;br /&gt;쿼리가 실패한 경우에 대한 재시도 횟수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;staleTime&lt;/b&gt;&lt;br /&gt;데이터가 stale state로 변경되는 시간 (Infinity로 설정하면 stale state로 변경되지 않음)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;cacheTime&lt;/b&gt;&lt;br /&gt;inactive state의 캐시 데이터가 메모리에 남아있는 시간&lt;/li&gt;
&lt;li&gt;&lt;b&gt;refetchInterval&lt;/b&gt;&lt;br /&gt;설정한 시간(밀리초)에 따라 주기적으로 fetching 실행.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;refetchOnWindowFocus&lt;/b&gt;&lt;br /&gt;창에 포커스가 된 경우에 대한 refetch 여부.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;initialData&lt;/b&gt;&lt;br /&gt;쿼리의 초기값 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;이 외의 다른 다양한 옵션들에 대한 내용은 아래 링크에서 확인할 수 있습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1656834800727&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;useQuery&quot; data-og-description=&quot;Subscribe to Bytes Your weekly dose of JavaScript news. Delivered every Monday to over 80,000 devs, for free.&quot; data-og-host=&quot;react-query.tanstack.com&quot; data-og-source-url=&quot;https://react-query.tanstack.com/reference/useQuery&quot; data-og-url=&quot;https://react-query.tanstack.com/reference/useQuery&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/M1Tmb/hyOYoKdfAK/fk0AyRgkGv1JAX8zdgO221/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/DdJKp/hyOYqnKz0n/5CJ8Qd94IkegIV6qEVNgik/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629&quot;&gt;&lt;a href=&quot;https://react-query.tanstack.com/reference/useQuery&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react-query.tanstack.com/reference/useQuery&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/M1Tmb/hyOYoKdfAK/fk0AyRgkGv1JAX8zdgO221/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/DdJKp/hyOYqnKz0n/5CJ8Qd94IkegIV6qEVNgik/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629');&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;useQuery&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Subscribe to Bytes Your weekly dose of JavaScript news. Delivered every Monday to over 80,000 devs, for free.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react-query.tanstack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.2. useMutation&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;useMutation은 서버를 대상으로 데이터를 create, update, delete 하기 위해 사용하는 hook 입니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;useMutation에서는 다음과 같은 상태와 프로퍼티를 제공합니다. 앞서 알아본 useQuery와 거의 동일합니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&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;isIdle&lt;br /&gt;&lt;/b&gt;mutation이 실행 되지 않아 아직 캐싱되지 않은 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;isLoading&lt;/b&gt;&lt;br /&gt;mutation이 실행중인 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;isError&lt;/b&gt;&lt;br /&gt;mutation에 에러가 발생한 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;isSuccess&lt;/b&gt;&lt;br /&gt;mutation이 성공적으로 실행되었고 데이터를 사용 가능한 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;error&lt;/b&gt;&lt;br /&gt;mutation이 isError 상태인 경우 에러 정보 확인을 위해 사용하는 프로퍼티.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;data&lt;/b&gt;&lt;br /&gt;mutation이 isSucess 상태인 경우 데이터 사용을 위해 사용하는 프로퍼티.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;다음 예제를 통해 useMutation의 사용 방법에 대해 알아보겠습니다. useMutation에 사용한 파라미터는 순서대로 다음과 같은데 파라미터의 구성도 useQuery와 동일합니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&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;mutationKey&lt;/b&gt;&lt;br /&gt;mutation에 사용할 unique key 값.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;mutationFn&lt;/b&gt;&lt;br /&gt;mutation에 사용할 promise 기반의 비동기 API 함수.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;options&lt;/b&gt;&lt;br /&gt;mutation에 사용할 옵션 값.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1656839011524&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const mutation = useMutation(
  'addUser',
  (param: any) =&amp;gt; axios.post(
    `https://jsonplaceholder.typicode.com/users`,
    param
  ),
  {
    onMutate: (variables) =&amp;gt; {
      console.log('onMutate', variables);
    },
    onError: (error, variables, context) =&amp;gt; {
      console.log('onError', context);
    },
    onSuccess: (data, variables, context) =&amp;gt; {
      console.log('onSuccess', data);
    },
    onSettled: (data, error, variables, context) =&amp;gt; {
      console.log('onSettled', data);
    }
  }
);

return (
  &amp;lt;&amp;gt;
    &amp;lt;button onClick={() =&amp;gt; mutation.mutate({title: `title`, body: `body`, userId: 1})}&amp;gt;
      [ ADD USER ]
    &amp;lt;/button&amp;gt;
  &amp;lt;/&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;mutate 함수를 호출하여 mutation을 실행해줍니다. 예제를 실행한 결과는 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BLW7B/btrGpEhdxS9/2dVx18yQvE5KKrybc211x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BLW7B/btrGpEhdxS9/2dVx18yQvE5KKrybc211x0/img.png&quot; data-alt=&quot;React Query - useMutation&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BLW7B/btrGpEhdxS9/2dVx18yQvE5KKrybc211x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBLW7B%2FbtrGpEhdxS9%2F2dVx18yQvE5KKrybc211x0%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;827&quot; height=&quot;263&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;React Query - useMutation&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;useMutation의 옵션에 사용된 함수를 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&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;onMutate&lt;/b&gt;&lt;br /&gt;mutation이 실행되기 전에 실행되는 함수.&lt;br /&gt;mutationFn에 전달되는 파라미터를 동일하게 받음.&lt;br /&gt;mutation 실패시 onError, onSettled 함수에 return 값을 전달함. &lt;br /&gt;rollback 처리가 필요한 경우에 사용되며 return 값은 context 파라미터로 사용 가능.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onError&lt;/b&gt;&lt;br /&gt;mutation 실패시 에러를 전달.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onSuccess&lt;/b&gt;&lt;br /&gt;mutation 성공시 데이터를 전달.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onSettled&lt;/b&gt;&lt;br /&gt;mutation 성공 또는 실패시 데이터나 에러를 전달.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.2.1. useMutation에서 쿼리 Invalidation 처리하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;일반적으로 mutation이 성공적으로 동작한 이후에는 다른 관련된 쿼리의 refetch를 필요로 할 가능성이 높습니다. 이러한 경우엔 다음과 같이 QueryClient의 invalidQueries 함수를 사용해줍니다. 이렇게 하면 mutation 성공 이후에 해당 쿼리가 stale 상태로 변경 되어 캐시에서 삭제되고 refetch가 실행되게 됩니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1656842245639&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const queryClient = new QueryClient();

const mutation = useMutation(
  'addUser',
  (param: any) =&amp;gt; axios.post(
    `https://jsonplaceholder.typicode.com/users`,
    param
  ),
  {
    onSuccess: (data, variables, context) =&amp;gt; {
      queryClient.invalidateQueries('getUsers');
    }
  }
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;이와 관련된 좀 더 자세한 내용은 아래 링크에서 확인할 수 있습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1656843679359&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;Invalidation from Mutations&quot; data-og-description=&quot;Invalidation from Mutations Invalidating queries is only half the battle. Knowing when to invalidate them is the other half. Usually when a mutation in your app succeeds, it's VERY likely that there are related queries in your application that need to be i&quot; data-og-host=&quot;react-query.tanstack.com&quot; data-og-source-url=&quot;https://react-query.tanstack.com/guides/invalidations-from-mutations&quot; data-og-url=&quot;https://react-query.tanstack.com/guides/invalidations-from-mutations&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c5JdyN/hyOYk2eNKn/hkEhpeZHD7mYZGsyZ2GUk1/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/cJPB54/hyOYkHWlJf/NfbSld4olH9ggMnJ3ToQHk/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629&quot;&gt;&lt;a href=&quot;https://react-query.tanstack.com/guides/invalidations-from-mutations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react-query.tanstack.com/guides/invalidations-from-mutations&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c5JdyN/hyOYk2eNKn/hkEhpeZHD7mYZGsyZ2GUk1/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/cJPB54/hyOYkHWlJf/NfbSld4olH9ggMnJ3ToQHk/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629');&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;Invalidation from Mutations&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Invalidation from Mutations Invalidating queries is only half the battle. Knowing when to invalidate them is the other half. Usually when a mutation in your app succeeds, it's VERY likely that there are related queries in your application that need to be i&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react-query.tanstack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3.2.2. useMutation에서 쿼리 데이터 업데이트하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;mutation의 성공 이후에는 response에 새로운 데이터가 반환되는 경우도 많은데 이러한 경우엔 다음과 같이 QueryClient의 setQueryData 함수를 사용하여 해당 쿼리의 데이터를 새로운 데이터로 업데이트해줍니다.&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1656843161118&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const queryClient = new QueryClient();

const mutation = useMutation(
  'updateUser',
  (param: any) =&amp;gt; axios.put(
    `https://jsonplaceholder.typicode.com/users/1`,
    param
  ),
  {
    onSuccess: (data, variables, context) =&amp;gt; {
      queryClient.setQueryData('getUser', data)
    }
  }
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;이와 관련된 좀 더 자세한 내용은 아래 링크에서 확인할 수 있습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1656843619329&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;Updates from Mutation Responses&quot; data-og-description=&quot;Updates from Mutation Responses When dealing with mutations that update objects on the server, it's common for the new object to be automatically returned in the response of the mutation. Instead of refetching any queries for that item and wasting a networ&quot; data-og-host=&quot;react-query.tanstack.com&quot; data-og-source-url=&quot;https://react-query.tanstack.com/guides/updates-from-mutation-responses&quot; data-og-url=&quot;https://react-query.tanstack.com/guides/updates-from-mutation-responses&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b9oqu2/hyOYeA9G9Z/Om8yo3ICd9HqieLfhMOFr1/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/neVLl/hyOYjbceR3/71NWNQBp9skW5OhkO3AD91/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629&quot;&gt;&lt;a href=&quot;https://react-query.tanstack.com/guides/updates-from-mutation-responses&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react-query.tanstack.com/guides/updates-from-mutation-responses&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b9oqu2/hyOYeA9G9Z/Om8yo3ICd9HqieLfhMOFr1/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629,https://scrap.kakaocdn.net/dn/neVLl/hyOYjbceR3/71NWNQBp9skW5OhkO3AD91/img.png?width=1200&amp;amp;height=629&amp;amp;face=0_0_1200_629');&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;Updates from Mutation Responses&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Updates from Mutation Responses When dealing with mutations that update objects on the server, it's common for the new object to be automatically returned in the response of the mutation. Instead of refetching any queries for that item and wasting a networ&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react-query.tanstack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이상으로 React Query의 기본적인 내용에 대해 알아봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;clear: none; float: none;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;※ References&lt;/span&gt;&lt;/b&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;react-query.tanstack.com, React Query - Hooks&amp;nbsp;for&amp;nbsp;fetching,&amp;nbsp;caching&amp;nbsp;and&amp;nbsp;updating&amp;nbsp;asynchronous&amp;nbsp;data&amp;nbsp;in&amp;nbsp;React, &lt;a href=&quot;https://react-query.tanstack.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://react-query.tanstack.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;www.rootstrap.com, React&amp;nbsp;Query&amp;nbsp;and&amp;nbsp;Management&amp;nbsp;of&amp;nbsp;Server&amp;nbsp;State, &lt;a href=&quot;https://www.rootstrap.com/blog/react-query-and-management-of-server-state/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.rootstrap.com/blog/react-query-and-management-of-server-state/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;maxkim-j.github.io, React-Query&amp;nbsp;살펴보기,&amp;nbsp;&lt;a href=&quot;https://maxkim-j.github.io/posts/react-query-preview&quot;&gt;https://maxkim-j.github.io/posts/react-query-preview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;kyounghwan01.github.io, react-query,&amp;nbsp;&lt;a href=&quot;https://kyounghwan01.github.io/blog/React/react-query/basic/&quot;&gt;https://kyounghwan01.github.io/blog/React/react-query/basic/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;techblog.woowahan.com, Store에서&amp;nbsp;비동기&amp;nbsp;통신&amp;nbsp;분리하기&amp;nbsp;(feat.&amp;nbsp;React&amp;nbsp;Query), &lt;a href=&quot;https://techblog.woowahan.com/6339/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/6339/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;velog.io/@woodong, 2.&amp;nbsp;useQuery의&amp;nbsp;활용, &lt;a href=&quot;https://velog.io/@woodong/2.-useQuery%EC%9D%98-%ED%99%9C%EC%9A%A9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@woodong/2.-useQuery%EC%9D%98-%ED%99%9C%EC%9A%A9&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/React</category>
      <category>development</category>
      <category>fetching</category>
      <category>frontend</category>
      <category>IT</category>
      <category>library</category>
      <category>Programming</category>
      <category>Query</category>
      <category>react</category>
      <category>State</category>
      <category>web</category>
      <author>freestrokes</author>
      <guid isPermaLink="true">https://freestrokes.tistory.com/170</guid>
      <comments>https://freestrokes.tistory.com/170#entry170comment</comments>
      <pubDate>Sun, 3 Jul 2022 19:25:09 +0900</pubDate>
    </item>
  </channel>
</rss>