<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Jiwoo.Log</title>
    <link>https://jiwooprogramming.tistory.com/</link>
    <description>안녕하세요 :) </description>
    <language>ko</language>
    <pubDate>Fri, 17 Jul 2026 10:38:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Yang Ji Woo</managingEditor>
    <image>
      <title>Jiwoo.Log</title>
      <url>https://tistory1.daumcdn.net/tistory/6499664/attach/6a812bbcb0994b83a23dd4a1ef151af5</url>
      <link>https://jiwooprogramming.tistory.com</link>
    </image>
    <item>
      <title>[OpenStack] 2. OpenStack 시작하기 &amp;ndash; DevStack 실습 환경 구축 ①</title>
      <link>https://jiwooprogramming.tistory.com/171</link>
      <description>&lt;p style=&quot;text-align: center;&quot; 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;OpenStack&amp;amp;reg;_Logo_2016.svg.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xz8p7/dJMcahEXwgj/Y6FLaXXW2gs70VlAa2de8k/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xz8p7/dJMcahEXwgj/Y6FLaXXW2gs70VlAa2de8k/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xz8p7/dJMcahEXwgj/Y6FLaXXW2gs70VlAa2de8k/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxz8p7%2FdJMcahEXwgj%2FY6FLaXXW2gs70VlAa2de8k%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;242&quot; data-filename=&quot;OpenStack&amp;reg;_Logo_2016.svg.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이전 글에서는 OpenStack이 여러 서버의 컴퓨팅, 네트워크, 스토리지 자원을 통합하여 클라우드 환경을 구축하고 관리할 수 있도록 도와주는 오픈소스 플랫폼이라는 점을 알아보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;OpenStack의 개념을 이해하는 것도 중요하지만, &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;각 서비스가 실제로 어떻게 구성되고 동작하는지를 이해하려면 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;직접 환경을 구축하고 사용해보는 과정이 필요하다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 이번 글부터는 OpenStack 실습 환경을 구성하고, 주요 기능을 하나씩 직접 사용해보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 실습에서는 설치된 &lt;b&gt;VirtualBox를 이용해 Ubuntu Server 가상머신을 생성&lt;/b&gt;한다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이후 &lt;b&gt;Ubuntu Server 내부에 DevStack을 설치&lt;/b&gt;하여 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;하나의 가상머신에서 OpenStack의 주요 서비스를 실행하는 All-in-One 환경을 구축&lt;/b&gt;할 예정이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기서 OpenStack이 아닌 DevStack이 갑자기 튀어나와서 당황할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack을 처음부터 수동으로 설치하면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;각 서비스의 &lt;b&gt;데이터베이스, 인증 정보, API 주소, 네트워크 등을 직접 설정&lt;/b&gt;해야 하므로&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;초보자가 실습 환경을 구성하기 어렵다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이번 실습에서는 주요 &lt;b&gt;OpenStack 서비스를 자동으로 구성&lt;/b&gt;해주는&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DevStack&lt;/b&gt;을 사용하여 먼저 동작하는 환경을 구축하고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이후 각 서비스의 역할과 연결 구조를 하나씩 살펴볼 예정이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;전체적인 실습 환경은 다음과 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Windows PC &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; └─ VirtualBox &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; └─ Ubuntu Server &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; └─ DevStack &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;├─ Keystone &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;├─ Nova &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;├─ Neutron &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;├─ Glance &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;├─ Cinder &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;└─ Horizon&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; 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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;VirtualBox는 Windows 위에서 Ubuntu Server 가상머신을 실행하는 역할을 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Ubuntu Server 내부에서는 KVM 또는 QEMU가 OpenStack 인스턴스를 실행하는 하이퍼바이저 역할을 담당한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, &lt;b&gt;VirtualBox와 KVM 중 하나를 선택하는 것이 아니라 다음과 같이 서로 다른 계층에서 함께 사용되는 구조&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 실습에서는 VirtualBox의 중첩 가상화 기능을 활성화하여&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Ubuntu 가상머신 내부에서 KVM을 사용할 수 있는지 확인할 예정이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;중첩 가상화를 사용할 수 없는 환경에서는 QEMU를 이용해 실습을 진행할 수 있지만,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;인스턴스 실행 성능이 다소 느려질 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;또한 DevStack은 &lt;b&gt;학습과 개발, 기능 테스트를 위한 도구&lt;/b&gt;이며&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제 서비스를 운영하기 위한 설치 방식은 아니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;운영체제의 패키지와 네트워크 설정을 크게 변경하기 때문에&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;개인 개발 환경에 직접 설치하기보다 &lt;b&gt;별도의 가상머신에서 사용하는 것&lt;/b&gt;이 안전하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;정리해서 말하자면&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 이번 실습의 최종 목표는 Ubuntu Server 가상머신에 DevStack 기반의 OpenStack All-in-One 환경을 구축하고, &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Keystone, Nova, Neutron, Glance, Cinder 등 주요 OpenStack 서비스가 정상적으로 실행되는지 확인하는 것이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이번 글에서는 Windows 환경에서 가상머신을 설치하고 실행하기 전까지의 과정을 설명한다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 실제 PC 사양 확인하기 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 최소한의 &lt;b&gt;가상 머신의 사양&lt;/b&gt;을 결정하기 위해&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Windows PC의 사양을 확인한다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;설정 &amp;rarr; 시스템 &amp;rarr; 정보&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;또는 작업 관리자에서 확인 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Ctrl + Shift + Esc &amp;rarr; 성능 &amp;rarr; CPU / 메모리&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;PC에 사양에 따른 &lt;b&gt;권장 사양&lt;/b&gt;은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; PC 메모리 32GB 이상&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;CPU&lt;/b&gt;: 6~8 vCPU &lt;br /&gt;&lt;b&gt;메모리&lt;/b&gt;: 12~16GB &lt;br /&gt;&lt;b&gt;디스크&lt;/b&gt;: 100GB&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; PC 메모리 16GB&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;CPU&lt;/b&gt;: 4 vCPU &lt;br /&gt;&lt;b&gt;메모리&lt;/b&gt;: 8GB &lt;br /&gt;&lt;b&gt;디스크&lt;/b&gt;: 80GB&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;8GB 메모리에서도 OpenStack 자체는 설치될 수 있지만,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Windows와 Ubuntu, OpenStack 인스턴스를 동시에 실행하면 상당히 답답할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;가상 머신의 디스크는 &lt;b&gt;동적 할당 100GB&lt;/b&gt;로 만들어도 처음부터 100GB를 모두 차지하지 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 사용한 만큼 파일 크기가 증가&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; BIOS 가상화 기능 확인하기 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실습을 위해서는 가상화 기능을 필수로 확인해야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;가상화 기능을 활성화하면 VirtualBox가 CPU의 하드웨어 가상화 기능을 사용해 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;가상머신을 더 안정적이고 빠르게 실행할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;또한 Ubuntu 가상머신 내부에서 KVM을 사용하는 &lt;b&gt;중첩 가상화&lt;/b&gt; 실습을 위해 반드시 확인해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;작업 관리자에서 다음 항목을 확인한다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;작업 관리자 &amp;rarr; 성능 &amp;rarr; CPU &amp;rarr; 가상화&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;183&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfQYig/dJMcabLEMd4/CGyyBFPrd5GKZYi5iPzIX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfQYig/dJMcabLEMd4/CGyyBFPrd5GKZYi5iPzIX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfQYig/dJMcabLEMd4/CGyyBFPrd5GKZYi5iPzIX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfQYig%2FdJMcabLEMd4%2FCGyyBFPrd5GKZYi5iPzIX1%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;183&quot; height=&quot;157&quot; data-origin-width=&quot;183&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사진과 같이 &lt;b&gt;자동화가 '사용'&lt;/b&gt;으로 표시되면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 사용 안 함&lt;/b&gt;으로 표시되면 BIOS 또는 UEFI에서 다음 기능을 활성화해야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Intel CPU &lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Intel Virtualization Technology &lt;br /&gt;Intel VT-x&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; AMD CPU&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;SVM Mode &lt;br /&gt;AMD-V&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;설정 이름은 메인보드 제조사마다 조금씩 다르다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; VirtualBox Host-Only 네트워크 만들기 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 실습에서는 VirtualBox 가상머신에 네트워크 어댑터를 두 개 연결하는 방법을 사용할 예정이다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;어댑터 1&lt;/b&gt;: NAT &lt;br /&gt;&lt;b&gt;어댑터 2&lt;/b&gt;: Host-Only&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;역할은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;NAT&lt;/b&gt; &lt;br /&gt;&amp;rarr; Ubuntu가 인터넷에 접속 &lt;br /&gt;&amp;rarr; 패키지와 OpenStack 소스 다운로드 &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Host-Only&lt;/b&gt; &lt;br /&gt;&amp;rarr; Windows와 Ubuntu가 직접 통신 &lt;br /&gt;&amp;rarr; Horizon 접속 &lt;br /&gt;&amp;rarr; 고정된 관리 IP 사용&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; Host-Only 네트워크 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;VirtualBox를 실행하고 네트워크 관리 화면으로 이동한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(VirtualBox 버전에 따라 메뉴 이름은 조금 다를 수 있다)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;도구 &amp;rarr; 네트워크 &amp;rarr; Host-only Networks&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;새 네트워크를 만들고 다음처럼 설정&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;IPv4 주소&lt;/b&gt;: 192.168.56.1 &lt;br /&gt;&lt;b&gt;IPv4 네트워크 마스크&lt;/b&gt;: 255.255.255.0 &lt;br /&gt;&lt;b&gt;DHCP 서버&lt;/b&gt;: 사용하지 않음&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 192.168.56.1은 &lt;b&gt;Windows 쪽 Host-Only 어댑터 주소&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Ubuntu에는 나중에 다음 주소를 직접 설정할 예정&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;192.168.56.10&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 구조는 다음과 같음&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Windows Host-Only&lt;/b&gt;: 192.168.56.1 &lt;br /&gt;&lt;b&gt;Ubuntu Host-Only&lt;/b&gt;: 192.168.56.10&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; Ubuntu Server 가상머신 생성하기 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;VirtualBox에서 새 가상머신을 생성한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;이름&lt;/b&gt;: OpenStack-Lab (이름은 자유)&lt;br /&gt;&lt;b&gt;종류&lt;/b&gt;: Linux &lt;br /&gt;&lt;b&gt;버전&lt;/b&gt;: Ubuntu (64-bit)&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 실습에서는 Ubuntu Server 24.04 LTS ISO를 연결한다.&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;771&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EhqMX/dJMcafHfOPV/7PnTr2vLnp0PwDFCMcirV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EhqMX/dJMcafHfOPV/7PnTr2vLnp0PwDFCMcirV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EhqMX/dJMcafHfOPV/7PnTr2vLnp0PwDFCMcirV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEhqMX%2FdJMcafHfOPV%2F7PnTr2vLnp0PwDFCMcirV1%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;771&quot; height=&quot;554&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 하드웨어 설정&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;PC 메모리가 16GB인 경우&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;메모리&lt;/b&gt;: 8192MB &lt;br /&gt;&lt;b&gt;CPU&lt;/b&gt;: 4개 &lt;br /&gt;&lt;b&gt;디스크&lt;/b&gt;: 80~100GB&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;PC 메모리가 32GB 이상인 경우&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;메모리&lt;/b&gt;: 12288~16384MB &lt;br /&gt;&lt;b&gt;CPU&lt;/b&gt;: 6~8개 &lt;br /&gt;&lt;b&gt;디스크&lt;/b&gt;: 100GB&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 디스크 형식&lt;/b&gt;은 VirtualBox 기본값인 VDI로 사용하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;디스크 형식&lt;/b&gt;: VDI &lt;br /&gt;&lt;b&gt;저장 방식&lt;/b&gt;: 동적 할당 &lt;br /&gt;&lt;b&gt;크기&lt;/b&gt;: 100GB&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WMXMk/dJMcahZh7PK/UlOdPN68RNEQptZGRaEaWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WMXMk/dJMcahZh7PK/UlOdPN68RNEQptZGRaEaWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WMXMk/dJMcahZh7PK/UlOdPN68RNEQptZGRaEaWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWMXMk%2FdJMcahZh7PK%2FUlOdPN68RNEQptZGRaEaWk%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;601&quot; height=&quot;154&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;555&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IjJfe/dJMcag0mdhW/uvkLRJpekfFyZ3mVD7rS1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IjJfe/dJMcag0mdhW/uvkLRJpekfFyZ3mVD7rS1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IjJfe/dJMcag0mdhW/uvkLRJpekfFyZ3mVD7rS1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIjJfe%2FdJMcag0mdhW%2FuvkLRJpekfFyZ3mVD7rS1k%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;767&quot; height=&quot;555&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;555&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 중첩 가상화 설정하기 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;가상머신을 실행하기 전에 설정한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;OpenStack-Lab 선택 &lt;br /&gt;&amp;rarr; 설정 &lt;br /&gt;&amp;rarr; 시스템 &lt;br /&gt;&amp;rarr; 프로세서&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음 항목을 &lt;b&gt;활성화&lt;/b&gt;한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Enable Nested VT-x/AMD-V&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;VirtualBox 공식 설명상 이 옵션은 &lt;b&gt;실제 CPU의 하드웨어 가상화 기능을 게스트 가상머신으로 전달&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;옵션이 회색으로 비활성화되어 있다면 가상머신이 완전히 종료됐는지 먼저 확인해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;명령어로도 설정이 가능&lt;/b&gt;하다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Windows PowerShell 또는 명령 프롬프트에서 실행한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;VBoxManage modifyvm &quot;OpenStack-Lab&quot; --nested-hw-virt on&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래도 활성화되지 않는다면 다음 항목을 확인해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;BIOS의 VT-x 또는 AMD-V &lt;br /&gt;Windows Hyper-V &lt;br /&gt;Virtual Machine Platform &lt;br /&gt;Windows Hypervisor Platform &lt;br /&gt;코어 격리의 메모리 무결성&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다만 &lt;b&gt;WSL2&lt;/b&gt;를 사용 중이라면 Hyper-V 관련 기능을 무작정 끄면 WSL2에 영향을 줄 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;우선 &lt;b&gt;VirtualBox 설정&lt;/b&gt;과 &lt;b&gt;/dev/kvm&lt;/b&gt; 확인까지 진행한 뒤, 실제로 문제가 있을 때 조정하는 게 안전하다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2oGbX/dJMcabLENX8/njSakTpcvIx75etZNCxYZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2oGbX/dJMcabLENX8/njSakTpcvIx75etZNCxYZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2oGbX/dJMcabLENX8/njSakTpcvIx75etZNCxYZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2oGbX%2FdJMcabLENX8%2FnjSakTpcvIx75etZNCxYZ0%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;798&quot; height=&quot;511&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 가상머신 네트워크 어댑터 설정하기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;VirtualBox 가상머신 설정에서 다음처럼 구성한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어댑터 1&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;네트워크 어댑터 사용&lt;/b&gt;: 체크 &lt;br /&gt;&lt;b&gt;다음에 연결됨&lt;/b&gt;: NAT&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdcQMF/dJMcacRcoiH/uyCpUMkwYl0U42r0EKK6X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdcQMF/dJMcacRcoiH/uyCpUMkwYl0U42r0EKK6X0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdcQMF/dJMcacRcoiH/uyCpUMkwYl0U42r0EKK6X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdcQMF%2FdJMcacRcoiH%2FuyCpUMkwYl0U42r0EKK6X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;556&quot; height=&quot;296&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어댑터 2 &lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;네트워크 어댑터 사용&lt;/b&gt;: 체크 &lt;br /&gt;&lt;b&gt;다음에 연결됨&lt;/b&gt;: 호스트 전용 &lt;br /&gt;&lt;b&gt;어댑터 이름&lt;/b&gt;: 앞에서 생성한 Host-Only 네트워크&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnXdS4/dJMcabx0eom/4kRZbFzaNrb2hvxE6KwKc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnXdS4/dJMcabx0eom/4kRZbFzaNrb2hvxE6KwKc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnXdS4/dJMcabx0eom/4kRZbFzaNrb2hvxE6KwKc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnXdS4%2FdJMcabx0eom%2F4kRZbFzaNrb2hvxE6KwKc0%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;565&quot; height=&quot;300&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 가상머신에는 네트워크 인터페이스가 두 개 생기게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Ubuntu에서는 일반적으로 다음과 비슷한 이름으로 보일 것이다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;enp0s3: NAT &lt;br /&gt;enp0s8: Host-Only&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 VirtualBox를 통해서 실습을 위한 가상머신의 설정을 해보았다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 가상머신을 실행해서 본격적인 실습을 진행할 예정이다!&lt;/p&gt;</description>
      <category>공부 기록/클라우드 공부</category>
      <category>devstack</category>
      <category>openstack</category>
      <category>ubuntu</category>
      <category>virtualbox</category>
      <category>가상머신</category>
      <category>오픈스택</category>
      <author>Yang Ji Woo</author>
      <guid isPermaLink="true">https://jiwooprogramming.tistory.com/171</guid>
      <comments>https://jiwooprogramming.tistory.com/171#entry171comment</comments>
      <pubDate>Sun, 5 Jul 2026 00:06:04 +0900</pubDate>
    </item>
    <item>
      <title>[OpenStack] 1. OpenStack이란?</title>
      <link>https://jiwooprogramming.tistory.com/170</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;OpenStack&amp;amp;reg;_Logo_2016.svg.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z498q/dJMcabSgSxG/U3rSIe7AmnV3SdChmRvnv0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z498q/dJMcabSgSxG/U3rSIe7AmnV3SdChmRvnv0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z498q/dJMcabSgSxG/U3rSIe7AmnV3SdChmRvnv0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ498q%2FdJMcabSgSxG%2FU3rSIe7AmnV3SdChmRvnv0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;242&quot; data-filename=&quot;OpenStack&amp;reg;_Logo_2016.svg.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;OpenStack이란?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;https://www.redhat.com/ko/topics/openstack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;https://www.redhat.com/ko/topics/openstack&lt;/b&gt;&lt;/a&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack을 한 문장으로 표현하면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;여러 대의 서버, 스토리지, 네트워크 장비를 하나의 거대한 자원처럼 묶어서&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;클라우드 환경을 만들고 관리할 수 있게 해주는 오픈소스 플랫폼&lt;/b&gt;&lt;/span&gt;이라고 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 한 문장을 이해하기 쉽게 설명하자면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;우리가 흔히 사용하는 AWS, Microsoft Azure, Google Cloud 같은 서비스에서는 웹 화면에서&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;몇 번 클릭하는 것만으로 가상 서버를 만들 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이때 사용자는 실제 서버가 어디에 있는지, 어떤 네트워크에 연결되는지,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저장 공간이 어떤 장비에서 제공되는지 크게 신경쓰지 않아도 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack도 이와 비슷한 기능을 제공한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;차이점은 AWS 같은 클라우드는 다른 회사가 구축해놓은 환경을 빌려 사용하는 서비스인 반면,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack은 &lt;b&gt;기업이나 기관이 자신이 보유한 서버와 네트워크 장비를 이용해 직접 클라우드를&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만들 수 있도록 해주는 소프트웨어&lt;/b&gt;라는 점이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 회사에 물리 서버가 10대 있다고 가정해보자.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack을 사용하지 않는다면 관리자가 각각의 서버에 직접 접속해 운영체제를 설치하고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;네트워크를 설정하고, 사용자를 관리해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 OpenStack을 구축하면 10대의 서버가 가진&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CPU, 메모리, 저장 공간, 네트워크 자원을 하나의 클라우드 자원처럼 관리&lt;/b&gt;할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사용자는 &lt;b&gt;OpenStack 대시보드나 명령어를 이용해 필요한 크기의 가상 서버를 생성&lt;/b&gt;할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;OpenStack은 다음과 같은 요청을 자동으로 처리&lt;/b&gt;해준다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;bull; 가상 서버를 생성한다.&lt;br /&gt;&amp;bull; 가상 서버에 사용할 운영체제 이미지를 선택한다.&lt;br /&gt;&amp;bull; CPU와 메모리를 할당한다.&lt;br /&gt;&amp;bull; 네트워크와 IP 주소를 연결한다.&lt;br /&gt;&amp;bull; 데이터를 저장할 디스크를 생성한다.&lt;br /&gt;&amp;bull; 사용자별 권한과 사용할 수 있는 자원을 관리한다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;풀링된 가상 리소스?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;참고 자료에도 나와 있고, OpenStack 설명에서 자주 등장하는 표현이 바로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;'풀링된 가상 리소스'&lt;/b&gt; &lt;/span&gt;다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 풀링이란 &lt;b&gt;여러 곳에 흩어져 있는 자원을 하나의 공간으로 모아 관리하는 것&lt;/b&gt;을 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 물리 서버 세 대가 다음과 같은 자원을 가지고 있다고 가정해보자.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 서버에는 CPU 16개와 메모리 64GB가 있고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;두 번째 서버에는 CPU 32개와 메모리 128GB가 있으며,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;세 번째 서버에는 CPU 16개와 메모리 64GB가 있다고 하자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack은 이러한 &lt;b&gt;서버들의 자원을 각각 따로 보는 것이 아니라,&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라우드 전체에서 사용할 수 있는 CPU와 메모리 자원으로 관리&lt;/b&gt;한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 CPU 2개와 메모리 4GB가 필요한 가상 서버를 요청하면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack은 현재 &lt;b&gt;여유 자원이 있는 물리 서버를 찾아 그곳에 가상 서버를 생성&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사용자는 실제로 어느 물리 서버에 가상 서버가 만들어졌는지 알 필요가 없다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack이 물리 서버들의 상태를 확인하고 적절한 위치를 자동으로 선택하기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;b&gt;여러 서버의 자원을 하나로 모아 필요할 때 나누어 사용하는 것&lt;/b&gt;을&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;리소스 풀링(Resource Pooling)&lt;/b&gt;&lt;/span&gt;이라고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 프라이빗 클라우드와 퍼블릭 클라우드&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack은 프라이빗 클라우드와 퍼블릭 클라우드를 모두 구축하는데 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;프라이빗 클라우드는 특정 회사나 기관 내부에서만 사용하는 클라우드 환경이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 금융회사, 공공기관, 연구소처럼 데이터 보안이 중요한 조직은&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;외부 클라우드 대신 자체 데이터센터에 서버를 설치&lt;/b&gt;하고 OpenStack을 구축할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 서버와 데이터를 직접 관리하면서도 AWS처럼 가상 서버를 빠르게 생성하고 삭제할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;퍼블릭 클라우드는 여러 고객에게 클라우드 자원을 제공하는 형태다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;클라우드 서비스 제공 업체가 OpenStack을 구축한 뒤,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여러 기업이나 사용자에게 가상 서버, 스토리지, 네트워크 등을 빌려줄 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 OpenStack은 내부 업무용 클라우드뿐만 아니라 외부 고객에게 제공하는 클라우드 서비스에도 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; OpenStack의 프로젝트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack은 하나의 거대한 프로그램으로 모든 기능을 처리하는 구조가 아니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;기능별로 여러 개의 서비스가 나누어져 있으며,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;각각의 서비스&lt;/b&gt;를 OpenStack에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;프로젝트(Project)&lt;/b&gt;&lt;/span&gt;라고 부른다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 가상 서버 생성, 네트워크 구성, 스토리지 관리, 사용자 인증을 각각 다른 프로젝트가 담당한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 프로젝트는 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;bull; &lt;b&gt;Nova&lt;/b&gt;: 가상 서버 생성과 관리를 담당한다.&lt;br /&gt;&amp;bull; &lt;b&gt;Neutron&lt;/b&gt;: 네트워크, IP 주소, 라우터, 보안 그룹 등을 관리한다.&lt;br /&gt;&amp;bull; &lt;b&gt;Cinder&lt;/b&gt;: 가상 서버에 연결하는 블록 스토리지를 관리한다.&lt;br /&gt;&amp;bull; &lt;b&gt;Swift&lt;/b&gt;: 파일과 같은 데이터를 객체 형태로 저장한다.&lt;br /&gt;&amp;bull; &lt;b&gt;Glance&lt;/b&gt;: 가상 서버를 생성할 때 사용하는 운영체제 이미지를 관리한다.&lt;br /&gt;&amp;bull; &lt;b&gt;Keystone&lt;/b&gt;: 사용자 로그인, 인증, 권한 관리를 담당한다.&lt;br /&gt;&amp;bull; &lt;b&gt;Horizon&lt;/b&gt;: OpenStack을 웹 화면에서 관리할 수 있게 해주는 대시보드다.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 &lt;b&gt;Horizon에서 Ubuntu 가상 서버 생성을 요청&lt;/b&gt;하면 하나의 서비스만 동작하는 것이 아니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Keystone이 사용자의 권한을 확인하고, Glance가 Ubuntu 이미지를 제공한다. &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Nova는 적절한 서버를 찾아 가상 머신을 생성하고, Neutron은 네트워크와 IP 주소를 연결한다. &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추가 디스크가 필요하다면 Cinder가 저장 공간을 제공한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 OpenStack은 여러 프로젝트가 서로 API를 통해 통신하면서 하나의 클라우드 기능을 완성한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 가상화와 OpenStack의 관계 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack을 이해하려면 &lt;b&gt;가상화와 클라우드의 차이&lt;/b&gt;도 알아두는 것이 좋다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; 가상화&lt;/span&gt;&lt;/b&gt;는 &lt;b&gt;한 대의 물리 서버를 여러 개의 가상 서버처럼 나누어 사용하는 기술&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 CPU 16개와 메모리 64GB를 가진 물리 서버 한 대가 있다고 하자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 서버를 다음과 같이 나눌 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;bull; &lt;b&gt;가상 서버 A&lt;/b&gt;: CPU 4개, 메모리 16GB&lt;br /&gt;&amp;bull;&lt;b&gt; 가상 서버 B&lt;/b&gt;: CPU 4개, 메모리 16GB&lt;br /&gt;&amp;bull; &lt;b&gt;가상 서버 C&lt;/b&gt;: CPU 8개, 메모리 32GB&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하나의 물리 서버 위에서 여러 가상 머신을 실행할 수 있도록 관리하는 소프트웨어가 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;하이퍼바이저(Hypervisor)&lt;/b&gt;&lt;/span&gt;다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 하이퍼바이저에는 KVM, VMware ESXi, Hyper-V 등이 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack 환경에서는 &lt;b&gt;주로 리눅스 기반의 KVM이 많이 사용&lt;/b&gt;된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;가상화만 사용하면 관리자가 각 물리 서버와 하이퍼바이저를 개별적으로 관리&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서버가 수십 대, 수백 대가 되면 어느 서버에 자원이 남아 있는지,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 서버에 가상 머신을 생성해야 하는지 직접 판단하기 어려워진다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이러한 여러 가상화 서버를 하나의 클라우드 환경으로 묶어서 관리&lt;/b&gt;&lt;/span&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack 자체가 가상 머신을 직접 실행하는 것은 아니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 가상 머신 실행은 KVM 같은 하이퍼바이저가 담당하고, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OpenStack은 어떤 서버에 가상 머신을 만들지 결정하고 생성 과정을 자동화하는 역할&lt;/b&gt;을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;추상화&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;자료에는 OpenStack이 가상 리소스를 한 단계 더 추상화한다는 표현이 나온다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;추상화라는 말은 복잡한 내부 구조를 숨기고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자가 필요한 기능만 간단하게 사용할 수 있도록 만드는 것&lt;/b&gt;을 의미한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 이런거다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;자동차를 운전할 때 운전자는 가속 페달을 밟으면 자동차가 앞으로 나간다는 것만 알면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;엔진 내부에서 연료가 어떻게 분사되고 어떤 부품이 움직이는지까지 모두 알 필요는 없다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OpenStack도 비슷하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 사용자가 가상 서버 생성을 요청할 때 다음과 같은 내부 작업을 모두 직접 처리할 필요가 없다. &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;bull; 어떤 물리 서버에 생성할지 결정&lt;br /&gt;&amp;bull; 하이퍼바이저에 가상 머신 생성 요청&lt;br /&gt;&amp;bull; 운영체제 이미지 전달&lt;br /&gt;&amp;bull; 가상 디스크 생성&lt;br /&gt;&amp;bull; 가상 네트워크 구성&lt;br /&gt;&amp;bull; IP 주소 할당&lt;br /&gt;&amp;bull; 보안 규칙 적용&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사용자는 단순히 CPU, 메모리, 운영체제 이미지, 네트워크 등을 선택하고 생성 버튼을 누르면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;복잡한 작업은 OpenStack이 내부에서 처리&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 실제 물리 장비와 복잡한 설정을 숨기고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 가상 서버나 네트워크처럼 단순한 형태로 자원을 사용할 수 있게 하는 것이 추상화다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;OpenStack API의 역할&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;앞서 설명한 것처럼 OpenStack의 각 프로젝트는 API를 통해 서로 통신한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;API는 서로 다른 프로그램이 요청과 응답을 주고받기 위한 약속이라고 이해하면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;b&gt;사용자가 가상 서버 생성을 요청&lt;/b&gt;하면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Horizon 대시보드는 Nova API에 가상 서버를 만들어 달라는 요청을 보낸다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Nova는 Glance API를 통해 운영체제 이미지를 가져오고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Neutron API를 통해 네트워크 생성을 요청한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사용자는 웹 대시보드뿐만 아니라 &lt;b&gt;명령어나 프로그램&lt;/b&gt;을 통해서도 OpenStack API를 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 명령어로 가상 서버를 생성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1782986021534&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;openstack server create \
  --image ubuntu \
  --flavor m1.small \
  --network private-network \
  my-server&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 명령을 실행하면 &lt;b&gt;OpenStack CLI가 Nova API에 가상 서버 생성 요청을 전달&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;API가 있기 때문에 OpenStack은 반복 작업을 자동화하기 쉽다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가상 서버 한 대를 직접 생성하는 것뿐만 아니라, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스크립트나 자동화 도구를 이용해 여러 대의 서버와 네트워크를 한꺼번에 생성&lt;/b&gt;할 수도 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부 기록/클라우드 공부</category>
      <category>openstack</category>
      <category>오픈스택</category>
      <category>클라우드</category>
      <category>퍼블릭클라우드</category>
      <category>프라이빗클라우드</category>
      <author>Yang Ji Woo</author>
      <guid isPermaLink="true">https://jiwooprogramming.tistory.com/170</guid>
      <comments>https://jiwooprogramming.tistory.com/170#entry170comment</comments>
      <pubDate>Thu, 2 Jul 2026 18:56:24 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 프로그래머스 &amp;lt;N개의 최소공배수&amp;gt;</title>
      <link>https://jiwooprogramming.tistory.com/169</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12953&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/12953&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1777940893584&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12953&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/7Dbmn/dJMb89yhoJS/Yp953wHkOQAFzZUwn3QLmK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/dyDyFU/dJMb83kwLiw/cZF6CDj8pTo4lJSimblkA1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12953&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12953&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/7Dbmn/dJMb89yhoJS/Yp953wHkOQAFzZUwn3QLmK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/dyDyFU/dJMb83kwLiw/cZF6CDj8pTo4lJSimblkA1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777940895986&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import math
