<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>noobdev25 님의 블로그</title>
    <link>https://noobdev25.tistory.com/</link>
    <description>noobdev25 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 5 Aug 2026 16:21:10 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>noobdev25</managingEditor>
    <item>
      <title>프로세스와 스레드</title>
      <link>https://noobdev25.tistory.com/71</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_wrtm6uwrtm6uwrtm.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wpq3q/dJMcaijTkJ3/1Uik34iBTpq2GoO0KCGkJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wpq3q/dJMcaijTkJ3/1Uik34iBTpq2GoO0KCGkJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wpq3q/dJMcaijTkJ3/1Uik34iBTpq2GoO0KCGkJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWpq3q%2FdJMcaijTkJ3%2F1Uik34iBTpq2GoO0KCGkJ1%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;2816&quot; height=&quot;1536&quot; data-filename=&quot;Gemini_Generated_Image_wrtm6uwrtm6uwrtm.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로세스란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에서 실행되고 있는 프로그램을 말하며, CPU 스케쥴링의 대상이 되는 작업(task)이라는 용어와 거의 같은 의미로 쓰임.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로세스&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;= 현재 실행되고 있는 프로그램 하나&lt;/p&gt;
&lt;p style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 프로그램 파일이 그냥 하드디스크에 있을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;땐&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;프로세스가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;아님.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;그&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;프로그램이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;메모리에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;올라가서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;실제로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;돌아가기&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;시작&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;해야&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;프로세스라고&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;부름&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스레드란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 내 작업의 흐름을 지칭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스레드&amp;nbsp;&lt;/b&gt;=&amp;nbsp;프로세스&amp;nbsp;안에서&amp;nbsp;실제로&amp;nbsp;일이&amp;nbsp;진행되는&amp;nbsp;흐름&amp;nbsp;하나&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로세스의 상태&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 여러가지 상태 값을 갖음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;생성&lt;/b&gt;: 프로세스가 막 만들어진 상태. fork, exec 함수를 통해 생성함. 이때 PCB(프로세스의 신상정보 카드 같은 것. 아래에서 후술함)가 하나 발급됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fork() - &lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;부모 프로세스의 주소 공간을 그대로 복사하여 자식 프로세스를 생성하는 함수&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;exec() - &lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;새롭게 프로세스를 생성하는 함수&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;대기(ready)&lt;/b&gt;: CPU 차례를 기다리는 상태. 메모리 공간이 충분하면 할당, 부족하면 대기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대기 중단&lt;/b&gt;: 순서를 기다리던 중인데 메모리가 부족해서 일시 중단된 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행(running)&lt;/b&gt;: 진짜로 CPU소유권과 메모리를 할당받아 일하고 있는 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중단(blocked)&lt;/b&gt;: 어떤 이벤트(예: 파일 읽기 끝나기 등)를 기다리느라 못 움직이는 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일시 중단&lt;/b&gt;: 중단된 상태인데 메모리까지 부족해서 일시 중단된 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;종료&lt;/b&gt;: 일 다 끝내고 CPU도, 메모리도 다 반납하고 가는 상태.자발적 종료와 비자발적 종료가 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로세스의 메모리 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_e0wy7ue0wy7ue0wy.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGihVJ/dJMcaa0rgK7/JD25qcApuxIlAp5JB6mfKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGihVJ/dJMcaa0rgK7/JD25qcApuxIlAp5JB6mfKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGihVJ/dJMcaa0rgK7/JD25qcApuxIlAp5JB6mfKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGihVJ%2FdJMcaa0rgK7%2FJD25qcApuxIlAp5JB6mfKK%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;2816&quot; height=&quot;1536&quot; data-filename=&quot;Gemini_Generated_Image_e0wy7ue0wy7ue0wy.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 프로세스에 적절한 메모리를 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택,힙,데이터,BSS,코드 영역으로 나뉘어짐. 스택은 위 주소부터 할당되고 힙은 아래 주소부터 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택과 힙&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스택&lt;/b&gt;:&lt;span&gt; &lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;런타임 단계에서 메모리를 할당 받음&lt;/span&gt;. &lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;잠깐&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;쓰고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;버리는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;것들&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;(지역변수,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;함수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;호출&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;정보,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;리턴값&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;등)이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;들어가는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;칸&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span data-wf=&quot;&quot;&gt;힙&lt;/span&gt;&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;:&lt;span&gt; &lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;런타임 단계에서 메모리를 할당 받음&lt;/span&gt;. &lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;필요할&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;때마다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;새로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;만드는(동적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;할당하는)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;변수들이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;들어가는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;칸&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span data-wf=&quot;&quot;&gt;데이터&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;영역&lt;/span&gt;&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;프로그램&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;실행&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;내내&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;계속&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;살아있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;변수(전역변수,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;정적변수)가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;들어가는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;칸&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BSS segment : 0으로 초기화 또는 초기화가 어떤 값으로도 되지 않은 변수들&lt;/li&gt;
&lt;li&gt;Data&amp;nbsp;segment&amp;nbsp;:&amp;nbsp;0이&amp;nbsp;아닌&amp;nbsp;값으로&amp;nbsp;초기화된&amp;nbsp;변수들&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span data-wf=&quot;&quot;&gt;코드&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;영역&lt;/span&gt;&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;우리가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;짠&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;소스&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;코드&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;자체가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;들어있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;칸&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;PCB란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PCB (Process Control Block)는 운영체제에서 프로세스에 대한 메타데이터를 저장한 '데이터'. 프로세스 제어블록이라고도 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 생성되면 운영체제는 해당 PCB를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 실행 -&amp;gt; 프로세스 생성 -&amp;gt; 프로세스 주소 값들에 스택,힙 등의 구조로 메모리 할당- &amp;gt; 메타데이터 PCB에 저장되어 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 사용자가 접근하지 못하도록 커널 스택 가장 앞부분에서 관리됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PCB 안에 들어있는 정보&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;8가지&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;프로세스 스케줄링 상태&lt;/b&gt;: 이 프로세스가 지금 대기 중인지, 실행 중인지 등&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로세스 ID&lt;/b&gt;: 이 프로세스가 누구인지 구분하는 고유 번호&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로세스 권한&lt;/b&gt;: 이 프로세스가 컴퓨터 자원이나 입출력 장치를 얼마나 써도 되는지에 대한 권한 정보&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로세스 카운터&lt;/b&gt;: 다음에 실행할 명령어가 메모리 어디에 있는지 가리키는 포인터. (일종의 책갈피)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CPU 레지스터&lt;/b&gt;: 프로세스를 실행하려면 CPU에 저장해둬야 하는 값들&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CPU 스케줄링 정보&lt;/b&gt;: 이 프로세스가 언제 CPU 사용을 멈췄는지 등의 정보&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계정 정보&lt;/b&gt;: 이 프로세스가 CPU를 얼마나 썼는지, 누가 실행시켰는지에 대한 정보&lt;/li&gt;
&lt;li&gt;&lt;b&gt;I/O 상태 정보&lt;/b&gt;: 이 프로세스에 할당된 입출력 장치 목록&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨텍스트 스위칭&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컨텍스트 스위칭&lt;/b&gt;: PCB를 기반으로 프로세스의 상태를 저장하고 로드시키는 과정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쉽게 말해 CPU가 프로세스 A를 하다가 프로세스 B로 바꿔서 일하는 것. 이때 A의 PCB를 저장해두고 B의 PCB를 불러오는 작업이 일어남.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 과정(프로세스끼리 전환할&lt;span&gt; &lt;/span&gt;때)에서 캐시미스 라는 비용이 발생함.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;그러나&amp;nbsp;&lt;/span&gt;&lt;b&gt;스레드끼리 전환할 땐 비용이 훨씬 적음.&lt;/b&gt;&lt;span&gt; &lt;/span&gt;스택 빼고는 다 같이 쓰고 있어서, 바꿔야 할 항목이 적기 때문.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;멀티 프로세싱이란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 프로세스, 즉 멀티프로세스를 통해 동시에 두 가지 이상의 일을 수행할 수 있는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 동시에 일을 병렬로 처리 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 프로세스의 메모리, 프로세스 중 일부에 문제 발생 시 다른 프로세스를 이용하여 처리할 수 있으므로 신뢰성이 높음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어 관점 멀티프로세싱과 소프트웨어 관점의 멀티프로세싱은 다름.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 소프트웨어 관점에서 멀티프로세싱을 설명함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹 브라우저&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 브라우저는 멀티 프로세스 구조를 가지고 있음. (크롬 같은 웹 브라우저)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;브라우저 프로세스&lt;/b&gt;: 주소창, 북마크바, 뒤로/앞으로 가기 버튼 같은 UI를 담당하고, 네트워크 요청이나 파일 접근 같은 권한도 관리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;렌더러 프로세스&lt;/b&gt;: 웹 페이지에서 눈에 보이는 부분을 그리는 걸 담당&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플러그인 프로세스&lt;/b&gt;: 웹 페이지에서 확장 프로그램(플러그인) 처리를 담당&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GPU 프로세스&lt;/b&gt;: 그래픽카드를 이용해서 화면을 그리는 부분을 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IPC&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 프로세스는 IPC(Inter Process Communication)가 가능한데 IPC란 &lt;b&gt;프로세스끼리 데이터를 주고받고 공유 데이터를 관리&lt;/b&gt;하는 메커니즘을 뜻함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IPC의 종류 : 공유 메모리, 파일, 소켓, 익명 파이프, 명명 파이프, 메세지 큐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 메모리가 완전히 공유되는 스레드보다는 속도가 떨어짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공유 메모리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 프로세스가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;같은 메모리 공간에 다 같이 접근&lt;/b&gt;할 수 있게 허락받는 방식.&lt;/li&gt;
&lt;li&gt;중간에 뭘 거치지 않고 바로 그 메모리를 읽고 쓰니까&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;속도가 제일 빠르다&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파일&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디스크에 저장된 데이터나, 파일 서버가 주는 데이터를 통해서 프로세스끼리 정보를 주고받는 방식.&lt;/li&gt;
&lt;li&gt;예: 한 프로세스가 파일에 써놓으면, 다른 프로세스가 그 파일을 읽는 식.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;소켓&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;네트워크를 통해서&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;데이터를 주고받는 방식이에요. TCP나 UDP를 사용.&lt;/li&gt;
&lt;li&gt;같은 컴퓨터 안이 아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;다른 컴퓨터에 있는 프로세스&lt;/b&gt;랑도 통신할 수 있다는 게 특징.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;익명 파이프&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;FIFO(먼저 넣은 게 먼저 나옴) 방식&lt;/b&gt;으로 데이터가 흐르는 임시 통로(파이프)를 만들어서 단방향 방식의 통신을 함.&lt;/li&gt;
&lt;li&gt;단,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;부모 프로세스와 자식 프로세스 사이에서만&lt;/b&gt;&lt;span&gt; 사용 가능. 다른 네트워크상에서는 사용 불가&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;명명된 파이프&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;익명 파이프와 달리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;부모-자식 관계가 아니어도&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;쓸 수 있음&lt;/li&gt;
&lt;li&gt;파이프 서버와 클라이언트가 &lt;b&gt;단방향&lt;/b&gt; 또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;양방향&lt;/b&gt;으로 통신 가능하고, 심지어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;네트워크상의 다른 컴퓨터&lt;/b&gt;와도 통신할 수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;있음.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메세지 큐&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주고받을 메시지들을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;큐(줄서기 구조)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형태로 쌓아두고 관리하는 방식.&lt;/li&gt;
&lt;li&gt;운영체제 커널이 전역적으로 관리해줘서, 사용 방법이 비교적 직관적이고 편하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스레드와 멀티스레딩&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스레드란?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드는 프로세스가 쪼갤 수 있는 가장 작은 일 단위. 프로세스 하나 안에 여러 스레드가 있을 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;같은 프로세스 안 스레드들은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;코드, 데이터, 힙을 공유.&lt;/b&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;스택&lt;/b&gt;만은 각자 따로&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가짐.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티스레딩이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 내 작업을 여러 개의 스레드, 멀티스레드로 처리하는 기법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티스레딩의 장점&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;효율성이 높음&lt;/b&gt;&lt;span&gt; :&lt;/span&gt; 스레드끼리 자원(코드/데이터/힙)을 공유하기 때문에 낭비 없이 효율적으로 일함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동시성(Concurrency)의 장점&lt;/b&gt;&lt;span&gt; :&lt;/span&gt; 서로 독립적인 작업들을 작게 나눠서, 마치 동시에 실행되는 것처럼 보여줄 수 있음.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티스레딩의 단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스레드로 이루어진 프로세스끼리 서로 영향을 줄 수 있음&lt;/b&gt;&lt;span&gt; :&lt;/span&gt; 자원을 공유한다는 게 장점이자 단점. 한 스레드가 공유 자원(예: 힙에 있는 변수)을 잘못 건드리면, 같은 프로세스 안의 다른 스레드들도 다 같이 영향을 받음. (반면 프로세스는 메모리가 독립적이라 이런 문제가 덜함)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공유 자원과 임계 영역&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공유 자원(shared resource)이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 안에서 각 프로세스, 스레드가 함께 접근할 수 있는 자원이나 변수 등을 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 공유 자원을 두 개 이상의 프로세스가 동시에 읽거나 쓰는 상황을 경쟁 상태 (race condition)라고 하며, 동시에 접근을 시도할 때접근의 타이밍이나 순서 등이 결괏값에 영향을 줄 수 있는 상태를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;임계 영역(critical section)이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘&amp;nbsp;이상의&amp;nbsp;프로세스,&amp;nbsp;스레드가&amp;nbsp;공유&amp;nbsp;자원에&amp;nbsp;접근할&amp;nbsp;때&amp;nbsp;순서&amp;nbsp;등의&amp;nbsp;이유로&amp;nbsp;결과가&amp;nbsp;달라지는&amp;nbsp;코드&amp;nbsp;영역&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;임계 영역을 해결하기 위한 방법 3가지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 뮤텍스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 세마포어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 모니터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법 모두 상호 배제, 한정 대기, 융통성이라는 조건을 만족함. 이 방법의 토대가 되는 메커니즘은 잠금(Lock).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;뮤텍스&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_ntf7u3ntf7u3ntf7.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dahAhk/dJMcadXa7QS/kNt7D0Yf5CjPa4JyI0lkGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dahAhk/dJMcadXa7QS/kNt7D0Yf5CjPa4JyI0lkGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dahAhk/dJMcadXa7QS/kNt7D0Yf5CjPa4JyI0lkGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdahAhk%2FdJMcadXa7QS%2FkNt7D0Yf5CjPa4JyI0lkGK%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;2816&quot; height=&quot;1536&quot; data-filename=&quot;Gemini_Generated_Image_ntf7u3ntf7u3ntf7.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스나 스레드가 공유 자원을 lock()을 통해 잠금 설정하고, 사용한 후에는 unlock()을 통해 잠금 해제하는 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠금이 설정되면 다른 프로세스나 스레드는 잠긴 코드 영역에 접근 불가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뮤텍스는 잠금 또는 잠금 해제라는 상태만을 가짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세마포어&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_o678dzo678dzo678.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct5YbX/dJMcadbIdKx/8CVRWbhk8bDZbyxNy55h6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct5YbX/dJMcadbIdKx/8CVRWbhk8bDZbyxNy55h6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct5YbX/dJMcadbIdKx/8CVRWbhk8bDZbyxNy55h6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct5YbX%2FdJMcadbIdKx%2F8CVRWbhk8bDZbyxNy55h6K%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;2816&quot; height=&quot;1536&quot; data-filename=&quot;Gemini_Generated_Image_o678dzo678dzo678.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세마포어는 일반화 된 뮤텍스로 간단한 정수 값과 두가지 함수 wait 및 signal로 공유 자원에 대한 접근을 처리함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wait() : 자신의 차례가 올 때까지 기다리는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;signal() : 다음 프로세스 순서를 넘겨주는 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이너리 세마포어 : 0과 1의 두가지 값만 가질 수 있는 세마포어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카운팅 세마포어 : 여러 개의 값을 가질 수 있어, 여러 자원에 대한 접근을 제어하는데 사용되는 세마포어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모니터&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_u9covnu9covnu9co (1).png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/db9xcY/dJMcafU2MTJ/8kvNAtNccXNK8b5VHjRIa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/db9xcY/dJMcafU2MTJ/8kvNAtNccXNK8b5VHjRIa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/db9xcY/dJMcafU2MTJ/8kvNAtNccXNK8b5VHjRIa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdb9xcY%2FdJMcafU2MTJ%2F8kvNAtNccXNK8b5VHjRIa0%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;2816&quot; height=&quot;1536&quot; data-filename=&quot;Gemini_Generated_Image_u9covnu9covnu9co (1).png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 이상의 스레드나 프로세스가 공유 자원에 안전하게 접근할 수 있도록 공유 자원을 숨기고 해당 접근에 대해 인터페이스만 제공함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모니터큐를 통해 공유 자원에 대한 작업들을 순차적으로 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 세마포어 보다 구현하기 쉬움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;뮤텍스(Mutex)&lt;/td&gt;
&lt;td style=&quot;width: 25.4651%;&quot;&gt;세마포어(Semaphore)&lt;/td&gt;
&lt;td style=&quot;width: 24.5349%;&quot;&gt;모니터(Monitor)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;비유&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;자물쇠가&amp;nbsp;1개인&amp;nbsp;화장실&lt;/td&gt;
&lt;td style=&quot;width: 25.4651%;&quot;&gt;칸이&amp;nbsp;여러&amp;nbsp;개인&amp;nbsp;화장실&lt;/td&gt;
&lt;td style=&quot;width: 24.5349%;&quot;&gt;통제실이&amp;nbsp;있는&amp;nbsp;고급&amp;nbsp;화장실&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;접근 가능 스레드&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1개&lt;/td&gt;
&lt;td style=&quot;width: 25.4651%;&quot;&gt;설정된&amp;nbsp;값(N)&amp;nbsp;만큼&amp;nbsp;(1개도&amp;nbsp;가능)&lt;/td&gt;
&lt;td style=&quot;width: 24.5349%;&quot;&gt;1개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;동작 원&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;락(Lock)을 가진 스레드만 접근&lt;/td&gt;
&lt;td style=&quot;width: 25.4651%;&quot;&gt;카운트(Count) 방식으로 접근 허용&lt;/td&gt;
&lt;td style=&quot;width: 24.5349%;&quot;&gt;언어 차원에서 제공하는 캡슐화된 락&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;락(Lock)해제 권한&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;반드시&amp;nbsp;락을&amp;nbsp;획득한&amp;nbsp;스레드만&amp;nbsp;해제&amp;nbsp;가능&lt;/td&gt;
&lt;td style=&quot;width: 25.4651%;&quot;&gt;락을&amp;nbsp;획득하지&amp;nbsp;않은&amp;nbsp;다른&amp;nbsp;스레드도&amp;nbsp;해제&amp;nbsp;가능&amp;nbsp;(신호&amp;nbsp;전송)&lt;/td&gt;
&lt;td style=&quot;width: 24.5349%;&quot;&gt;락을&amp;nbsp;획득한&amp;nbsp;스레드만&amp;nbsp;해제&amp;nbsp;가능&amp;nbsp;(자동&amp;nbsp;처리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;구현 수준&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;span data-path-to-node=&quot;3,5,0,0&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;3,5,1,0&quot;&gt;운영체제(OS) 단에서 제공&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25.4651%;&quot;&gt;&lt;span data-path-to-node=&quot;3,5,0,0&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;3,5,1,0&quot;&gt;운영체제(OS) 단에서 제공&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 24.5349%;&quot;&gt;프로그래밍&amp;nbsp;언어(프레임워크)&amp;nbsp;단에서&amp;nbsp;제공&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;교착 상태&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;교착 상태(Deadlock)란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;프로세스(또는 스레드) 두 개 이상이 서로 상대방이 가진 자원을 기다리다가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;둘 다 영원히 멈춰버리는 상태&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: A는 B가 가진 프린터를 기다리고, B는 A가 가진 파일을 기다리는데, 둘 다 안 놓아주면 영원히 못 움직이는 상황 발생.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;교착 상태가 발생하는 4가지 원인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;상호 배제 : &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;한 프로세스가 자원을 독점하고 있으며 다른 프로세스는 접근이 불가능&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;- 자원을 한 번에 하나만 쓸 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;점유 대기 : &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;특정 프로세스가 점유한 자원을 다른 프로세스가 요청하는 상태&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;- 자원 잡은 채로 다른 자원 기다림&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;비선점 : &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;다른 프로세스의 자원을 강제적으로 가져올 수 없음&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;- 남의 자원 강제로 뺏을 수 없음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;환형 대기 : &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;서로가 서로의 자원을 요구하는 상황&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;- 서로 돌고 도는 형태로 기다림&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;교착&amp;nbsp;상태의&amp;nbsp;해결&amp;nbsp;방법&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;예방 (설계 단계에서 아예 막기)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음부터 저 4가지 원인 중 하나라도 성립 안 하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;설계&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;예를 들어 자원을 여러 개가 동시에 써도 되게 만들면 상호 배제 조건이 깨지니까 교착&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;상태&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;자체가&lt;span&gt; 발생하지 않음&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-wf=&quot;&quot;&gt;&lt;span&gt;단, &lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;미리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;막으려다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;보면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;자원&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;활용&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;효율이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;떨어질&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;회피 (은행원 알고리즘)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자원을 내주기&lt;span&gt;&amp;nbsp;&lt;/span&gt;전에&lt;span&gt; &lt;/span&gt;이걸 내줘도 안전한지 미리 계산해보는 방법&lt;/li&gt;
&lt;li&gt;&lt;b&gt;은행원 알고리즘&lt;/b&gt;: 은행이 대출해줄 때 이 돈을 빌려줘도 은행이 파산 안 하겠지?를 미리 따져보는 것처럼, 운영체제도 자원을 주기 전에 이걸 주면 나중에 교착 상태가 안 생기겠지?를 검사하고 안전할 때만 내줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탐지 및 복구 (일단 놔뒀다가 걸리면 처리)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;교착 상태가 생기는 걸 막지 않고 일단 두다가, 실제로 발생하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;사이클(서로 물고 물리는 고리)을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;찾아내서&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;관련된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;프로세스를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;강제로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;없애버리는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;방법&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;무시 (현대 운영체제가 실제로 쓰는 방식)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;교착 상태는 매우 드물게 일어나고, 미리 막으려는 비용이 크기 때문에 현대 운영체제들은&lt;span&gt;&amp;nbsp;&lt;/span&gt;그냥 놔뒀다가 사용자가 직접 작업을 종료하는 방법을 채택&lt;/li&gt;
&lt;li&gt;예: 프로그램이 멈춘 것 같으면 사람이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;직접&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;작업&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;관리자에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;강제&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;종료&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>스터디</category>
      <author>noobdev25</author>
      <guid isPermaLink="true">https://noobdev25.tistory.com/71</guid>
      <comments>https://noobdev25.tistory.com/71#entry71comment</comments>
      <pubDate>Sat, 25 Jul 2026 16:45:03 +0900</pubDate>
    </item>
    <item>
      <title>만들자 게시판 4주차 (4) - 게시글과 첨부파일 연동하기</title>
      <link>https://noobdev25.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;첨부파일 API(업로드/다운로드/삭제)는 만들어놨는데, 게시글 기능이랑 따로 놀고 있었다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;게시글을 지워도 파일은 안 지워지고, 글 쓸 때 파일을 붙이려면 API를 두 번 불러야 했다. 좀 이걸 개선해보려고 한다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 게시글 삭제 시 파일이 안 지워지는 버그&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;files&lt;span&gt;&amp;nbsp;&lt;/span&gt;테이블은&lt;span&gt;&amp;nbsp;&lt;/span&gt;board_id를&lt;span&gt;&amp;nbsp;&lt;/span&gt;ON DELETE CASCADE로 참조하고 있어서, 게시글을 지우면 DB의&lt;span&gt;&amp;nbsp;&lt;/span&gt;files&lt;span&gt;&amp;nbsp;&lt;/span&gt;행은 자동으로 같이 지워진다.&lt;/p&gt;
&lt;p style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;근데 이건 DB 얘기고 디스크에 있는 실제 파일은 CASCADE가 손댈 수 없는 영역이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;BoardService.deleteBoard를 보니&lt;span&gt;&amp;nbsp;&lt;/span&gt;boardRepository.deleteBoard(id)만 부르고 끝이었고, 그래서 게시글을 지울 때마다 디스크에 안 쓰는 파일이 계속 쌓이고 있었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1784914692485&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; // FileService.java
 // 게시글 삭제 시 호출됨. 해당 게시글의 파일들을 디스크에서만 삭제 (DB는 CASCADE로 이미 처리됨)
  public void deleteFilesFromDisk(Long boardId) throws IOException {
    // 삭제되기 전에 파일 목록을 미리 조회해둠
    List&amp;lt;FileEntity&amp;gt; files = fileRepository.selectFilesByBoardId(boardId);
    // 목록을 돌면서 디스크에서 하나씩 삭제
    for (FileEntity file : files) {
      Path filePath = Paths.get(file.getFilePath()).resolve(file.getStoredName());
      Files.deleteIfExists(filePath);
    }
  }
  
//
  
 // BoardService.java
public void deleteBoard(Long id, String password) throws IOException {
  BoardEntity board = boardRepository.selectBoardDetail(id);
  if (!board.getPassword().equals(password)) {
    throw new IllegalArgumentException(&quot;비밀번호가 일치하지 않습니다.&quot;);
  }
  fileService.deleteFilesFromDisk(id);   // 먼저: 디스크 파일 정리
  boardRepository.deleteBoard(id);       // 그다음: DB에서 게시글 삭제 (CASCADE)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한건 순서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;boardRepository.deleteBoard(id)&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;를 먼저 부르면 CASCADE가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;files&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;행을 먼저 지워버려서, 그다음엔 이 게시글에 어떤 파일이 있었는지 알 방법이 없어진다. 그래서 디스크&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;정리를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;먼저&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;DB&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;삭제를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;나중에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 게시글 등록 시 파일을 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;보내기&lt;/span&gt;&lt;/b&gt; &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;지금까지 JSON은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;@RequestBody&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;, 파일은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;@RequestParam&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;으로 완전히 다른 요청으로 받았다. 두번이나 요청을 받는 구조가 별로다. 이번엔 하나의 요청 안에 게시글 정보(JSON)랑 파일들을 같이 실어보았다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;multipart는 요청을 여러 칸으로 나누는 형식이라고 했었는데, 그 칸 중 하나에 파일 대신 JSON 텍스트를 넣을 수도 있다. 이 칸을 객체로 변환해서 받는 어노테이션이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;@RequestPart&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;@RequestBody&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;multipart&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;버전이라고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;보면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1784914895875&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// BoardController.java
@PostMapping(&quot;/api/boards&quot;)
public BoardResponse createBoard(
    @RequestPart(&quot;request&quot;) @Valid BoardCreateRequest request,  //JSON -&amp;gt; BoardCreateRequest
    @RequestParam(value = &quot;files&quot;, required = false) List&amp;lt;MultipartFile&amp;gt; files
) throws IOException {
  return boardService.createBoard(request, files);
}


// BoardService.java
public BoardResponse createBoard(BoardCreateRequest request, List&amp;lt;MultipartFile&amp;gt; files) throws IOException {
  BoardEntity board = boardMapper.toEntity(request);
  board.setViewCount(0);
  board.setCreatedAt(LocalDateTime.now());
  boardRepository.insertBoard(board);   // 이 순간 board.getId()에 생성된 id가 채워짐

  if (files != null &amp;amp;&amp;amp; !files.isEmpty()) {
    fileService.uploadFiles(board.getId(), files);
  }
  return boardMapper.toResponse(board);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;insertBoard&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;로 id가 생기자마자 그 id로 기존&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;uploadFiles&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;를 그대로 불렀다. 파일은 옵션이라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;files&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;없거나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;비어있으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;그냥&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;건너뛴다&lt;/span&gt; &lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 게시글 수정 시 파일 추가/삭제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;요구사항은 첨부된 파일 삭제 가능, 취소하면 삭제되지 않음 이었는데, 여기서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;갈림길이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;두&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;개&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;있었다&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수정 요청에 파일 정보를 어떻게 실을지? 지울 id + 새로 추가할 파일을 따로 받을지, 아니면 최종 파일 목록을 통째로 받아서 서버가 비교할지&lt;/li&gt;
&lt;li&gt;저장해야만 반영을 누가 책임질지? 프론트가 저장 버튼 누르기 전엔 API를 아예 안 부르는 걸로 충분한지, 아니면 서버가 임시 변경사항을 들고 있어야 하는지&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;둘 다 후자가 더 정교하지만 훨씬 복잡하다. 시간 관계상 전자(라고 쓰고 쉬운 쪽)를 선택했다 (ㅋㅋ)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1784915136259&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//BoardService.java
public BoardResponse updateBoard(Long id, BoardUpdateRequest request, List&amp;lt;MultipartFile&amp;gt; files) throws IOException {
  BoardEntity board = boardRepository.selectBoardDetail(id);

  if (!board.getPassword().equals(request.password())) {
    throw new IllegalArgumentException(&quot;비밀번호가 일치하지 않습니다.&quot;);
  }

  board.setCategoryId(request.categoryId());
  board.setTitle(request.title());
  board.setContent(request.content());
  board.setUpdatedAt(LocalDateTime.now());
  boardRepository.updateBoard(board);

  // 지울 파일이 있으면 하나씩 삭제 (기존 deleteFile 재사용)
  if (request.deleteFileIds() != null) {
    for (Integer fileId : request.deleteFileIds()) {
      fileService.deleteFile(fileId);
    }
  }

  // 새로 추가할 파일이 있으면 업로드 (기존 uploadFiles 재사용)
  if (files != null &amp;amp;&amp;amp; !files.isEmpty()) {
    fileService.uploadFiles(id, files);
  }
  return boardMapper.toResponse(board);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉬운 쪽을 선택하니 새로 만든 로직이 거의 없었다. 파일 삭제 API 만들 때 이미 짰던 fileService.deleteFile(id), 업로드 API 만들 때 짰던 fileService.uploadFiles(id, files)를 그대로 갖다 썼다. 쉬운 설계가 재사용 가능한 설계랑 같은 의미인걸까&lt;br /&gt;&lt;br /&gt;테스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;실제로 서버를 켜서 게시글 생성(파일 포함) &amp;rarr; 파일 목록 조회 &amp;rarr; 게시글 수정(기존 파일 삭제 + 새 파일 추가) &amp;rarr; 파일 목록 재조회까지 전체 흐름을 확인했다. 지운 파일은 목록에서도,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;uploads/&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;폴더에서도 사라졌고, 새로 추가한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;파일만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfb; color: #0b0b0b; text-align: start;&quot; data-wf=&quot;&quot;&gt;남았다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄청 복잡할 줄 알았는데 재사용 가능한 방향으로 가다보니 코드를 재활용해서 시간이 많이 소요되지는 않았다.&lt;/p&gt;</description>
      <category>게시판 만들기/4주차</category>
      <author>noobdev25</author>
      <guid isPermaLink="true">https://noobdev25.tistory.com/70</guid>
      <comments>https://noobdev25.tistory.com/70#entry70comment</comments>
      <pubDate>Sat, 25 Jul 2026 02:47:57 +0900</pubDate>
    </item>
    <item>
      <title>만들자 게시판 4주차 (3) - 게시글 검색/페이징/첨부파일 표시 구현</title>
      <link>https://noobdev25.tistory.com/69</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 이번 파트가 제일 오래 걸린거 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리에 익숙하지 않아서인지 한줄 한줄 해석하는데 시간이 오래 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 보면&lt;br /&gt;1. 제목/작성자/내용 텍스트 검색 &lt;br /&gt;2. 카테고리 필터 &lt;br /&gt;3. 등록일 검색, 디폴트 최근 1년 &lt;br /&gt;4. 페이징 (10개씩, 총 건수) &lt;br /&gt;5. 목록에 첨부파일 유무 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 SQL을 사용했는데, 조건이 있을 수도 있고 없을수도 있으니, SQL을 상황에 따라 조립해야 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1784893792538&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//&quot;selectBoardList라는 이름의 SQL이고, 실행 결과 각 행을 BoardEntity 객체에 담아라&quot;는 선언.
&amp;lt;select id=&quot;selectBoardList&quot; resultType=&quot;com.study.board.entity.BoardEntity&quot;&amp;gt;
//board 테이블(별명 b)에서 가져올 컬럼들 나열. BoardEntity의 필드 이름과 매칭돼서 자동으로 채워짐.
  SELECT b.id, b.category_id, b.title, b.writer, b.content, b.view_count, b.created_at, b.updated_at,
         EXISTS (SELECT 1 FROM files f WHERE f.board_id = b.id) AS has_attachment
  FROM board b
  &amp;lt;where&amp;gt;
  //&quot;파라미터로 넘어온 condition 객체의 categoryId가 null이 아니면, AND category_id = (그 값) 조건을 SQL에 끼워 넣어라.&quot; 
  //null이면 이 줄 자체가 통째로 빠짐.
    &amp;lt;if test=&quot;condition.categoryId != null&quot;&amp;gt;
      AND b.category_id = #{condition.categoryId}
    &amp;lt;/if&amp;gt;
      //&quot;keyword가 null도 아니고 빈 문자열도 아니면&quot; 끼워 넣는 조건. 
      //LIKE '%키워드%'는 그 단어가 앞뒤로 뭐가 붙든 상관없이 포함만 되면 매칭되게 하는 문법. 
      //제목/작성자/내용 세 곳 중 어디든 하나만 매칭돼도 되게 OR로 묶고, 전체를 괄호로 감싸서 카테고리 조건과는 AND로 분리되게 함.
    &amp;lt;if test=&quot;condition.keyword != null and condition.keyword != ''&quot;&amp;gt;
      AND (b.title LIKE CONCAT('%', #{condition.keyword}, '%')
           OR b.writer LIKE CONCAT('%', #{condition.keyword}, '%')
           OR b.content LIKE CONCAT('%', #{condition.keyword}, '%'))
    &amp;lt;/if&amp;gt;
    //시작일이 있으면 &quot;그 날짜 이상&quot;이라는 조건 추가. &amp;amp;gt;는 XML에서 &amp;gt;를 못 그대로 쓰니까 대신 쓰는 표기.
    &amp;lt;if test=&quot;condition.startDate != null&quot;&amp;gt;
      AND b.created_at &amp;amp;gt;= #{condition.startDate}
    &amp;lt;/if&amp;gt;
    //종료일이 있으면, &quot;그 날짜의 다음 날 0시 미만&quot;으로 조건을 잡음. 이렇게 하는 이유는 created_at이 시분초까지 있는 값이라
    //그냥 &amp;lt;= endDate만 쓰면 종료일 당일 자정 이후에 쓴 글이 빠지는 문제가 생기기 때문.
    &amp;lt;if test=&quot;condition.endDate != null&quot;&amp;gt;
      AND b.created_at &amp;amp;lt; DATE_ADD(#{condition.endDate}, INTERVAL 1 DAY)
    &amp;lt;/if&amp;gt;
  &amp;lt;/where&amp;gt;
  //작성일이 최신인 순서로 정렬하고, 혹시 작성일이 완전히 같은 행이 여러 개면 id가 큰 순서로 한 번 더 정렬(순서를 안정적으로 고정하는 보조 기준).
  ORDER BY b.created_at DESC, b.id DESC
  //정렬까지 끝난 결과 중에서, offset만큼 앞에서부터 건너뛰고, 그다음부터 size개만 잘라서 가져와라. 이게 페이지네이션의 실체
  LIMIT #{condition.size} OFFSET #{offset}
&amp;lt;/select&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;if&amp;nbsp;test=&quot;...&quot;&amp;gt;는&amp;nbsp;조건이&amp;nbsp;참일&amp;nbsp;때만&amp;nbsp;안의&amp;nbsp;SQL을&amp;nbsp;끼워&amp;nbsp;넣고,&amp;nbsp;&amp;lt;where&amp;gt;는&amp;nbsp;조립된&amp;nbsp;조건들&amp;nbsp;맨&amp;nbsp;앞의&amp;nbsp;불필요한&amp;nbsp;AND를&amp;nbsp;알아서&amp;nbsp;지워준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EXISTS&amp;nbsp;(...)는&amp;nbsp;값의&amp;nbsp;내용엔&amp;nbsp;관심&amp;nbsp;없고&amp;nbsp;&quot;행이&amp;nbsp;존재하는지&quot;만&amp;nbsp;boolean으로&amp;nbsp;알려주는&amp;nbsp;서브쿼리라서,&amp;nbsp;첨부파일&amp;nbsp;유무&amp;nbsp;체크에&amp;nbsp;딱&amp;nbsp;맞았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이징은 두 쿼리가 필요하다&lt;br /&gt;LIMIT으로&amp;nbsp;잘라낸&amp;nbsp;목록만으로는&amp;nbsp;전체&amp;nbsp;건수를&amp;nbsp;알&amp;nbsp;수&amp;nbsp;없어서,&amp;nbsp;COUNT(*)&amp;nbsp;쿼리를&amp;nbsp;별도로&amp;nbsp;하나&amp;nbsp;더&amp;nbsp;돌려야&amp;nbsp;했다.&amp;nbsp;목록용&amp;nbsp;쿼리와&amp;nbsp;카운트용&amp;nbsp;쿼리에&amp;nbsp;WHERE&amp;nbsp;조건이&amp;nbsp;똑같이&amp;nbsp;들어가야&amp;nbsp;검색&amp;nbsp;결과&amp;nbsp;개수와&amp;nbsp;총&amp;nbsp;건수가&amp;nbsp;일치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1784894775882&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//BoardService.java
public BoardListResponse getBoardList(BoardSearchCondition condition) {
  // 등록일 종료값이 없으면 오늘, 시작값이 없으면 종료값 기준 1년 전 (요구사항: 디폴트 값이 최근 1년)
  LocalDate resolvedEndDate = (condition.endDate() != null) ? condition.endDate() : LocalDate.now();
  // 종료일을 먼저 구해놔야 시작일을 구할 수 있음
  LocalDate resolvedStartDate = (condition.startDate() != null) ? condition.startDate() : resolvedEndDate.minusYears(1);

  BoardSearchCondition resolvedCondition = new BoardSearchCondition(
      condition.categoryId(), condition.keyword(), resolvedStartDate, resolvedEndDate, condition.page(), condition.size()
  );

  List&amp;lt;BoardResponse&amp;gt; boards = boardRepository.selectBoardList(resolvedCondition, resolvedCondition.offset()).stream()
      .map(boardMapper::toResponse)
      .toList();
  long totalCount = boardRepository.countBoardList(resolvedCondition);
  return new BoardListResponse(boards, totalCount);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록일 기본값(없으면 최근 1년) 계산은 Controller가 아니라 Service에 뒀다. Controller는 요청을 그대로 전달만 하고 빈 값을 뭘로 채울지 판단하는 건 비즈니스 로직이라 Service의 몫이라고 정리했다.&lt;/p&gt;</description>
      <category>게시판 만들기/4주차</category>
      <author>noobdev25</author>
      <guid isPermaLink="true">https://noobdev25.tistory.com/69</guid>
      <comments>https://noobdev25.tistory.com/69#entry69comment</comments>
      <pubDate>Fri, 24 Jul 2026 21:09:01 +0900</pubDate>
    </item>
    <item>
      <title>만들자 게시판 4주차 (2) - 유효성 검증 추가</title>
      <link>https://noobdev25.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;게시판 만들기 요구사항을 보면 서버 측 유효성 검증 조건이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성자 3~4자, 비밀번호 4~15자(영문/숫자/특수문자 포함), 제목 4~99자, 내용 4~1999자, 카테고리 필수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 유효성 검증을하지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이&amp;nbsp;제공하는&amp;nbsp;spring-boot-starter-validation을&amp;nbsp;쓰면,&amp;nbsp;DTO&amp;nbsp;필드&amp;nbsp;위에&amp;nbsp;애노테이션만&amp;nbsp;붙여서&amp;nbsp;검증&amp;nbsp;규칙을&amp;nbsp;선언할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;pre id=&quot;code_1784892509529&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradle에 넣고 싱크 돌려주면 사용이 가능하다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 사용하지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 어노테이션을 지원하는데 대표적으로 @Size, @Pattern, @Min, @Max, @Email 등이 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1784892852020&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// BoardCreateRequest.java
public record BoardCreateRequest(
    @NotNull(message = &quot;카테고리는 필수입니다.&quot;)
    Integer categoryId,

    @NotBlank(message = &quot;제목은 필수입니다.&quot;)
    @Size(min = 4, max = 99, message = &quot;제목은 4자 이상 100자 미만이어야 합니다.&quot;)
    String title,

    @NotBlank(message = &quot;작성자는 필수입니다.&quot;)
    @Size(min = 3, max = 4, message = &quot;작성자는 3자 이상 5자 미만이어야 합니다.&quot;)
    String writer,

    @NotBlank(message = &quot;내용은 필수입니다.&quot;)
    @Size(min = 4, max = 1999, message = &quot;내용은 4자 이상 2000자 미만이어야 합니다.&quot;)
    String content,

    @NotBlank(message = &quot;비밀번호는 필수입니다.&quot;)
    @Size(min = 4, max = 15, message = &quot;비밀번호는 4자 이상 16자 미만이어야 합니다.&quot;)
    @Pattern(regexp = &quot;^(?=.*[A-Za-z])(?=.*\\d)(?=.*[^A-Za-z0-9]).+$&quot;, message = &quot;비밀번호는 영문/숫자/특수문자를 모두 포함해야 합니다.&quot;)
    String password
) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Size는&amp;nbsp;글자&amp;nbsp;수&amp;nbsp;범위,&amp;nbsp;@Pattern은&amp;nbsp;정규식으로&amp;nbsp;영문/숫자/특수문자&amp;nbsp;포함&amp;nbsp;여부를&amp;nbsp;검사한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의깊게 봐야할 점은 왜 @NotNull이 아니라 @NotBlank를 썼을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성자, 제목, 내용, 비밀번호는 전부 사용자가 실제로 뭔가 타이핑했는지를 확인해야 하는 필드다 만약 @NotNull만 썼다면, 사용자가 제목란에 스페이스바만 몇 번 누르고 제출해도 유효한 제목으로 통과해버렸을 것. 그래서 진짜 내용이 있는지까지 확인하는 @NotBlank를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 이렇다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@NotNull&amp;nbsp;&amp;lt;&amp;nbsp;@NotEmpty&amp;nbsp;&amp;lt;&amp;nbsp;@NotBlank&amp;nbsp;(뒤로&amp;nbsp;갈수록&amp;nbsp;더&amp;nbsp;깐깐해짐)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 89px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;입력값&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;@NotNull&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;@NotEmpty&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;@NotBlank&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;null&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;❌&amp;nbsp;실패&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;❌&amp;nbsp;실패&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;❌&amp;nbsp;실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&quot;&quot;(빈 문자열)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;✅&amp;nbsp;통과&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;❌&amp;nbsp;실패&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;❌&amp;nbsp;실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;&quot; &quot;(공백)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;✅&amp;nbsp;통과&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;✅&amp;nbsp;통과&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;❌&amp;nbsp;실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&quot;문자&quot;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;✅&amp;nbsp;통과&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;✅&amp;nbsp;통과&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;✅&amp;nbsp;통과&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO에&amp;nbsp;애노테이션을&amp;nbsp;아무리&amp;nbsp;붙여도,&amp;nbsp;Controller에서&amp;nbsp;@Valid를&amp;nbsp;안&amp;nbsp;붙이면&amp;nbsp;검증이&amp;nbsp;실행되지&amp;nbsp;않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1784893185292&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//BoardController
@PostMapping(&quot;/api/boards&quot;)
public BoardResponse createBoard(@Valid @RequestBody BoardCreateRequest request) {
  return boardService.createBoard(request);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증에 실패하면 스프링이 자동으로 400 Bad Request를 응답해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>게시판 만들기/4주차</category>
      <author>noobdev25</author>
      <guid isPermaLink="true">https://noobdev25.tistory.com/68</guid>
      <comments>https://noobdev25.tistory.com/68#entry68comment</comments>
      <pubDate>Fri, 24 Jul 2026 20:40:29 +0900</pubDate>
    </item>
    <item>
      <title>만들자 게시판 4주차 (1) - 첨부파일 삭제 API 구현</title>
      <link>https://noobdev25.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이번에도 늦어버렸다. 언제쯤이면 바쁘다는 구차한 핑계로 스스로 정당화하지 않을수 있을까..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반성 또 반성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그치만 바쁜걸 이잉&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드, 다운로드에 이어 삭제. 파일은 디스크와 DB 두 곳 모두에 흔적이 남아서 삭제도 두 곳 다 지워야 완전하다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서가 왜 중요한가?&lt;br /&gt;두 작업을 순서대로 실행하다가 중간에 하나가 실패하면 어떻게 될까. &lt;br /&gt;&lt;br /&gt;1. 디스크 먼저 지우고 DB가 실패하면 &amp;rarr; DB엔 있는데 실물이 없는 &quot;유령 파일&quot; (목록엔 뜨는데 다운로드하면 에러)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. DB&amp;nbsp;먼저&amp;nbsp;지우고&amp;nbsp;디스크가&amp;nbsp;실패하면&amp;nbsp;&amp;rarr;&amp;nbsp;실물은&amp;nbsp;있는데&amp;nbsp;DB엔&amp;nbsp;없는&amp;nbsp;&quot;고아&amp;nbsp;파일&quot;&amp;nbsp;(아무도&amp;nbsp;못&amp;nbsp;찾지만&amp;nbsp;용량만&amp;nbsp;차지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;둘&amp;nbsp;다&amp;nbsp;완벽하진&amp;nbsp;않은데,&amp;nbsp;고아&amp;nbsp;파일&amp;nbsp;쪽이&amp;nbsp;낫다고&amp;nbsp;판단했다.&amp;nbsp;조용히&amp;nbsp;방치돼도&amp;nbsp;사용자는&amp;nbsp;아무&amp;nbsp;문제를&amp;nbsp;못&amp;nbsp;느끼지만,&amp;nbsp;유령&amp;nbsp;파일은&amp;nbsp;사용자가&amp;nbsp;클릭했다가&amp;nbsp;대놓고&amp;nbsp;에러를&amp;nbsp;만나기&amp;nbsp;때문이다.&amp;nbsp;그래서&amp;nbsp;DB&amp;nbsp;먼저,&amp;nbsp;디스크&amp;nbsp;나중&amp;nbsp;순서로&amp;nbsp;정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1784892176319&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// FileService.java
public void deleteFile(Integer id) throws IOException {
  FileEntity file = fileRepository.selectFileById(id);
  Path filePath = Paths.get(file.getFilePath()).resolve(file.getStoredName());
  fileRepository.deleteFile(id);
  Files.deleteIfExists(filePath);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Files.delete&amp;nbsp;대신&amp;nbsp;Files.deleteIfExists를&amp;nbsp;쓴&amp;nbsp;이유도&amp;nbsp;있다.&amp;nbsp;delete는&amp;nbsp;파일이&amp;nbsp;이미&amp;nbsp;없으면&amp;nbsp;예외를&amp;nbsp;던지는데,&amp;nbsp;deleteIfExists는&amp;nbsp;없으면&amp;nbsp;조용히&amp;nbsp;false만&amp;nbsp;반환한다.&amp;nbsp;나중에&amp;nbsp;다른&amp;nbsp;기능(게시글&amp;nbsp;삭제&amp;nbsp;시&amp;nbsp;첨부파일&amp;nbsp;정리&amp;nbsp;등)에서&amp;nbsp;이&amp;nbsp;메서드를&amp;nbsp;재사용할&amp;nbsp;때,&amp;nbsp;이미&amp;nbsp;지워진&amp;nbsp;파일을&amp;nbsp;또&amp;nbsp;지우려는&amp;nbsp;상황이&amp;nbsp;생길&amp;nbsp;수&amp;nbsp;있어서&amp;nbsp;더&amp;nbsp;안전한&amp;nbsp;쪽을&amp;nbsp;골랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 삭제는 크게 어려운 구현은 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽질 기록&lt;br /&gt;처음엔 Files.deleteIfExists(filePath) (디스크 먼저)를 위에, fileRepository.deleteFile(id) (DB 나중)를 아래에 써서 순서를 거꾸로 짰었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서를 이미 정해놨는데 코드는 반대로 짠거다.. DB와 디스크 중 뭘 먼저 지울지 결정하는 것과 그 결정대로 코드를 쓰는 것은 다른 문제라는 걸 깨달았다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>게시판 만들기/4주차</category>
      <author>noobdev25</author>
      <guid isPermaLink="true">https://noobdev25.tistory.com/67</guid>
      <comments>https://noobdev25.tistory.com/67#entry67comment</comments>
      <pubDate>Fri, 24 Jul 2026 20:26:54 +0900</pubDate>
    </item>
    <item>
      <title>HTTP 프로토콜의 발전</title>
      <link>https://noobdev25.tistory.com/66</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HTTP&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;애플리케이션 계층으로서 웹 서비스 통신에 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;HTTP/1.0 부터 발전을 거듭해 현재는 HTTP/3&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;HTTP/1.0&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_slf1uwslf1uwslf1.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vRwvm/dJMcag7jBBK/oPKwwTzSfvTKZNNr3QMJ8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vRwvm/dJMcag7jBBK/oPKwwTzSfvTKZNNr3QMJ8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vRwvm/dJMcag7jBBK/oPKwwTzSfvTKZNNr3QMJ8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvRwvm%2FdJMcag7jBBK%2FoPKwwTzSfvTKZNNr3QMJ8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;327&quot; data-filename=&quot;Gemini_Generated_Image_slf1uwslf1uwslf1.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 한 연결당 하나의 요청을 처리하도록 설계&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버에서 파일을 가져올 때마다 TCP의 3-way-handshake를 계속 열어야 하므로 RTT가 증가하는 단점이 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RTT : 패킷이 목적지에 도달하고 나서 다시 출발지로 돌아오기까지 걸리는 시간. 즉 패킷 왕복 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RTT의 증가를 해결하기 위한 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 스플리팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이미지가 합쳐진 하나의 이미지를 다운 이를 기반으로&amp;nbsp;background-image의 position을 이용하여 이미지를 표기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 압축&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 코드를 압축해 개행 문자, 빈칸을 없애 코드의 크기를 최소화 하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 Base64 인코딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이미지 파일을 64진법으로 이루어진 문자열로 인코딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 이 방법 사용시 서버와의 연결을 열고 서버에 HTTP 요청할 필요 없음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- Base64 문자열로 변환할 경우 37% 정도 크기가 더 커지는 단점 존재&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;HTTP/1.1&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_xhtjzkxhtjzkxhtj.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wGzrc/dJMcaijNzLQ/TLqQFJwWhgr7czE77hLv00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wGzrc/dJMcaijNzLQ/TLqQFJwWhgr7czE77hLv00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wGzrc/dJMcaijNzLQ/TLqQFJwWhgr7czE77hLv00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwGzrc%2FdJMcaijNzLQ%2FTLqQFJwWhgr7czE77hLv00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;327&quot; data-filename=&quot;Gemini_Generated_Image_xhtjzkxhtjzkxhtj.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 매번 TCP 연결 하지않고 한번 TCP 초기화 이후 keep-alive라는 옵션으로 여러개의 파일을 송수신 할수 있게 변경&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 문서 안에 포함된 다수의 리소스를 처리하려면, 요청할 리소스 개수에 비례해 대기시간이 길어지는 단점이 존재&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HOL Blocking&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 네트워크에서 같은 큐에 있는 패킷이 첫 번째 패킷에 의해 지연될 때 발생하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;성능 저하 현상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 첫번째 파일의 다운로드가 늦어진다면, 그 뒤에 있는 파일들도 대기하게 되어 다운로드가 지연되는 상태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무거운 헤더 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- HTTP/1.1 헤더에는 쿠키 등 많은 메타데이터가 들어 있고 압축되지 않아 무거움&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;HTTP/2&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_cmmx5acmmx5acmmx.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buPL9X/dJMcad3NqbJ/X6wP03B5so3dfBvt8PeHjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buPL9X/dJMcad3NqbJ/X6wP03B5so3dfBvt8PeHjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buPL9X/dJMcad3NqbJ/X6wP03B5so3dfBvt8PeHjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuPL9X%2FdJMcad3NqbJ%2FX6wP03B5so3dfBvt8PeHjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;327&quot; data-filename=&quot;Gemini_Generated_Image_cmmx5acmmx5acmmx.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- HTTP/1.x 보다 지연 시간을 줄이고 응답 시간을 더 빠르게 할 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 멀티플렉싱, 헤더 압축, 서버 푸시, 요청의 우선순위 처리를 지원함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티플렉싱&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여러 개의 스트림을 사용하여 송수신&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 트림의 패킷이 손실되도 해당 스트림에만 영향이 있고, 다른 스트림은 멀쩡하게 동작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 단일 연결을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;병렬&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;로 여러 요청을 받을 수 있고 응답을 줄 수 있음(HOL Blocking 해결)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림 : &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;시간이 지남에 따라 사용할 수 있게 되는 일련의 데이터 요소를 가리키는 데이터 흐름&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;헤더 압축&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 허프만 코딩 압축 알고리즘을 사용하는 HPACK 압축 형식을 가짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허프만 코딩 : 문자열을 문자 단위로 쪼개 빈도수를 세어 &lt;b&gt;빈도가 높은 정보는 적은 비트 수를 사용&lt;/b&gt;하여 표현하고, 빈&lt;b&gt;도가 낮은 정보는 비트 수를 많이 사용하여 표현&lt;/b&gt;해서 전체 데이터의 필요한 비트양을 줄이는 원리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 푸시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;클라이언트 요청&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;없이&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;서버가 바로 리소스를 푸시할 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;예를 들어 html에는 css나 js파일이 포함되기 마련인데, html을 읽으면서 그 안에 있는 css 파일을 서버에서 푸시하여 클라이언트에게 먼저 줄 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;HTTPS&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- HTTP/2는 HTTPS 위에서 동작&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- HTTPS는 애플리케이션 계층과 전송 계층 사이에 신뢰 계층인 SSL/TLS 계층을 넣은 신뢰할 수 있는 HTTP 요청&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 이를 통해 통신을 '암호화' 함&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_ov1y87ov1y87ov1y.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTANi2/dJMcab52O8P/C8CuOeJg4fTikPMUkVachk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTANi2/dJMcab52O8P/C8CuOeJg4fTikPMUkVachk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTANi2/dJMcab52O8P/C8CuOeJg4fTikPMUkVachk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTANi2%2FdJMcab52O8P%2FC8CuOeJg4fTikPMUkVachk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;327&quot; data-filename=&quot;Gemini_Generated_Image_ov1y87ov1y87ov1y.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;SSL/TLS&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 전송 계층에서 보안을 제공하는 프로토콜&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 클라이언트와 서버가 통신 시 제3자가 도청하거나 변조하지 못하도록 함&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 보안 세션을 기반으로 데이터를 암호화(보안 세션이 만들어질 때 인증 메커니즘, 키 교환 암호화 알고리즘, 해싱 알고리즘이 사용됨)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;보안 세션 : &lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;보안이 시작되고 끝나는 동안 유지되는 세션&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;세션 : &lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;운영체제가 어떠한 사용자로부터 자신의 자산 이용을 허락하는 일정한 기간&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 클라이언트와 서버가 서버와 키를 공유하고 인증, 인증 확인 등의 작업이 일어나는 단 한 번의 1-RTT가 생긴 이후 데이터를 송수신 함&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 클라이언트에서 사이퍼 슈트를 서버에 전달하면 서버는 받은 사이퍼 슈트의 암호화 알고리즘 리스트를 제공할 수 있는지 확인 후 클라이언트로 인증서를 보내는 인증 메커니즘 시작&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 이후 해싱 알고리즘 등으로 암호화된 데이터 송수신 시작&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;사이퍼 슈트&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 프로토콜, AEAD 사이퍼 모드, 해싱 알고리즘이 나열된 규약을 말함. 총 다섯개가 있음&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;인증 메커니즘&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- CA에서 발급한 인증서를 기반으로 이루어짐&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 안전한 연결을 시작하는데 있어 필요한 '공개키'를 클라이언트에 제공하고 사용자가 '서버가 신뢰'할 수있는 서버임을 보장&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 인증서는 서비스 정보, 공개키, 지문, 디지털 서명 등으로 이루어져 있음&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- CA는 아무나 할 수있는 것이 아니고 신뢰성이 엄격하게 공인된 기업들만 참여 가능&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;CA 발급 과정&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 자신의 서비스가 CA 인증서를 발급 받으려면 자신의 사이트 정보과 공개키를 CA에 제출&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 이후 CA는 공개키를 해시한 값인 지문을 사용하는 CA의 비밀 키 등을 기반으로 CA인증서 발급&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;암호화 알고리즘&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 키 교환 암호화 알고리즘으로는 대수곡선 기반의 ECDHE 또는 모듈식 기반의 DHE를 사용함&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 둘 다 디피-헬만 방식을 근간으로 만들어짐&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;디피-헬만 키 교환 암호화 알고리즘&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;디피헬만.png&quot; data-origin-width=&quot;2296&quot; data-origin-height=&quot;1461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NJMEc/dJMcahrH4dS/p8s6kiMmMsKEbiPoczPvc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NJMEc/dJMcahrH4dS/p8s6kiMmMsKEbiPoczPvc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NJMEc/dJMcahrH4dS/p8s6kiMmMsKEbiPoczPvc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNJMEc%2FdJMcahrH4dS%2Fp8s6kiMmMsKEbiPoczPvc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;382&quot; data-filename=&quot;디피헬만.png&quot; data-origin-width=&quot;2296&quot; data-origin-height=&quot;1461&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 암호키를 교환하는 하나의 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;처음에 공개 값을 공유하고 각자의 비밀 값과 혼합한 후 혼합 값을 공유, 그 후 각자의 비밀 값과 다시 혼합&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 최종적으로 공통의 암호키인 PSK가 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- PSK가 생성되면, 악의적인 공격자가 개인키 또는 공개키를 가지고도 PSK가 없기 때문에 아무것도 할 수 없음.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;해싱 알고리즘&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 데이터를 추정하기 힘든 더 작고, 섞여 있는 조각으로 만드는 알고리즘&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- SSL/TLS는 해싱 알고리즘으로 SHA-256, SHA-384 알고리즘을 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;SHA-256 알고리즘&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 해시 함수의 결괏값이 256비트인 알고리즘&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 비트 코인을 비롯한 많은 블록체인 시스템에서 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 해싱을 해야 할 메시지에 1을 추가하는 등 전처리 후 그걸 기반으로 해시를 반환함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;해시: 다양한 길이를 가진 데이터를 고정된 길이를 가진 데이터로 매핑한 값&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;해싱: 임의의 데이터를 해시로 바꿔주는 일. 해시 함수가 이를 담당&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;해시 함수: 임의의 데이터를 입력받아 일정한 길이의 데이터로 바꿔주는 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;HTTPS 구축 방법&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 직접 CA에서 구매한 인증키를 기반으로 구축&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 서버 앞단의 HTTPS를 제공하는 로드밸런서 두고 구축&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;- 서버 앞단에 HTTPS를 제공하는 CDN을 두고 구축&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;HTTP/3&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_mg46cmg46cmg46cm.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WA2Tq/dJMcafglA5a/qNwIALAj7c62PGOXQzYJfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WA2Tq/dJMcafglA5a/qNwIALAj7c62PGOXQzYJfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WA2Tq/dJMcafglA5a/qNwIALAj7c62PGOXQzYJfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWA2Tq%2FdJMcafglA5a%2FqNwIALAj7c62PGOXQzYJfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;327&quot; data-filename=&quot;Gemini_Generated_Image_mg46cmg46cmg46cm.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- TCP 위에서 돌아가는 HTTP/2와는 달리 3은 QUIC이라는 계층 위에서 돌아감&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- TCP 기반이 아닌&lt;span&gt;&amp;nbsp;&lt;/span&gt;UDP&lt;span&gt;&amp;nbsp;&lt;/span&gt;기반으로 돌아감&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 멀티플렉싱을 가지고 있으며, 초기 연결 설정 시 지연 시간 감소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 연결 설정 시 지연 시간 감소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- QUIC은 TCP를 사용하지 않기 때문에 3-way-handshake 과정을 거치지 않아도 됨&lt;br /&gt;- 첫 연결 설정에 1-RTT만 소요(클라이언트가 서버에 신호를 주고, 서버가 거기에 응답하면 바로 통신 시작 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 순방향 오류 수정 메커니즘이 적용되어, 전송한 패킷이 손실 되었다면 수신 측에서 에러를 검출하고 수정 하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 열악한 네트워크 환경에서도 낮은 패킷 손실률&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #fdfdfc; color: #6e6c68; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;HTTP&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;프로토콜&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;발전&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;요약&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP/1.0&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;요청마다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;매번&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;새&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;TCP&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;연결(3-way&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;handshake)을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;맺어야&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;해서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;RTT가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;늘어남.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;우회하려고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이미지&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;스플리팅,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;코드&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;압축,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;Base64&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;인코딩&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;같은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;편법을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;씀.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP/1.1&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; TCP 연결을 재사용하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;keep-alive&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;매번&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;새&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;연결을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;안&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;맺어도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;되게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;개선.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;하지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;커넥션에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;앞&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;패킷이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;막히면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;뒤&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;패킷도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;대기하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span data-wf=&quot;&quot;&gt;HOL&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;Blocking&lt;/span&gt;&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;압축&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;안&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;되는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;무거운&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;헤더가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;한계.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP/2&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;멀티플렉싱&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;(여러&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;스트림&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;병렬&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;전송으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;HOL&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;Blocking&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;해결),&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span data-wf=&quot;&quot;&gt;HPACK&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;헤더&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;압축&lt;/span&gt;&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span data-wf=&quot;&quot;&gt;서버&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;푸시&lt;/span&gt;&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;(요청&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;전에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;필요할&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;리소스&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;미리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;전송)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;도입.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;사실상&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;HTTPS(TLS)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;기반으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;동작.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTPS/TLS&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;인증서(CA&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;발급)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;기반&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;인증&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;키&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;교환(ECDHE/DHE,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;디피-헬만)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;해싱(SHA-256&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;등)으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;통신을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;암호화하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;계층.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP/3&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; TCP 대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;UDP 기반 QUIC&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;위에서 동작. 3-way handshake가 아예 필요 없어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;1-RTT&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;연결&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;시작,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;멀티플렉싱&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;유지,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;패킷&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;손실을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;자체&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;복구하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span data-wf=&quot;&quot;&gt;순방향&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;오류&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;수정&lt;/span&gt;&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;지원.&lt;/span&gt;&lt;/p&gt;</description>
      <category>스터디</category>
      <author>noobdev25</author>
      <guid isPermaLink="true">https://noobdev25.tistory.com/66</guid>
      <comments>https://noobdev25.tistory.com/66#entry66comment</comments>
      <pubDate>Sun, 19 Jul 2026 04:23:07 +0900</pubDate>
    </item>
    <item>
      <title>네트워크의 기초</title>
      <link>https://noobdev25.tistory.com/65</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;네트워크란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드와 링크가 서로 연결되어 있으며, 리소스를 공유하는 집합을 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드 : 서버, 라우터, 스위치 등 네트워크 장치&lt;br /&gt;링크 : 유선 또는 무선&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;처리량과 지연 시간&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좋은 네트워크&lt;/b&gt;를 구축하는것이 중요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 네트워크 : 많은 처리량을 가지고, 지연 시간이 짧고 장애 빈도가 적으며 좋은 보안을 갖춤&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리량 : 링크 내에서 성공적으로 전달된 데이터의 양&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위로 bps(초당 전송or수신되는 비트 수) 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리량은 트래픽, 대역폭, 에러, 하드웨어 스펙에 영향을 받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽 : 링크 내에 '흐르는' 데이터의 양&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽과 처리량은 다른 개념&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트래픽이 많아졌다 = 흐르는 데이터가 많아졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 처리량이 많아졌다 = 처리되는 트래픽이 많아졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대역폭 : 주어진 시간동안 네트워크 연결을 통해 흐를 수 있는 최대 비트 수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지연시간&lt;/b&gt; : 요청이 처리되는 시간. 메세지가 두 장치를 왕복하는데 걸리는 시간.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;매체 타입(유,무선), 패킷 크기, 라우터의 패킷 처리 시간에 영향을 받음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;네트워크 토폴로지와 병목 현상&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 토폴로지란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 설계시 고려하는 노드와 링크의 &lt;b&gt;배치 방식&lt;/b&gt;이자 &lt;b&gt;연결 형태&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;토폴로지 수정.png&quot; data-origin-width=&quot;2811&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X3Tdu/dJMcadixlUR/5Gnx0hWYtZDWsXQVOzK1Mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X3Tdu/dJMcadixlUR/5Gnx0hWYtZDWsXQVOzK1Mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X3Tdu/dJMcadixlUR/5Gnx0hWYtZDWsXQVOzK1Mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX3Tdu%2FdJMcadixlUR%2F5Gnx0hWYtZDWsXQVOzK1Mk%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;2811&quot; height=&quot;1090&quot; data-filename=&quot;토폴로지 수정.png&quot; data-origin-width=&quot;2811&quot; data-origin-height=&quot;1090&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트리 토폴로지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층형 토폴로지라고 하며, 트리 형태로 배치한 네트워크 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 노드의 추가, 삭제가 쉬움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 노드에 트래픽이 집중될 때, 하위 노드에 영향이 있을수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;버스 토폴로지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중앙 통신 회선 하나에 여러 노드가 연결되어 공유하는 네트워크 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 근거리 통신망에서 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 설치 비용이 적고 신뢰성이 우수하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 중앙 통신 회선에 노드를 추가,삭제가 쉽다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스푸핑이 가능한 문제점이 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스푸핑 : &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;LAN 상에서 송신부의 패킷을 송신과 관련 없는 다른 호스트에게 가지 않도록 하는 스위칭 기능을 마비시키거나 속여서, 특정 노드에 해당 패킷이 오도록 처리하는 것&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;스푸핑 적용시 올바르게 가야 할 패킷이 악의적인 노드에 전달되게 됨&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스타 토폴로지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중앙에 있는 노드에 모두 연결된 네트워크 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 노드를 추가하거나 에러를 탐지하기 쉬움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 패킷 충돌 발생 가능성이 적음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 장애 노드가 중앙 노드가 아니라면, 다른 노드에 영향을 끼치는 것이 적음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 중앙 노드에 장애 발생 시, 전체 네트워크 정지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 설치 비용이 높다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;링형 토폴로지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고리처럼 하나의 연속된 길을 통해 통신하는 네트워크 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터는 노드에서 노드로 이동. 각각의 노드는 고리 모양의 길을 통해 패킷 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 노드 수가 증가되도 네트워크 상 손실이 적음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 충돌 발생 가능성 낮음. 노드의 고장 발견을 쉽게 찾을 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네트워크 구성 변경 어려움.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 회선 장애 발생 시, 전체 네트워크에 영향을 크게 미침.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시 토폴로지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;망형 토폴로지라고도 부름. 그물망처럼 연결되어 있는 네트워크 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여러 경로가 존재해서 에러 발생 시에도 네트워크 사용 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트래픽 분산 처리도 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 노드의 추가 어려움.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 구축 비용과 운용 비용이 높음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;병목 현상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 시스템의 성능이나 용량이 하나의 구성 요소로 인해 제한을 받는 현상을 말한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크가 어떤 토폴로지를 갖는지, 어떤 경로로 이루어져 있는지 파악해야 병목 현상을 올바르게 처리 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네트워크 구조는 병목현상을 찾을 때 중요한 기준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;네트워크 분류&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규모를 기반으로 분류&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_184dup184dup184d.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CFa7F/dJMb998cy2h/ctKREIHhWwXLGPp7IWkdPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CFa7F/dJMb998cy2h/ctKREIHhWwXLGPp7IWkdPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CFa7F/dJMb998cy2h/ctKREIHhWwXLGPp7IWkdPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCFa7F%2FdJMb998cy2h%2FctKREIHhWwXLGPp7IWkdPk%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;2816&quot; height=&quot;1536&quot; data-filename=&quot;Gemini_Generated_Image_184dup184dup184d.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LAN : 근거리 통신망&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 같은 건물, 캠퍼스 같은 좁은 공간에서 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전송 속도가 빠르고 혼잡하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MAN : 대도시 지역 네트워크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도시 같은 넓은 지역에서 운영&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전송 속도 평균. LAN보다 더 많이 혼잡&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WAN : 광역 네트워크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 국가 또는 대륙같은 넓은 지역에서 운영&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전송 속도 낮음. MAN보다 더 혼잡&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;네트워크 성능 분석 명령어&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 병목 현상의 주된 원인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네트워크 대역폭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네트워크 토폴로지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버 CPU, 메모리 사용량&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 비효율적인 네트워크 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;네트워크 성능 분석을 위한 명령어&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ping&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네트워크 상태를 확인하려는 대상 노드를 향해 일정 크기의 패킷을 전송하는 명령어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TCP/IP 프로토콜 중 ICMP 프로토콜을 통해 동작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ping [IP 주소 or 도메인 주소]로 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;netstat&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 접속되어 있는 서비스들의 네트워크 상태를 표시하는데 사용하는 명령어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네트워크 접속, 라우팅 테이블, 네트워크 프로토콜 등 리스트를 보여줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 주로 서비스의 포트가 열려있는지 확인 할 때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nslookup&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DNS 관련 내용을 확인하기 위해 쓰는 명령어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 도메인에 매핑된 IP를 확인할 때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tracert&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 목적지 노드까지 네트워크 경로를 확인할 때 사용하는 명령어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어느 구간에서 응답 시간이 느려지는지 확인할 때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;네트워크 프로토콜 표준화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 장치들끼리 데이터를 주고받기 위해 설정된 공통된 인터페이스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 프로토콜은 기업이나 개인이 발표해서 정하는것이 아닌 IEEE 또는 IETF라는 표준화 단체가 이를 정함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex : 웹에 접속할 때 쓰는 '서로 약속된' 인터페이스인 HTTP라는 프로토콜을 통해, 노드들은 웹 서비스를 기반으로 데이터를 주고 받을수 있음.&lt;/p&gt;</description>
      <category>스터디</category>
      <author>noobdev25</author>
      <guid isPermaLink="true">https://noobdev25.tistory.com/65</guid>
      <comments>https://noobdev25.tistory.com/65#entry65comment</comments>
      <pubDate>Sun, 19 Jul 2026 01:22:14 +0900</pubDate>
    </item>
    <item>
      <title>만들자 게시판 3주차 (2) - 첨부파일 다운로드 API 구현</title>
      <link>https://noobdev25.tistory.com/63</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항에 보면 '&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #6e6c68; text-align: start;&quot;&gt;해당 첨부의 서버 URI 링크가 아닌, 바이너리 다운로드 형태로 구현되어야 함'&amp;nbsp;&lt;/span&gt;&amp;nbsp;이라는 조건이 있다&lt;/p&gt;
&lt;h3 style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;URI&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;직링크는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;왜&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;안&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;되나&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;편한 방법은 uploads 폴더를 웹에 노출시키고&lt;span&gt;&amp;nbsp;&lt;/span&gt;http://서버/uploads/uuid_파일명.png&lt;span data-wf=&quot;&quot;&gt;으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;직접&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;접근하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;것이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;하지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이러면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;저장&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;폴더&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;구조와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;실제&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;파일명이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;외부에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;노출되고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;주소만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;알면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;누구나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;아무&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;검증&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;없이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;파일을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;가져갈&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;나중에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;권한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;검사&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;같은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;걸&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;넣을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;자리도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;없는게 문제다&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;그래서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;모든&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;다운로드가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;API를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;통과하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;만든다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;클라이언트는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;파일&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;id만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;알고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;실제&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;파일&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;위치는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;서버만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;안다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784312725096&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;클라이언트: GET /api/files/1/download  (id만 앎)
    ① 서버가 DB에서 id=1 조회 &amp;rarr; 저장명(UUID), 폴더, 원본명 획득
    ② 디스크에서 해당 파일을 읽음
    ③ 응답 본문에 바이너리를 싣고 헤더를 붙여 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;물품보관소랑&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;똑같다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;손님은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;보관증&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;번호만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;들고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;오고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;장부(DB)에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;번호로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;선반&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;위치를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;찾아서,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;창고(디스크)에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;실물을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;꺼내&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;준다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;다운로드&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;응답의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;생김새&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;다운로드라고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;특별한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;응답&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;본문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;JSON&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;파일&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;바이너리를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;실으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;그게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;다운로드다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;업로드와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;방향만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;반대라는 것을 알 수있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784312760930&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Disposition: attachment; filename=&quot;스크린샷.png&quot;

(파일의 바이너리 바이트)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Content-Disposition: attachment&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot;&gt;가 핵심이다. 브라우저에게 &quot;열어서 보여주지 말고 파일로 저장해&quot;라고 지시하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;filename&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot;&gt;으로 저장될 이름을 알려준다. 여기에 UUID가 아니라 원본 파일명을 넣는다. 업로드 때 원본명을 DB에 따로 저장해둔 이유가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;Repository&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;/&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;Service&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;id로 한 건 조회하는 쿼리를 추가하자&lt;/p&gt;
&lt;pre id=&quot;code_1784312889949&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // repository.xml
  &amp;lt;!-- 파일 id로 상세 조회 --&amp;gt;
  &amp;lt;select id=&quot;selectFileById&quot; resultType=&quot;com.study.file.entity.FileEntity&quot;&amp;gt;
    SELECT id, board_id, origin_name, stored_name, file_path, file_size, file_format
    FROM files
    WHERE id = #{id}
  &amp;lt;/select&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Service에는 조회 메서드와, 디스크의 실제 파일을 가리키는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Resource&lt;span data-wf=&quot;&quot;&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;만드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;메서드를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;뒀다&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784312947675&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // 파일 id로 상세 조회
  public FileEntity getFileById(Integer id) {
    FileEntity file = fileRepository.selectFileById(id);

    return file;
  }

  // 파일 다운로드
  public Resource loadAsResource(FileEntity file) throws IOException {
    // 폴더 경로 + UUID 저장명으로 실제 경로 조립
    Path filePath = Paths.get(file.getFilePath()).resolve(file.getStoredName());
    // 그 경로를 가리키는 손잡이를 만들어 반환
    return new UrlResource(filePath.toUri());
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div data-code-text=&quot;// 파일 id로 상세 조회
  public FileEntity getFileById(Integer id) {
    FileEntity file = fileRepository.selectFileById(id);

    return file;
  }

  // 파일 다운로드
  public Resource loadAsResource(FileEntity file) throws IOException {
    // 폴더 경로 + UUID 저장명으로 실제 경로 조립
    Path filePath = Paths.get(file.getFilePath()).resolve(file.getStoredName());
    // 그 경로를 가리키는 손잡이를 만들어 반환
    return new UrlResource(filePath.toUri());
  }&quot;&gt;
&lt;div style=&quot;background-color: #fdfdfc;&quot; data-gutter=&quot;&quot;&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Resource&lt;span data-wf=&quot;&quot;&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;&quot;디스크의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;파일을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;가리키는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;손잡이&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;정도로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이해했다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;손잡이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;만드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;시점에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;파일을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;메모리로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;읽는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;아니라,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;응답을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;보낼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;스프링이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;조금씩&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;흘려보낸다(스트리밍).&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;그래서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;큰&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;파일도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;메모리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;걱정이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;없다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 주목할 점:&lt;span&gt;&amp;nbsp;&lt;/span&gt;getFileById는 DTO가 아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Entity를 그대로 반환&lt;/b&gt;한다. 지금까지는 Service가 항상 DTO를 반환했는데, 이번엔 Controller가&lt;span&gt;&amp;nbsp;&lt;/span&gt;storedName과&lt;span&gt;&amp;nbsp;&lt;/span&gt;filePath를 써야 하고, 그 둘은 DTO에서 일부러 뺀 필드다. 기준을 이렇게 정리했다 &amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;클라이언트에게 JSON으로 나갈 거면 DTO, 서버 안에서만 쓸 거면 Entity.&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;단,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;Entity가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;컨트롤러까지&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;오는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;건&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;괜찮지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;컨트롤러&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;밖(JSON&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;응답)으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;나가면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;안&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;된다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1784313024045&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // 첨부파일 다운로드 (JSON이 아니라 파일 바이너리를 응답 본문에 실어 보냄)
  @GetMapping(&quot;/api/files/{id}/download&quot;)
  public ResponseEntity&amp;lt;Resource&amp;gt; downloadFile(@PathVariable Integer id) throws IOException {
    // 장부(DB)에서 파일 정보 조회
    FileEntity file = fileService.getFileById(id);
    // 디스크의 실제 파일을 가리키는 손잡이 받아오기
    Resource resource = fileService.loadAsResource(file);

    // 저장될 파일명 이름표 (한글 깨짐 방지 UTF-8 인코딩)
    ContentDisposition contentDisposition = ContentDisposition.attachment()
        .filename(file.getOriginName(), StandardCharsets.UTF_8)
        .build();

    // 응답 조립: 200 OK + 헤더 2개 + 본문(손잡이)
    return ResponseEntity.ok()
        .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .body(resource);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지는 return만 하면 스프링이 알아서 &quot;200 + JSON&quot;으로 포장해줬는데, 다운로드는 헤더를 직접 붙여야 해서&lt;span&gt;&amp;nbsp;&lt;/span&gt;ResponseEntity&lt;span data-wf=&quot;&quot;&gt;라는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;조립&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;키트로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;응답을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;수동으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;만든다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;읽히는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;그대로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;&quot;200으로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;헤더&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;붙이고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;타입으로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이걸&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;본문에&quot;다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한글 파일명은 헤더 값을 손으로 만들면 깨지는 고전적인 함정이 있어서, 스프링의&lt;span&gt;&amp;nbsp;&lt;/span&gt;ContentDisposition&lt;span data-wf=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;빌더에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;UTF-8을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;지정해서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;표준&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;방식으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;인코딩했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;테스트&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;다운로드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;GET이고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;본문이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;없어서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;브라우저&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;주소창으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;바로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;테스트할&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;주소창에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;URL을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;치는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;곧&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;GET&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;요청이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;http://localhost:8080/api/files/2/download&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 접속하니 디스크에는 UUID 이름으로 저장된 파일이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;원본 한글 이름 그대로&lt;/b&gt;&lt;span data-wf=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;다운로드됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-07-18 025144.png&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HLxsl/dJMcacRlC2r/vdmPc6lvJkao0ru5g1Ikbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HLxsl/dJMcacRlC2r/vdmPc6lvJkao0ru5g1Ikbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HLxsl/dJMcacRlC2r/vdmPc6lvJkao0ru5g1Ikbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHLxsl%2FdJMcacRlC2r%2FvdmPc6lvJkao0ru5g1Ikbk%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;478&quot; height=&quot;219&quot; data-filename=&quot;스크린샷 2026-07-18 025144.png&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트러블 슈팅을 빙자한 삽질 기록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot;&gt;- 테스트하는데&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;405 Method Not Allowed&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot;&gt;가 떴다. 코드는 멀쩡했고, 해결책도 간단했다. 원인은 재시작을 안 한 것. 자바 앱은 시작 시점의 코드로 돌기 때문에 소스를 고쳐도 떠 있는 서버는 모른다. 405는 &quot;URL은 아는데 그 메서드 입구가 없다&quot;는 뜻&lt;/span&gt;&lt;/p&gt;</description>
      <category>게시판 만들기/3주차</category>
      <author>noobdev25</author>
      <guid isPermaLink="true">https://noobdev25.tistory.com/63</guid>
      <comments>https://noobdev25.tistory.com/63#entry63comment</comments>
      <pubDate>Sat, 18 Jul 2026 13:34:48 +0900</pubDate>
    </item>
    <item>
      <title>만들자 게시판 3주차 (1) - 첨부파일 업로드 API 구현</title>
      <link>https://noobdev25.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이건 글을 쓰기 전에 미리 반성하고 시작한다&lt;br /&gt;바쁘다는 핑계로 결국 리액트도 백엔드 API도 제대로 다 구현을 못했다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부끄럽지만 시간 대비 효율이 안나오는 인간인지라 진득하게 의자에 앉아 있는 시간을 늘려도 아직 한참 모자른거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르게 진행해 보겠습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;게시판에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;첨부파일&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;기능을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;붙이기&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;시작했다.&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;왜&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;JSON으로는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;파일을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;못&lt;span&gt; 보낼까?&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 만든 API는 전부 요청 본문에 JSON을 실어서 보냈다. 그런데 파일 업로드는 JSON으로 할 수가 없다. JSON은 텍스트 형식이라서, 이미지나 zip 같은 바이너리 데이터를 담을 그릇이 아니기 때문이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;요청 본문(body)은 그냥 &quot;데이터를 싣는 화물칸&quot;일 뿐이고, 거기에 뭘 싣는지는 그때그때 다르다. 뭘 실었는지는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Content-Type&lt;span&gt;&amp;nbsp;&lt;/span&gt;헤더가 알려준다. JSON을 실으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;application/json, 파일을 실으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;multipart/form-data인 것이다.&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;multipart/form-data의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;생김새&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;multipart는 이름 그대로 본문을 여러 칸(part)으로 나누는 형식이다. 칸막이 문자열(boundary)로 본문을 나누고, 각 칸에 이름표&lt;span data-wf=&quot;&quot;&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;붙인다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;실제&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;요청은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;대략&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이렇게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;생겼다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784312159230&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST /api/boards/1/files HTTP/1.1
Content-Type: multipart/form-data; boundary=----ABC123

------ABC123
Content-Disposition: form-data; name=&quot;files&quot;; filename=&quot;example1.png&quot;

(example1.png의 바이너리 바이트가 그대로 들어감)
------ABC123
Content-Disposition: form-data; name=&quot;files&quot;; filename=&quot;example2.jpg&quot;

(example2.jpg의 바이너리 바이트)
------ABC123--&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;칸 안에는 바이너리를 그대로 넣을 수 있다. 스프링은 이 칸 하나를&lt;span&gt;&amp;nbsp;&lt;/span&gt;MultipartFile&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체로 포장해서 주고, 같은 이름표의 칸이 여러 개면&lt;span&gt;&amp;nbsp;&lt;/span&gt;List&amp;lt;MultipartFile&amp;gt;&lt;span data-wf=&quot;&quot;&gt;로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;묶어준다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;요구사항이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;&quot;게시글당&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;첨부&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;2개&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이상&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;가능&quot;이라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;List로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;받으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;된다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;저장&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;파일명&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;설계&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-wf=&quot;&quot;&gt;파일을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;받으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;디스크에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;원본&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이름&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;그대로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;저장하지&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;않고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;UUID를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;붙여서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;저장하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;원본&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이름은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;DB에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;따로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;보관하기로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;했다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이유는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;두&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;가지다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;다른 사용자가 같은 이름의 파일을 올리면 덮어써지는 충돌 방지&lt;/li&gt;
&lt;li&gt;&lt;span data-wf=&quot;&quot;&gt;다운로드할&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;때는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;원본&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이름으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;돌려줘야&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;하니까&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;원본&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이름도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;버리면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;안&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;됨&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1784312274611&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // 첨부 파일 업로드
  public FileResponse uploadFile(Long boardId, MultipartFile file) throws IOException {

    // 사용자가 업로드한 원본 파일 명
    String originName = file.getOriginalFilename();
    // 저장할 파일 명(UUID)
    String storedName = UUID.randomUUID().toString() + &quot;_&quot; + originName;

    // &quot;./uploads&quot; 문자열을 자바가 다룰 수 있는 경로 객체로 변환
    Path uploadPath = Paths.get(uploadDir);
    // 업로드 폴더가 없으면 새로 생성, 이미 있으면 그냥 넘어감
    Files.createDirectories(uploadPath);
    // 업로드 폴더 경로 + 저장할 파일명을 합쳐서 최종 저장 경로 생성
    Path destination = uploadPath.resolve(storedName);

    // 디스크에 파일 바이트 쓰기
    file.transferTo(destination);

    // DB에 파일 정보 저장
    FileEntity fileEntity = new FileEntity();
    fileEntity.setBoardId(boardId);
    fileEntity.setOriginName(originName);
    fileEntity.setStoredName(storedName);
    fileEntity.setFilePath(uploadDir);
    fileEntity.setFileSize(file.getSize());
    fileEntity.setFileFormat(originName.substring(originName.lastIndexOf(&quot;.&quot;) + 1));

    fileRepository.insertFile(fileEntity);

    return fileMapper.toResponse(fileEntity);
  }

  // 첨부 파일 여러 개 업로드
  public List&amp;lt;FileResponse&amp;gt; uploadFiles(Long boardId, List&amp;lt;MultipartFile&amp;gt; files) throws IOException {
    // 업로드 결과(응답 DTO)들을 담을 빈 리스트 생성
    List&amp;lt;FileResponse&amp;gt; responses = new ArrayList&amp;lt;&amp;gt;();
    // 받은 파일들을 하나씩 꺼내서
    for (MultipartFile file : files) {
      // 기존의 한 개짜리 업로드 메서드를 재사용하고, 그 결과를 리스트에 추가
      responses.add(uploadFile(boardId, file));
    }
    // 업로드된 파일들의 정보 목록을 반환
    return responses;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 로직&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot;&gt;메서드를 두 개로 나눴다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;uploadFile&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot;&gt;은 파일 한 개를 실제로 저장하는 메서드고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;uploadFiles&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;받은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;파일들을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;돌면서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;일꾼을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;N번&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;호출하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;메서드다.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;여러&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;개&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;업로드&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;개&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;업로드&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;&amp;times;&lt;span&gt; N회차 &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;니까&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;저장&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;로직을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;복사하지&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;않고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;기존&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;메서드를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;재사용했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1784312387748&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // 게시글에 첨부파일 업로드 (multipart/form-data 요청)
  @PostMapping(&quot;/api/boards/{boardId}/files&quot;)
  public List&amp;lt;FileResponse&amp;gt; uploadFiles(
      @PathVariable Long boardId,
      @RequestParam(&quot;files&quot;) List&amp;lt;MultipartFile&amp;gt; files) throws IOException {
    return fileService.uploadFiles(boardId, files);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;첨부파일도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;게시글에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a; text-align: start;&quot; data-wf=&quot;&quot;&gt;소속되니까 &lt;/span&gt;URL은 댓글 때 쓴 패턴(/api/boards/{boardId}/comments&lt;span data-wf=&quot;&quot;&gt;)을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;그대로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;따랐다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주의할 점은&lt;span&gt;&amp;nbsp;&lt;/span&gt;@RequestBody가 아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;@RequestParam이라는 것.&lt;span&gt;&amp;nbsp;&lt;/span&gt;@RequestBody는 본문의 JSON을 객체로 바꿔주는 어노테이션이라 multipart에는 못 쓴다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;@RequestParam(&quot;files&quot;)&lt;span data-wf=&quot;&quot;&gt;는&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이름표가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;files인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;칸들을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;꺼내와라 라는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;뜻이고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;괄호&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;안&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;문자열이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;클라이언트가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;보내는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;이름표와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;정확히&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;일치해야&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-wf=&quot;&quot;&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfc; color: #0a0a0a; text-align: start;&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;스크린샷 2026-07-18 015201.png&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pXZ7y/dJMcahkW3C1/JUfKu4uCbKB1z9ntW37IG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pXZ7y/dJMcahkW3C1/JUfKu4uCbKB1z9ntW37IG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pXZ7y/dJMcahkW3C1/JUfKu4uCbKB1z9ntW37IG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpXZ7y%2FdJMcahkW3C1%2FJUfKu4uCbKB1z9ntW37IG1%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;715&quot; height=&quot;580&quot; data-filename=&quot;스크린샷 2026-07-18 015201.png&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-07-18 020752.png&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqmaZR/dJMcahyquqp/VuxynBmhKeqdsJRbfMSEQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqmaZR/dJMcahyquqp/VuxynBmhKeqdsJRbfMSEQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqmaZR/dJMcahyquqp/VuxynBmhKeqdsJRbfMSEQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqmaZR%2FdJMcahyquqp%2FVuxynBmhKeqdsJRbfMSEQ1%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;710&quot; height=&quot;575&quot; data-filename=&quot;스크린샷 2026-07-18 020752.png&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스트맨으로 업로드, 조회시 잘 작동하는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알아두면 좋은거&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfc; color: #0a0a0a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응답 DTO(FileResponse)에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;storedName과&lt;span&gt;&amp;nbsp;&lt;/span&gt;filePath는 뺐다. 서버 내부의 저장 위치는 클라이언트가 알 필요가 없는 정보다&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>게시판 만들기/3주차</category>
      <author>noobdev25</author>
      <guid isPermaLink="true">https://noobdev25.tistory.com/62</guid>
      <comments>https://noobdev25.tistory.com/62#entry62comment</comments>
      <pubDate>Sat, 18 Jul 2026 03:22:25 +0900</pubDate>
    </item>
    <item>
      <title>디자인 패턴</title>
      <link>https://noobdev25.tistory.com/61</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8,1&quot; data-ke-size=&quot;size16&quot;&gt;면접을 준비하면서 느낀건데 CS 지식이 많이 부족한거 같습니다. 그래서 모자른 지식을 채우기 위해 책을 읽고 있는데, 그 내용을 기반으로 정리해 보려고 합니다&lt;/p&gt;
&lt;p data-path-to-node=&quot;8,1&quot; data-ke-size=&quot;size16&quot;&gt;그 첫 번째 챕터로 '디자인 패턴과 프로그래밍 패러다임'을 선정했습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;8,2&quot; data-ke-size=&quot;size16&quot;&gt;방대한 양의 패턴들을 무작정 외우기보다는 각 패턴이 '어떤 문제를 해결하는가?'에 초점을 맞추어 제가 사용하고있는 자바 언어 기반으로 재해석해 보았습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 싱글톤(Singleton) 패턴&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3&quot;&gt;[개념]&lt;/b&gt; 애플리케이션 내에서 특정 클래스의 인스턴스가 오직 하나만 생성되도록 보장하고, 이 인스턴스에 접근할 수 있는 전역적인 접촉점을 제공하는 생성 디자인 패턴&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;[쉬운 풀이]&lt;/b&gt; 회사에 있는 '공용 프린터'를 생각해보자. 직원(객체)들이 프린트를 할 때마다 새로운 프린터를 사오는 것이 아니라, 이미 설치된 프린터 1대를 다 같이 공유해서 사용한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스 연결 모듈처럼 똑같은 객체를 여러 번 만들 필요가 없는 경우, 객체를 매번 새로 생성하면 메모리 낭비가 심해진다. 그래서 &quot;딱 하나만 만들어서 메모리에 올려두고, 다 같이 공유해서 쓰자&quot;는 것이 싱글톤 패턴의 핵심이다&lt;/p&gt;
&lt;pre id=&quot;code_1783783266979&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Singleton {
    // 1. 단 하나뿐인 인스턴스를 저장할 전역(static) 변수
    private static Singleton instance;

    // 2. 외부에서 새로운 객체를 생성하지 못하도록 차단
    private Singleton() {
    }

    // 3. 외부에서 인스턴스를 가져다 쓸 수 있는 유일한 접근점(메서드)
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton(); // 인스턴스가 아예 없을 때만 새로 생성
        }
        return instance; // 이미 있다면 기존 것을 반환
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0&quot;&gt;[싱글톤 패턴의 트레이드 오프(Trade-off)]&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0,0&quot;&gt;장점:&lt;/b&gt; 객체를 한 번만 생성하므로 메모리 낭비를 줄이고, 어디서든 동일한 인스턴스에 접근할 수 있어 데이터 공유가 효율적&lt;/p&gt;
&lt;p data-path-to-node=&quot;4,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,1,0&quot;&gt;단점:&lt;/b&gt; 객체 간의 결합도가 높아져서, 나중에 코드를 테스트하거나 변경할 때 까다로워질 수 있다. (의존성 문제)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 팩토리(factory) 패턴&lt;/h3&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;[개념]&lt;/b&gt; 객체를 사용하는 코드에서 객체 생성 로직을 분리하여 추상화한 패턴. 상위 클래스는 전체적인 뼈대(인터페이스)를 정의하고, 구체적인 객체 생성은 하위 클래스가 담당하도록 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;[쉬운 풀이]&lt;/b&gt; 우리가 자주 가는 카페를 떠올려보자. 손님(클라이언트)은 &quot;아메리카노 주세요&quot;라고 주문만 할 뿐, 바리스타(팩토리)가 원두를 어떻게 갈고 샷을 어떻게 내리는지 구체적인 생성 과정은 알 필요가 없다. 그저 '아메리카노'라는 조건만 넘겨주면, 바리스타가 알아서 완성된 커피(객체)를 만들어 준다. 프로그래밍에서도 여기저기서 복잡하게 new 키워드로 객체를 만들지 않고, 객체를 전문적으로 찍어내는 '공장' 역할을 하는 클래스에 생성을 맡기는 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1783783603136&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 공통 인터페이스 (커피)
interface Coffee {
    void drink();
}

// 2. 구체적인 객체들 (아메리카노, 라떼)
class Americano implements Coffee {
    public void drink() { System.out.println(&quot;아메리카노를 마신다.&quot;); }
}
class Latte implements Coffee {
    public void drink() { System.out.println(&quot;라떼를 마신다.&quot;); }
}

// 3. 객체를 대신 생성해주는 팩토리 클래스
class CoffeeFactory {
    public static Coffee createCoffee(String type) {
        if (&quot;americano&quot;.equals(type)) {
            return new Americano(); // 아메리카노 생성
        } else if (&quot;latte&quot;.equals(type)) {
            return new Latte();     // 라떼 생성
        }
        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9&quot;&gt;[팩토리 패턴의 트레이드 오프(Trade-off)]&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;장점:&lt;/b&gt; 객체 생성 로직이 한곳에 집중되어 유지보수가 쉬움. 클라이언트는 객체 생성 방식을 몰라도 되므로 결합도가 낮아진다. 그리고 객체 생성 로직이 따로 뗴어져 있으므로 리팩토링 과정에서도 한 곳만 고치면 되니 유지 보수성이 증가한다&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;단점:&lt;/b&gt; 새로운 객체를 추가할 때마다 팩토리 클래스의 코드를 수정해야 할 수도 있음. 클래스 개수가 많아져서 구조가 조금 복잡해질 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 전략(strategy 패턴&lt;/h3&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;[개념]&lt;/b&gt; 특정 연산을 수행하는 알고리즘(동작)들을 각각 하나의 클래스로 캡슐화하고, 이들을 상호 교체할 수 있도록 만드는 행위 디자인 패턴이다. 이를 통해 클라이언트 코드를 변경하지 않고도 런타임(프로그램 실행 중)에 알고리즘을 동적으로 변경할 수 있음. 정책 패턴 (policy pattern) 이라고도 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;[쉬운 풀이]&lt;/b&gt; 스마트폰의 '지도 앱(내비게이션)'을 생각해보자. 목적지는 같지만 '자동차', '대중교통', '도보' 중 어떤 버튼을 누르느냐에 따라 길을 찾는 방식(전략)이 완전히 달라진다. 지도 앱 자체(뼈대)는 그대로 두고, 길 찾기 '전략' 부품만 갈아 끼우는 것. 프로그래밍에서도 어떤 동작을 하는 로직을 독립된 클래스로 분리해두고, 필요할 때마다 부품을 교체하듯이 갈아 끼워 사용하는 방식.&lt;/p&gt;
&lt;pre id=&quot;code_1783784503805&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 전략 인터페이스 (길 찾기 방법)
interface RouteStrategy {
    void buildRoute(String start, String end);
}

// 2. 구체적인 전략 클래스들 (자동차, 도보)
class CarStrategy implements RouteStrategy {
    public void buildRoute(String start, String end) {
        System.out.println(start + &quot;에서 &quot; + end + &quot;까지 자동차 도로로 안내합니다.&quot;);
    }
}
class WalkStrategy implements RouteStrategy {
    public void buildRoute(String start, String end) {
        System.out.println(start + &quot;에서 &quot; + end + &quot;까지 좁은 골목길을 포함한 도보 경로로 안내합니다.&quot;);
    }
}

// 3. 전략을 사용하는 컨텍스트 (내비게이션)
class Navigator {
    private RouteStrategy strategy; // 어떤 전략을 사용할지 저장

    // 프로그램 실행 중에도 언제든 전략을 교체할 수 있게 해주는 메서드
    public void setStrategy(RouteStrategy strategy) {
        this.strategy = strategy;
    }

    public void navigate(String start, String end) {
        strategy.buildRoute(start, end); // 현재 설정된 전략 실행
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[전략 패턴 사용 예시] &lt;/b&gt;결제 시스템에 자주 사용된다. 신용카드, 카카오페이, 네이버페이 등 결제 수단을 전략만 바꿔서 결제가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[전략 패턴의 트레이드 오프(Trade-off)]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점:&lt;/b&gt; 알고리즘(전략)을 런타임에 동적으로 교체할 수 있어 유연하다. if-else나 switch로 도배된 복잡한 로직을 별도의 클래스로 분리하여 가독성을 높임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점:&lt;/b&gt; 각 전략을 클래스로 구현해야 하므로 프로젝트 전체의 클래스 개수가 늘어남.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 옵저버(observer) 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;[개념]&lt;/b&gt; 주체가 어떤 객체의 상태 변화를 관찰하다가 상태변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴이다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;[쉬운 풀이]&lt;/b&gt; 유튜브 구독 알림 시스템을 생각하면 잘 맞다 우리가 좋아하는 유튜버의 채널을 구독해두면, 유튜버가 새 영상을 올렸을 때 나의 기기로 &quot;새 영상이 올라왔습니다&quot; 하고 알림이 온다. 우리가 매분 매초 채널에 들어가서 &quot;영상 올렸나?&quot; 하고 확인할 필요가 없음. 여기서 유튜버는 '관찰 대상(Subject)'이고, 구독자는 '관찰자(Observer)'가 됨. 상태 변화(영상 업로드)가 생기면 관찰자들에게 자동으로 알림을 쫙 뿌려주는 것이 옵저버 패턴.&lt;/p&gt;
&lt;pre id=&quot;code_1783785997450&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 관찰자(구독자) 인터페이스
interface Observer {
    void update(String videoTitle);
}

// 2. 구체적인 관찰자 (실제 유튜브 사용자)
class Subscriber implements Observer {
    private String name;

    public Subscriber(String name) { this.name = name; }

    public void update(String videoTitle) {
        System.out.println(name + &quot;님, 알림: [&quot; + videoTitle + &quot;] 영상이 업로드되었습니다!&quot;);
    }
}

// 3. 관찰 대상 (유튜브 채널)
class YoutubeChannel {
    // 구독자 명단
    private List&amp;lt;Observer&amp;gt; subscribers = new ArrayList&amp;lt;&amp;gt;();

    // 구독하기
    public void subscribe(Observer observer) {
        subscribers.add(observer);
    }

    // 영상 업로드 및 알림 보내기
    public void uploadVideo(String title) {
        System.out.println(&quot;--- 유튜브 채널에 '&quot; + title + &quot;' 업로드 됨 ---&quot;);
        // 명단에 있는 모든 구독자에게 알림 전송
        for (Observer sub : subscribers) {
            sub.update(title);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;1&quot;&gt;[옵저버 패턴의 트레이드 오프(Trade-off)]&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,0,0&quot;&gt;장점:&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,0,1,0,0&quot;&gt;느슨한 결합(Loose Coupling):&lt;/b&gt; 알림을 보내는 쪽(채팅방)은 알림을 받는 쪽(사용자)이 정확히 누구인지 몰라도 된다. 서로 코드가 강하게 얽혀있지 않아서, 새로운 구독자를 추가하거나 빼는 등의 코드 수정이 매우 쉽고 유연.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,0,1,1,0&quot;&gt;불필요한 확인 작업 제거:&lt;/b&gt; 데이터가 바뀌었는지 매번 주기적으로 물어볼 필요(Polling) 없이, 변화가 생겼을 때만 즉각적으로 대응할 수 있어 시스템이 효율적으로 동작.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,1,0&quot;&gt;단점:&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,1,1,0,0&quot;&gt;메모리 누수 위험:&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 알림을 받겠다고 등록(구독)만 해두고, 다 쓴 후에 제대로 해지(구독 취소)하지 않으면 안 쓰는 객체가 메모리에 계속 남아 자원을 낭비하게 됨.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,1,1,1,0&quot;&gt;디버깅의 어려움:&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 알림을 받는 옵저버가 너무 많아지거나 서로 꼬리에 꼬리를 물고 알림을 발생시키면, 코드가 실행되는 순서를 예측하기 어렵고 버그가 났을 때 원인을 추적하기가 까다로워진다..&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 프록시(proxy) 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;[개념]&lt;/b&gt; 대상 객체에 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;접근하기 전 그 접근에 대한 흐름을 가로채 대상 객체 앞단의 인터페이스 역할을 하는 디자인 패턴이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이를 통해 객체의 속성, 변환 등을 보완하며 보안, 데이터 검증, 캐싱, 로깅에 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3&quot;&gt;[쉬운 풀이]&lt;/b&gt; 우리가 상점에서 물건을 살 때 쓰는 '체크카드'를 생각해보자. 결제를 할 때 은행에 있는 내 '계좌(실제 객체)'에서 직접 현금을 꺼내서 주지 않는다. 대신 '체크카드(프록시)'를 점원에게 건넨다. 체크카드는 결제 요청을 받으면 내 계좌에 잔액이 충분한지 먼저 검사(접근 제어)한 뒤에, 조건이 맞을 때만 진짜 은행 계좌에 접근해 돈이 빠져나가게 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1783787290704&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 공통 인터페이스 (결제 수단)
interface Payment {
    void pay(int amount);
}

// 2. 실제 객체 (내 은행 계좌 - 실제 돈이 빠져나가는 곳)
class BankAccount implements Payment {
    public void pay(int amount) {
        System.out.println(&quot;은행 계좌에서 &quot; + amount + &quot;원이 출금되었습니다.&quot;);
    }
}

// 3. 대리인 객체 (체크카드)
class CheckCard implements Payment {
    private BankAccount account;
    private int balance = 10000; // 현재 잔액

    public void pay(int amount) {
        System.out.println(&quot;[카드 단말기] 결제를 시도하며 잔액을 확인합니다...&quot;); // 부가 기능 추가
        
        // 프록시에서 먼저 조건을 검사 (접근 제어)
        if (amount &amp;gt; balance) {
            System.out.println(&quot;[결제 실패] 잔액이 부족합니다.&quot;); 
            return; // 실제 계좌(BankAccount)에는 접근조차 하지 않음
        }
        
        // 조건이 맞을 때만 실제 객체 생성 및 위임
        if (account == null) {
            account = new BankAccount();
        }
        account.pay(amount);
        balance -= amount; // 잔액 차감
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;1&quot;&gt;[프록시 패턴의 트레이드 오프(Trade-off)]&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,0,0&quot;&gt;장점:&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,0,1,0,0&quot;&gt;안전한 접근 제어:&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 실제 객체(은행 계좌)에 아무나 직접 접근하는 것을 막고, 대리인(체크카드) 선에서 유효성 검사(잔액 확인 등)를 먼저 처리하여 시스템을 안전하게 보호할 수 있음.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,0,1,1,0&quot;&gt;관심사의 분리(단일 책임 원칙):&lt;/b&gt; 진짜 중요한 로직(실제 출금)과 부가적인 로직(검증, 로깅 등)을 분리할 수 있음. 기존 은행 계좌 클래스의 코드를 수정하지 않고도 체크카드 클래스에 새로운 기능을 얼마든지 추가할 수 있어 유지보수가 편해짐.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,1,0&quot;&gt;단점:&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2,1,1,0,0&quot;&gt;코드 복잡도 증가:&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 인터페이스를 만들고 대리인 클래스를 추가로 구현해야 하므로, 프로젝트 전체의 클래스 개수가 늘어나 구조가 조금 복잡해짐&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 이터레이터(iterator) 패턴&lt;/h3&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;[개념] &lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이터레이터(iterator)를 사용하여 컬렉션(collection)의 요소들에 접근하는 디자인 패턴.&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 컬렉션(리스트, 큐, 트리 등)이 내부에 데이터를 어떻게 저장하고 있는지 그 복잡한 속사정을 노출하지 않고도, 그 안에 들어있는 모든 항목을 순차적으로 하나씩 꺼내볼 수 있게 해줌&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;[쉬운 풀이]&lt;/b&gt; 우리가 매일 듣는 '음악 앱의 플레이리스트'를 생각해보자. 플레이리스트 안에 노래들이 배열(Array) 형태로 나란히 저장되어 있는지, 아니면 꼬리에 꼬리를 무는 리스트(Linked List) 형태로 흩어져 있는지 우리는 알 필요가 없다. 그저 앱 화면에서 '다음 곡(Next)' 버튼만 누르면 알아서 다음 노래가 흘러나온다. 이처럼 데이터가 어떤 구조로 저장되어 있든 상관없이 hasNext()(다음 곡 있어?)와 next()(그럼 다음 곡 틀어줘)라는 공통된 버튼(인터페이스)만 제공해서, 데이터를 일관된 방식으로 순회할 수 있게 해주는 것이 이터레이터 패턴의 핵심이다.&lt;/p&gt;
&lt;pre id=&quot;code_1783792045430&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 이터레이터(반복자) 인터페이스 - '다음 곡' 버튼 역할
interface Iterator {
    boolean hasNext(); // 다음 데이터가 있는지 확인
    String next();     // 다음 데이터를 가져옴
}

// 2. 컬렉션 인터페이스 - 이터레이터를 만들어내는 역할
interface Aggregate {
    Iterator createIterator();
}

// 3. 구체적인 컬렉션 (음악 플레이리스트)
class MusicPlaylist implements Aggregate {
    private String[] songs;
    private int count = 0;

    public MusicPlaylist(int size) {
        songs = new String[size];
    }

    public void addSong(String song) {
        if (count &amp;lt; songs.length) {
            songs[count++] = song;
        }
    }

    // 데이터 구조를 순회할 '전용 이터레이터'를 생성해서 반환
    @Override
    public Iterator createIterator() {
        return new PlaylistIterator(this);
    }

    public String getSongAt(int index) {
        return songs[index];
    }

    public int getCount() {
        return count;
    }
}

// 4. 구체적인 이터레이터 (플레이리스트 안을 돌아다니는 탐색기)
class PlaylistIterator implements Iterator {
    private MusicPlaylist playlist;
    private int index = 0; // 현재 탐색 위치를 기억함

    public PlaylistIterator(MusicPlaylist playlist) {
        this.playlist = playlist;
    }

    @Override
    public boolean hasNext() {
        return index &amp;lt; playlist.getCount(); // 노래가 더 남았는지 확인
    }

    @Override
    public String next() {
        return playlist.getSongAt(index++); // 현재 노래를 반환하고, 위치를 한 칸 이동
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7&quot;&gt;[이터레이터 패턴의 트레이드 오프(Trade-off)]&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;장점:&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,1,0,0&quot;&gt;내부 구조 숨김 (캡슐화):&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 데이터를 사용하는 쪽(클라이언트)에서는 데이터가 배열인지 리스트인지 몰라도 next()만 호출하면 됨. 나중에 데이터 저장 방식을 배열에서 리스트로 완전히 뜯어고쳐도, 밖에서 호출하는 코드는 단 한 줄도 수정할 필요가 없어 매우 유연함.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,1,1,0&quot;&gt;책임 분리 (단일 책임 원칙):&lt;/b&gt; 컬렉션(플레이리스트)은 '데이터를 저장하고 관리하는 일'만 신경 쓰고, 이터레이터는 '데이터를 순서대로 꺼내는 일'만 전담하므로 코드가 훨씬 깔끔해짐.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;단점:&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,1,0,0&quot;&gt;구조의 복잡화:&lt;/b&gt; 요소가 몇 개 없는 아주 단순한 배열이나 리스트라면, 굳이 인터페이스를 만들고 이터레이터 클래스까지 따로 구현하는 것이 오버엔지니어링이 될 수 있음. 클래스 개수가 늘어나 프로젝트가 다소 복잡해짐.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 노출 모듈(reavling module) 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-path-to-node=&quot;4&quot; data-index-in-node=&quot;0&quot;&gt;[개념]&lt;span&gt; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;즉시 실행 함수&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;를 통해 자바의 private, public 과 같은 접근 제어자를 만드는 디자인 패턴이다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6&quot;&gt;[자바 개발자를 위한 참고 사항]&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;7&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;7,0&quot; data-ke-size=&quot;size16&quot;&gt;사실 이 패턴은 자바(Java)보다는 과거의 자바스크립트(JavaScript)에서 아주 중요하게 쓰이던 패턴입니다. 자바에는 변수나 메서드를 숨길 수 있는 private, public 같은 접근 제어자가 처음부터 있었지만, 예전 자바스크립트에는 그런 기능이 없었습니다. 그래서 모든 데이터가 밖으로 새어 나가는 것을 막기 위해 '함수'로 내부를 감싸고, 바깥에서 쓸 기능만 return으로 빼주는 '노출 모듈 패턴'을 억지로 만들어 써야만 했습니다. 즉, &quot;자바의 클래스와 private/public 키워드가 하는 역할을 자바스크립트에서 흉내 낸 것&quot;이라고 이해하시면 가장 정확합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9&quot;&gt;[노출 모듈 패턴의 트레이드 오프(Trade-off)]&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;장점:&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,1,0,0&quot;&gt;데이터 캡슐화와 보호:&lt;/b&gt; 외부에서 알 필요가 없거나 함부로 건드리면 안 되는 중요한 변수를 안전하게 숨겨둘 수 있음.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,1,1,0&quot;&gt;명확한 가독성:&lt;/b&gt; 코드를 읽는 사람 입장에서 파일의 맨 밑(return 부분)만 보면 &quot;아, 외부에서 쓸 수 있는 기능은 이것들뿐이구나&quot; 하고 직관적으로 파악할 수 있어 코드가 깔끔해짐.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;단점:&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,1,0,0&quot;&gt;테스트의 어려움:&lt;/b&gt; 내부(private)에 꽁꽁 숨겨둔 함수나 변수들은 외부에서 접근할 수 없기 때문에, 단위 테스트(Unit Test)를 꼼꼼하게 작성하기가 매우 까다로움.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,1,1,0&quot;&gt;내부 확장성 부족:&lt;/b&gt; 숨겨진 함수를 나중에 상속받거나 외부에서 수정해서 써야 할 일이 생기면, 구조를 다 뜯어고쳐야 하는 불편함 존재.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. MVC, MVP, MVVM 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;[개념]&lt;/b&gt; 애플리케이션을 세 가지 역할로 나누어, 화면에 보이는 시각적 요소(UI)와 뒤에서 돌아가는 비즈니스 로직(데이터 처리)을 분리하는 설계 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6&quot;&gt;[쉬운 풀이]&lt;/b&gt; 식당의 운영 시스템을 생각해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;Model(모델):&lt;/b&gt; 주방 (식재료, 요리법, 데이터)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;View(뷰):&lt;/b&gt; 손님이 보는 테이블과 메뉴판 (사용자 화면, UI)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;Controller / Presenter / ViewModel:&lt;/b&gt; 홀 매니저 (손님의 주문을 주방에 전달하고, 음식을 테이블로 가져다주는 중간 역할)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;이 세 가지 패턴은 홀 매니저가 주방(Model)과 테이블(View) 사이를 어떤 방식으로 조율하는지에 따라 나뉜다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_eoh4uzeoh4uzeoh4.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRk4Al/dJMcacqjEWz/u7E5EUk5CgN95YyQ2J4kbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRk4Al/dJMcacqjEWz/u7E5EUk5CgN95YyQ2J4kbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRk4Al/dJMcacqjEWz/u7E5EUk5CgN95YyQ2J4kbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRk4Al%2FdJMcacqjEWz%2Fu7E5EUk5CgN95YyQ2J4kbk%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;2816&quot; height=&quot;1536&quot; data-filename=&quot;Gemini_Generated_Image_eoh4uzeoh4uzeoh4.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size20&quot;&gt;MVC (Model - View - Controller)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;가장 기본이 되는 패턴. 손님(사용자)이 컨트롤러에게 주문(입력)을 하면, 컨트롤러가 주방(모델)에 요리를 지시하고, 주방에서 요리가 완성되면 테이블(뷰)이 주방의 상태를 직접 확인해서 화면을 갱신한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;특징:&lt;/b&gt; 자바의 '스프링(Spring Web MVC)'이 이 패턴을 기반으로 설계되었다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;문제점:&lt;/b&gt; 뷰와 모델이 서로 쳐다보고 있어(의존성이 있어), 프로젝트가 커지면 컨트롤러가 비대해지고 코드가 스파게티처럼 꼬이기 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size20&quot;&gt;MVP (Model - View - Presenter)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;MVC의 문제를 해결하기 위해 등장. 이제 테이블(뷰)과 주방(모델)은 철저히 남남이 된다. 모든 소통은 오직 홀 매니저(Presenter)를 통해서만 이루어진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;특징:&lt;/b&gt; 뷰와 모델 간의 결합도가 완전히 사라져서 코드를 테스트하기가 매우 좋아진다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;문제점:&lt;/b&gt; 뷰와 프레젠터가 1:1로 강하게 짝지어지기 때문에, 화면이 많아질수록 프레젠터 클래스도 많아지고 결합도도 높아진다는 단점이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size20&quot;&gt;MVVM (Model - View - ViewModel)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;MVP에서 한 단계 더 진화한 형태. 매니저(ViewModel)가 주방(모델)에서 가져온 데이터를 '진열장'에 올려두면, 테이블(뷰)이 진열장을 '구독(Data Binding)'하고 있다가 데이터가 바뀌는 순간 스스로 화면을 갱신한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;특징:&lt;/b&gt; Vue.js나 React 같은 현대 프론트엔드 프레임워크의 핵심 사상이다. 뷰가 뷰모델을 쳐다볼 뿐, 뷰모델은 뷰가 누군지 신경 쓰지 않아도 되므로 코드가 매우 독립적이고 유연해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;마무리: 수많은 패턴, 결국 실무에서는 뭘 제일 많이 쓸까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 여러 가지 디자인 패턴을 살펴보았는데 처음 보면 &quot;이걸 언제 다 외우고 언제 다 쓰지?&quot; 싶을 수 있습니다. 결론부터 말씀드리면, 이 모든 패턴의 구조를 달달 외울 필요는 전혀 없다고 합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연한 얘기겠지만, 자바 기반의 스프링 생태계를 다룬다면 그래도 아래의 세가지 패턴 만큼은 그 원리를 꼭 기억해두는게 좋을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;싱글톤(Singleton) 패턴:&lt;/b&gt; 오늘날 우리가 직접 getInstance()를 만들 일은 거의 없습니다. 하지만 스프링이 객체(Bean)를 관리하는 기본 방식이 바로 싱글톤입니다. 스프링 컨테이너가 알아서 딱 하나의 객체만 만들어 메모리를 효율적으로 관리해 주고 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;프록시(Proxy) 패턴:&lt;/b&gt; 스프링의 핵심인 AOP(관점 지향 프로그래밍)나 @Transactional 같은 마법 같은 어노테이션들이 모두 프록시 패턴을 기반으로 돌아갑니다. 진짜 핵심 로직을 건드리지 않고, 대리인이 앞뒤로 부가 기능(로그, 트랜잭션 등)을 붙여주는 역할을 프레임워크가 대신해 주는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;전략(Strategy) 패턴:&lt;/b&gt; 소셜 로그인이나 결제 수단을 갈아 끼워야 할 때 가장 많이 쓰입니다. 또한 스프링 시큐리티(Spring Security)의 인증 아키텍처 자체가 이 전략 패턴의 집합체라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인 패턴은 무조건적인 정답이 아니라 오답노트 느낌이 강한것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작정 패턴의 틀에 코드를 억지로 끼워 맞추기보다는 현재 내가 어떤 문제를 해결하려고 하는가? 에 대한 고민이 먼저인거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스터디</category>
      <author>noobdev25</author>
      <guid isPermaLink="true">https://noobdev25.tistory.com/61</guid>
      <comments>https://noobdev25.tistory.com/61#entry61comment</comments>
      <pubDate>Sun, 12 Jul 2026 04:08:00 +0900</pubDate>
    </item>
  </channel>
</rss>