<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>맨땅에 코딩</title>
    <link>https://coding-to-the-ground.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 21 Aug 2026 06:36:12 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jiubuntu</managingEditor>
    <item>
      <title>네트워크 - 쿠키(Cookie)</title>
      <link>https://coding-to-the-ground.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 무상태 프로토콜이라 서버가 클라이언트 상태를 기억하지 못한다고 앞서 정리했었는데, 이 문제를 실무에서 가장 널리 해결하는 방법이 바로 쿠키입니다. 이에 대한 내용을 정리해보려고 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Set-Cookie와 Cookie&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Set-Cookie&lt;/b&gt;: 서버 &amp;rarr; 클라이언트로 쿠키를 전달(응답 헤더)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Cookie&lt;/b&gt;: 클라이언트가 서버로부터 받은 쿠키를 저장해두었다가, 이후 요청 시 서버로 함께 전달(요청 헤더)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 쿠키의 사용처와 주의점&lt;/b&gt;&lt;/h2&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;&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;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠키 정보는 요청할 때마다 &lt;b&gt;항상 서버로 전송&lt;/b&gt;되기 때문에, 쿠키에 담는 데이터가 많아질수록 네트워크 트래픽이 늘어나기 때문에 세션 id, 인증 토큰 정도의 &lt;b&gt;최소한의 정보만&lt;/b&gt; 담는 것이 좋음&lt;/li&gt;