from functools import reduce

def lcm(a, b):
    return abs(a*b) // math.gcd(a, b)

def solution(arr):
    return reduce(lcm, arr)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩테스트/코딩테스트 문제풀이</category>
      <category>Python</category>
      <category>최소공배수</category>
      <category>코딩테스트</category>
      <category>코테</category>
      <author>Yang Ji Woo</author>
      <guid isPermaLink="true">https://jiwooprogramming.tistory.com/169</guid>
      <comments>https://jiwooprogramming.tistory.com/169#entry169comment</comments>
      <pubDate>Tue, 5 May 2026 09:28:39 +0900</pubDate>
    </item>
    <item>
      <title>EP25. 상품 관리 애플리케이션에 객체지향 더하기 3편</title>
      <link>https://jiwooprogramming.tistory.com/167</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;모킹과&amp;nbsp;단위&amp;nbsp;테스트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&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;blob&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQj0lw/dJMcaffnfaj/VAkx4PmgDwvBH8mYkDJpx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQj0lw/dJMcaffnfaj/VAkx4PmgDwvBH8mYkDJpx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQj0lw/dJMcaffnfaj/VAkx4PmgDwvBH8mYkDJpx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQj0lw%2FdJMcaffnfaj%2FVAkx4PmgDwvBH8mYkDJpx0%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;697&quot; height=&quot;392&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;단위 테스트를 설명할 때는 보통 먼저 &lt;b&gt;통합 테스트&lt;/b&gt;와 비교해서 보는 것이 이해가 쉽다.&lt;br /&gt;통합 테스트는 말 그대로 &lt;b&gt;두 개 이상의 클래스나 컴포넌트가 함께 협력하는 상태를 검증하는 테스트&lt;/b&gt;다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 내가 지금 개발 중인 코드만 따로 보는 것이 아니라,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 코드가 의존하고 있는 다른 요소들까지 실제로 함께 실행하면서 동작을 확인하는 방식이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 SimpleProductService가 ProductRepository와 함께 동작하는 상태에서 상품 추가와 조회를 테스트했다면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그건 &lt;b&gt;서비스 하나만 보는 것이 아니라 서비스와 저장소의 협력까지 포함해서 본 것이므로 통합 테스트&lt;/b&gt;라고 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;965&quot; data-start=&quot;749&quot; data-ke-size=&quot;size16&quot;&gt;반면 단위 테스트는 훨씬 더 범위를 좁게 본다.&lt;br /&gt;단위 테스트는 &lt;b&gt;오직 하나의 클래스&lt;/b&gt;, 또는 하나의 컴포넌트만을 검증하는 테스트를 의미한다.&lt;br /&gt;즉, 어떤 클래스가 다른 클래스에 의존하고 있더라도, 그 의존 대상까지 실제로 함께 실행하는 것이 아니라&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;965&quot; data-start=&quot;749&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;지금 내가 검증하고 싶은 대상 하나&amp;rdquo;&lt;/b&gt;에만 집중하는 방식이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;965&quot; data-start=&quot;749&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;965&quot; data-start=&quot;749&quot; data-ke-size=&quot;size16&quot;&gt;이번 편에서 다루는 핵심은, SimpleProductService를 테스트할 때&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;965&quot; data-start=&quot;749&quot; data-ke-size=&quot;size16&quot;&gt;매번 실제 ProductRepository까지 같이 사용할 필요가 있을까 하는 질문이다.&lt;br /&gt;서비스의 로직 자체만 보고 싶다면, 저장소 구현체까지 실제로 함께 동작시키는 것은 오히려 범위를 넓혀버리는 일이 될 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;965&quot; data-start=&quot;749&quot; data-ke-size=&quot;size16&quot;&gt;이 지점에서 단위 테스트와 모킹이 필요한 이유가 자연스럽게 드러난다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;965&quot; data-start=&quot;749&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;모킹을&amp;nbsp;통해서&amp;nbsp;SimpleProductService를&amp;nbsp;단위&amp;nbsp;테스트하는&amp;nbsp;코드를&amp;nbsp;작성해보기&lt;/b&gt; &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;SimpleProductService를 단위 테스트하기 위해 ProductRepository를 사용하지 않도록&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;코드를 직접 바꾸는 방법을 생각해볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 원래는 &lt;b&gt;productRepository.add(product)를 호출하던 부분&lt;/b&gt;을,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트를 위해 임시로 &lt;b&gt;Product savedProduct = product; savedProduct.setId(1L);&lt;/b&gt; 같은 형태로 바꿔버리는 방식이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 저장소 없이도 서비스 메서드 자체는 실행해볼 수 있다. 즉, 기술적으로는 가능한 방법이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 방식에는 아주 큰 문제가 있다.&lt;br /&gt;&lt;b&gt;오직 테스트를 위해 실제 애플리케이션 로직을 수정해야 한다는 점&lt;/b&gt;이다.&lt;br /&gt;서비스를 단위 테스트할 때마다 저장소를 사용하는 부분을 손으로 제거하거나 바꿔야 한다면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트를 위해 본래 코드가 오염되는 셈이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;테스트가 끝난 뒤 다시 원래 코드로 되돌려야&lt;/b&gt; 할 수도 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 과정은 번거롭기도 하고, 무엇보다 실수의 여지가 너무 크다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트용으로 잠깐 바꿔놓은 코드를 되돌리지 못하면, 그 자체가&lt;b&gt; 버그나 장애&lt;/b&gt;로 이어질 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;모킹은 &lt;b&gt;특정 클래스나 메서드가 하는 역할을 테스트 코드 안에서 가짜로 흉내 내는 것&lt;/b&gt;이다.&lt;br /&gt;즉, &lt;b&gt;실제 ProductRepository를 호출하지 않더라도, 마치 저장소가 동작한 것처럼 보이게 테스트 상황을 만들어주는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 덕분에 서비스는 여전히 ProductRepository에 의존하는 구조 를 유지하면서도,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트에서는 그 의존 대상을 실제 구현이 아닌 &amp;ldquo;가짜 객체&amp;rdquo;로 대체할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;자바에서 이런 모킹을 할 때 많이 사용하는 라이브러리가 &lt;b&gt;Mockito&lt;/b&gt;다.&lt;br /&gt;Mockito를 사용하면&lt;b&gt; &amp;ldquo;이 메서드가 호출되면 이런 값을 돌려줘&amp;rdquo;&lt;/b&gt; 같은 동작을 테스트 코드 안에서 정의할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3410&quot; data-start=&quot;3347&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 SimpleProductService의 add() 메서드는 내부적으로 대략 이런 흐름을 가진다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3410&quot; data-start=&quot;3347&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3410&quot; data-start=&quot;3347&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① ProductDto를 Product로 변환한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3410&quot; data-start=&quot;3347&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② 유효성 검사를 수행한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3410&quot; data-start=&quot;3347&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;③ productRepository.add(product)를 호출한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3410&quot; data-start=&quot;3347&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;④ 저장 결과를 다시 ProductDto로 변환한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3410&quot; data-start=&quot;3347&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3410&quot; data-start=&quot;3347&quot; data-ke-size=&quot;size16&quot;&gt;단위 테스트에서 정말 보고 싶은 것은 이 흐름이 &lt;b&gt;&amp;ldquo;서비스 로직 기준으로 올바르게 이어지는가&amp;rdquo;&lt;/b&gt;이지,&lt;br /&gt;실제 데이터베이스 저장소가 동작하는지까지 매번 함께 검증하는 것은 아닐 수 있다.&lt;br /&gt;그래서 &lt;b&gt;저장소는 Mockito가 흉내 내고, 서비스는 그 가짜 저장소를 믿고 로직을 수행하게 만드는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3410&quot; data-start=&quot;3347&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3410&quot; data-start=&quot;3347&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;SimpleProductServiceUnitTest&lt;/b&gt;.java 파일 생성&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1777284581059&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ExtendWith(MockitoExtension.class)