&lt;li&gt;서버에 전송할 필요 없이 브라우저 내부에만 데이터를 저장하고 싶다면 쿠키 대신 &lt;b&gt;웹 스토리지(localStorage, sessionStorage)&lt;/b&gt; 사용 고려&lt;/li&gt;
&lt;li&gt;주민번호, 신용카드 번호 같은 &lt;span style=&quot;color: #ee2323;&quot;&gt;보안에 민감한 데이터는 절대 저장 X.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 쿠키의 생명주기 - Expires, max-age&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 60px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;속성&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;expires&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;만료 날짜/시각을 직접 지정 (예: expires=Sat, 26-Dec-2020 04:39:21 GMT), 해당 시점이 되면 쿠키 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;max-age&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;초 단위로 유효 시간 지정 (예: max-age=3600 &amp;rarr; 3600초). 0이나 음수를 지정하면 즉시 쿠키 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값의 지정 여부에 따라 쿠키의 종류도 나뉩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;세션 쿠키&lt;/b&gt;: 만료 날짜를 생략하면 브라우저를 종료할 때까지만 유지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;영속 쿠키&lt;/b&gt;: 만료 날짜(expires/max-age)를 지정하면 해당 시점까지 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 쿠키의 유효 범위 - Domain, Path&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Domain&lt;/b&gt;은 쿠키가 어떤 도메인까지 접근 가능한지를 결정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;명시한 경우&lt;/b&gt; (domain=example.org): 지정한 도메인은 물론, dev.example.org 같은 서브 도메인에서도 쿠키 접근 가능&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;b&gt;Path&lt;/b&gt;는 쿠키가 적용되는 URL 경로 범위를 결정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지정한 경로와 그 하위 경로에서만 쿠키에 접근 가능&lt;/li&gt;
&lt;li&gt;일반적으로 path=/ (루트)로 지정해 모든 경로에서 사용 가능하게 함&lt;/li&gt;
&lt;li&gt;예를 들어 path=/home으로 지정하면:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/home, /home/level1, /home/level1/level2 &amp;rarr; 접근 가능&lt;/li&gt;
&lt;li&gt;/hello &amp;rarr; 접근 불가능 (경로가 겹치지 않음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 쿠키 보안 - Secure, HttpOnly, SameSite&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 80px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 11.2791%; height: 20px;&quot;&gt;속성&lt;/td&gt;
&lt;td style=&quot;width: 88.6047%; height: 20px;&quot;&gt;동작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 11.2791%; height: 20px;&quot;&gt;Secure&lt;/td&gt;
&lt;td style=&quot;width: 88.6047%; height: 20px;&quot;&gt;기본적으로 쿠키는 http/https를 구분하지 않고 전송되지만, Secure를 적용하면 &lt;b&gt;https인 경우에만&lt;/b&gt; 전송됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 11.2791%; height: 20px;&quot;&gt;HttpOnly&lt;/td&gt;
&lt;td style=&quot;width: 88.6047%; height: 20px;&quot;&gt;자바스크립트(document.cookie)로 쿠키에 접근하지 못하게 막음. HTTP 전송에만 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 11.2791%; height: 20px;&quot;&gt;SameSite&lt;/td&gt;
&lt;td style=&quot;width: 88.6047%; height: 20px;&quot;&gt;요청을 보낸 도메인과 쿠키에 설정된 도메인이 같은 경우에만 쿠키를 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠키는 무상태인 HTTP에서 로그인 세션 같은 상태를 유지하기 위한 대표적인 수단입니다. 다만 요청마다 자동으로 서버에 전송된다는 특성 때문에 트래픽과 보안 이슈가 함께 따라오므로, &lt;b&gt;최소한의 정보만 담고, Domain/Path로 범위를 제한하고, Secure/HttpOnly/SameSite로 보안을 강화&lt;/b&gt;하는 것을 권장합니다.&lt;/p&gt;</description>
      <category>네트워크</category>
      <category>cookie</category>
      <category>네트워크</category>
      <category>쿠키</category>
      <author>jiubuntu</author>
      <guid isPermaLink="true">https://coding-to-the-ground.tistory.com/29</guid>
      <comments>https://coding-to-the-ground.tistory.com/29#entry29comment</comments>
      <pubDate>Mon, 20 Jul 2026 20:55:55 +0900</pubDate>
    </item>
    <item>
      <title>네트워크 - HTTP 헤더 정리</title>
      <link>https://coding-to-the-ground.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 헤더는 &lt;b&gt;HTTP 메시지를 전송하는 데 필요한 모든 부가 정보&lt;/b&gt;를 담습니다. 어떤 형식의 데이터를 보내는지, 클라이언트가 어떤 응답을 선호하는지, 인증은 어떻게 할지 등이 전부 헤더에 담깁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 헤더 분류&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 헤더는 적용 범위에 따라 크게 4가지로 분류됩니다.&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&gt;분류&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General 헤더&lt;/td&gt;
&lt;td&gt;메시지 전체에 적용되는 정보&lt;/td&gt;
&lt;td&gt;Connection: close&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request 헤더&lt;/td&gt;
&lt;td&gt;요청에 대한 정보&lt;/td&gt;
&lt;td&gt;User-Agent: Mozilla/5.0 (Macintosh; ..)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response 헤더&lt;/td&gt;
&lt;td&gt;응답에 대한 정보&lt;/td&gt;
&lt;td&gt;Server: Apache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Representation 헤더&lt;/td&gt;
&lt;td&gt;표현 데이터 바디에 대한 정보&lt;/td&gt;
&lt;td&gt;Content-Type: text/html, Content-Length: 3423&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 표현(Representation) 헤더&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표현 헤더는 &lt;b&gt;주고받는 실제 데이터(바디)의 속성&lt;/b&gt;을 설명하는 헤더입니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Content-Type&lt;/b&gt;: 표현 데이터의 형식 (예: text/html, application/json)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Content-Encoding&lt;/b&gt;: 표현 데이터의 압축 방식. 데이터를 전송하는 쪽에서 압축을 수행한 뒤 이 헤더를 추가해서, 받는 쪽이 어떤 방식으로 압축이 되어있는지 알고 압축을 해제할 수 있게 해줌.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Content-Language&lt;/b&gt;: 표현 데이터가 어떤 자연 언어(한국어, 영어 등)로 작성되었는지 표현&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Content-Length&lt;/b&gt;: 표현 데이터의 길이(바이트 수)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 콘텐츠 협상 (Content Negotiation)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘텐츠 협상은 &lt;b&gt;클라이언트가 서버에게 자신이 선호하는 표현 방식을 요청&lt;/b&gt;하는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Accept&lt;/b&gt;: 클라이언트가 선호하는 미디어 타입 전달&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Accept-Charset&lt;/b&gt;: 클라이언트가 선호하는 문자 인코딩 전달&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Accept-Encoding&lt;/b&gt;: 클라이언트가 선호하는 압축 인코딩 전달&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Accept-Language&lt;/b&gt;: 클라이언트가 선호하는 자연 언어 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;협상의 우선순위: Quality Values(q)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 여러 값을 나열할 때는 &lt;b&gt;Quality Values(q)&lt;/b&gt; 값으로 우선순위를 표현합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;q 값은 0~1 사이이며, &lt;b&gt;값이 클수록 우선순위가 높음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;q 값을 생략하면 기본값은 &lt;b&gt;1&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 Accept-Language 헤더가 있다면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
&lt;/code&gt;&lt;/pre&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&gt;순위&lt;/td&gt;
&lt;td&gt;값&lt;/td&gt;
&lt;td&gt;비고&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;ko-KR&lt;/td&gt;
&lt;td&gt;q 생략 &amp;rarr; 기본값 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;ko&lt;/td&gt;
&lt;td&gt;q=0.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;q=0.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;en&lt;/td&gt;
&lt;td&gt;q=0.7&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 전송 방식 4가지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 상황에 따라 데이터를 다른 방식으로 전송할 수 있습니다.&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&gt;전송방식&lt;/td&gt;
&lt;td&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단순 전송&lt;/td&gt;
&lt;td&gt;Content-Length를 알고 있을 때 한 번에 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;압축 전송&lt;/td&gt;
&lt;td&gt;Content-Encoding으로 압축 방식을 명시하고 압축해서 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;분할 전송&lt;/td&gt;
&lt;td&gt;Transfer-Encoding: chunked를 사용해 데이터를 여러 조각으로 나누어 전송&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;범위 전송&lt;/td&gt;
&lt;td&gt;클라이언트가 원하는 범위(Range)만 지정해서 요청/응답&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 일반 정보 헤더&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Referer&lt;/b&gt;: 이전 웹페이지의 주소가 담기는 헤더로, 주로 유입 경로 분석에 활용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;User-Agent&lt;/b&gt;: 클라이언트의 애플리케이션(웹 브라우저 등) 정보를 담음. 어떤 브라우저 환경에서 장애가 발생하는지 로그로 파악할 때 유용함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Server&lt;/b&gt;: 요청을 처리하는 origin 서버의 소프트웨어 정보를 담음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Date&lt;/b&gt;: 메시지가 생성된 날짜와 시각을 담음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Host&lt;/b&gt;: 요청한 호스트(도메인) 정보를 담으며, 요청 시 필수값&lt;b&gt;.&lt;/b&gt; &lt;span style=&quot;color: #ee2323;&quot;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 인증 헤더&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Authorization&lt;/b&gt;: 클라이언트의 인증 정보를 서버에 전달하는 헤더&lt;/li&gt;
&lt;li&gt;&lt;b&gt;WWW-Authenticate&lt;/b&gt;: 리소스에 접근하기 위해 필요한 인증 방법을 정의하는 헤더 (서버가 401 응답과 함께 내려줌)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>네트워크</category>
      <category>http</category>
      <category>HTTP Header</category>
      <category>네트워크</category>
      <author>jiubuntu</author>
      <guid isPermaLink="true">https://coding-to-the-ground.tistory.com/28</guid>
      <comments>https://coding-to-the-ground.tistory.com/28#entry28comment</comments>
      <pubDate>Mon, 20 Jul 2026 20:45:10 +0900</pubDate>
    </item>
    <item>
      <title>네트워크 - HTTP의 특징과 속성</title>
      <link>https://coding-to-the-ground.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP의 기본 구조와 특징, 그리고 HTTP 메서드가 갖는 세 가지 속성(안전, 멱등성, 캐시 가능)을 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. HTTP는 클라이언트-서버 구조&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP(HyperText Transfer Protocol)는 기본적으로 &lt;b&gt;클라이언트가 요청을 보내고, 서버가 응답을 돌려주는&lt;/b&gt; 단방향 요청-응답 구조로 동작합니다. 브라우저(클라이언트)가 서버에 요청하면, 서버는 그에 맞는 데이터를 응답으로 돌려주는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. HTTP의 두 가지 핵심 특징&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무상태 프로토콜 (Stateless)&lt;/h3&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;: 온라인 쇼핑몰에서 로그인을 한 뒤 상품 목록 페이지로 이동했다고 가정해봅시다. 만약 HTTP가 완전히 무상태라면, 서버는 상품 목록을 요청받는 순간 이 사람이 아까 로그인한 사람이라는 사실을 전혀 알지 못합니다. 그래서 매 요청마다 로그인 여부를 다시 증명해야 하는데, 이를 해결하기 위해 실무에서는 쿠키나 세션, 토큰(JWT) 같은 별도의 방법으로 클라이언트 상태를 서버 혹은 클라이언트 쪽에 저장해서 활용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무상태의 장점은 서버가 클라이언트 상태를 신경 쓰지 않아도 되기 때문에 서버를 자유롭게 늘리거나 줄이는 확장(스케일 아웃)이 쉬워진다는 점입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비연결성 (Connectionless)&lt;/h3&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;: 하나의 웹페이지를 열 때 HTML 문서, CSS 파일, 이미지 여러 장 등 수십 개의 리소스를 요청해야 합니다. 원칙적으로 비연결성을 따르면 이 리소스 하나하나를 요청할 때마다 매번 새로 연결을 맺고, 응답을 받은 뒤 바로 연결을 끊는 것을 반복해야 합니다. 이렇게 되면 매번 TCP 연결(3-way handshake)을 새로 맺어야 하니 자원 낭비가 심해지는데, 이 문제를 완화하기 위해 실제로는 HTTP의 &lt;b&gt;Keep-Alive&lt;/b&gt; 같은 기법으로 일정 시간 동안 연결을 유지하며 여러 요청을 처리하도록 보완하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. HTTP 메서드가 갖는 세 가지 속성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 메서드(GET, POST, PUT, PATCH, DELETE 등)는 각각 &lt;b&gt;안전(Safe)&lt;/b&gt;, &lt;b&gt;멱등(Idempotent)&lt;/b&gt;, &lt;b&gt;캐시 가능(Cacheable)&lt;/b&gt; 이라는 세 가지 속성을 기준으로 구분할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;안전(Safe)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 보내도 서버의 상태(데이터)를 변경하지 않는 성질을 말합니다. 즉, 조회만 할 뿐 아무것도 바꾸지 않는다는 뜻입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET, HEAD &amp;rarr; 안전함 (단순 조회이므로 리소스를 변경하지 않음)&lt;/li&gt;
&lt;li&gt;POST, PUT, PATCH, DELETE &amp;rarr; 안전하지 않음 (리소스를 생성/수정/삭제하므로 상태가 변경됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멱등(Idempotent)&lt;/h3&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET, PUT, DELETE &amp;rarr; 멱등함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET: 여러 번 조회해도 결과는 동일&lt;/li&gt;
&lt;li&gt;PUT: 특정 리소스를 통째로 같은 값으로 덮어쓰는 것이므로, 여러 번 반복해도 최종 상태는 같음&lt;/li&gt;
&lt;li&gt;DELETE: 같은 리소스를 여러 번 삭제 요청해도 결과적으로 리소스가 없는 상태는 동일&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;POST &amp;rarr; 멱등하지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 게시글을 작성하는 POST 요청을 두 번 보내면 게시글이 두 개 생성되므로, 요청할 때마다 결과(서버 상태)가 달라짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐시 가능(Cacheable)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 결과를 캐시(임시 저장)해서 재사용할 수 있는지를 나타내는 속성입니다. 캐시가 가능하면 매번 서버에 요청을 보내지 않고 저장해둔 응답을 재활용할 수 있어 성능상 이점이 큽니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주로 &lt;b&gt;GET, HEAD&lt;/b&gt; 메서드에 캐시가 사용. 조회 요청의 응답은 자주 재사용될 가능성이 높기 때문임&lt;/li&gt;
&lt;li&gt;POST, PATCH도 스펙상으로는 캐시가 가능하도록 정의되어 있지만, 응답 내용이 매번 달라지는 경우가 많고 실제로 이를 지원하는 형태로 구현하는 경우는 많지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 정리&lt;/b&gt;&lt;/h2&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&gt;메서드&lt;/td&gt;
&lt;td&gt;안전&lt;/td&gt;
&lt;td&gt;멱등&lt;/td&gt;
&lt;td&gt;캐시가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HEAD&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;조건부(실무에서는 거의 사용 안 함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATCH&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;조건부(실무에서는 거의 사용 안 함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&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;</description>
      <category>네트워크</category>
      <category>http</category>
      <category>네트워크</category>
      <author>jiubuntu</author>
      <guid isPermaLink="true">https://coding-to-the-ground.tistory.com/27</guid>
      <comments>https://coding-to-the-ground.tistory.com/27#entry27comment</comments>
      <pubDate>Mon, 20 Jul 2026 20:28:44 +0900</pubDate>
    </item>
    <item>
      <title>네트워크 - IP(Internet Protocol) 그리고 TCP와 UDP</title>
      <link>https://coding-to-the-ground.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;IP(인터넷 프로토콜)한계와, 그 한계를 TCP가 어떻게 보완하는지, 그리고 UDP는 왜 다른 선택을 했는지 정리해보려고 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 인터넷 프로토콜 스택은 4계층으로 이루어져 있다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 인터넷으로 데이터를 주고받을 때, 실제로는 여러 계층의 프로토콜이 순서대로 데이터를 감싸고 벗기며 동작하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜 4계층을 표로 정리해보자면 다음과 같습니다.&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&gt;계층&lt;/td&gt;
&lt;td&gt;이름&lt;/td&gt;
&lt;td&gt;프로토콜/예시&lt;/td&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;애플리케이션 계층&lt;/td&gt;
&lt;td&gt;HTTP, FTP, DNS&lt;/td&gt;
&lt;td&gt;실제 우리가 사용하는 서비스 데이터(웹 문서 등)를 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;전송 계층&lt;/td&gt;
&lt;td&gt;TCP, UDP&lt;/td&gt;
&lt;td&gt;포트를 이용해 프로세스 간 데이터 전달, 신뢰성 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;인터넷 계층&lt;/td&gt;
&lt;td&gt;IP&lt;/td&gt;
&lt;td&gt;목적지 IP 주소까지 패킷을 전달(라우팅)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;네트워크 인터페이스 계층&lt;/td&gt;
&lt;td&gt;이더넷, LAN 드라이버, 물리 장치&lt;/td&gt;
&lt;td&gt;실제 케이블/신호로 데이터를 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 보낼 때는 4계층 &amp;rarr; 1계층 순으로 각 계층의 헤더가 씌워지고(캡슐화), 받는 쪽에서는 1계층 &amp;rarr; 4계층 순으로 헤더를 하나씩 벗겨내며(역캡슐화) 원래 데이터를 복원하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. IP(인터넷 프로토콜)만으로는 부족하다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷 계층의 IP는 패킷을 목적지까지 전달하는 역할을 하지만, 딱 여기까지만 책임집니다. IP 패킷에는 출발지/목적지 IP 주소 정보만 있을 뿐, 다음과 같은 정보가 빠져 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;포트 정보 없음&lt;/b&gt;: 같은 컴퓨터 안에서 어떤 프로세스(애플리케이션)에게 데이터를 전달해야 하는지 알 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순서 정보 없음&lt;/b&gt;: 패킷을 여러 개로 나눠 보내도, 도착한 순서가 보낸 순서와 같다는 보장이 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전송 신뢰성 없음&lt;/b&gt;: 패킷이 중간에 소실되거나 유실되어도 IP 자체는 이를 감지하거나 재전송하지 않다.&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. TCP: 가상의 연결로 신뢰성을 보장한다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP(Transmission Control Protocol)는 IP가 채우지 못한 빈틈을 메워주는 전송 계층 프로토콜입니다. TCP 세그먼트에는 다음 정보가 담겨 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;출발지 포트 / 목적지 포트&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전송 제어 정보(플래그, 윈도우 크기 등)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순서(Sequence) 정보&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세그먼트가 다시 IP 패킷 안에 담겨서 전송되기 때문에, 최종적으로는 &lt;b&gt;IP 패킷 안에 TCP 세그먼트가 들어있고, 그 안에 실제 데이터가 들어있는 구조&lt;/b&gt;가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP의 대표적인 특징 중 하나가 &lt;b&gt;3-way handshake &lt;/b&gt;인데, 실제로 물리적인 회선이 연결되는 것은 아니지만, 통신을 시작하기 전에 서로 연결할 준비가 되었는지 확인하는 가상의 연결 과정입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-way handshake 과정&lt;/h3&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&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;단계&lt;/td&gt;
&lt;td&gt;플래그&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;클라이언트 &amp;rarr; 서버&lt;/td&gt;
&lt;td&gt;SYN&lt;/td&gt;
&lt;td&gt;클라이언트가 연결을 요청하며 SYN(동기화) 패킷 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;서버 &amp;rarr; 클라이언트&lt;/td&gt;
&lt;td&gt;SYN&amp;nbsp; ACK&lt;/td&gt;
&lt;td&gt;서버가 요청을 수락하고, 자신도 SYN을 보내며 동시에 클라이언트의 SYN에 대한 ACK(응답) 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;클라이언트 &amp;rarr; 서버&lt;/td&gt;
&lt;td&gt;ACK&lt;/td&gt;
&lt;td&gt;클라이언트가 서버의 SYN에 대한 ACK를 전송하며 연결 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 양쪽 모두 상대방이 내 요청을 받았고, 통신할 준비가 되었다는 것을 서로 확인한 뒤에 데이터 전송을 시작합니다. 이 덕분에 TCP는 순서 보장과 데이터 전달 신뢰성을 확보할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. UDP: 연결 없이, 단순하고 빠르게&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UDP(User Datagram Protocol)는 TCP와 정반대의 선택을 한 프로토콜입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3-way handshake 같은 연결 과정이 없음&lt;/li&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;이렇게만 보면 단점처럼 보이지만, 사실 이것이 UDP의 핵심 장점이기도 합니다. 가상 연결 과정 자체가 없다 보니 구조가 단순하고, 그만큼 속도가 빠릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UDP는 IP와 거의 비슷한 수준의 기능만 제공하는데, IP에 없는 다음 두 가지 정보만 추가로 가지고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;포트 정보&lt;/b&gt;: 어떤 프로세스로 전달할지 구분 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;체크섬(checksum)&lt;/b&gt;: 데이터가 훼손되지 않았는지 검증하는 최소한의 무결성 체크&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. TCP vs UDP, 언제 무엇을 쓸까?&lt;/b&gt;&lt;/h2&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&gt;구분&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;연결 방식&lt;/td&gt;
&lt;td&gt;3-way handshake(가상 연결)&lt;/td&gt;
&lt;td&gt;연결 과정 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;순서 보장&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전송 신뢰성&lt;/td&gt;
&lt;td&gt;O (재전송, 흐름/혼잡 제어)&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;속도&lt;/td&gt;
&lt;td&gt;상대적으로 느림&lt;/td&gt;
&lt;td&gt;상대적으로 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대표 활용&lt;/td&gt;
&lt;td&gt;웹(HTTP), 파일 전송, 이메일&lt;/td&gt;
&lt;td&gt;실시간 스트리밍, 온라인 게임, DNS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면, IP는 일단 목적지로 보내기만 담당하는 최소한의 프로토콜이고, 그 위에서 TCP는 신뢰성과 순서를, UDP는 속도와 단순함을 각각 선택한 전송 계층 프로토콜이라고 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>네트워크</category>
      <category>http</category>
      <category>IP</category>
      <category>TCP</category>
      <category>UDP</category>
      <category>네트워크</category>
      <author>jiubuntu</author>
      <guid isPermaLink="true">https://coding-to-the-ground.tistory.com/26</guid>
      <comments>https://coding-to-the-ground.tistory.com/26#entry26comment</comments>
      <pubDate>Mon, 20 Jul 2026 20:18:46 +0900</pubDate>
    </item>
    <item>
      <title>Java - 돈 계산에 BigDecimal을 써야 하는 이유</title>
      <link>https://coding-to-the-ground.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하면서 돈과 관련된 필드는 BigDecimal을 사용했었는데 ,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 double로 돈계산을 할 때 왜 위험한지, BigDecimal이 정확히 어떤 문제를 해결해주는지, 그리고 BigDecimal을 사용할때 마주칠 수 있는 주의점을 코드로 직접 확인하며 정리해보려고 합니다.&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. double의 함정: 2진법은 10진 소수를 정확히 표현하지 못한다&lt;/h3&gt;
&lt;pre id=&quot;code_1782696937546&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;double a = 0.1;
double b = 0.2;
System.out.println(a + b);
// 결과: 0.30000000000000004&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;double은 IEEE 754 이진 부동소수점 방식으로 숫자를 저장합니다. 컴퓨터는 모든 실수를 2의 거듭제곱의 합으로 표현하는데, 0.1처럼 흔한 10진 소수도 2진법으로는 무한소수가 되어버립니다. 10진법에서 1/3이 0.333...으로 끝나지 않는 것과 같은 원리입니다. 결국 컴퓨터는 이 무한소수를 어느 지점에서 끊어서 근사값으로 저장하고, 그 결과 아주 작은 오차가 생깁니다.&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;pre id=&quot;code_1782697006450&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;double sum = 0;
for (int i = 0; i &amp;lt; 10; i++) {
    sum += 0.1;
}
System.out.println(sum);
// 결과: 0.9999999999999999&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;middot;정산 시스템에서, 이런 오차는 &quot;장부가 1원 안 맞는&quot; 형태의 실제 버그로 이어질 수 있습니다.&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;2. BigDecimal을 쓴다고 끝이 아니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의할 점은&amp;nbsp;&lt;b&gt;BigDecimal을 쓰는 것 자체가 중요한 게 아니라, 어떻게 생성하느냐가 중요합니다.&lt;/b&gt; &lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782697126905&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BigDecimal wrong = new BigDecimal(0.1);
System.out.println(wrong);
// 결과: 0.1000000000000000055511151231257827021181583404541015625

BigDecimal right1 = new BigDecimal(&quot;0.1&quot;);
BigDecimal right2 = BigDecimal.valueOf(0.1);
System.out.println(right1); // 0.1
System.out.println(right2); // 0.1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new BigDecimal(double) 생성자에 double 리터럴을 그대로 넘기면, 이미 부동소수점으로 깨져버린 값을 그대로 가져와서 BigDecimal로 감싸는 것뿐입니다. BigDecimal은 오차를 자동으로 없애주는 객체가 아니라, &lt;b&gt;10진수를 정확하게 표현할 수 있는 자료구조&lt;/b&gt;일 뿐입니다. 이미 깨진 값을 넣으면 깨진 값 그대로 보존됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;46:1-46:18;1562-1579&quot; data-ke-size=&quot;size16&quot;&gt;그래서 사용규칙은 명확합니다.&lt;/p&gt;
&lt;blockquote data-sourcepos=&quot;48:1-48:162;1581-1742&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-sourcepos=&quot;48:3-48:162;1583-1742&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;돈 관련 값은 항상 String으로 받아 new BigDecimal(&quot;19.99&quot;)로 생성하거나, double에서 변환해야 한다면 BigDecimal.valueOf(double)을 사용해야 합니다. new BigDecimal(double)은 절대 쓰지 않아야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. BigDecimal이 정확한 이유: 내부 구조 자체가 다르다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;double이 2의 거듭제곱 분수로 값을 표현하는 반면, BigDecimal은 내부적으로&lt;b&gt; BigInteger unscaledValue와 int scale&lt;/b&gt; 두 값으로 숫자를 표현합니다. 예를 들어 19.99는 unscaledValue = 1999, scale = 2로 저장되어 1999 &amp;times; 10⁻&amp;sup2;를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10진수 기반이기 때문에 10진 소수를 손실 없이 정확히 표현할 수 있어서, 2진법으로 변환하는 과정 자체가 없으니, 애초에 오차가 생길 여지가 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782701440482&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BigDecimal p = new BigDecimal(&quot;19.99&quot;);
BigDecimal q = new BigDecimal(&quot;3&quot;);
System.out.println(p.multiply(q));
// 결과: 59.97&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. equals() 주의 : 값이 같아도 false가 나올 수 있다&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;BigDecimal x = new BigDecimal(&quot;1.0&quot;);
BigDecimal y = new BigDecimal(&quot;1.00&quot;);

System.out.println(x.equals(y));     // false
System.out.println(x.compareTo(y) == 0); // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal.equals()는 값뿐 아니라 &lt;b&gt;scale(소수점 자릿수)까지&lt;/b&gt; 같아야 true를 반환합니다. 1.0과 1.00은 수학적으로 같은 값이지만, equals() 기준으로는 다르다고 판단합니다.&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;5. 나눗셈은 반올림 정책을 강제로 명시해야 한다&lt;/h3&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;BigDecimal total = new BigDecimal(&quot;100&quot;);
BigDecimal parts = new BigDecimal(&quot;3&quot;);

total.divide(parts);
// ArithmeticException: Non-terminating decimal expansion;
// no exact representable decimal result.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100을 3으로 나누면 33.333...처럼 무한소수가 됩니다. BigDecimal은 이를 정확히 표현할 수 없으면 &lt;b&gt;예외를 던져서, 반올림 방식을 직접 선택하도록 강제합니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;total.divide(parts, 2, RoundingMode.HALF_UP);
// 33.33
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 double과 가장 본질적으로 다른 지점입니다. double은 그냥 알아서 (잘린) 근사값을 내놓고 끝나지만, BigDecimal은 어떤 방식으로 반올림할지를 코드에서 명시적으로 선택하게 만듭니다.&lt;/p&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;middot;포인트 적립&amp;middot;환율 변환처럼 &lt;b&gt;법적&amp;middot;회계적으로 정해져 있는 경우가 많기 때문&lt;/b&gt;입니다. 같은 코드베이스 안에서도 계산 종류마다 다른 반올림 규칙이 필요할 수 있는데, 이를 암묵적으로 처리하면 나중에 정산 불일치가 생겼을 때 어디서 얼마가 어떻게 반올림됐는지 추적이 거의 불가능해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BigDecimal을 쓰는 이유는 크게 두 가지&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;부동소수점 오차 방지&lt;/b&gt; : double은 2진법 한계로 10진 소수를 정확히 표현하지 못하지만, BigDecimal은 10진수 기반이라 정확&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반올림 정책의 명시적 통제&lt;/b&gt; : 나눗셈처럼 무한소수가 나오는 연산에서 반올림 방식을 코드 차원에서 강제로 선택하게 만들어, 암묵적 반올림으로 인한 회계 불일치 방지&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>언어/Java</category>
      <category>bigdecimal</category>
      <category>java</category>
      <author>jiubuntu</author>
      <guid isPermaLink="true">https://coding-to-the-ground.tistory.com/25</guid>
      <comments>https://coding-to-the-ground.tistory.com/25#entry25comment</comments>
      <pubDate>Mon, 29 Jun 2026 11:58:59 +0900</pubDate>
    </item>
    <item>
      <title>JPA 연관관계 매핑 - @OneToOne</title>
      <link>https://coding-to-the-ground.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;JPA를 사용하는 프로젝트를 진행하면서 @OneToOne 연관관계 매핑을 하는 경우가 종종 발생했는데,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다대일이나 일대다 관계보다 고려할 것이 많아&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;엔티티를 설계에 어려움을 겪으며 얻은 지식과 경험을 작성해 보려고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;상품, 그리고 상품에 대한 재고 엔티티가 있다고 가정을 해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765897278790&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;product_id&quot;)
    private Long id;
    
    private String name;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1765897341557&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Stock {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;stock_id&quot;)
    private Long id;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 2개의 엔티티가 있을때&lt;br /&gt;1대1 연관관계를 매핑하기 위해서는 &lt;b&gt;주 테이블과 대상 테이블을 식별해야 합니다.&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;주 테이블과 대상 테이블을 나누는 기준은 &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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;재고는 상품이 없으면 존재할 이유가 없고,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상품은 재고가 없어도 존재할 이유가 있기 때문에&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;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;외래키를 어느 테이블이 관리할 것인가에 대한 고민을 하게 됩니다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주 테이블이 외래키를 관리 (단방향)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1766926556022&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;product_id&quot;)
    private Long id;

    private String name;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;stock_id&quot;)
    private Stock stock;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인할 수 있다.&amp;nbsp;&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;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 값이 없으면 외래키에 null을 허용한다.&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;pre id=&quot;code_1766927353833&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;product_id&quot;)
    private Long id;

    private String name;

    @OneToOne(mappedBy = &quot;product&quot;)
    private Stock stock;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766927376677&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Stock {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;stock_id&quot;)
    private Long id;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;product_id&quot;)
    private Product product;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&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;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 지연로딩이 불가능하고 &lt;b&gt;항상 즉시 로딩된다&lt;/b&gt;. (주 테이블을 조회할 때 ID값을 모르기 때문)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 대상테이블에 외래키 단방향 관계는 JPA에서 지원하지 않기 때문에 항상 양방향 매핑을 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정리해볼 수 있는데,&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 &amp;amp; 프레임워크/Spring &amp;amp; SpringBoot</category>
      <category>java</category>
      <category>jpa</category>
      <category>springboot</category>
      <author>jiubuntu</author>
      <guid isPermaLink="true">https://coding-to-the-ground.tistory.com/23</guid>
      <comments>https://coding-to-the-ground.tistory.com/23#entry23comment</comments>
      <pubDate>Sun, 28 Dec 2025 22:13:54 +0900</pubDate>
    </item>
    <item>
      <title>SpringBoot - QueryDSL 환경세팅</title>
      <link>https://coding-to-the-ground.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현재 사용하는 SpringBoot 버전 3.3.5 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QueryDSL 5.0.0 버전부터는 javax가 아닌 jakarta Persistence 기반이기 때문에&lt;br /&gt;jakarta EE 기반인 3.x.x 버전과 호환이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 5.0.0 버전으로 세팅했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1749270105456&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'

annotationProcessor &quot;com.querydsl:querydsl-apt:5.0.0:jakarta&quot;
annotationProcessor &quot;jakarta.annotation:jakarta.annotation-api&quot;
annotationProcessor &quot;jakarta.persistence:jakarta.persistence-api&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle &amp;gt; dependencies에 이와같이 추가하면 된다.&lt;/p&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;추가하고나서 인텔리제이 우측상단 Gradle에서 build &amp;gt; clean 후에 build 하고나면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OBeq1/btsOsDaGqQT/TN3qz42Pa3JljpfXP2mxk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OBeq1/btsOsDaGqQT/TN3qz42Pa3JljpfXP2mxk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OBeq1/btsOsDaGqQT/TN3qz42Pa3JljpfXP2mxk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOBeq1%2FbtsOsDaGqQT%2FTN3qz42Pa3JljpfXP2mxk0%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;348&quot; height=&quot;379&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;379&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;build/generated 폴더에 Q클래스가 생성되는것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 build/generated 폴더에 생성되었기 때문에 SpringBoot에서 import를 하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 추가적인 설정이 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1749271923417&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def generated = 'src/main/generated'

// querydsl QClass 파일 생성 위치를 지정
tasks.withType(JavaCompile) {
	options.generatedSourceOutputDirectory = file(generated)
}