public class SimpleProductServiceUnitTest {
    @Mock
    private ProductRepository productRepository;
    
    @Mock
    private ValidationService validationService;

    @InjectMocks
    private SimpleProductService simpleProductService;

    @Test
    @DisplayName(&quot;상품 추가 후에는 추가된 상품이 변경되어야 한다. &quot;)
    void productAddTest() {
        ProductDto productDto = new ProductDto(&quot;연필&quot;, 300, 20);
        Long PRODUCT_ID = 1L;

        Product product = ProductDto.toEntity(productDto);
        product.setId(PRODUCT_ID);
        when(productRepository.add(any())).thenReturn(product);

        ProductDto savedProductDto = simpleProductService.add(productDto);

        assertTrue(savedProductDto.getId().equals(PRODUCT_ID));
        assertTrue(savedProductDto.getName().equals(productDto.getName()));
        assertTrue(savedProductDto.getPrice().equals(productDto.getPrice()));
        assertTrue(savedProductDto.getAmount().equals(productDto.getAmount()));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 클래스 위의 &lt;b&gt;@ExtendWith(MockitoExtension.class)&lt;/b&gt;는&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 테스트가 &lt;b&gt;Mockito 기능을 사용하도록 설정하는 역할&lt;/b&gt;을 한다.&lt;br /&gt;이 어노테이션을 붙이면 스프링 부트 애플리케이션 전체를 띄우지 않아도&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Mockito가 필요한 mock 객체들을 생성&lt;/b&gt;해준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 이 테스트는 &lt;b&gt;@SpringBootTest처럼 애플리케이션을 통째로 실행하는 방식이 아니라, 훨씬 가볍고 빠르게 동작&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;@SpringBootTest는 스프링 부트 애플리케이션을 실제로 띄우면서 빈 등록과 주입 과정을 거치지만,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Mockito 기반 단위 테스트는 그런 전체 실행 없이도 테스트를 수행할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; @Mock&lt;/b&gt;은 가짜 객체를 만드는 어노테이션이다.&lt;br /&gt;여기서는&lt;b&gt; ProductRepository와 ValidationService&lt;/b&gt;가 &lt;b&gt;mock 객체&lt;/b&gt;로 만들어진다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; @InjectMocks&lt;/b&gt;는 &lt;b&gt;mock 객체들을 주입해서 테스트 대상 객체를 만들어주는 역할&lt;/b&gt;을 한다.&lt;br /&gt;즉, SimpleProductService는 실제 ProductRepository가 아니라&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Mockito가 만든 productRepository mock을 주입받아 생성&lt;/b&gt;된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 되면 서비스는 평소처럼 의존성을 가진 상태로 동작하지만,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트에서는 외부 의존성이 모두 &lt;b&gt;가짜 객체&lt;/b&gt;로 대체되어 있는 셈이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그다음 핵심 구문이 &lt;b&gt;when(productRepository.add(any())).thenReturn(product);&lt;/b&gt;이다.&lt;br /&gt;이 코드는&lt;b&gt; &amp;ldquo;productRepository.add(...)가 어떤 값으로 호출되든, 결과로는 product를 반환해라&amp;rdquo;&lt;/b&gt;는 뜻이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 저장소가 실제로 insert를 수행하는 대신, 테스트 안에서는 미리 만들어둔 product를 결과로 돌려주게 만든다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 product에는 id가 1L로 들어가 있으므로, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스 입장에서는 마치 저장소가 저장 후 생성된 id를 넣어 반환해준 것처럼&lt;/b&gt; 보이게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이후 simpleProductService.add(productDto)를 호출하면, 서비스는 내부적으로 DTO를 엔티티로 바꾸고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;검증을 수행하고, 저장소에 저장하고, 다시 DTO로 바꾸는 자기 로직을 그대로 수행한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다만 여기서 &lt;b&gt;저장소 부분만 실제 구현이 아니라 mock으로 대체된 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마지막 &lt;b&gt;assertTrue(...)&lt;/b&gt; 들은 &lt;b&gt;저장 후 반환된 DTO가 우리가 기대한 id와 이름, 가격, 재고 수량을 가지고 있는지 확인&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;즉, 이 테스트는 저장소가 실제로 동작하는지를 보는 것이 아니라,&lt;br /&gt;&lt;b&gt;저장소가 이런 값을 돌려줬다고 가정했을 때 서비스가 올바른 결과를 만들어내는지&lt;/b&gt;를 검증하는 테스트라고 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부 기록/이것이 백엔드 개발이다 With Java</category>
      <category>단위 테스트</category>
      <category>모킹</category>
      <category>백엔드</category>
      <category>백엔드 공부</category>
      <category>통합 테스트</category>
      <author>Yang Ji Woo</author>
      <guid isPermaLink="true">https://jiwooprogramming.tistory.com/167</guid>
      <comments>https://jiwooprogramming.tistory.com/167#entry167comment</comments>
      <pubDate>Mon, 27 Apr 2026 19:20:16 +0900</pubDate>
    </item>
    <item>
      <title>EP24. 상품 관리 애플리케이션에 객체지향 더하기 2편</title>
      <link>https://jiwooprogramming.tistory.com/166</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 리팩토링과 테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&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;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhPz3n/dJMcaib4deL/qXPUVNDUDeG19IUZzkCqYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhPz3n/dJMcaib4deL/qXPUVNDUDeG19IUZzkCqYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhPz3n/dJMcaib4deL/qXPUVNDUDeG19IUZzkCqYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhPz3n%2FdJMcaib4deL%2FqXPUVNDUDeG19IUZzkCqYk%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;623&quot; height=&quot;467&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;리팩토링은 흔히 &amp;ldquo;코드를 더 깔끔하게 고치는 것&amp;rdquo; 정도로 이해되곤 하지만,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;더 정확하게 말하면 &lt;b&gt;동일한 입력에 대해 동일한 결과를 유지한 채, 코드 구조를 개선하는 작업&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 겉으로 드러나는 동작은 바뀌지 않아야 하고, 내부 구조만 더 읽기 좋고 유지보수하기 쉬운 형태로 바뀌어야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이때 말하는 결과에는 단순히 반환값만 포함되는 것이 아니라, &lt;b&gt;사이드 이펙트&lt;/b&gt; 역시 포함된다고 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;b&gt;메서드가 반환하는 값은 같더라도, 원래 저장되던 데이터가 다르게 저장되거나, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예외가 다르게 발생한다면&lt;/b&gt; 그것은 더 이상 리팩토링이라고 보기 어렵다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 리팩토링과 테스트 코드&lt;/b&gt;는 거의 한 세트처럼 같이 이야기된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;리팩토링 전에 테스트 코드가 먼저 준비되어 있으면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;적어도 &lt;b&gt;테스트 코드가 검증하고 있는 범위 안에서는 결과가 변하지 않았는지 확인할 수 있다&lt;/b&gt;.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 리팩토링을 한 뒤에도 테스트가 그대로 통과한다면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;적어도 이 시나리오들에 대해서는 기존 동작이 유지되고 있다&amp;rdquo;&lt;/b&gt;는 신뢰를 얻을 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트 코드는 단순히 기능이 잘 동작하는지 확인하는 용도만 있는 것이 아니다.&lt;br /&gt;기존 코드를 고치거나 구조를 바꾸는 과정에서,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내가 의도하지 않게 원래 동작을 망가뜨리지 않았는지 확인하는 안전장치&lt;/b&gt; 역할도 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 테스트 코드 작성해보기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트 코드라고 하면 많은 사람들이 &lt;b&gt;&amp;ldquo;정상적인 입력을 넣고 기대한 결과가 나오는지 확인하는 코드&amp;rdquo;&lt;/b&gt;를 먼저 떠올린다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;b&gt;상품을 추가한 뒤 id로 조회했을 때, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방금 저장한 상품이 그대로 조회되어야 한다는 것&lt;/b&gt;은 아주 대표적인 정상 시나리오다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이런 흐름을 코드로 검증하면 다음과 같은 테스트가 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777167546299&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootTest
@ActiveProfiles(&quot;test&quot;)
class SimpleProductServiceTest {

    @Autowired
    SimpleProductService simpleProductService;

    @Test
    @DisplayName(&quot;상품을 추가한 후 id로 조회하면 해당 상품이 조회되어야 한다.&quot;)
    void productAddAndFindByIdTest() {
        ProductDto productDto = new ProductDto(&quot;연필&quot;, 300, 20);

        ProductDto savedProductDto = simpleProductService.add(productDto);
        Long savedProductId = savedProductDto.getId();

        ProductDto foundProductDto = simpleProductService.findById(savedProductId);

        assertTrue(foundProductDto.getId()).isEqualTo(savedProductDto.getId());
        assertTrue(foundProductDto.getName()).isEqualTo(savedProductDto.getName());
        assertTrue(foundProductDto.getPrice()).isEqualTo(savedProductDto.getPrice());
        assertTrue(foundProductDto.getAmount()).isEqualTo(savedProductDto.getAmount());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 ProductDto를 하나 만들고, 서비스의 add()를 통해 저장한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그다음 저장된 결과에서 id를 꺼내고, 다시 findById()로 조회한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 저장된 객체와 조회된 객체의 id, 이름, 가격, 재고 수량이 모두 같은지를 비교한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;assert 계열 메서드&lt;/b&gt;는 테스트 코드에서 핵심적인 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트는 결국 &lt;b&gt;&amp;ldquo;무엇을 기대하는가&amp;rdquo;&lt;/b&gt;를 코드로 적는 작업인데,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;assertTrue, assertEquals, assertThat&lt;/b&gt; 같은 구문은 그 기대를 명시하는 방법이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;만약 기대와 다른 결과가 나오면 테스트는 실패하게 되고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여러 테스트가 있어도 어떤 테스트가 깨졌는지를 한눈에 파악할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 테스트 코드는 &lt;b&gt;정상 동작만 검증하는 것이 아니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;예외가 발생하는 상황&lt;/b&gt;도 충분히 &amp;ldquo;정상적인 기대 결과&amp;rdquo;가 될 수 있다.&lt;br /&gt;예를 들어 존재하지 않는 상품 id로 조회했을 때 &lt;b&gt;EntityNotFoundException&lt;/b&gt;이 발생하도록 설계했다면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 경우에는 &lt;b&gt;예외가 발생하는 것이 오히려 올바른 동작&lt;/b&gt;이다. 이런 상황은 &lt;b&gt;assertThrows()로 검증&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777167742359&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;존재하지 않는 상품 id로 조회하면 EntityNotFoundException이 발생해야 한다.&quot;)
void findProductNotExistIdTest() {
    Long notExistId = -1L;

    assertThrows(EntityNotFoundException.class, () -&amp;gt; {
        simpleProductService.findById(notExistId);
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 테스트는 &lt;b&gt;&amp;ldquo;없는 상품을 조회했을 때 실패해야 한다&amp;rdquo;&lt;/b&gt;는 것을 검증하는 것이 아니라,&lt;br /&gt;&lt;b&gt;&amp;ldquo;우리가 의도한 방식으로 실패해야 한다&amp;rdquo;&lt;/b&gt;는 것을 검증한다.&lt;br /&gt;즉, 예외가 난다는 사실 자체가 문제가 아니라, &lt;b&gt;설계한 예외가 정확히 던져지는지&lt;/b&gt;를 확인하는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;또 한 가지 눈여겨볼 점은, 이번 테스트 클래스에서 &lt;b&gt;@Autowired를 사용해 필드 주입 방식으로 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SimpleProductService를 받고 있다는 점&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 애플리케이션 코드에서는 &lt;b&gt;생성자 주입&lt;/b&gt;이 더 권장되는 경우가 많지만,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;테스트 코드에서는 이런 필드 주입 방식이 큰 문제를 만들지 않는 경우가 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;테스트 코드는 애플리케이션 자체의 확장성보다는, &lt;b&gt;특정 기능을 빠르게 검증하는 목적&lt;/b&gt;이 더 크기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;특히 여기서는 &lt;b&gt;테스트 대상이 명확하고, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다른 다형적 구성까지 복잡하게 고려할 필요가 없어서&lt;/b&gt; 비교적 단순하게 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; @Transactional &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 데이터베이스를 사용하는 테스트&lt;/b&gt;를 작성할 때는 한 가지 중요한 문제가 생긴다.&lt;br /&gt;테스트가 데이터를 직접 추가하거나 수정하면, 그 &lt;b&gt;결과가 실제 데이터베이스에 남아버릴 수 있다는 점&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 &amp;ldquo;상품 추가 후 조회&amp;rdquo; 테스트를 한 번 실행할 때마다 상품이 DB에 계속 누적된다면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;같은 테스트를 여러 번 돌렸을 때 결과가 달라질 수 있고, 다른 테스트에도 영향을 줄 수 있다. &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;테스트가 서로를 오염시키는 문제&lt;/b&gt;가 생긴다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;이런 상황을 막기 위해 테스트 코드에 &lt;b&gt;@Transactional&lt;/b&gt;을 붙일 수 있다.&lt;br /&gt;스프링에서 @Transactional은 &lt;b&gt;하나의 논리적 트랜잭션 안에서 작업을 수행하게 해주는 어노테이션&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;그리고 테스트 코드에 이 어노테이션이 붙으면, 보통 테스트가 끝난 뒤 변경 사항을 &lt;b&gt;커밋하지 않고 롤백하는 방식&lt;/b&gt;으로 동작한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;즉, 테스트 실행 중에는 실제로 DB에 insert/update/delete가 수행되지만,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트가 끝나면 그 모든 변경이 취소되어 원래 상태&lt;/b&gt;로 돌아간다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4586&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음처럼 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1777168017971&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
@Test&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 두 가지가 함께 있을 때, 테스트 실행 중에는 트랜잭션이 열리고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트가 끝나면 커밋이 아니라 롤백이 일어나도록 하는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 결과 테스트는 &lt;b&gt;실제 데이터베이스를 사용하는 것처럼 검증할 수 있으면서도, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트가 끝난 뒤에는 데이터가 남지 않는다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>공부 기록/이것이 백엔드 개발이다 With Java</category>
      <category>assert</category>
      <category>Transactional</category>
      <category>리팩토링</category>
      <category>백엔드</category>
      <category>백엔드 공부</category>
      <category>테스트</category>
      <category>테스트 코드</category>
      <author>Yang Ji Woo</author>
      <guid isPermaLink="true">https://jiwooprogramming.tistory.com/166</guid>
      <comments>https://jiwooprogramming.tistory.com/166#entry166comment</comments>
      <pubDate>Sun, 26 Apr 2026 10:50:36 +0900</pubDate>
    </item>
    <item>
      <title>EP23. 상품 관리 애플리케이션에 객체지향 더하기 1편</title>
      <link>https://jiwooprogramming.tistory.com/165</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;클래스&amp;nbsp;추상화하기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;애플리케이션을 개발하다 보면, 같은 기능을 하더라도 실행 환경에 따라 다른 구현을 써야 하는 요구사항이 자주 생긴다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를 들어 로컬 개발 환경에서는 테스트가 편하도록 List를 사용하는 저장소를 쓰고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제 서버에서는 데이터베이스를 사용하는 저장소를 쓰고 싶을 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이런 요구는 학습용 예제에서만 나오는 특별한 상황이 아니라, 실무에서도 아주 흔하게 마주치는 문제다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p4QfC/dJMcadPgGYM/a2UM3yVii3KKwveaqSkUE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p4QfC/dJMcadPgGYM/a2UM3yVii3KKwveaqSkUE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p4QfC/dJMcadPgGYM/a2UM3yVii3KKwveaqSkUE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp4QfC%2FdJMcadPgGYM%2Fa2UM3yVii3KKwveaqSkUE0%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;595&quot; height=&quot;446&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 처음 떠올렸을 때 가장 단순하게 생각할 수 있는 방법은, &lt;b&gt;실행 환경에 따라 코드를 직접 바꿔치기하는 것&lt;/b&gt;이다.&lt;br /&gt;즉, 내 노트북에서 실행할 때는 &lt;b&gt;ListProductRepository&lt;/b&gt;를 연결해두고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서버에 배포할 때는 &lt;b&gt;DatabaseProductRepository&lt;/b&gt;를 연결하도록 소스를 수정하는 방식이다.&lt;br /&gt;하지만 이 방법은 금방 한계를 드러낸다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수정해야 하는 코드가 많아질 수 있고, 배포 직전에 바꿔야 할 부분을 빠뜨리면 그대로 장애로 이어질 수 있다. &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 더 좋은 방법은, &lt;b&gt;환경에 따라 서로 다른 Bean이 주입되도록 만드는 것&lt;/b&gt;이다.&lt;br /&gt;즉, &lt;b&gt;서비스 코드는 &amp;ldquo;상품 저장소&amp;rdquo;라는 추상적인 대상만&lt;/b&gt; 바라보고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제로는 로컬 테스트 환경에서는 ListProductRepository가 주입되고, 운&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;영 환경에서는 DatabaseProductRepository가 주입되도록 만드는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 방식이 되면 &lt;b&gt;서비스 계층은 구체적인 저장 방식이 무엇인지 몰라도 되고, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;환경에 맞는 구현체가 바뀌더라도 서비스 코드는 거의 손대지 않아도 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;인터페이스 도입&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 이 문제를 코드로 풀기 위해, 자바의 기본 문법인 &lt;b&gt;인터페이스(interface)&lt;/b&gt; 를 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;현재 애플리케이션에서 저장소 역할은 ListProductRepository와 DatabaseProductRepository가 각각 수행하고 있다.&lt;br /&gt;이 둘은 구현 방식은 다르지만, 사실상 같은 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;상품을 추가하고, id로 조회하고, 전체 조회를 하고, 이름 검색을 하고, 수정하고, 삭제하는 기능을 공통으로 가진다.&lt;br /&gt;즉, 이미 &lt;b&gt;논리적으로는 같은 인터페이스를 공유하고 있었던 셈&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 그것을 자바 코드에 명시적으로 드러내는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 도메인 패키지에 &lt;b&gt;ProductRepository&lt;/b&gt; 인터페이스를 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1777074243623&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ProductRepository {
    Product add(Product product);
    Product findById(Long id);
    List&amp;lt;Product&amp;gt; findAll();
    List&amp;lt;Product&amp;gt; findByNameContaining(String name);
    Product update(Product product);
    void delete(Long id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 인터페이스에는 &lt;b&gt;두 저장소 클래스가 공통으로 제공해야 하는 메서드들&lt;/b&gt;을 정의한다.&lt;br /&gt;즉, &amp;ldquo;상품 저장소라면 최소한 이런 기능은 있어야 한다&amp;rdquo;는 약속을 코드로 만든 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그다음 DatabaseProductRepository와 ListProductRepository는 각각 이 인터페이스를 구현하도록 바꾼다.&lt;/p&gt;
&lt;pre id=&quot;code_1777074280419&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DatabaseProductRepository implements ProductRepository&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1777074286873&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ListProductRepository implements ProductRepository&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2614&quot; data-start=&quot;2520&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 두 클래스는 더 이상 &amp;ldquo;각자 따로 존재하는 저장소 클래스&amp;rdquo;가 아니라,&lt;br /&gt;&lt;b&gt;둘 다 ProductRepository라는 공통 역할을 구현하는 클래스&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2614&quot; data-start=&quot;2520&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2740&quot; data-start=&quot;2616&quot; data-ke-size=&quot;size16&quot;&gt;이제 &lt;b&gt;SimpleProductService&lt;/b&gt;는 더 이상 DatabaseProductRepository 같은 구체 클래스에 직접 의존하지 않고, &lt;b&gt;ProductRepository 인터페이스에 의존하도록&lt;/b&gt; 바꿀 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1777074333236&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class SimpleProductService {
    private ProductRepository productRepository;
    private ModelMapper modelMapper;
    private ValidationService validationService;

    @Autowired
    SimpleProductService(ProductRepository productRepository, ModelMapper modelMapper, ValidationService validationService){
        this.productRepository = productRepository;
        this.modelMapper = modelMapper;
        this.validationService = validationService;
    }

    public ProductDto add(ProductDto productDto){
        Product product = modelMapper.map(productDto, Product.class);
        validationService.checkValid(product);

        Product savedProduct = productRepository.add(product);
        ProductDto savedProductDto = modelMapper.map(savedProduct, ProductDto.class);
        return savedProductDto;
    }

    public ProductDto findById(Long id){
        Product product = productRepository.findById(id);
        ProductDto productDto = modelMapper.map(product, ProductDto.class);
        return productDto;
    }

    public List&amp;lt;ProductDto&amp;gt; findAll(){
        List&amp;lt;Product&amp;gt; products = productRepository.findAll();
        List&amp;lt;ProductDto&amp;gt; productDtos = products.stream()
            .map(product -&amp;gt; modelMapper.map(product, ProductDto.class))
            .toList();
        return productDtos;
    }

    public List&amp;lt;ProductDto&amp;gt; findByNameContaining(String name){
        List&amp;lt;Product&amp;gt; products = productRepository.findByNameContaining(name);
        List&amp;lt;ProductDto&amp;gt; productDtos = products.stream()
            .map(product -&amp;gt; modelMapper.map(product, ProductDto.class))
            .toList();
        return productDtos;
    }

    public ProductDto update(ProductDto productDto){
        Product product = modelMapper.map(productDto, Product.class);
        Product updatedProduct = productRepository.update(product);
        ProductDto updatedProductDto = modelMapper.map(updatedProduct, ProductDto.class);
        return updatedProductDto;
    }

    public void delete(Long id){
        productRepository.delete(id);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 서비스는 &amp;ldquo;DB에 저장하는지, 리스트에 저장하는지&amp;rdquo;를 직접 알 필요가 없어진다.&lt;br /&gt;서비스가 아는 것은 &lt;b&gt;오직 ProductRepository가 제공하는 기능 뿐&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다만 이 상태로 바로 실행하면 애플리케이션이 정상적으로 뜨지 않을 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;왜냐하면 스프링 입장에서는 &lt;b&gt;ProductRepository 타입으로 주입할 수 있는 Bean이 두 개나 있기 때문&lt;/b&gt;이다.&lt;br /&gt;&lt;b&gt;@Repository가 붙은 두 클래스 모두 Bean으로 등록되므로, 스프링은 &amp;ldquo;둘 중 어느 것을 넣어야 하지?&amp;rdquo;를 결정하지 못한다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; @Profile &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ProductRepository 구현체가 두 개이기 때문에, 스프링은 어떤 Bean을 주입해야 할지 알 수 없다.&lt;br /&gt;&lt;b&gt;이럴 때 사용할 수 있는 대표적인 방법이 바로 @Profile&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;프로파일(Profile)은 애플리케이션이 &lt;b&gt;어떤 환경에서 실행되는지&lt;/b&gt;를 구분하는 방법이다.&lt;br /&gt;여기서는 환경을 test와 prod 두 가지로 나눈다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;test 환경&lt;/b&gt;에서는 &lt;b&gt;ListProductRepository&lt;/b&gt;를 사용&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;prod 환경&lt;/b&gt;에서는 &lt;b&gt;DatabaseProductRepository&lt;/b&gt;를 사용&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이를 위해 각 저장소 클래스에 &lt;b&gt;프로파일&lt;/b&gt;을 붙인다.&lt;/p&gt;
&lt;pre id=&quot;code_1777074489569&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Profile(&quot;test&quot;)
@Repository
public class ListProductRepository implements ProductRepository&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777074496136&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Profile(&quot;prod&quot;)
@Repository
public class DatabaseProductRepository implements ProductRepository&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 애플리케이션이 &lt;b&gt;test 프로파일로 실행될 때는 ListProductRepository만 Bean으로 등록&lt;/b&gt;되고,&lt;br /&gt;&lt;b&gt;prod 프로파일로 실행될 때는 DatabaseProductRepository만 Bean으로 등록&lt;/b&gt;된다.&lt;br /&gt;즉, 동시에 두 Bean이 뜨는 것이 아니라, &lt;b&gt;환경에 맞는 Bean 하나만 생성되도록 만들 수 있다&lt;/b&gt;.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그다음에는 어떤 프로파일로 실행할지 지정해야 한다.&lt;br /&gt;가장 간단한 방법은 &lt;b&gt;application.properties에 설정&lt;/b&gt;하는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1777074539320&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.profiles.active=test&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;또는&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1777074556764&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.profiles.active=prod&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 &lt;b&gt;애플리케이션이 시작될 때&lt;/b&gt; 어떤 환경 기준으로 Bean을 구성할지 결정된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 한 가지 더 생각할 점이 있다.&lt;br /&gt;test 환경에서는 리스트 기반 저장소만 사용할 것이므로, 굳이 &lt;b&gt;데이터베이스 커넥션 풀&lt;/b&gt;을 만들 필요가 없다.&lt;br /&gt;그런데 이전에 ApplicationRunner로 커넥션을 확인하는 Bean을 만들어두었기 때문에,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;test 환경에서도 이 코드가 실행되면 오히려 문제가 될 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서 이 Bean도 prod 환경에서만 생성되도록 바꿔주는 것이 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1777074615117&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
@Profile(&quot;prod&quot;)
public ApplicationRunner runner(DataSource dataSource){
    return args -&amp;gt; {
        Connection connection = dataSource.getConnection();
    };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 &lt;b&gt;prod일 때만 데이터베이스 연결 확인용 Bean이 생성&lt;/b&gt;되고,&lt;br /&gt;&lt;b&gt;test 환경에서는 굳이 DB 연결을 만들지 않아도 애플리케이션이 정상적으로&lt;/b&gt; 뜰 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;의존성&amp;nbsp;주입(DI)과&amp;nbsp;의존성&amp;nbsp;역전&amp;nbsp;원칙(DIP)&lt;/b&gt; &lt;/p&gt;
&lt;p style=&quot;text-align: center;&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;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q9Pog/dJMcagSPw1Z/dZmw8tV2os84M4VwxaJD20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q9Pog/dJMcagSPw1Z/dZmw8tV2os84M4VwxaJD20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q9Pog/dJMcagSPw1Z/dZmw8tV2os84M4VwxaJD20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq9Pog%2FdJMcagSPw1Z%2FdZmw8tV2os84M4VwxaJD20%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;693&quot; height=&quot;390&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 편에서 자연스럽게 같이 등장하는 개념이 &lt;b&gt;DI(의존성 주입)&lt;/b&gt; 와 &lt;b&gt;DIP(의존성 역전 원칙)&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이름이 비슷해서 헷갈릴 수 있는데, 둘은 같은 말이 아니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;의존성 주입(DI)&lt;/b&gt;&lt;/span&gt; 은 객체가 필요한 의존성을 자기 스스로 new로 만들어 쓰는 것이 아니라, &lt;b&gt;외부에서 주입받는 방식&lt;/b&gt;을 말한다.&lt;br /&gt;지금 애플리케이션에서는 &lt;b&gt;스프링 프레임워크&lt;/b&gt;가 이 역할을 해준다.&lt;br /&gt;즉, SimpleProductService는 직접 new DatabaseProductRepository()를 하지 않고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성자에서 ProductRepository를 주입&lt;/b&gt;받는다.&lt;br /&gt;이것이 바로 &lt;b&gt;DI&lt;/b&gt;다.&lt;br /&gt;쉽게 말하면 &lt;b&gt;&amp;ldquo;필요한 부품을 내가 직접 만들지 않고 외부에서 꽂아준다&amp;rdquo;&lt;/b&gt;는 개념이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;반면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;의존성 역전 원칙(DIP)&lt;/b&gt;&lt;/span&gt; 은 설계 원칙이다.&lt;br /&gt;&lt;b&gt;고수준 컴포넌트가 저수준 컴포넌트에 직접 의존하지 말고, 둘 다 추상화에 의존해야 한다는 원칙&lt;/b&gt;이다.&lt;br /&gt;여기서 &lt;b&gt;고수준 컴포넌트&lt;/b&gt;는 상대적으로 더 추상적이고, 애플리케이션의 핵심 흐름을 담당하는 쪽이다.&lt;br /&gt;지금 예제에서는 &lt;b&gt;SimpleProductService&lt;/b&gt;가 여기에 해당한다.&lt;br /&gt;반대로 &lt;b&gt;ListProductRepository, DatabaseProductRepository처럼 구체적인 구현 기술을 다루는 클래스&lt;/b&gt;는&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저수준 컴포넌트&lt;/b&gt;라고 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;인터페이스를 도입하기 전에는 &lt;b&gt;SimpleProductService가 구체적인 저장소 클래스에 직접 의존할 가능성&lt;/b&gt;이 있었다.&lt;br /&gt;즉, 고수준 컴포넌트가 저수준 컴포넌트에 의존하고 있었던 것이다.&lt;br /&gt;하지만 지금은 중간에 &lt;b&gt;ProductRepository 인터페이스&lt;/b&gt;를 넣어서,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서비스는 더 이상 구체적인 구현이 아니라 &lt;b&gt;추상화에 의존&lt;/b&gt;하게 되었다.&lt;br /&gt;오히려 &lt;b&gt;저수준 구현체들이 ProductRepository라는 추상화에 맞춰 구현되는 구조&lt;/b&gt;가 되었다.&lt;br /&gt;이게 바로 &lt;b&gt;&amp;ldquo;역전&amp;rdquo;&lt;/b&gt;이라는 말의 핵심이다.&lt;br /&gt;원래는&lt;b&gt; 서비스 &amp;rarr; 구현체 방향&lt;/b&gt;으로 의존했는데,&lt;br /&gt;지금은 구현체들도 추상화에 맞춰 움직이게 되면서 &lt;b&gt;의존 방향이 달라진 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;8283&quot; data-start=&quot;8264&quot; data-ke-size=&quot;size16&quot;&gt;이 구조가 가지는 장점은 분명하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;8283&quot; data-start=&quot;8264&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;8283&quot; data-start=&quot;8264&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① 구현 기술이 바뀌어도 서비스 계층은 덜 영향을 받는다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;8283&quot; data-start=&quot;8264&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② 테스트 환경과 운영 환경에서 다른 저장소를 쉽게 바꿔 끼울 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;8283&quot; data-start=&quot;8264&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;③ 상대적으로 변경 가능성이 큰 저수준 구현에 고수준 로직이 직접 묶이지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;8283&quot; data-start=&quot;8264&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;8283&quot; data-start=&quot;8264&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;DI&lt;/b&gt;는 &lt;b&gt;객체를 어떻게 연결할 것인가&lt;/b&gt;의 문제이고,&lt;br /&gt;&lt;b&gt;DIP&lt;/b&gt;는 &lt;b&gt;무엇에 의존하게 설계할 것인가&lt;/b&gt;의 문제라고 이해하면 훨씬 깔끔하다.&lt;/p&gt;</description>
      <category>공부 기록/이것이 백엔드 개발이다 With Java</category>
      <category>@Profile</category>
      <category>Di</category>
      <category>dip</category>
      <category>백엔드</category>
      <category>백엔드 공부</category>
      <category>인터페이스</category>
      <author>Yang Ji Woo</author>
      <guid isPermaLink="true">https://jiwooprogramming.tistory.com/165</guid>
      <comments>https://jiwooprogramming.tistory.com/165#entry165comment</comments>
      <pubDate>Sat, 25 Apr 2026 09:02:36 +0900</pubDate>
    </item>
    <item>
      <title>EP22. 상품 관리 애플리케이션에 데이터베이스 연동하기 3편</title>
      <link>https://jiwooprogramming.tistory.com/164</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 상품 조회 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 상품 관리 애플리케이션이 리스트가 아니라 데이터베이스를 사용하게 되었으므로,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;조회 기능도 메모리 리스트를 순회하는 방식이 아니라 SQL을 실행해서 결과를 받아오는 방식으로 바뀌어야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;가장 먼저 구현할 수 있는 조회는 &lt;b&gt;상품 번호(id)를 기준으로 하나의 상품을 조회하는 기능&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DatabaseProductRepositoy&lt;/b&gt; 코드를 수정해준다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776994900891&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Product findById(Long id){
    SqlParameterSource namedParameter = new MapSqlParameterSource(&quot;id&quot;, id);
    Product product = namedParameterJdbcTemplate.queryForObject(
        &quot;SELECT id, name, price, amount FROM products WHERE id = :id&quot;,
        namedParameter,
        new BeanPropertyRowMapper&amp;lt;&amp;gt;(Product.class)
    );
    return product;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;901&quot; data-start=&quot;882&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 포인트는 세 가지다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;901&quot; data-start=&quot;882&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1065&quot; data-start=&quot;903&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째는 &lt;b&gt;MapSqlParameterSource&lt;/b&gt;다.&lt;br /&gt;이 객체는 &lt;b&gt;&quot;id&quot;라는 이름에 뒤의 id 값을 연결&lt;/b&gt;해주는 역할을 한다.&lt;br /&gt;즉, &lt;b&gt;SQL 안에 있는 :id 자리에 실제 파라미터 값을 매핑&lt;/b&gt;하는 것이다.&lt;br /&gt;문자열을 직접 이어붙이는 방식보다 훨씬 안전하고, 가독성도 좋다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1065&quot; data-start=&quot;903&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1177&quot; data-start=&quot;1067&quot; data-ke-size=&quot;size16&quot;&gt;두 번째는 &lt;b&gt;queryForObject()&lt;/b&gt;다.&lt;br /&gt;이 메서드는 &lt;b&gt;&amp;ldquo;결과가 하나일 것&amp;rdquo;을 기대할 때 사용하는 조회 메서드&lt;/b&gt;다.&lt;br /&gt;즉, 상품 번호처럼 고유한 값을 기준으로 단건 조회를 할 때 적합하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1177&quot; data-start=&quot;1067&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1369&quot; data-start=&quot;1179&quot; data-ke-size=&quot;size16&quot;&gt;세 번째는 &lt;b&gt;BeanPropertyRowMapper&amp;lt;&amp;gt;(Product.class)&lt;/b&gt;다.&lt;br /&gt;이 부분은 S&lt;b&gt;QL 조회 결과를 Product 객체로 바꿔주는 역할&lt;/b&gt;을 한다.&lt;br /&gt;예를 들어 SELECT 결과에서 id, name, price, amount 컬럼이 나오면, 이 값을 Product 클래스의 필드에 대응시켜 넣어준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1369&quot; data-start=&quot;1179&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1369&quot; data-start=&quot;1179&quot; data-ke-size=&quot;size16&quot;&gt;여기서 알아둘 점은 BeanPropertyRowMapper가 Product를 만들 때,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1369&quot; data-start=&quot;1179&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아무 파라미터도 받지 않는 기본 생성자&lt;/b&gt;로 객체를 먼저 생성한 뒤, 각 필드를 setter를 통해 채운다는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1369&quot; data-start=&quot;1179&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서 &lt;b&gt;Product 클래스에는 다음과 같은 setter 메서드&lt;/b&gt;가 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1776995215825&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void setId(Long id) {
    this.id = id;
}

public void setName(String name) {
    this.name = name;
}

public void setPrice(Integer price) {
    this.price = price;
}

public void setAmount(Integer amount) {
    this.amount = amount;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 전체 상품 조회와 이름 포함 검색&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1978&quot; data-start=&quot;1952&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 조회 코드&lt;/b&gt;는 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776995300534&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;Product&amp;gt; findAll(){
    List&amp;lt;Product&amp;gt; products = namedParameterJdbcTemplate.query(
        &quot;SELECT * FROM products&quot;,
        new BeanPropertyRowMapper&amp;lt;&amp;gt;(Product.class)
    );
    return products;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 &lt;b&gt;products 테이블의 모든 행을 조회해서 List&amp;lt;Product&amp;gt;로 반환&lt;/b&gt;한다.&lt;br /&gt;단건 조회와 다른 점은&lt;b&gt; queryForObject() 대신 query()&lt;/b&gt;를 사용한다는 것이다.&lt;br /&gt;즉, &lt;b&gt;결과가 여러 개일 수 있을 때는 query()를 사용해 리스트 형태&lt;/b&gt;로 받는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2418&quot; data-start=&quot;2370&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상품 이름에 특정 문자열이 포함된 상품을 검색하는 기능&lt;/b&gt;은 다음과 같이 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776995379548&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;Product&amp;gt; findByNameContaining(String name){
    SqlParameterSource namedParameter = new MapSqlParameterSource(&quot;name&quot;, &quot;%&quot; + name + &quot;%&quot;);
    List&amp;lt;Product&amp;gt; products = namedParameterJdbcTemplate.query(
        &quot;SELECT * FROM products WHERE name LIKE :name&quot;,
        namedParameter,
        new BeanPropertyRowMapper&amp;lt;&amp;gt;(Product.class)
    );
    return products;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 % 기호는 SQL의 &lt;b&gt;와일드카드&lt;/b&gt;다.&lt;br /&gt;&lt;b&gt;LIKE '%abc%'는 문자열 어딘가에 abc가 포함되어 있으면 매칭된다는 뜻&lt;/b&gt;이다.&lt;br /&gt;즉, 이름의 앞뒤에 어떤 문자가 오든 상관없이, 중간에 해당 문자열이 포함되어 있으면 검색 대상이 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이름 검색 기능도 결국 구조는 단건 조회와 크게 다르지 않다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 상품 수정 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;상품 수정 기능은 이전 리스트 기반 구현에서도 &lt;b&gt;id를 기준으로 특정 상품을 찾은 뒤, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그 내용을 교체하는 방식&lt;/b&gt;으로 구현했었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;데이터베이스를 사용할 때도 본질은 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다만 이제는 자바 리스트를 직접 수정하는 대신, &lt;b&gt;SQL의 UPDATE 문을 사용해 데이터베이스 안의 행을 수정&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 레포지토리&lt;/b&gt; 코드는 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776995592108&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Product update(Product product){
    SqlParameterSource namedParameter = new BeanPropertySqlParameterSource(product);
    namedParameterJdbcTemplate.update(
        &quot;UPDATE products SET name = :name, price = :price, amount = :amount WHERE id = :id&quot;,
        namedParameter
    );
    return product;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 코드의 핵심은 &lt;b&gt;BeanPropertySqlParameterSource(product)&lt;/b&gt;다.&lt;br /&gt;이 객체는 &lt;b&gt;product 객체 안의 필드 이름과 값을 읽어서 SQL의 이름 기반 파라미터와 자동으로 연결&lt;/b&gt;해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;:name &amp;rarr; product.getName()&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;:price &amp;rarr; product.getPrice()&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;:amount &amp;rarr; product.getAmount()&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;:id &amp;rarr; product.getId()&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4057&quot; data-start=&quot;4041&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 매핑된다고 보면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4057&quot; data-start=&quot;4041&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4057&quot; data-start=&quot;4041&quot; data-ke-size=&quot;size16&quot;&gt;이 수정 SQL은 &lt;b&gt;&amp;ldquo;id가 같은 행을 찾아서, name/price/amount를 새로운 값으로 바꾼다&amp;rdquo;&lt;/b&gt;는 의미다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4057&quot; data-start=&quot;4041&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 상품 삭제 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;상품 삭제 기능은 CRUD 중에서도 구조가 가장 단순한 편이다.&lt;br /&gt;조회는 결과를 객체로 매핑해야 하고, 수정은 바뀔 값을 여러 개 반영해야 하지만,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;삭제는 &lt;b&gt;특정 조건에 맞는 데이터를 제거&lt;/b&gt;하면 되기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 레포지토리&lt;/b&gt; 코드는 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776996169038&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void delete(Long id){
    SqlParameterSource namedParameter = new MapSqlParameterSource(&quot;id&quot;, id);
    namedParameterJdbcTemplate.update(
        &quot;DELETE FROM products WHERE id = :id&quot;,
        namedParameter
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;5202&quot; data-start=&quot;5059&quot; data-ke-size=&quot;size16&quot;&gt;이 코드에서 &lt;b&gt;MapSqlParameterSource(&quot;id&quot;, id)&lt;/b&gt;는 &lt;b&gt;SQL 안의 :id와 실제 id 값을 연결&lt;/b&gt;한다.&lt;br /&gt;그리고 &lt;b&gt;DELETE FROM products WHERE id = :id는 상품 번호가 같은 행을 찾아 삭제&lt;/b&gt;하는 SQL이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;5202&quot; data-start=&quot;5059&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;5260&quot; data-start=&quot;5204&quot; data-ke-size=&quot;size16&quot;&gt;즉, 상품 삭제는 결국 &lt;b&gt;상품 번호를 기준으로 특정 행 하나를 제거하는 작업&lt;/b&gt;이라고 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;5260&quot; data-start=&quot;5204&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;5260&quot; data-start=&quot;5204&quot; data-ke-size=&quot;size16&quot;&gt;리스트 기반 구현에서는 먼저 findById(id)로 상품을 찾고, remove()로 리스트에서 제거했다.&lt;br /&gt;데이터베이스 기반 구현에서는 그 과정을 SQL이 대신 수행하는 셈이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;5260&quot; data-start=&quot;5204&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>공부 기록/이것이 백엔드 개발이다 With Java</category>
      <category>delete</category>
      <category>mysql</category>
      <category>select</category>
      <category>Update</category>
      <category>백엔드</category>
      <category>백엔드 공부</category>
      <author>Yang Ji Woo</author>
      <guid isPermaLink="true">https://jiwooprogramming.tistory.com/164</guid>
      <comments>https://jiwooprogramming.tistory.com/164#entry164comment</comments>
      <pubDate>Fri, 24 Apr 2026 11:05:53 +0900</pubDate>
    </item>
    <item>
      <title>EP21. 상품 관리 애플리케이션에 데이터베이스 연동하기 2편</title>
      <link>https://jiwooprogramming.tistory.com/163</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;데이터베이스와&amp;nbsp;애플리케이션&amp;nbsp;연동&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제부터는 상품 데이터를 리스트가 아니라 실제 데이터베이스에 저장하도록 바꿔볼 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그러려면 애플리케이션이 MySQL과 통신할 수 있어야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;스프링부트에서 이런 작업을 시작할 때 가장 먼저 해야 하는 것은 &lt;b&gt;의존성 추가&lt;/b&gt;와 &lt;b&gt;접속 정보 설정&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 &lt;b&gt;pom.xml&lt;/b&gt;에 데이터베이스 연동에 필요한 의존성을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1776912706200&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-jdbc&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.mysql&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;mysql-connector-j&amp;lt;/artifactId&amp;gt;
    &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;spring-boot-starter-jdbc&lt;/b&gt;는 스프링부트가 자바 애플리케이션에서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터베이스를 다룰 수 있도록 도와주는 JDBC 관련 기능들&lt;/b&gt;을 한 번에 가져오는 스타터다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; mysql-connector-j&lt;/b&gt;는 자바 프로그램이 &lt;b&gt;MySQL과 실제로 통신할 수 있게 해주는 드라이버&lt;/b&gt;다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그다음에는 &lt;b&gt;application.properties&lt;/b&gt;에 데이터베이스 접속 정보를 추가해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1776912789314&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.datasource.url=jdbc:mysql://localhost:3306/product_management
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;url&lt;/b&gt;은 어떤 데이터베이스에 접속할지 나타낸다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;localhost:3306&lt;/b&gt;은 현재 내 컴퓨터에서 실행 중인 MySQL의 주소와 포트이고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;product_management&lt;/b&gt;는 사용할 데이터베이스 이름이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;username과 password&lt;/b&gt;는 접속 계정 정보다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;b&gt;driver-class-name&lt;/b&gt;은 MySQL 드라이버 클래스를 지정하는 부분이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;의존성과 접속 정보를 추가했다고 해서 바로 끝나는 것은 아니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 애플리케이션이 MySQL과 연결되는지 확인해볼 필요가 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이때 간단히 사용할 수 있는 방법이 &lt;b&gt;ApplicationRunner를 추가해서 DataSource로부터 커넥션을 얻어보는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776912926132&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public ApplicationRunner runner(DataSource dataSource){
    return args -&amp;gt; {
        Connection connection = dataSource.getConnection();
    };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 애플리케이션이 시작될 때 실행되며, 스프링이 관리하는 DataSource로부터 커넥션을 하나 가져와본다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;만약 데이터베이스 &lt;b&gt;연결 설정이 올바르다면 애플리케이션은 정상적으로 실행될 것이고, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;연결이 실패하면 시작 과정에서 예외가 발생하면서 애플리케이션이 뜨지 않을 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 같이 알아두면 좋은 개념이 &lt;b&gt;커넥션 풀(connection pool)&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커넥션&lt;/b&gt;은 &lt;b&gt;애플리케이션과 데이터베이스 사이에 연결된 통신 채널&lt;/b&gt;이라고 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;만약 요청이 들어올 때마다 매번 새로운 연결을 만들고, 작업이 끝나면 바로 끊는 방식을 사용하면 성능상 비효율이 크다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 실제 애플리케이션은 &lt;b&gt;보통 시작할 때 미리 여러 개의 커넥션을 준비해두고, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;필요할 때 꺼내 쓰고 다시 반환하는 방식&lt;/b&gt;으로 동작한다. 이것이 바로 &lt;b&gt;커넥션 풀&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;애플리케이션&amp;nbsp;상품&amp;nbsp;추가&amp;nbsp;기능&amp;nbsp;구현&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 애플리케이션이 데이터베이스와 연결될 수 있게 되었으니,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제 상품 추가 기능이 리스트가 아니라 DB에 저장되도록 바꿔야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이를 위해 &lt;b&gt;기존의 ListProductRepository 대신 새로운 저장소 클래스( &lt;span&gt;DatabaseProductRepository)&lt;/span&gt;&lt;/b&gt;를 만든다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776913097732&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public class DatabaseProductRepository {

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public DatabaseProductRepository(JdbcTemplate jdbcTemplate){
        this.jdbcTemplate = jdbcTemplate;
    }

    public Product add(Product product){
        jdbcTemplate.update(
            &quot;INSERT INTO products (name, price, amount) VALUES (?, ?, ?)&quot;, 
            product.getName(), product.getPrice(), product.getAmount()
        );
        return product;
    }

    public Product findById(Long id){
        return null;
    }

    public List&amp;lt;Product&amp;gt; findAll(){
        return Collections.EMPTY_LIST;
    }

    public List&amp;lt;Product&amp;gt; findByNameContaining(String name){
        return Collections.EMPTY_LIST;
    }

    public Product update(Product product){
        return null;
    }

    public void delete(Long id){

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;지금은 우선 상품 추가 기능만 구현하고, 나머지 조회/수정/삭제는 다음 편에서 이어서 구현할 예정이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 부분은 &lt;b&gt;jdbcTemplate.update(...)&lt;/b&gt;다.&lt;br /&gt;이 코드는 결국 다음 SQL을 애플리케이션 안에서 실행하는 것과 같은 의미를 가진다.&lt;/p&gt;
&lt;pre id=&quot;code_1776913141484&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO products(name, price, amount) VALUES ('product1', 100, 10);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 이전까지는 우리가 MySQL 콘솔에 직접 SQL을 입력했다면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제부터는 &lt;b&gt;자바 코드 안에서 SQL을 실행해 데이터베이스에 상품을 저장&lt;/b&gt;하는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4183&quot; data-start=&quot;4103&quot; data-ke-size=&quot;size16&quot;&gt;이 작업을 하려면 &lt;b&gt;Product 객체&lt;/b&gt;에서 name, price, amount 값을 꺼낼 수 있어야 하므로 &lt;b&gt;getter&lt;/b&gt;가 필요하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4183&quot; data-start=&quot;4103&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776913191207&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String getName() {
    return name;
}

public Integer getPrice() {
    return price;
}

public Integer getAmount() {
    return amount;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 서비스 계층에서는 &lt;b&gt;기존의 ListProductRepository 대신 DatabaseProductRepository를 사용하도록&lt;/b&gt; 바꿔야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1776913264643&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private DatabaseProductRepositoy databaseProductRepositoy;

// ListProductRepository 부분을 모두 DatabaseProductRepositoy로 수정
// (listProductRepository는 databaseProductRepositoy로 수정)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서비스의 입장에서는 &amp;ldquo;상품을 저장한다&amp;rdquo;는 기능 자체는 같지만, 실제 저장 위치가 리스트에서 DB로 바뀐 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4930&quot; data-start=&quot;4810&quot; data-ke-size=&quot;size16&quot;&gt;이제 애플리케이션을 실행한 뒤 Postman으로 상품 추가를 테스트해보면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4930&quot; data-start=&quot;4810&quot; data-ke-size=&quot;size16&quot;&gt;상품이 실제로 데이터베이스에 잘 들어가는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4930&quot; data-start=&quot;4810&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;705&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzpaFS/dJMcai349AQ/nK9bKezBlSX3CocWRyAoI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzpaFS/dJMcai349AQ/nK9bKezBlSX3CocWRyAoI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzpaFS/dJMcai349AQ/nK9bKezBlSX3CocWRyAoI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzpaFS%2FdJMcai349AQ%2FnK9bKezBlSX3CocWRyAoI0%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;655&quot; height=&quot;455&quot; data-origin-width=&quot;705&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4930&quot; data-start=&quot;4810&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4930&quot; data-start=&quot;4810&quot; data-ke-size=&quot;size16&quot;&gt;그런데 응답을 보면 &lt;b&gt;id가 null&lt;/b&gt;로 들어오는 문제가 생길 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4999&quot; data-start=&quot;4932&quot; data-ke-size=&quot;size16&quot;&gt;이 현상은 저장이 실패해서가 아니라, &lt;b&gt;애플리케이션이 DB가 생성한 id 값을 아직 다시 받아오지 않았기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4999&quot; data-start=&quot;4932&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4999&quot; data-start=&quot;4932&quot; data-ke-size=&quot;size16&quot;&gt;생성된 id를 응답에 반영하려면, INSERT 이후에 데이터베이스가 만들어준 기본 키 값을 자바 코드에서 받아와야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4999&quot; data-start=&quot;4932&quot; data-ke-size=&quot;size16&quot;&gt;이를 위해 &lt;b&gt;JdbcTemplate 대신 NamedParameterJdbcTemplate을 사용하는 방식&lt;/b&gt;을 적용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4999&quot; data-start=&quot;4932&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4999&quot; data-start=&quot;4932&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DatabaseProductRepository&lt;/b&gt;를 다음과 같이 수정한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776913394335&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

@Autowired
public DatabaseProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate){
    this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}

public Product add(Product product){
    KeyHolder keyHolder = new GeneratedKeyHolder();
    SqlParameterSource namedParameter = new BeanPropertySqlParameterSource(product);

    namedParameterJdbcTemplate.update(
        &quot;INSERT INTO products (name, price, amount) VALUES (:name, :price, :amount)&quot;,
        namedParameter,
        keyHolder
    );

    Long generatedId = keyHolder.getKey().longValue();
    product.setId(generatedId);

    return product;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 몇 가지 중요한 변화가 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째는 &lt;b&gt;SQL 파라미터 방식&lt;/b&gt;이다.&lt;br /&gt;&lt;b&gt;JdbcTemplate에서는 ?를 사용&lt;/b&gt;해서 순서대로 값을 매핑했다.&lt;br /&gt;반면 &lt;b&gt;NamedParameterJdbcTemplate에서는 :name, :price, :amount&lt;/b&gt;처럼 &lt;b&gt;이름 기반 파라미터&lt;/b&gt;를 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;두 번째는 &lt;b&gt;BeanPropertySqlParameterSource(product)&lt;/b&gt;다.&lt;br /&gt;이 객체는 &lt;b&gt;product의 필드 이름과 값을 읽어서, SQL 안의 :name, :price, :amount와 자동으로 매핑&lt;/b&gt;해준다.&lt;br /&gt;즉, product 객체 안에 있는 name, price, amount 값을 기반으로 SQL 파라미터를 채워주는 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;세 번째는 &lt;b&gt;KeyHolder&lt;/b&gt;다.&lt;br /&gt;&lt;b&gt;GeneratedKeyHolder를 만들어 update()에 넘겨주면, INSERT가 끝난 뒤 DB가 생성한 키 값을 받을 수 있다.&lt;/b&gt;&lt;br /&gt;이후 keyHolder.getKey()를 통해 생성된 id를 꺼내고, product.setId(generatedId)로 자바 객체에 다시 넣어준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 과정을 거치면, 이제 상품을 추가한 뒤 응답으로 반환되는 &lt;b&gt;Product 객체에도 id가 정상적으로 들어가게 된다. &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 데이터베이스가 생성한 값을 애플리케이션이 다시 받아와 응답에 반영할 수 있게 되는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다시 실행한 뒤 테스트하면 id도 응답으로 정상적으로 반환되는 것을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crEOLI/dJMcaipxShi/sM8kdf6JfJWelARVMVR380/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crEOLI/dJMcaipxShi/sM8kdf6JfJWelARVMVR380/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crEOLI/dJMcaipxShi/sM8kdf6JfJWelARVMVR380/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrEOLI%2FdJMcaipxShi%2FsM8kdf6JfJWelARVMVR380%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;667&quot; height=&quot;467&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부 기록/이것이 백엔드 개발이다 With Java</category>
      <category>JDBC</category>
      <category>mysql</category>
      <category>백엔드</category>
      <category>백엔드 공부</category>
      <category>상품 추가</category>
      <author>Yang Ji Woo</author>
      <guid isPermaLink="true">https://jiwooprogramming.tistory.com/163</guid>
      <comments>https://jiwooprogramming.tistory.com/163#entry163comment</comments>
      <pubDate>Thu, 23 Apr 2026 12:08:18 +0900</pubDate>
    </item>
    <item>
      <title>EP20. 상품 관리 애플리케이션에 데이터베이스 연동하기 1편</title>
      <link>https://jiwooprogramming.tistory.com/162</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 웹 애플리케이션이 데이터베이스를 사용해야 하는 이유 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 만든 상품 관리 애플리케이션은 데이터를 &lt;b&gt;리스트에 담아두는 방식&lt;/b&gt;으로 동작했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;학습용&lt;/b&gt;으로는 이 방식이 단순하고 이해하기 쉽기 때문에 충분히 의미가 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 서비스 관점에서 보면, 메모리 안의 리스트에만 데이터를 저장하는 구조에는 &lt;b&gt;분명한 한계&lt;/b&gt;가 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 떠올릴 수 있는 문제는 &lt;b&gt;데이터의 휘발성&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리스트에 저장한 데이터는&lt;/b&gt; 애플리케이션이 실행되는 동안에만 유지된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;서버가 종료되거나 애플리케이션이 재시작되면 그 안에 있던 데이터는 모두 사라진다. &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;학습이나 테스트용 예제에서는 괜찮을 수 있지만,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제 서비스에서 사용자의 데이터가 애플리케이션 종료와 함께 사라진다면 그건 치명적인 문제다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 데이터를 &lt;b&gt;파일에 저장&lt;/b&gt;하면 되지 않을까 생각할 수도 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 파일 저장은 &amp;ldquo;메모리만 사용하면 데이터가 사라진다&amp;rdquo;는 문제를 해결하는 하나의 방법이 될 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 여기에도 또 다른 한계가 있다. 바로 &lt;b&gt;애플리케이션 간 데이터 공유 문제&lt;/b&gt;다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제 서비스를 운영하다 보면 &lt;b&gt;하나의 애플리케이션만 띄워두는 경우보다, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여러 개의 애플리케이션 인스턴스를 동시에 띄워서 트래픽을 분산 처리&lt;/b&gt;하거나,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스 중단 없이 배포&lt;/b&gt;하기 위해 무중단 배포 구조를 사용하는 경우가 많다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이런 상황에서 각 애플리케이션이 자기 로컬 파일에만 데이터를 저장한다면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떤 요청은 A 서버에서 처리되고 어떤 요청은 B 서버에서 처리될 때 데이터가 서로 다르게 보일 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 필요한 것이 &lt;b&gt;공통된 데이터 저장소&lt;/b&gt;다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여러 애플리케이션이 함께 바라볼 수 있는 하나의 저장소가 있어야,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 서버가 요청을 처리하든 같은 데이터를 기준으로 동작할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터베이스&lt;/b&gt;는 바로 이런 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;데이터베이스를&amp;nbsp;사용하는&amp;nbsp;방법&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1582&quot; data-start=&quot;1513&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스를 사용하려면, 당연히 먼저 데이터베이스를 실행할 수 있어야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1582&quot; data-start=&quot;1513&quot; data-ke-size=&quot;size16&quot;&gt;보통 방법은 크게 두 가지로 생각할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1582&quot; data-start=&quot;1513&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1735&quot; data-start=&quot;1584&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째는 &lt;b&gt;데이터베이스를 직접 설치하는 방법&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1735&quot; data-start=&quot;1584&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 내 컴퓨터에 MySQL을 직접 설치하고, 운영체제 환경에서 실행하는 방식이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1735&quot; data-start=&quot;1584&quot; data-ke-size=&quot;size16&quot;&gt;이 방법은 실제 설치 경험을 할 수 있다는 장점이 있지만, &lt;b&gt;설정 과정이 번거롭고 환경이 꼬였을 때 되돌리기 어려울 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;두 번째는 &lt;b&gt;도커를 설치하고, 도커 컨테이너 안에서 데이터베이스를 실행하는 방법&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 실습에서는 이 방법을 사용할 예정이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이유는 간단하다. &lt;b&gt;학습용 실습에서는 &amp;ldquo;쉽게 실행하고, 쉽게 지우고, 다시 처음 상태로 돌릴 수 있는가&amp;rdquo;가 굉장히 중요&lt;/b&gt;하기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;도커를 사용하면 MySQL을 내 컴퓨터 운영체제에 직접 설치하는 것이 아니라,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;격리된 컨테이너 환경 안에서 실행&lt;/b&gt;하게 된다. 이렇게 하면 실습 환경을 만들고 없애는 것이 훨씬 쉬워진다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;b&gt;설정이 꼬였거나 다시 처음부터 실습하고 싶다면, 도커 컨테이너를 삭제하고 다시 실행&lt;/b&gt;하면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Docker 설치&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;도커를 사용하려면 먼저 Docker Desktop을 설치해야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.docker.com/products/docker-desktop/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.docker.com/products/docker-desktop/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Docker 공식 사이트에서 운영체제에 맞는 설치 파일을 내려받아 설치하면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;설치 후 실행했을 때 Docker Desktop 화면 왼쪽 아래에 &lt;b&gt;Engine running&lt;/b&gt; 같은 표시가 보이면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;도커 엔진이 정상적으로 실행 중이라는 뜻이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 MySQL을 도커 컨테이너로 실행할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 CMD에 다음 명령어를 입력해 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776838129617&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run --name some-mysql -p 3306:3306 
	-e MYSQL_ROOT_PASSWORD=root -d mysql:8.0.29 
	--character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci&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;1088&quot; data-origin-height=&quot;347&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIOBCE/dJMcadV07ss/ldVloVKZwXQqYxOJoyRBck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIOBCE/dJMcadV07ss/ldVloVKZwXQqYxOJoyRBck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIOBCE/dJMcadV07ss/ldVloVKZwXQqYxOJoyRBck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIOBCE%2FdJMcadV07ss%2FldVloVKZwXQqYxOJoyRBck%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;844&quot; height=&quot;269&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;347&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;처음 보면 길고 복잡해 보일 수 있지만, 각각의 부분은 역할이 분명하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;docker run&lt;/b&gt; : 새로운 컨테이너를 실행하겠다는 명령어&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;--name some-mysql&lt;/b&gt; : 이 컨테이너의 이름을 some-mysql로 지정&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-p 3306:3306&lt;/b&gt; : 내 컴퓨터의 3306 포트와 컨테이너 내부의 3306 포트를 연결&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-e MYSQL_ROOT_PASSWORD=root&lt;/b&gt; : MySQL root 계정 비밀번호를 root로 설정&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-d&lt;/b&gt; : 백그라운드에서 컨테이너를 실행&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;mysql:8.0.29&lt;/b&gt; : MySQL 8.0.29 버전 이미지를 사용&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;--character-set-server=utf8mb4&lt;/b&gt; : 문자 인코딩 설정&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2903&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;--collation-server=utf8mb4_general_ci&lt;/b&gt; : 정렬 및 비교 규칙 설정&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 이 명령어 하나로 &lt;b&gt;&amp;ldquo;MySQL 8.0.29를 root 비밀번호 root로 설정하고, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내 컴퓨터 3306 포트로 접근할 수 있게 백그라운드 실행하라&amp;rdquo;&lt;/b&gt;는 의미가 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너가 실행된 뒤에는 Docker Desktop에서 실행 중인 컨테이너를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX1HKx/dJMcacv6uc1/JMkJVgSodyodofLlxouPkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX1HKx/dJMcacv6uc1/JMkJVgSodyodofLlxouPkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX1HKx/dJMcacv6uc1/JMkJVgSodyodofLlxouPkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX1HKx%2FdJMcacv6uc1%2FJMkJVgSodyodofLlxouPkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1632&quot; height=&quot;297&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;297&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 메뉴에서 &lt;b&gt;Open in terminal&lt;/b&gt; 같은 기능을 사용하면, &lt;b&gt;컨테이너 내부의 터미널 환경&lt;/b&gt;으로 들어갈 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은, 지금 보고 있는 명령줄 환경이 내 컴퓨터 본체 운영체제가 아니라 &lt;b&gt;도커 컨테이너 안의 가상 환경&lt;/b&gt;이라는 점이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이후 MySQL에 접속하려면 다음 명령어를 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776838339000&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql -u root -p&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;693&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnJ4O5/dJMcagZwKBR/K1nOpYKMqmd0B8a9rebFs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnJ4O5/dJMcagZwKBR/K1nOpYKMqmd0B8a9rebFs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnJ4O5/dJMcagZwKBR/K1nOpYKMqmd0B8a9rebFs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnJ4O5%2FdJMcagZwKBR%2FK1nOpYKMqmd0B8a9rebFs1%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;693&quot; height=&quot;244&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;엔터를 치면 비밀번호 입력을 요구하고, 앞서 지정한 root를 입력하면 MySQL에 root 권한으로 접속할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 이제 단순히 &amp;ldquo;도커 컨테이너를 띄운 상태&amp;rdquo;를 넘어서,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제로 MySQL 서버 안으로 들어와 SQL 명령을 실행할 준비&lt;/b&gt;가 된 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;데이터베이스 안에 스키마, 테이블 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;MySQL에 접속했다고 해서 바로 상품 데이터를 넣을 수 있는 것은 아니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 &lt;b&gt;어디에 저장할지 구조를 만들어야 한다&lt;/b&gt;.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 &lt;b&gt;스키마&lt;/b&gt;를 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1776838477737&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE SCHEMA product_management;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 스키마는 데이터를 담는 큰 논리적 공간이라고 보면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;MySQL에서는 &lt;b&gt;데이터베이스&lt;/b&gt;와 비슷한 개념으로 이해해도 무방하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;product_management라는 이름의 데이터 저장 공간&lt;/b&gt;을 만든 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이후 현재 존재하는 &lt;b&gt;데이터베이스 목록&lt;/b&gt;을 보고 싶다면 다음 명령어를 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776838531864&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SHOW DATABASES;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이제부터는 그 스키마를 사용하겠다고 명시해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1776838552063&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;USE product_management;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 명령어를 입력하면 이후에 실행하는 &lt;b&gt;테이블 생성, 조회, 삽입 명령은 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모두 product_management 스키마 기준으로 동작&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그다음에는 &lt;b&gt;테이블&lt;/b&gt;을 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1776838592320&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE products (
    id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT, 
    name VARCHAR(100) NOT NULL, 
    price INT NOT NULL, 
    amount INT NOT NULL
);&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;433&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIkiKX/dJMcaaZjXq5/p4YtRfDYRiUKb9j9xy1EuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIkiKX/dJMcaaZjXq5/p4YtRfDYRiUKb9j9xy1EuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIkiKX/dJMcaaZjXq5/p4YtRfDYRiUKb9j9xy1EuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIkiKX%2FdJMcaaZjXq5%2Fp4YtRfDYRiUKb9j9xy1EuK%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;433&quot; height=&quot;173&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 테이블은 지금까지 자바 코드에서 사용하던 &lt;b&gt;Product 구조&lt;/b&gt;와 거의 대응된다.&lt;br /&gt;&lt;b&gt;id, name, price, amount&lt;/b&gt;라는 컬럼을 가지고 있고,&lt;br /&gt;&lt;b&gt;id는 기본 키이면서 자동 증가&lt;/b&gt;하도록 설정되어 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 지금까지 메모리 안에서 Product 객체로만 존재하던 데이터 구조를,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제는 &lt;b&gt;관계형 데이터베이스의 테이블 구조&lt;/b&gt;로 옮겨놓은 셈이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테이블이 잘 만들어졌는지 보고 싶다면 다음 명령어를 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776838640604&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SHOW TABLES;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그다음에는 &lt;b&gt;실제 데이터&lt;/b&gt;를 넣어볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776838660638&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO products(name, price, amount) VALUES ('product1', 100, 10);
INSERT INTO products(name, price, amount) VALUES ('product2', 100, 10);
INSERT INTO products(name, price, amount) VALUES ('product3', 100, 10);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 구문은 products 테이블의 &lt;b&gt;name, price, amount 컬럼에 각각 값을 넣겠다는 뜻&lt;/b&gt;이다.&lt;br /&gt;&lt;b&gt;id는 AUTO_INCREMENT로 설정했기 때문에 직접 넣지 않아도 자동으로 증가하면서 채워진다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;b&gt;전체 데이터&lt;/b&gt;를 보고 싶다면 다음과 같이 조회할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776838702203&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM products;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;다음 편에서...&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지는 MySQL 터미널에 직접 접속해서 스키마를 만들고, 테이블을 만들고, 데이터를 직접 넣고 조회했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 아직은 &lt;b&gt;데이터베이스를 사람이 수동으로 다루고 있는 상태&lt;/b&gt;다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 웹 애플리케이션에서는 사용자가 터미널에 직접 들어와서 INSERT INTO를 치지는 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자가 상품 추가 요청을 보내면, 애플리케이션이 그 요청을 받아 데이터베이스에 SQL 형태로 반영&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;지금까지 직접 했던 작업을 다음 단계부터는 애플리케이션이 대신 수행&lt;/b&gt;하게 되는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음 편에서는 상품 관리 애플리케이션에서 &lt;b&gt;상품 추가 API를 호출&lt;/b&gt;하면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 결과가 리스트가 아니라 실제 &lt;b&gt;MySQL 데이터베이스에 저장&lt;/b&gt;되도록 바꾸게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;이전까지의 &amp;ldquo;메모리 기반 저장소&amp;rdquo;가 이제는 &amp;ldquo;데이터베이스 기반 저장소&amp;rdquo;로 교체되는 셈&lt;/b&gt;이다.&lt;/p&gt;</description>
      <category>공부 기록/이것이 백엔드 개발이다 With Java</category>
      <category>docker</category>
      <category>mysql</category>
      <category>데이터베이스</category>
      <category>백엔드</category>
      <category>백엔드 공부</category>
      <author>Yang Ji Woo</author>
      <guid isPermaLink="true">https://jiwooprogramming.tistory.com/162</guid>
      <comments>https://jiwooprogramming.tistory.com/162#entry162comment</comments>
      <pubDate>Wed, 22 Apr 2026 15:23:55 +0900</pubDate>
    </item>
    <item>
      <title>EP19. 상품 관리 애플리케이션에 유효성 검사 추가하기 2편</title>
      <link>https://jiwooprogramming.tistory.com/161</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 전역 예외 핸들러&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&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;blob&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k064c/dJMcaaLNmdY/8aznuMFkkmMhjJrkJxKTj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k064c/dJMcaaLNmdY/8aznuMFkkmMhjJrkJxKTj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k064c/dJMcaaLNmdY/8aznuMFkkmMhjJrkJxKTj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk064c%2FdJMcaaLNmdY%2F8aznuMFkkmMhjJrkJxKTj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;420&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;앞에서 유효성 검사를 추가한 뒤 실제로 요청을 보내보면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;잘못된 값이 들어왔을 때 예외가 발생하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 자체는 나쁜 일이 아니다. 오히려 잘못된 입력을 제대로 막아냈다는 뜻이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;문제는 그 다음이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예외가 발생했을 때 클라이언트 입장에서는 &lt;b&gt;왜 요청이 실패했는지, 무엇을 고쳐야 하는지&lt;/b&gt; 알기 어려운 경우가 많다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;단순히 500 에러만 받는다면, 사용자는 &amp;ldquo;서버가 고장 났나?&amp;rdquo; 정도로만 느낄 수 있고 &lt;b&gt;실제 문제 원인&lt;/b&gt;은 알 수 없다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;우리가 흔히 아는 예외 처리 방식은 &lt;b&gt;try-catch&lt;/b&gt;다. 물론 이 방식도 자바에서는 유효한 예외 처리 수단이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 웹 애플리케이션에서는 모든 컨트롤러나 서비스 안에&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;일일이 try-catch를 넣는 방식이 오히려 코드를 더 지저분하게 만들 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;특히 지금처럼 &lt;b&gt;유효성 검사 실패처럼 &amp;ldquo;클라이언트가 잘못된 요청을 보낸 경우&amp;rdquo;&lt;/b&gt;는,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서비스 내부에서 억지로 복구하는 것보다 &lt;b&gt;적절한 HTTP 상태 코드와 에러 메시지로 응답하는 것&lt;/b&gt;이 더 중요하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이럴 때 필요한 것이 &lt;b&gt;전역 예외 핸들러&lt;/b&gt;다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;전역 예외 핸들러는 컨트롤러까지 올라온 예외를 한 곳에서 잡아서,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 이해할 수 있는 형태의 응답으로 바꿔주는 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;예외를 없애는 것이 아니라 &amp;ldquo;예외를 응답으로 번역하는 과정&amp;rdquo;&lt;/b&gt;이라고 보면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; @RestControllerAdvice로 유효성 검사 예외 처리&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;스프링에서는 전역적으로 예외를 처리하기 위해 &lt;b&gt;@RestControllerAdvice&lt;/b&gt;를 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 어노테이션이 붙은 클래스는&lt;b&gt; 여러 컨트롤러에서 발생한 예외를 공통으로 처리하는 역할&lt;/b&gt;을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;b&gt;도메인 객체&lt;/b&gt;의 Bean Validation 검사에서 발생하는 &lt;b&gt;ConstraintViolationException&lt;/b&gt;을 처리하고 싶다면,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같이 핸들러를 만들 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776749624080&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity&amp;lt;String&amp;gt; handleConstraintViolationException(
        ConstraintViolationException ex
    ){
        String errorMessage = &quot;에러 메시지&quot;;
        return new ResponseEntity&amp;lt;&amp;gt;(errorMessage, HttpStatus.BAD_REQUEST);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 ConstraintViolationException이 발생했을 때,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단순한 문자열과 함께 400 Bad Request&lt;/b&gt;를 응답으로 돌려준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 &amp;ldquo;예외가 났다&amp;rdquo;는 사실을 500 에러로 방치하지 않고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라이언트 요청이 잘못되었다는 의미의 400으로 바꿔주는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 단순히 &quot;에러 메시지&quot; 같은 문자열 하나만 내려주는 것은 충분히 친절하지 않다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제로는 &lt;b&gt;어떤 필드가 어떤 규칙을 위반했는지까지 보여주는 것&lt;/b&gt;이 훨씬 좋다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이를 위해 &lt;b&gt;예외 객체가 가지고 있는 정보를 꺼내서 가공&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776749756306&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity&amp;lt;ErrorMessage&amp;gt; handleConstraintViolationException(
        ConstraintViolationException ex
    ){
        Set&amp;lt;ConstraintViolation&amp;lt;?&amp;gt;&amp;gt; constraintViolations = ex.getConstraintViolations();
        List&amp;lt;String&amp;gt; errors = constraintViolations.stream()
            .map(
                constraintViolation -&amp;gt;
                    constraintViolation.getPropertyPath() + &quot;, &quot; + constraintViolation.getMessage()
            )
            .toList();

        ErrorMessage errorMessage = new ErrorMessage(errors);
        return new ResponseEntity&amp;lt;&amp;gt;(errorMessage, HttpStatus.BAD_REQUEST);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 유효성 검사에 &lt;b&gt;실패한 필드 이름과 에러 메시지&lt;/b&gt;를 모아서 응답 바디에 담아줄 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 price, 0 이상이어야 합니다 같은 메시지 형태로 내려줄 수 있는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;ErrorMessage&lt;/b&gt;라는 클래스를 따로 만드는 이유는, 가능한 한 &lt;b&gt;일관된 응답 구조&lt;/b&gt;를 유지하기 위해서다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;에러가 날 때마다 문자열 하나를 던지는 방식보다, errors라는 필드를 가진 객체 형태로 응답하면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트도 파싱하기 쉽고, 나중에 확장하기도 편하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776749851401&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ErrorMessage {
    private List&amp;lt;String&amp;gt; errors;

    public ErrorMessage(List&amp;lt;String&amp;gt; errors){
        this.errors = errors;
    }

    public List&amp;lt;String&amp;gt; getErrors(){
        return errors;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3784&quot; data-start=&quot;3646&quot; data-ke-size=&quot;size16&quot;&gt;에러 메시지를 리스트로 가지는 이유는, &lt;b&gt;유효성 검사 실패가 하나만 발생하는 것이 아니라 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3784&quot; data-start=&quot;3646&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여러 필드에서 동시에 발생&lt;/b&gt;할 수도 있기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3784&quot; data-start=&quot;3646&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 이름도 비어 있고 가격도 음수라면, 하나의 요청에서 여러 오류를 함께 알려주는 것이 더 자연스럽다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3784&quot; data-start=&quot;3646&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 컨트롤러 바인딩 예외 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;컨트롤러에서 @Valid @RequestBody ProductDto를 사용하다가 발생하는 예외는&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;보통 &lt;b&gt;MethodArgumentNotValidException&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 예외는 &lt;b&gt;요청 바디를 DTO로 바인딩하고 검증하는 과정에서 발생&lt;/b&gt;하며,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;내부적으로 가지고 있는 정보 구조가 ConstraintViolationException과는 다르다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 처리 코드도 달라진다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776750162582&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity&amp;lt;ErrorMessage&amp;gt; handleMethodArgumentNotValidException(
    MethodArgumentNotValidException ex
){
    List&amp;lt;FieldError&amp;gt; fieldErrors = ex.getBindingResult().getFieldErrors();
    List&amp;lt;String&amp;gt; errors = fieldErrors.stream()
        .map(
            fieldError -&amp;gt;
                fieldError.getField() + &quot;, &quot; + fieldError.getDefaultMessage()
        )
        .toList();

    ErrorMessage errorMessage = new ErrorMessage(errors);
    return new ResponseEntity&amp;lt;&amp;gt;(errorMessage, HttpStatus.BAD_REQUEST);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 getBindingResult().getFieldErrors()를 통해 필드 단위 오류 목록을 꺼내고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;각 필드 이름과 기본 메시지를 조합&lt;/b&gt;해 에러 응답을 만든다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;b&gt;name, null일 수 없습니다 같은 메시지&lt;/b&gt;를 내려줄 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 부분에서 중요한 것은, &lt;b&gt;예외 처리 코드는 예외마다 조금씩 다를 수밖에 없다는 점&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예외 타입이 다르면 그 예외 안에 어떤 정보가 들어 있는지도 다르고, 따라서 꺼내는 방법도 달라진다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 실무에서는 &lt;b&gt;어떤 예외를 만났을 때, &amp;ldquo;이 예외가 어떤 정보를 가지고 있지?&amp;rdquo;를 먼저 확인&lt;/b&gt;하고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그 정보를 어떻게 응답 바디로 가공&lt;/b&gt;할지 고민해야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; grep.app&lt;/b&gt; 같은 곳에서 &lt;b&gt;예외 클래스 이름으로 검색해보면, 다른 사람들이 어떻게 핸들링하는지&lt;/b&gt; 참고할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예외&amp;nbsp;처리&amp;nbsp;전략&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예외 처리 전략을 이야기할 때 기본이 되는 개념이 &lt;b&gt;Checked Exception&lt;/b&gt;과 &lt;b&gt;Unchecked Exception&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;자바에서는 예외를 크게 이 두 가지로 나누어 생각할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 &lt;b&gt;Checked Exception&lt;/b&gt;은 컴파일러가 처리를 강제하는 예외다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 이런 예외를 던질 수 있는 메서드를 호출하면 &lt;b&gt;try-catch로 직접 처리하거나, t&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;hrows를 사용해 바깥으로 넘겨야 한다.&lt;/b&gt; 그렇지 않으면 컴파일 자체가 되지 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대표적으로 &lt;b&gt;Exception 클래스를 직접 상속받는 예외들이 여기에 해당&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;반대로 &lt;b&gt;Unchecked Exception&lt;/b&gt;은 예외 처리가 강제되지 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;보통 &lt;b&gt;RuntimeException을 상속받는 예외들이 여기에 해당&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이런 예외는 발생할 수 있지만, 컴파일러가 try-catch나 throws를 강제하지는 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 코드 흐름이 훨씬 덜 번잡해진다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;물론 둘 다 장단점이 있다. Checked Exception은 &amp;ldquo;이 메서드는 이런 예외를 낼 수 있다&amp;rdquo;는 사실을&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;컴파일 단계에서 더 명시적으로 드러낼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실무에서는 오히려 이 장점이 불편으로 이어지는 경우가 많다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제로 발생한 예외를 중간 로직에서 의미 있게 처리할 수 없는 경우&lt;/b&gt;가 많기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;유효성 검사 실패나 요청 오류&lt;/b&gt;처럼, 결국 클라이언트가 잘못된 입력을 수정해야만 해결되는 문제라면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스나 레포지토리 중간에서 억지로 try-catch를 넣는 것이 오히려 코드만 복잡하게&lt;/b&gt; 만들 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 많은 경우에는 &lt;b&gt;Unchecked Exception을 사용하고, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정말 필요한 지점에서만 전역적으로 핸들링하는 방식&lt;/b&gt;이 더 자연스럽다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;지금 하고 있는 전역 예외 처리도 바로 그런 관점과 잘 맞는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예외는 위로 올라오게 두고, 컨트롤러 바깥에서 한 번에 잡아서 적절한 HTTP 응답으로 바꿔주는 것&lt;/b&gt;이다.&lt;/p&gt;</description>
      <category>공부 기록/이것이 백엔드 개발이다 With Java</category>
      <category>백엔드</category>
      <category>백엔드 공부</category>
      <category>예외</category>
      <category>예외 처리</category>
      <category>전역 예외 핸들러</category>
      <author>Yang Ji Woo</author>
      <guid isPermaLink="true">https://jiwooprogramming.tistory.com/161</guid>
      <comments>https://jiwooprogramming.tistory.com/161#entry161comment</comments>
      <pubDate>Tue, 21 Apr 2026 15:02:01 +0900</pubDate>
    </item>
  </channel>
</rss>