// java source Set 에 querydsl QClass 위치 추가
sourceSets {
	main.java.srcDirs += &quot;$projectDir/src/main/generated&quot;
}

// build clean 시에 QClass 디렉토리 삭제
clean {
	delete file(generated)
}&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;이렇게 설정해주면 /src/main/generated에 Q클래스들이 만들어지게되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동적으로 import 할 수 있게된다.&lt;/p&gt;</description>
      <category>서버 &amp;amp; 프레임워크/Spring &amp;amp; SpringBoot</category>
      <category>gradle</category>
      <category>querydsl</category>
      <category>querydsl 환경설정</category>
      <category>springboot</category>
      <author>jiubuntu</author>
      <guid isPermaLink="true">https://coding-to-the-ground.tistory.com/22</guid>
      <comments>https://coding-to-the-ground.tistory.com/22#entry22comment</comments>
      <pubDate>Sat, 7 Jun 2025 13:56:26 +0900</pubDate>
    </item>
    <item>
      <title>Factory method 'sqlSessionFactory' threw exception; nested exception is java.io.FileNotFoundException: class path resource [mapper/] cannot be resolved to URL because it does not exist 에러 해결</title>
      <link>https://coding-to-the-ground.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Factory method 'sqlSessionFactory' threw exception; nested exception is java.io.FileNotFoundException: class path resource [mapper/] cannot be resolved to URL because it does not exist &lt;br /&gt;&lt;br /&gt;Mybatis 환경세팅을하고 스프링부트를 실행시켰는데 해당 에러가 나면서 실행이 되지 않았다.&lt;br /&gt;파악해보니, Mybatis가 Mapper XML 파일을 읽어들이지못해 나는 에러였다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739841427242&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; 
    &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&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;XML 파일에 추가해주었더니 해결되었다.&lt;/p&gt;</description>
      <category>서버 &amp;amp; 프레임워크/Spring &amp;amp; SpringBoot</category>
      <category>mapper</category>
      <category>mybatis</category>
      <category>springboot</category>
      <category>XML</category>
      <author>jiubuntu</author>
      <guid isPermaLink="true">https://coding-to-the-ground.tistory.com/21</guid>
      <comments>https://coding-to-the-ground.tistory.com/21#entry21comment</comments>
      <pubDate>Tue, 18 Feb 2025 10:18:10 +0900</pubDate>
    </item>
    <item>
      <title>hikaripool-1 - failed to execute isvalid() for connection 에러 해결</title>
      <link>https://coding-to-the-ground.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;hikaripool-1&amp;nbsp;-&amp;nbsp;failed&amp;nbsp;to&amp;nbsp;execute&amp;nbsp;isvalid()&amp;nbsp;for&amp;nbsp;connection,&amp;nbsp;configure&amp;nbsp;connection&amp;nbsp;test&amp;nbsp;query&amp;nbsp;(receiver&amp;nbsp;class&amp;nbsp;co&lt;a href=&quot;http://m.ibm.as400.access.as400jdbcconnectionimpl&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://m.ibm.as400.access.as400jdbcconnectionimpl&lt;/a&gt; does not define or inherit an implementation of the resolved method 'abstract boolean isvalid(int)' of interface java.sql.connection.). &lt;br /&gt;현재 AS400 DB2를 사용하고있는데 Spring Boot 에서 Mybatis를 연동하는 중, 이 에러를 마주했다.&lt;br /&gt;&lt;br /&gt;에러를 분석해보니 hikariCP에서는 isValid()라는 함수를 사용해서 유효성 검사를 하고 DB와 커넥션을 맺는다고 한다.&lt;br /&gt;그런데 jt400 JDBC드라이버에는 isValid()함수가 구현이 안되어있는 것 같다. (jt400을 최신버전으로 바꿔도 안됨)&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;application.properties에&lt;/p&gt;
&lt;pre id=&quot;code_1739777973665&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;spring.datasource.hikari.connection-test-query: values 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 추가했더니 해결됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유효성 검사를 ' values 1 '이라는 쿼리를 통해서 하는 것이다.&lt;br /&gt;출처 : &lt;a href=&quot;https://stackoverflow.com/questions/68120283/unable-to-connect-ibmi-machine-with-spring-data-jpa&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/68120283/unable-to-connect-ibmi-machine-with-spring-data-jpa&lt;/a&gt;&lt;/p&gt;</description>
      <category>서버 &amp;amp; 프레임워크/Spring &amp;amp; SpringBoot</category>
      <category>AS400</category>
      <category>DB2</category>
      <category>error</category>
      <category>hikaricp</category>
      <category>jt400</category>
      <author>jiubuntu</author>
      <guid isPermaLink="true">https://coding-to-the-ground.tistory.com/20</guid>
      <comments>https://coding-to-the-ground.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 17 Feb 2025 16:44:29 +0900</pubDate>
    </item>
    <item>
      <title>AWS - Nginx에 React 프로젝트 빌드 후 배포하기</title>
      <link>https://coding-to-the-ground.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에 존재하는 React 프로젝트를 AWS EC2에 배포해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포하기전에 EC2에 Nginx가 미리 설치되어 있어야 하고, 보안그룹의 인바운드 규칙에 80번 포트가 추가되어 있어야 한다.&lt;/p&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. 내 로컬 PC에서 React 프로젝트 빌드&lt;/p&gt;
&lt;pre id=&quot;code_1739109975252&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. SSH로 AWS 접속 후, Nginx 기본 설정 비활성화&lt;/p&gt;
&lt;pre id=&quot;code_1739109950344&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo rm /etc/nginx/sites-enabled/default&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 로컬에 있는 build 폴더를 EC2 서버로 옮기기&lt;/p&gt;
&lt;pre id=&quot;code_1739110160206&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;scp -i [pem파일경로] -r build/ ubuntu@[AWS퍼블릭ip]:~/react-app
// 로컬의 build 폴더를 서버의 홈 디렉토리에 react-app 이라는 폴더로 만든다.&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;4. 로컬에서 가져온 폴더를 Nginx 루트로 이동&lt;/p&gt;
&lt;pre id=&quot;code_1739110230826&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo mv ~/react-app /var/www/react-app&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;5. 권한설정&lt;/p&gt;
&lt;pre id=&quot;code_1739110256901&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo chown -R www-data:www-data /var/www/react-app&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;6. Nginx 설정&lt;/p&gt;
&lt;pre id=&quot;code_1739110364742&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo vi /etc/nginx/sites-available/react-app&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어를 통해 vi 에디터로 설정파일을 새로 만드는 동시에 파일을 연다. (처음엔 sudo 명령어 붙여야 저장이 된다. 안그러면 계속 읽기모드로 켜짐)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739192862634&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    listen 80;
    listen [::]:80 default_server;
    server_name _;

    root /var/www/react-app;
    index index.html;

    location / {
            #try_files $uri /index.html;
            try_files $uri $uri/ =404;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이와같이 작성했다. ipv6 지원 하지않을거면 listen [::]:80 default_server 부분은 제외해도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 모드에서 :wq를 하고 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 설정 적용&lt;/p&gt;
&lt;pre id=&quot;code_1739193029934&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/react-app /etc/nginx/sites-enabled/
sudo nginx -t  # 설정 확인
sudo systemctl restart nginx&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;sudo &lt;span&gt;ln&lt;/span&gt; -s /etc/nginx/sites-available/react-app /etc/nginx/sites-enabled/ 명령어로 sites-available 폴더에 만든 설정 파일들을 sites-enabled 로 연결하는 심볼릭 링크를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 nginx를 restart하면 배포완료 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 &amp;amp; 프레임워크/Server</category>
      <category>AWS</category>
      <category>AWS ec2</category>
      <category>nginx</category>
      <category>react</category>
      <category>react 배포</category>
      <author>jiubuntu</author>
      <guid isPermaLink="true">https://coding-to-the-ground.tistory.com/19</guid>
      <comments>https://coding-to-the-ground.tistory.com/19#entry19comment</comments>
      <pubDate>Mon, 10 Feb 2025 22:13:22 +0900</pubDate>
    </item>
  </channel>
</rss>