<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: junghwan</title>
    <description>The latest articles on DEV Community by junghwan (@junghwan18).</description>
    <link>https://dev.to/junghwan18</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2930355%2F5e3b2065-0d31-4340-a4a8-f44465be2c1b.jpeg</url>
      <title>DEV Community: junghwan</title>
      <link>https://dev.to/junghwan18</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/junghwan18"/>
    <language>en</language>
    <item>
      <title>적당히 괜찮은 소프트웨어</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Sat, 15 Mar 2025 08:30:43 +0000</pubDate>
      <link>https://dev.to/junghwan18/jeogdanghi-gwaencanheun-sopeuteuweeo-geurigo-aejail-gaebal-4k15</link>
      <guid>https://dev.to/junghwan18/jeogdanghi-gwaencanheun-sopeuteuweeo-geurigo-aejail-gaebal-4k15</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;《실용주의 프로그래머(The Pragmatic Programmer)》 4장을 읽고 정리한 내용입니다.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  품질과 ‘적당히 괜찮음’의 균형
&lt;/h3&gt;

&lt;p&gt;소프트웨어 개발에서 &lt;strong&gt;완벽함을 추구하는 것&lt;/strong&gt;이 꼭 좋은 것은 아니다.&lt;br&gt;&lt;br&gt;
때로는 &lt;strong&gt;적당히 괜찮은 수준&lt;/strong&gt;에서 멈추는 것이 더 좋은 결과를 가져온다.  &lt;/p&gt;

&lt;p&gt;이와 관련해 재미있는 농담이 있다.  &lt;/p&gt;

&lt;h4&gt;
  
  
  일본 제조사와 결함이 있는 IC 칩 이야기
&lt;/h4&gt;

&lt;p&gt;미국 회사가 일본 제조사에 &lt;strong&gt;십만 개의 집적회로(IC)&lt;/strong&gt;를 주문하면서&lt;br&gt;&lt;br&gt;
"결함 비율: 만 개당 하나"를 명세에 포함했다.  &lt;/p&gt;

&lt;p&gt;몇 주 후, 일본 제조사에서 커다란 상자와 작은 상자가 도착했다.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;큰 상자에는 &lt;strong&gt;정상적인 10만 개의 칩&lt;/strong&gt;이 들어 있었고,
&lt;/li&gt;
&lt;li&gt;작은 상자에는 &lt;strong&gt;결함이 있는 10개의 칩&lt;/strong&gt;이 따로 담겨 있었다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;그리고 작은 상자에는 이런 라벨이 붙어 있었다.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"이것이 결함이 있는 칩입니다."  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;일본 제조사는 결함을 허용하는 개념 자체를 이해하지 못했던 것이다.  &lt;/p&gt;

&lt;p&gt;하지만 소프트웨어에서는 다르다. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;완벽함을 추구하기보다는 ‘적당히 괜찮은’ 수준에서 현실적인 타협이 필요하다.&lt;/strong&gt;  &lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Tip 7: 품질을 요구사항으로 만들어라  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;‘적당히 괜찮다’는 것은 허접한 코드를 의미하지 않는다.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
오히려 중요한 것은 &lt;strong&gt;사용자가 만족할 수준에서 품질과 생산성을 균형 있게 맞추는 것&lt;/strong&gt;이다.  &lt;/p&gt;

&lt;h4&gt;
  
  
  타협 과정에 사용자를 참여시키자
&lt;/h4&gt;

&lt;p&gt;소프트웨어를 만들 때,  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;우리는 요구사항을 수집하는 데 집중하지만,
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“이 소프트웨어가 얼마나 좋아야 하나요?”&lt;/strong&gt; 라는 질문은 거의 하지 않는다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;하지만 품질 기준도 명확히 요구사항에 포함되어야 한다.  &lt;/p&gt;

&lt;p&gt;예를 들어,  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;방위산업이나 의료 소프트웨어&lt;/strong&gt;는 타협이 불가능한 품질이 요구되지만,
&lt;/li&gt;
&lt;li&gt;일반적인 웹앱이나 모바일 앱에서는 &lt;strong&gt;완벽한 버전보다 빠른 출시와 피드백 반영이 더 중요&lt;/strong&gt;할 수도 있다.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  언제 멈춰야 할까?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“완벽을 추구하다가 망치는 일이 많다.”  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;소프트웨어 개발자들은 종종  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;기능을 과도하게 추가하거나&lt;/strong&gt;,
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;불필요한 최적화에 집착&lt;/strong&gt;하다가
프로젝트를 망치곤 한다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;때로는 &lt;strong&gt;현재 상태에서 만족하고, 그대로 두는 것이 최선의 선택&lt;/strong&gt;일 수 있다.  &lt;/p&gt;

&lt;h4&gt;
  
  
  오버 엔지니어링을 피하자
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;멀티미디어 기능이 추가된 버전을 1년 후에 출시할래?&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;아니면, 지금이라도 기본적인 기능을 갖춘 버전을 출시할래?&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;대부분의 사용자들은 &lt;strong&gt;완벽한 버전을 기다리기보다는, 지금 당장 쓸 수 있는 소프트웨어를 원한다.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“완벽함”이 아니라 “실용성”을 목표로 해야 한다.&lt;/strong&gt;  &lt;/p&gt;




&lt;h3&gt;
  
  
  내 생각
&lt;/h3&gt;

&lt;p&gt;이 장을 읽으면서,&lt;br&gt;&lt;br&gt;
소프트웨어 개발뿐만 아니라 &lt;strong&gt;삶의 여러 부분에도 적용할 수 있는 원칙&lt;/strong&gt;이라는 생각이 들었다.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;완벽을 추구하다가 오히려 결과물이 늦어지는 경우가 많다.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ 가끔은 "이 정도면 충분해"라고 생각하고 멈추는 용기도 필요하다.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;사용자의 피드백을 빨리 받는 것이 중요하다.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ 너무 완벽하게 만들려고 하지 말고, 일단 작동하는 버전을 보여주고 피드백을 받아야 한다.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;앞으로 개발할 때도 &lt;strong&gt;빠르게 만들고, 사용자의 반응을 보면서 개선하는 방식&lt;/strong&gt;을 더 적극적으로 적용해야겠다...&lt;/p&gt;

</description>
    </item>
    <item>
      <title>돌멩이 수프와 삶은 개구리</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Sat, 15 Mar 2025 08:10:46 +0000</pubDate>
      <link>https://dev.to/junghwan18/dolmengi-supeuwa-salmeun-gaeguri-okm</link>
      <guid>https://dev.to/junghwan18/dolmengi-supeuwa-salmeun-gaeguri-okm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;《실용주의 프로그래머(The Pragmatic Programmer)》 3장을 읽고 정리한 내용입니다.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  돌멩이 수프 이야기: 변화의 촉매가 되라
&lt;/h3&gt;

&lt;p&gt;돌멩이 수프(stone soup) 이야기는 군인들이 마을 사람들의 협력을 이끌어내어 결국 모두가 이득을 보게 된다는 교훈을 담고 있다.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;군인들은 마을 사람들에게 돌멩이 수프를 만들겠다고 말한다. 처음에는 돌과 물뿐이지만, 사람들이 조금씩 재료를 보태면서 결국 훌륭한 수프가 완성된다.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이 이야기에서 군인들은 &lt;strong&gt;변화의 촉매(catalyst)&lt;/strong&gt; 역할을 한다. 처음부터 완벽한 계획이 아니라, &lt;strong&gt;작은 시작점(돌멩이)&lt;/strong&gt;을 던짐으로써 변화를 유도한다.  &lt;/p&gt;

&lt;h4&gt;
  
  
  Tip 5: 변화의 촉매가 되라
&lt;/h4&gt;

&lt;p&gt;어떤 시스템이 옳다는 걸 알고 있지만,&lt;br&gt;&lt;br&gt;
&lt;strong&gt;“이걸 허락받고 시작하려면 시간이 걸릴 텐데…”&lt;/strong&gt; 같은 생각 때문에 주저한 적이 있는가?  &lt;/p&gt;

&lt;p&gt;이럴 때는 &lt;strong&gt;돌멩이를 내놓아야 할 때다.&lt;/strong&gt;  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;큰 허가 없이 할 수 있는 &lt;strong&gt;작은 기능을 먼저 구현&lt;/strong&gt;해보자.
&lt;/li&gt;
&lt;li&gt;그리고 사람들에게 보여주어 &lt;strong&gt;감탄을 이끌어내자.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;마지막으로 &lt;strong&gt;“이걸 조금만 더 다듬으면 더 좋아질 거예요”&lt;/strong&gt;라고 자연스럽게 유도하자.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;작은 성공이 거듭되면 사람들은 자연스럽게 변화에 합류하게 된다.&lt;br&gt;&lt;br&gt;
이렇게 해서 프로젝트를 더 나은 방향으로 이끌 수 있다.  &lt;/p&gt;




&lt;h3&gt;
  
  
  삶은 개구리: 큰 그림을 기억하라
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;개구리를 끓는 물에 바로 넣으면 튀어나온다. 하지만 차가운 물에 넣고 서서히 가열하면, 온도가 올라가는 걸 눈치채지 못하고 결국 삶아진다.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이 비유는 &lt;strong&gt;점진적인 변화에 무감각해지는 위험&lt;/strong&gt;을 경고한다.  &lt;/p&gt;

&lt;h4&gt;
  
  
  Tip 6: 큰 그림을 기억하라
&lt;/h4&gt;

&lt;p&gt;우리는 매일 업무에 집중하다 보면,&lt;br&gt;&lt;br&gt;
&lt;strong&gt;내가 맡은 일만 신경 쓰느라 전체적인 흐름을 놓칠 때가 많다.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;소프트웨어 프로젝트도 마찬가지다.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;비효율적인 프로세스가 쌓이고,
&lt;/li&gt;
&lt;li&gt;코드 품질이 조금씩 나빠지고,
&lt;/li&gt;
&lt;li&gt;팀 문화가 서서히 악화된다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;그러다 보면 결국 &lt;strong&gt;돌이킬 수 없는 지점까지 가게 된다.&lt;/strong&gt;  &lt;/p&gt;

&lt;h4&gt;
  
  
  깨진 창문 이론과 차이점
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;깨진 창문 이론&lt;/strong&gt;: 사람들이 문제를 방치하다가 점점 더 악화되는 상황
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;삶은 개구리 이론&lt;/strong&gt;: 변화가 너무 서서히 일어나서 문제를 인식하지 못하는 상황
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  내 생각
&lt;/h3&gt;

&lt;p&gt;이 두 가지 개념을 읽으면서, 개발뿐만 아니라 &lt;strong&gt;팀 문화와 개인 성장에도 적용할 수 있겠다는 생각&lt;/strong&gt;이 들었다.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;변화를 주저하지 말 것&lt;/strong&gt;
→ "회사에서 허락할까?" "팀에서 받아들일까?" 고민만 하지 말고, 작은 돌멩이를 던져보자.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;내 일에만 집중하지 말 것&lt;/strong&gt;
→ 내가 맡은 일만 열심히 하다가, 팀 전체가 엉망이 되는 걸 뒤늦게 깨닫는 개구리가 되지 말자.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;작은 변화를 시도하고, 주기적으로 큰 그림을 점검하는 습관을 들이면&lt;br&gt;&lt;br&gt;
더 나은 개발자가 될 수 있을 것 같다. 😊&lt;/p&gt;

</description>
    </item>
    <item>
      <title>소프트웨어 엔트로피</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Sat, 15 Mar 2025 08:09:06 +0000</pubDate>
      <link>https://dev.to/junghwan18/sopeuteuweeo-enteuropi-4i72</link>
      <guid>https://dev.to/junghwan18/sopeuteuweeo-enteuropi-4i72</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;《실용주의 프로그래머(The Pragmatic Programmer)》 2장을 읽고 정리한 내용입니다.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;소프트웨어도 시간이 지나면 점점 &lt;strong&gt;엔트로피(무질서도)&lt;/strong&gt;가 증가한다. 우주가 끊임없이 무질서해지는 것처럼, 유지보수가 제대로 되지 않은 소프트웨어는 점점 더 엉망이 된다. 문제는 단순히 기술적인 요소만이 아니라 &lt;strong&gt;심리적, 문화적 요인&lt;/strong&gt;도 소프트웨어의 부패를 가속화한다는 것이다.  &lt;/p&gt;

&lt;h3&gt;
  
  
  깨진 창문 이론
&lt;/h3&gt;

&lt;p&gt;이 책에서는 &lt;strong&gt;깨진 창문 이론(Broken Windows Theory)&lt;/strong&gt;을 소프트웨어 개발에 적용해 설명한다.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;건물에 깨진 창문 하나가 오랫동안 방치되면, 거주자들은 그 건물이 버려졌다고 느낀다. 그러면 다른 창문도 깨지기 시작하고, 결국 건물 전체가 황폐해진다.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이 이론은 뉴욕 경찰이 경범죄를 단속함으로써 강력범죄까지 줄이는 데 도움을 줬다. 마찬가지로, 개발에서도 &lt;strong&gt;작은 문제를 방치하면 코드 전체가 무너질 위험이 있다.&lt;/strong&gt;  &lt;/p&gt;

&lt;h3&gt;
  
  
  Tip 4: 깨진 창문을 내버려두지 말라
&lt;/h3&gt;

&lt;p&gt;나쁜 설계, 잘못된 결정, 혹은 형편없는 코드가 깨진 창문과 같다.&lt;br&gt;&lt;br&gt;
이걸 발견하면 방치하지 말고 바로 조치해야 한다.  &lt;/p&gt;

&lt;h4&gt;
  
  
  어떻게 고칠까?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;문제가 되는 코드를 &lt;strong&gt;주석 처리&lt;/strong&gt;하거나
&lt;/li&gt;
&lt;li&gt;‘Not Implemented’라는 메시지를 표시하거나
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;더미 데이터&lt;/strong&gt;로 대체하는 방법이 있다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;작은 문제라도 그냥 두면 점점 커지면서 프로젝트 전체를 부패시킨다.  &lt;/p&gt;

&lt;h3&gt;
  
  
  깨끗한 코드가 팀 문화에 미치는 영향
&lt;/h3&gt;

&lt;p&gt;만약 코드가 깨끗하고 우아하게 설계된 프로젝트라면?&lt;br&gt;&lt;br&gt;
그 팀에 속한 사람들은 자연스럽게 &lt;strong&gt;그 청결함을 유지하려고 노력&lt;/strong&gt;한다.&lt;br&gt;&lt;br&gt;
심지어 데드라인이 급박한 상황에서도 &lt;strong&gt;최초로 코드 품질을 희생하는 사람&lt;/strong&gt;이 되는 것은 꺼리게 된다.  &lt;/p&gt;

&lt;h3&gt;
  
  
  내 생각
&lt;/h3&gt;

&lt;p&gt;이 내용을 읽으면서 개발뿐만 아니라 &lt;strong&gt;팀 문화 전반에도 적용할 수 있겠다는 생각&lt;/strong&gt;이 들었다.  &lt;/p&gt;

&lt;p&gt;작은 문제를 그냥 두는 습관이 쌓이면, 결국 &lt;strong&gt;“우리 프로젝트는 원래 이래”&lt;/strong&gt;라는 분위기가 형성된다.&lt;br&gt;&lt;br&gt;
그러다 보면 점점 코드가 더러워지는 걸 아무도 신경 쓰지 않게 된다.  &lt;/p&gt;

&lt;p&gt;반대로, 코드 품질을 중요하게 생각하는 분위기가 만들어지면 팀원들도 자연스럽게 &lt;strong&gt;더 나은 코드를 유지하려고 노력&lt;/strong&gt;한다.&lt;br&gt;&lt;br&gt;
결국 깨진 창문을 방치하지 않는 것이 &lt;strong&gt;개발자 개개인의 성장뿐만 아니라 팀 전체의 성장&lt;/strong&gt;에도 영향을 미친다.  &lt;/p&gt;

&lt;p&gt;나도 앞으로는 “이 정도는 그냥 넘어가도 되겠지”라는 생각을 줄이고, 작은 문제라도 발견하면 바로 정리하는 습관을 들여야겠다. 😊&lt;/p&gt;

</description>
    </item>
    <item>
      <title>고양이가 내 소스코드를 삼켰어요</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Sat, 15 Mar 2025 08:07:47 +0000</pubDate>
      <link>https://dev.to/junghwan18/goyangiga-nae-soseukodeureul-samkyeosseoyo-57kd</link>
      <guid>https://dev.to/junghwan18/goyangiga-nae-soseukodeureul-samkyeosseoyo-57kd</guid>
      <description>&lt;h3&gt;
  
  
  실용주의 프로그래머 1장 - 고양이가 내 소스코드를 삼켰어요
&lt;/h3&gt;

&lt;p&gt;《실용주의 프로그래머(The Pragmatic Programmer)》 1장을 읽고 정리한 내용입니다.  &lt;/p&gt;

&lt;p&gt;개발을 하다 보면 예상치 못한 문제가 터지기 마련이다. 심지어 최고의 프로젝트에서도 납품이 지연되거나 기술적 문제가 발생한다. 중요한 건 이런 상황에서 어떻게 대처하느냐다.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;전문가다운 태도란?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
개발자는 문제를 해결하는 사람이다. 변명으로 시간을 끌거나 책임을 회피하는 대신, 솔직하게 상황을 공유하고 해결책을 고민해야 한다. 예를 들어, "서버 장애 때문에 배포가 늦어졌습니다"라고 말하는 대신, "현재 서버 장애가 발생했으며, 복구를 위해 A와 B 작업을 진행 중입니다. 예상 완료 시간은 X시입니다"라고 말하는 것이 더 나은 접근 방식이다.  &lt;/p&gt;

&lt;p&gt;벤더(외부 업체)가 일정대로 결과물을 내놓지 못할 가능성이 있다면, 이에 대한 대비책(contingency plan)을 미리 세워야 한다. 만약 소스코드와 디스크가 망가졌는데 백업이 없다면, 그것은 불운이 아니라 철저한 관리 부실이다. 그때 가서 "고양이가 내 소스코드를 삼켰어요"라고 변명해도 아무도 책임져 주지 않는다.  &lt;/p&gt;

&lt;h3&gt;
  
  
  문제가 발생했을 때의 태도
&lt;/h3&gt;

&lt;p&gt;이 책에서는 문제가 발생했을 때 취할 수 있는 네 가지 행동을 제시한다.  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;잠깐 멈추고 자신의 목소리를 들어라.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ 문제를 감정적으로 대응하기 전에 스스로 상황을 되짚어보자.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;머릿속에서 대화를 진행시켜보라.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ "이 문제를 상사에게 보고해야 하는데, 어떻게 말해야 하지?"라고 미리 연습해보면 도움이 된다.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;변명 대신 제안을 제시하라.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ "예기치 못한 에러가 발생했습니다"보다 "현재 문제를 해결하기 위해 A 방법을 시도 중이고, 추가적으로 B 방안을 고려하고 있습니다"가 더 신뢰를 준다.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;어설픈 변명을 늘어놓기 전에 변명거리를 없애도록 노력하라.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ 근본적인 문제를 해결하지 않고 핑계를 대는 건 도움이 되지 않는다.  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  내 생각
&lt;/h3&gt;

&lt;p&gt;이 부분을 읽으면서 '내가 서비스를 이용하는 입장이라면 어떨까?'라는 질문을 던져봤다.  &lt;/p&gt;

&lt;p&gt;예를 들어, 은행원이나 자동차 수리공이 내 앞에서 어설픈 변명을 늘어놓는다면? 신뢰가 떨어지고 다시는 그곳을 이용하고 싶지 않을 것이다. 고객의 입장에서는 결과가 중요하지, 내부 사정은 크게 중요하지 않다. 개발자로서도 마찬가지다. 사용자가 겪는 문제를 책임지고 해결하는 태도가 필요하다.  &lt;/p&gt;

&lt;p&gt;결국 중요한 건 &lt;strong&gt;변명이 아니라 해결책&lt;/strong&gt;이다.&lt;br&gt;&lt;br&gt;
다음부터는 문제가 생겼을 때 순간적인 핑계부터 찾기보다, 먼저 해결 방안을 고민해 보는 습관을 들여야겠다. 😊&lt;/p&gt;

</description>
    </item>
    <item>
      <title>힙(heapq)으로 메모리 초과 해결하기</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Wed, 12 Mar 2025 07:15:06 +0000</pubDate>
      <link>https://dev.to/junghwan18/baegjun-2075beon-nbeonjjae-keun-su-paisseon-hibheapqeuro-memori-cogwa-haegyeolhagi-3ak4</link>
      <guid>https://dev.to/junghwan18/baegjun-2075beon-nbeonjjae-keun-su-paisseon-hibheapqeuro-memori-cogwa-haegyeolhagi-3ak4</guid>
      <description>&lt;h2&gt;
  
  
  문제 소개 (백준 2075번)
&lt;/h2&gt;

&lt;p&gt;N x N 크기의 행렬이 주어지고, 각 행은 한 줄씩 입력으로 주어집니다. 이 행렬에서 &lt;em&gt;N번째로 큰 수&lt;/em&gt;를 찾는 문제입니다.  단순히 모든 수를 정렬하면 될 것 같지만, 다음과 같은 제약 조건이 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;N:&lt;/strong&gt; 최대 1500&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;메모리 제한:&lt;/strong&gt; 12MB&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  흔한 실수와 메모리 초과
&lt;/h2&gt;

&lt;p&gt;많은 분들이 처음 시도하는 방법은 모든 수를 리스트에 담고, 내림차순 정렬 후 &lt;code&gt;N-1&lt;/code&gt;번째 요소를 출력하는 것입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 메모리 초과 (Memory Limit Exceeded) 발생!
&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())))&lt;/span&gt;

&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;얼핏 보면 메모리 제한 내에 들어올 것 같지만, 이 코드는 &lt;em&gt;메모리 초과&lt;/em&gt;로 실패합니다.  N=1500일 때, &lt;code&gt;arr&lt;/code&gt; 리스트는 2,250,000개의 정수를 저장하며, 약 9MB를 차지합니다.  문제는 파이썬의 &lt;code&gt;list.sort()&lt;/code&gt; (Timsort 알고리즘)가 정렬 과정에서 &lt;em&gt;추가적인 메모리&lt;/em&gt;를 최대 O(N)만큼 사용한다는 점입니다.  따라서 정렬 시 거의 두 배의 메모리를 사용하게 되어 12MB 제한을 넘어서게 됩니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  해결 전략: 최소 힙(Min-Heap)
&lt;/h2&gt;

&lt;p&gt;핵심은 &lt;em&gt;모든 수를 저장하지 않는 것&lt;/em&gt;입니다.  우리는 N번째로 큰 수만 알면 됩니다.  이를 위해 &lt;em&gt;크기가 N인 최소 힙(min-heap)&lt;/em&gt;을 사용합니다. 최소 힙은 항상 가장 &lt;em&gt;작은&lt;/em&gt; 원소가 루트에 위치하는 자료구조입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heapq&lt;/span&gt;

&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;heap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heappush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 힙이 N보다 작으면 그냥 추가
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;  &lt;span class="c1"&gt;# 힙의 루트(최솟값)보다 크면
&lt;/span&gt;                &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heapreplace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 루트를 제거하고 새 원소 삽입
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# 힙의 루트가 N번째로 큰 수
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;동작 원리:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;heapq&lt;/code&gt; 모듈:&lt;/strong&gt; 파이썬의 &lt;code&gt;heapq&lt;/code&gt; 모듈을 사용하여 최소 힙을 구현합니다.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;크기 N 유지:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  힙의 크기가 N보다 작으면, 입력받은 수를 &lt;code&gt;heapq.heappush&lt;/code&gt;로 힙에 추가합니다.&lt;/li&gt;
&lt;li&gt;  힙의 크기가 N이면, 입력받은 수 &lt;code&gt;x&lt;/code&gt;를 힙의 루트(&lt;code&gt;heap[0]&lt;/code&gt;, 최솟값)와 비교합니다.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;x&lt;/code&gt;가 &lt;code&gt;heap[0]&lt;/code&gt;보다 크면, &lt;code&gt;heap[0]&lt;/code&gt;은 N번째 큰 수가 될 수 없으므로 제거하고, &lt;code&gt;x&lt;/code&gt;를 힙에 넣습니다. &lt;code&gt;heapq.heapreplace&lt;/code&gt;는 이 과정을 효율적으로 수행합니다(pop 후 push보다 빠름).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;결과:&lt;/strong&gt; 모든 수를 처리한 후, 힙에는 가장 큰 N개의 수가 남아있고, 그중 가장 작은 수(힙의 루트)가 N번째로 큰 수가 됩니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;시간/공간 복잡도:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;시간 복잡도:&lt;/strong&gt; O(N² log N).  &lt;code&gt;heappush&lt;/code&gt;와 &lt;code&gt;heapreplace&lt;/code&gt;는 O(log N)이고, N*N개의 원소에 대해 수행합니다.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;공간 복잡도:&lt;/strong&gt; O(N). 힙의 크기는 N으로 일정합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  결론
&lt;/h2&gt;

&lt;p&gt;백준 2075번 문제는 메모리 제한 때문에 단순한 정렬로는 풀 수 없습니다. 최소 힙을 사용하여 가장 큰 N개의 수만 유지하는 것이 핵심 전략입니다. 이 문제는 자료구조의 선택이 얼마나 중요한지를 잘 보여줍니다.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>NoSQL vs RDB: 데이터베이스 선택, 무엇이 다르고 언제 선택해야 할까요?</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Tue, 11 Mar 2025 07:26:43 +0000</pubDate>
      <link>https://dev.to/junghwan18/nosql-rdbwa-mweoga-dareulgga-mongodbwa-redis-yejero-swibge-ihaehagi-433d</link>
      <guid>https://dev.to/junghwan18/nosql-rdbwa-mweoga-dareulgga-mongodbwa-redis-yejero-swibge-ihaehagi-433d</guid>
      <description>&lt;p&gt;안녕하세요! 오늘은 웹 개발에서 뗄 수 없는 관계, 데이터베이스에 대한 이야기를 해볼까 합니다. 특히 최근 몇 년간 뜨겁게 떠오르고 있는 &lt;strong&gt;NoSQL&lt;/strong&gt;과 전통의 강자 &lt;strong&gt;RDB (Relational Database)&lt;/strong&gt;, 이 두 데이터베이스 사이에서 어떤 것을 선택해야 할지 고민해 본 적, 다들 있으시죠?&lt;/p&gt;

&lt;p&gt;이번 포스팅에서는 NoSQL과 RDB의 차이점을 명확하게 짚어보고, 각각의 특징과 장단점을 비교 분석하여 여러분의 데이터베이스 선택 고민을 조금이나마 덜어드리고자 합니다. 자, 그럼 함께 NoSQL과 RDB의 세계로 떠나볼까요?&lt;/p&gt;

&lt;h3&gt;
  
  
  1. RDB (Relational Database) - 전통적인 데이터 관리의 한계?
&lt;/h3&gt;

&lt;p&gt;RDB는 수십 년간 데이터 관리의 표준으로 자리매김해 왔습니다. 하지만 급변하는 웹 서비스 환경은 RDB에게 새로운 도전을 요구하기 시작했죠. RDB가 가진 구조적인 한계는 무엇일까요?&lt;/p&gt;

&lt;h4&gt;
  
  
  1.1. 확장성의 부족: 유연하지 못한 데이터 모델 변경
&lt;/h4&gt;

&lt;p&gt;RDB의 가장 큰 약점 중 하나는 &lt;strong&gt;확장성&lt;/strong&gt;입니다. RDB는 엄격하게 정의된 &lt;strong&gt;스키마&lt;/strong&gt;를 기반으로 데이터를 관리합니다. 이는 데이터의 무결성을 높이는 데 효과적이지만, 반대로 데이터 모델을 변경하는 것을 매우 어렵게 만듭니다.&lt;/p&gt;

&lt;p&gt;수백만 건, 수천만 건의 데이터가 쌓인 테이블에 새로운 컬럼을 추가해야 하는 상황을 상상해 보세요. 스키마 변경 작업은 상당한 시간과 자원을 소모하며, 심지어 서비스 중단으로 이어질 수도 있습니다. 빠르게 변화하는 비즈니스 요구사항에 유연하게 대처하기 어려운 구조인 것이죠.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.2. JOIN 지옥: 성능 저하의 주범
&lt;/h4&gt;

&lt;p&gt;RDB의 또 다른 특징은 &lt;strong&gt;정규화&lt;/strong&gt;입니다. 정규화는 데이터 중복을 최소화하고 데이터 무결성을 확보하는 데 효과적인 방법입니다. 하지만 과도한 정규화는 필연적으로 &lt;strong&gt;JOIN 연산&lt;/strong&gt;의 증가를 불러옵니다.&lt;/p&gt;

&lt;p&gt;복잡한 JOIN 쿼리는 데이터량이 많아질수록 READ 성능 저하의 주범이 됩니다. 특히 여러 테이블을 조인하는 쿼리는 데이터베이스에 상당한 부담을 주고, 사용자 응답 시간을 지연시키는 원인이 되죠.  개발자들이 흔히 겪는 "JOIN 지옥"은 RDB의 구조적인 한계를 잘 보여주는 사례입니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.3. Scale-Up의 한계: 고부하 트래픽 감당의 어려움
&lt;/h4&gt;

&lt;p&gt;RDB는 전통적으로 &lt;strong&gt;Scale-Up&lt;/strong&gt; 방식, 즉 서버 자체의 성능을 향상시켜 부하를 감당하는 방식을 택해왔습니다. 초기에는 Scale-Up이 효과적일 수 있지만, 하드웨어 성능 향상에는 분명한 한계가 존재하며, 비용 역시 기하급수적으로 증가합니다.&lt;/p&gt;

&lt;p&gt;물론 &lt;strong&gt;Replication (복제)&lt;/strong&gt; 과 같은 기술을 통해 READ 트래픽을 분산시킬 수 있습니다. 하지만 WRITE 트래픽이 많은 환경에서는 복제본 역시 부하를 감당하기 어렵습니다. RDB에도 &lt;strong&gt;Multi-master, Sharding&lt;/strong&gt; 과 같은 Scale-Out 기술이 있지만, NoSQL에 비하면 복잡하고 유연성이 떨어지는 것이 사실입니다. RDB는 태생적으로 Scale-Out에 최적화된 데이터베이스는 아닌 것이죠.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.4. ACID 속성: 성능과의 Trade-off
&lt;/h4&gt;

&lt;p&gt;RDB는 &lt;strong&gt;ACID (Atomicity, Consistency, Isolation, Durability)&lt;/strong&gt; 속성을 엄격하게 보장하려고 노력합니다. 이는 데이터의 무결성을 최우선으로 생각하는 RDB의 핵심 가치입니다. 하지만 ACID 속성을 보장하기 위한 &lt;strong&gt;트랜잭션 관리&lt;/strong&gt; 과정은 데이터베이스 서버의 성능에 영향을 미치기도 합니다. 특히 동시성이 높은 환경에서는 ACID 속성을 유지하기 위한 복잡한 과정으로 인해 성능 저하가 발생할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. NoSQL의 등장 배경: RDB의 한계를 넘어서
&lt;/h3&gt;

&lt;p&gt;2000년대 초, 중반을 지나면서 인터넷은 폭발적으로 성장했고, 웹 서비스 환경은 급격하게 변화했습니다. 소셜 미디어, 전자상거래, 대규모 온라인 게임 등 기존 RDBMS로는 감당하기 힘든 새로운 유형의 서비스들이 등장하기 시작했죠. 이러한 서비스들은 다음과 같은 새로운 요구사항을 제시했습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;높은 처리량 (High-Throughput):&lt;/strong&gt; 수많은 사용자의 동시 접속과 대량의 데이터 처리 요구&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;낮은 응답 시간 (Low-Latency):&lt;/strong&gt; 사용자 경험 향상을 위한 빠른 응답 속도&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;비정형 데이터 처리:&lt;/strong&gt; 텍스트, 이미지, 비디오 등 다양한 형태의 비정형 데이터 증가&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;기존 RDB는 이러한 새로운 요구사항들을 만족시키기에는 역부족이었습니다. 바로 이러한 배경 속에서 &lt;strong&gt;NoSQL (Not only SQL)&lt;/strong&gt; 이라는 새로운 데이터베이스 패러다임이 등장하게 된 것입니다. NoSQL은 "SQL을 대체한다"는 의미가 아니라, &lt;strong&gt;"SQL 뿐만 아니라 다양한 데이터 관리 방식이 필요하다"&lt;/strong&gt; 는  의미를 담고 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. NoSQL의 주요 특징: RDB와 무엇이 다를까?
&lt;/h3&gt;

&lt;p&gt;NoSQL은 RDB의 한계를 극복하고 현대적인 웹 서비스 환경에 최적화된 다양한 특징들을 가지고 있습니다. 주요 특징들을 하나씩 살펴볼까요?&lt;/p&gt;

&lt;h4&gt;
  
  
  3.1. Flexible Schema (유연한 스키마): 자유로운 데이터 모델링
&lt;/h4&gt;

&lt;p&gt;NoSQL의 가장 큰 특징은 &lt;strong&gt;유연한 스키마&lt;/strong&gt;입니다. RDB와 달리 NoSQL 데이터베이스는 스키마를 미리 정의할 필요가 없습니다. 데이터를 &lt;strong&gt;Document&lt;/strong&gt; 형태로 저장하며, 각 Document는 서로 다른 필드를 가질 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MongoDB&lt;/strong&gt;를 예로 들어볼까요? MongoDB는 &lt;strong&gt;Collection&lt;/strong&gt; (RDB의 테이블과 유사) 생성 시 스키마를 정의하지 않습니다. 아래와 같이 &lt;code&gt;insertOne()&lt;/code&gt; 명령어를 통해 자유롭게 Document를 삽입할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;student&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;john&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Korea&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Seoul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS solution architect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[Image of MongoDB flexible schema example]&lt;/p&gt;

&lt;p&gt;보시는 것처럼, 각 Document는 name 필드만 가질 수도 있고, address, certificate 와 같은 다양한 필드를 포함할 수도 있습니다. 이렇게 유연한 스키마는 개발 초기 단계나 데이터 구조가 자주 변경되는 환경에서 매우 유용합니다. 애플리케이션 요구사항 변화에 빠르게 대응할 수 있도록 높은 유연성을 제공하죠.&lt;/p&gt;

&lt;p&gt;하지만 유연한 스키마는 &lt;strong&gt;스키마 관리 책임&lt;/strong&gt;을 데이터베이스 레벨에서 &lt;strong&gt;애플리케이션 레벨&lt;/strong&gt;로 이동시킨다는 점을 기억해야 합니다. 데이터 무결성 및 일관성 유지를 위해서는 애플리케이션 단에서 데이터 검증 및 관리가 더욱 중요해집니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  3.2. Denormalization (반정규화): JOIN 연산 없는 빠른 조회
&lt;/h4&gt;

&lt;p&gt;NoSQL의 두 번째 특징은 &lt;strong&gt;중복 허용 (Denormalization)&lt;/strong&gt; 입니다. NoSQL 데이터베이스는 &lt;strong&gt;JOIN 연산&lt;/strong&gt;을 최소화하기 위해 데이터 중복을 허용하는 &lt;strong&gt;반정규화&lt;/strong&gt; 기법을 적극적으로 활용합니다.&lt;/p&gt;

&lt;p&gt;RDB에서도 성능 향상을 위해 반정규화를 사용하기도 하지만, NoSQL에서는 더욱 일반적인 방식입니다. &lt;strong&gt;주문 정보&lt;/strong&gt;를 예시로 들어보겠습니다. RDB라면 &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;products&lt;/code&gt;, &lt;code&gt;users&lt;/code&gt; 테이블로 분리했을 정보를 NoSQL에서는 하나의 Document 안에 모두 포함시키는 것입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
{
    "_id": ObjectId("..."),
    // 프로덕트 정보도 저장 (RDB였다면 테이블 분리)
    "product_name": "아이폰 16e",
    "price_per_product": 2000000,
    "count": 5.0,
    "total_price": 1000000,
    // 주문한 사용자 정보도 저장 (RDB였다면 테이블 분리)
    "user_name": "john",
    "phone_number": "010-1234-1234"
}
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[Image of NoSQL denormalization example]&lt;/p&gt;

&lt;p&gt;주문 Document 안에 제품 정보, 사용자 정보 등을 중복해서 저장하는 것이죠. 이렇게 중복을 허용하면 얻는 가장 큰 장점은 바로 &lt;strong&gt;JOIN 연산의 필요성이 사라진다&lt;/strong&gt;는 것입니다. JOIN 연산 자체가 없어지니 데이터 조회 성능이 획기적으로 향상됩니다. 특히 복잡한 JOIN 연산으로 인한 성능 병목 현상을 효과적으로 해결할 수 있습니다.&lt;/p&gt;

&lt;p&gt;하지만 중복된 데이터의 &lt;strong&gt;일관성 유지&lt;/strong&gt;는 애플리케이션의 책임으로 남게 됩니다. 데이터 업데이트 시 모든 중복된 데이터를 최신 상태로 유지하는 로직을 개발해야 합니다. 데이터 무결성 관리의 책임이 개발자에게 있다는 점을 명심해야 합니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  3.3. Scale-Out 용이성: 수평 확장을 위한 최적화
&lt;/h4&gt;

&lt;p&gt;NoSQL의 세 번째 특징은 &lt;strong&gt;Scale-Out (수평 확장) 의 용이성&lt;/strong&gt;입니다. NoSQL 데이터베이스는 Scale-Out에 특화된 구조를 가지고 있습니다. JOIN 연산이 필요 없다는 점은 데이터베이스를 여러 서버에 분산하기 매우 용이하게 만들어줍니다.&lt;/p&gt;

&lt;p&gt;데이터를 여러 서버에 &lt;strong&gt;Sharding (샤딩)&lt;/strong&gt; 하여 저장하고, 각 샤드에서 독립적으로 데이터를 처리할 수 있습니다. NoSQL 데이터베이스는 여러 대의 서버를 묶어 하나의 &lt;strong&gt;클러스터&lt;/strong&gt;로 구성하여 대용량 데이터를 처리하고, 높은 가용성을 확보하는 데 효과적입니다. JOIN 연산 감소와 샤딩 용이성 덕분에 RDBMS에 비해 Scale-Out이 훨씬 수월합니다.&lt;/p&gt;

&lt;p&gt;[Image of NoSQL sharding example]&lt;/p&gt;

&lt;h4&gt;
  
  
  3.4. 성능 우선: ACID 속성 완화
&lt;/h4&gt;

&lt;p&gt;NoSQL은 RDB와 달리 &lt;strong&gt;ACID 속성 중 일부를 완화&lt;/strong&gt;하고 &lt;strong&gt;성능&lt;/strong&gt;을 우선시하는 경향이 있습니다. 특히 &lt;strong&gt;Consistency (일관성)&lt;/strong&gt; 속성을 완화하고 &lt;strong&gt;High-Throughput (높은 처리량)&lt;/strong&gt; 과 &lt;strong&gt;Low-Latency (낮은 응답 시간)&lt;/strong&gt; 성능을 극대화하는 데 초점을 맞춥니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CAP 이론 (Consistency, Availability, Partition Tolerance)&lt;/strong&gt; 관점에서 NoSQL은 일반적으로 &lt;strong&gt;Eventual Consistency (최종적 일관성)&lt;/strong&gt; 모델을 채택합니다. 이는 "데이터가 언젠가는 일관성을 갖게 된다"는 의미로, 높은 가용성과 성능을 확보하는 대신, 데이터 일관성에 대한 엄격한 보장을 포기하는 것입니다.&lt;/p&gt;

&lt;p&gt;[Image of CAP Theorem]&lt;/p&gt;

&lt;p&gt;물론 금융 시스템이나 거래 시스템처럼 데이터의 엄격한 일관성이 중요한 시스템에서는 NoSQL 적용에 신중해야 합니다. 이러한 시스템에서는 여전히 RDBMS가 더 적합한 선택일 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. NoSQL 대표 주자 - Redis 살펴보기
&lt;/h3&gt;

&lt;p&gt;NoSQL 데이터베이스는 정말 다양하지만, 여기서는 대표적인 NoSQL 데이터베이스 중 하나인 &lt;strong&gt;Redis&lt;/strong&gt;를 간단하게 살펴보겠습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redis&lt;/strong&gt;는 &lt;strong&gt;In-Memory 데이터베이스&lt;/strong&gt;, &lt;strong&gt;Key-Value 데이터베이스&lt;/strong&gt;, &lt;strong&gt;캐시 시스템&lt;/strong&gt; 등 정말 다양한 용도로 활용되는 NoSQL 데이터베이스입니다. 단순 캐시를 넘어 세션 관리, 실시간 분석, 리더보드, 메시지 큐 등 폭넓은 분야에서 사용되고 있습니다.&lt;/p&gt;

&lt;p&gt;Redis의 핵심은 &lt;strong&gt;Key-Value 구조&lt;/strong&gt;입니다. 모든 데이터는 Key와 Value 쌍으로 저장되며, 각 Key에 &lt;strong&gt;String, List, Set, Hash, Sorted Set&lt;/strong&gt; 등 다양한 Value 형태를 저장할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;redis&amp;gt; SET name ppap
"OK"

redis&amp;gt; GET name
"ppap"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[Image of Redis Key-Value example]&lt;/p&gt;

&lt;p&gt;Redis는 &lt;strong&gt;Hash 기반 샤딩 클러스터&lt;/strong&gt;를 통해 Scale-Out을 지원하며, &lt;strong&gt;Replication, Automatic Failover&lt;/strong&gt; 기능을 통해 고가용성을 확보합니다.&lt;/p&gt;

&lt;p&gt;[Image of Redis cluster architecture]&lt;/p&gt;

&lt;p&gt;Redis의 가장 대표적인 사용 사례는 &lt;strong&gt;캐시 레이어&lt;/strong&gt;입니다. 데이터베이스 앞단에 Redis를 배치하여 자주 조회되는 데이터를 메모리에 캐싱함으로써 데이터베이스 응답 속도를 획기적으로 향상시킬 수 있습니다. In-Memory 기반이기 때문에 디스크 기반 데이터베이스보다 훨씬 빠른 성능을 제공하는 것이죠.&lt;/p&gt;

&lt;p&gt;[Image of Redis cache layer example]&lt;/p&gt;

&lt;p&gt;이 외에도 Redis는 세션 관리, 실시간 순위 집계, 메시지 큐, Pub/Sub 시스템 등 다양한 용도로 활용될 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. NoSQL, RDB - 어떤 데이터베이스를 선택해야 할까요?
&lt;/h3&gt;

&lt;p&gt;지금까지 NoSQL과 RDB의 차이점과 특징들을 자세히 살펴보았습니다. 그렇다면 어떤 상황에서 어떤 데이터베이스를 선택해야 할까요?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RDB는 다음과 같은 경우에 적합합니다.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;데이터 무결성이 최우선:&lt;/strong&gt; 금융 거래, 결제 시스템 등 데이터의 정확성과 신뢰성이 매우 중요한 시스템&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;복잡한 트랜잭션 처리:&lt;/strong&gt; 여러 단계를 거치는 복잡한 트랜잭션과 ACID 속성 보장이 필수적인 시스템&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;정형 데이터:&lt;/strong&gt; 데이터 구조가 명확하고 관계가 중요한 정형 데이터를 다루는 경우&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;NoSQL은 다음과 같은 경우에 적합합니다.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;높은 성능과 확장성:&lt;/strong&gt; 대용량 데이터 처리, 실시간 데이터 분석, 높은 트래픽을 처리해야 하는 서비스&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;유연한 데이터 모델:&lt;/strong&gt;  데이터 구조가 자주 변경되거나 비정형 데이터를 다루는 경우&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;스키마리스 개발:&lt;/strong&gt;  개발 초기 단계, 애자일 개발 방식 등 빠르게 변화에 대응해야 하는 환경&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;물론 위 기준은 일반적인 가이드라인일 뿐이며, 실제 데이터베이스 선택은 서비스의 특성, 데이터 규모, 성능 요구사항, 개발팀의 숙련도 등 다양한 요소를 종합적으로 고려하여 결정해야 합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NoSQL 데이터베이스 종류:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key-Value:&lt;/strong&gt; Redis, Memcached&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document:&lt;/strong&gt; MongoDB, Couchbase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Column-Family:&lt;/strong&gt; Cassandra, HBase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph:&lt;/strong&gt; Neo4j&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;NoSQL 데이터 모델:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key-Value 모델:&lt;/strong&gt; 단순 Key-Value 쌍으로 데이터 저장, 캐싱, 세션 관리 등에 적합&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document 모델:&lt;/strong&gt; JSON, XML 형태의 Document로 데이터 저장, 유연한 스키마, 비정형 데이터 처리에 적합&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Column-Family 모델:&lt;/strong&gt; Column Family 기반으로 데이터 저장, 대용량 데이터, 분산 처리에 적합&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph 모델:&lt;/strong&gt;  Node와 Relationship으로 데이터 관계 표현,  소셜 네트워크, 추천 시스템 등 관계 분석에 적합&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[Image of NoSQL database types and data models]&lt;/p&gt;

&lt;h3&gt;
  
  
  마무리
&lt;/h3&gt;

&lt;p&gt;NoSQL과 RDB는 각각 뚜렷한 장단점을 가지고 있으며, 특정 상황에 더 적합한 데이터베이스라고 단정 지을 수는 없습니다. 중요한 것은 서비스의 특징과 요구사항을 정확하게 파악하고, 각 데이터베이스의 장단점을 고려하여 &lt;strong&gt;최적의 데이터베이스를 선택&lt;/strong&gt;하는 것입니다.&lt;/p&gt;

&lt;p&gt;이번 포스팅이 NoSQL과 RDB에 대한 이해를 높이고, 데이터베이스 선택에 대한 막막함을 조금이나마 해소하는 데 도움이 되었기를 바랍니다.  혹시 더 궁금한 점이나 의견 있으시면 언제든지 댓글로 문의해주세요!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;더 읽어보면 좋은 자료:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.google.com/search?q=https://www.mongodb.com/compare/mongodb-vs-mysql" rel="noopener noreferrer"&gt;RDBMS vs NoSQL: Key Differences Explained&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;긴 글 읽어주셔서 감사합니다!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>JPA의 @ManyToOne 관계와 조인 전략</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Tue, 11 Mar 2025 04:14:09 +0000</pubDate>
      <link>https://dev.to/junghwan18/jpayi-manytoone-gwangyewa-join-jeonryag-3i6p</link>
      <guid>https://dev.to/junghwan18/jpayi-manytoone-gwangyewa-join-jeonryag-3i6p</guid>
      <description>&lt;h1&gt;
  
  
  JPA의 @ManyToOne 관계와 조인 전략
&lt;/h1&gt;

&lt;p&gt;JPA(Java Persistence API)를 사용하면 데이터베이스 작업을 객체 지향적으로 처리할 수 있다. 그러나 엔티티 간의 관계 설정과 쿼리 방식에 따라 애플리케이션의 성능과 데이터 무결성이 크게 달라질 수 있다.&lt;/p&gt;

&lt;p&gt;이 글에서는 &lt;code&gt;@ManyToOne&lt;/code&gt; 연관 관계를 중심으로, JPA의 조인 전략과 &lt;code&gt;@JoinColumn&lt;/code&gt;의 &lt;code&gt;nullable&lt;/code&gt; 속성이 쿼리에 미치는 영향을 살펴보고, 최적의 설정 방법을 제시한다.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 엔티티 관계와 @ManyToOne
&lt;/h2&gt;

&lt;p&gt;다음 예제 코드를 통해 &lt;code&gt;@ManyToOne&lt;/code&gt; 관계를 설정하는 방법을 살펴보자.&lt;/p&gt;

&lt;h3&gt;
  
  
  Team 엔티티
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Getter&lt;/span&gt; &lt;span class="nd"&gt;@Setter&lt;/span&gt;
&lt;span class="nd"&gt;@NoArgsConstructor&lt;/span&gt;
&lt;span class="nd"&gt;@AllArgsConstructor&lt;/span&gt;
&lt;span class="nd"&gt;@ToString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"members"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 순환 참조 방지&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Team&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt; &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TEAM_ID"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@OneToMany&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mappedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"team"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 양방향 관계&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Member&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 양방향 관계 편의 메서드&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;addMember&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Member&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTeam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Member 엔티티
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Getter&lt;/span&gt; &lt;span class="nd"&gt;@Setter&lt;/span&gt;
&lt;span class="nd"&gt;@NoArgsConstructor&lt;/span&gt;
&lt;span class="nd"&gt;@AllArgsConstructor&lt;/span&gt;
&lt;span class="nd"&gt;@ToString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"team"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 순환 참조 방지&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Member&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt; &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MEMBER_ID"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@ManyToOne&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FetchType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;LAZY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 지연 로딩&lt;/span&gt;
    &lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TEAM_ID"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// NULL 허용 여부 설정&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Team&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Member&lt;/code&gt; 엔티티는 &lt;code&gt;Team&lt;/code&gt; 엔티티와 &lt;code&gt;@ManyToOne&lt;/code&gt; 관계를 가진다. &lt;code&gt;@JoinColumn(name = "TEAM_ID")&lt;/code&gt;을 통해 &lt;code&gt;Member&lt;/code&gt; 테이블에 외래 키(Foreign Key) 컬럼을 생성한다.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. fetch 속성: 즉시 로딩 vs. 지연 로딩
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@ManyToOne&lt;/code&gt; 어노테이션의 &lt;code&gt;fetch&lt;/code&gt; 속성은 연관된 엔티티를 언제 가져올지를 결정한다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;fetch 속성&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;장점&lt;/th&gt;
&lt;th&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EAGER&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Member&lt;/code&gt; 조회 시 &lt;code&gt;Team&lt;/code&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;LAZY&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Member&lt;/code&gt; 조회 시 &lt;code&gt;Team&lt;/code&gt;은 필요할 때 조회&lt;/td&gt;
&lt;td&gt;성능 최적화 가능&lt;/td&gt;
&lt;td&gt;추가 쿼리 발생 가능 (N+1 문제)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;N+1 문제를 방지하려면 &lt;code&gt;JOIN FETCH&lt;/code&gt; (JPQL) 또는 &lt;code&gt;@EntityGraph&lt;/code&gt;를 활용하는 것이 좋다.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. nullable 속성: NULL 허용 여부와 조인 전략
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@JoinColumn&lt;/code&gt;의 &lt;code&gt;nullable&lt;/code&gt; 속성은 외래 키 컬럼의 NULL 허용 여부를 설정하며, JPA가 생성하는 조인 쿼리의 종류에 영향을 준다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;nullable 속성&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;조인 방식&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;true&lt;/code&gt; (기본값)&lt;/td&gt;
&lt;td&gt;외래 키 컬럼이 NULL을 허용&lt;/td&gt;
&lt;td&gt;LEFT OUTER JOIN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;외래 키 컬럼이 NULL을 허용하지 않음&lt;/td&gt;
&lt;td&gt;INNER JOIN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  NULL 허용 여부에 따른 조인 방식 변화
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nullable = true&lt;/code&gt; → &lt;code&gt;LEFT OUTER JOIN&lt;/code&gt; 사용 (연결된 &lt;code&gt;Team&lt;/code&gt;이 없어도 &lt;code&gt;Member&lt;/code&gt; 조회 가능)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nullable = false&lt;/code&gt; → &lt;code&gt;INNER JOIN&lt;/code&gt; 사용 (연결된 &lt;code&gt;Team&lt;/code&gt;이 없는 &lt;code&gt;Member&lt;/code&gt;는 조회되지 않음)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;INNER JOIN이 LEFT OUTER JOIN보다 성능상 유리한 경우가 많다.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. 실험: fetch와 nullable 조합에 따른 쿼리 비교
&lt;/h2&gt;

&lt;h3&gt;
  
  
  EAGER + nullable = true (LEFT OUTER JOIN)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEMBER_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAM_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Member&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Team&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAM_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAM_ID&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEMBER_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  EAGER + nullable = false (INNER JOIN)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEMBER_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAM_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAM_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Member&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Team&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAM_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAM_ID&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEMBER_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  LAZY (JOIN 없음, 추가 쿼리 발생)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Member 조회 (Team 조인 없음)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEMBER_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAM_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Member&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEMBER_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- findMember.getTeam().getName() 호출 시 추가 쿼리 실행&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAM_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Team&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAM_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;code&gt;em.getReference(Team.class, id)&lt;/code&gt;를 사용하면 프록시 객체가 반환된다.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 최적의 설정 선택 가이드
&lt;/h2&gt;

&lt;p&gt;✅ 데이터 무결성을 유지하려면:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nullable = false&lt;/code&gt; (모든 &lt;code&gt;Member&lt;/code&gt;는 반드시 &lt;code&gt;Team&lt;/code&gt;에 속해야 함)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;INNER JOIN&lt;/code&gt;을 통해 성능 향상 가능&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ 성능 최적화를 원한다면:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FetchType.LAZY&lt;/code&gt; 사용 (불필요한 데이터 로딩 방지)&lt;/li&gt;
&lt;li&gt;필요 시 &lt;code&gt;JOIN FETCH(JPQL)&lt;/code&gt; 또는 &lt;code&gt;@EntityGraph&lt;/code&gt; 활용&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ 유연한 데이터 모델이 필요하다면:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nullable = true&lt;/code&gt; (소속되지 않은 &lt;code&gt;Member&lt;/code&gt;도 허용)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LEFT OUTER JOIN&lt;/code&gt;을 사용하여 데이터 손실 방지&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ ID만 필요할 경우:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;em.getReference()&lt;/code&gt; 활용 (불필요한 데이터 조회 방지)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  결론
&lt;/h2&gt;

&lt;p&gt;JPA의 조인 전략과 NULL 제약 조건을 적절히 활용하면 애플리케이션의 성능과 데이터 무결성을 동시에 확보할 수 있다.&lt;/p&gt;

&lt;p&gt;프로젝트에서 요구하는 데이터 모델과 성능 요건을 고려하여 최적의 설정을 선택하는 것이 중요하다.&lt;/p&gt;

</description>
      <category>java</category>
      <category>jpa</category>
      <category>spring</category>
    </item>
    <item>
      <title>다대다 관계가 아닌데도 조인 테이블이 필요할까?</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Tue, 11 Mar 2025 04:12:21 +0000</pubDate>
      <link>https://dev.to/junghwan18/dadaeda-gwangyega-anindedo-join-teibeuli-pilyohalgga-3h1b</link>
      <guid>https://dev.to/junghwan18/dadaeda-gwangyega-anindedo-join-teibeuli-pilyohalgga-3h1b</guid>
      <description>&lt;h1&gt;
  
  
  다대다 관계가 아닌데도 조인 테이블이 필요할까?
&lt;/h1&gt;

&lt;h2&gt;
  
  
  언제, 왜 사용해야 하는지 알아보기
&lt;/h2&gt;

&lt;p&gt;데이터베이스를 설계하다 보면 "다대다(many-to-many) 관계가 아닐 때도 조인 테이블을 사용해야 할까?"라는 고민이 생길 수 있습니다. 일반적으로 조인 테이블은 다대다 관계를 관리하기 위해 사용되지만, &lt;strong&gt;일대다(one-to-many)나 일대일(one-to-one) 관계에서도 유용한 경우가 있습니다.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;이 글에서는 조인 테이블이 필요한 특별한 상황을 살펴보고, 장점과 주의할 점을 정리해보겠습니다.  &lt;/p&gt;




&lt;h2&gt;
  
  
  1. 조인 테이블이란?
&lt;/h2&gt;

&lt;p&gt;조인 테이블(연결 테이블, bridge table)은 &lt;strong&gt;두 테이블 간의 관계를 관리하는 중간 테이블&lt;/strong&gt;입니다.  &lt;/p&gt;

&lt;h3&gt;
  
  
  예제: 다대다 관계에서의 조인 테이블
&lt;/h3&gt;

&lt;p&gt;학생과 강의 테이블이 있다고 가정해봅시다.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;한 학생은 여러 강의를 들을 수 있고,
&lt;/li&gt;
&lt;li&gt;한 강의에는 여러 학생이 참여할 수 있습니다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 경우 &lt;code&gt;학생_강의&lt;/code&gt;라는 &lt;strong&gt;조인 테이블&lt;/strong&gt;을 만들어 관계를 표현합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="err"&gt;학생&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;강의&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="err"&gt;학생&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;강의&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;학생&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;강의&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;학생&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="err"&gt;학생&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;강의&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="err"&gt;강의&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 하면 학생과 강의 간의 다대다 관계를 효과적으로 관리할 수 있습니다.  &lt;/p&gt;




&lt;h2&gt;
  
  
  2. 일대다 관계에서 조인 테이블이 필요할 때
&lt;/h2&gt;

&lt;p&gt;일반적으로 &lt;strong&gt;일대다 관계&lt;/strong&gt;는 외래 키(FK)를 사용해 해결합니다.  &lt;/p&gt;

&lt;p&gt;예를 들어, 한 부서에 여러 직원이 속하는 경우, 직원 테이블에 &lt;code&gt;부서_ID&lt;/code&gt; 외래 키를 추가하면 됩니다.  &lt;/p&gt;

&lt;p&gt;하지만 &lt;strong&gt;특정한 경우에는 조인 테이블을 사용하는 것이 더 유리할 수 있습니다.&lt;/strong&gt;  &lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ 2.1 관계에 추가 정보(메타데이터)가 필요할 때
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;직원과 부서의 관계를 단순히 저장하는 것뿐만 아니라,

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;배정 날짜&lt;/strong&gt;, &lt;strong&gt;직위&lt;/strong&gt;, &lt;strong&gt;급여&lt;/strong&gt; 등의 정보를 함께 관리해야 한다면?
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;조인 테이블을 사용하면 관계에 대한 추가 정보를 저장할 수 있습니다.
&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="err"&gt;직원&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;부서&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="err"&gt;직원&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;부서&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;배정&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;날짜&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;직위&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="err"&gt;급여&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;직원&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;부서&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;직원&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="err"&gt;직원&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;부서&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="err"&gt;부서&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ 2.2 시간에 따른 이력 관리가 필요할 때
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;직원이 과거에 속했던 부서 정보를 유지하고 싶다면?&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;기존 방식(직원 테이블에 &lt;code&gt;부서_ID&lt;/code&gt; FK 추가)으로는 현재 소속 부서만 저장할 수 있습니다.
&lt;/li&gt;
&lt;li&gt;조인 테이블을 사용하면 부서 변경 이력을 관리할 수 있습니다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="err"&gt;직원&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;부서&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;이력&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="err"&gt;직원&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;부서&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;시작&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;날짜&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;종료&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;날짜&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;직원&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;부서&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;시작&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;날짜&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;직원&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="err"&gt;직원&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;부서&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="err"&gt;부서&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ 2.3 미래 확장성을 고려할 때
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;현재는 직원이 &lt;strong&gt;한 부서에만&lt;/strong&gt; 속할 수 있지만,
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;나중에 한 직원이 여러 부서에 속할 가능성이 있다면?&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;조인 테이블을 미리 설계해두면 향후 변경이 쉬워집니다.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. 일대일 관계에서 조인 테이블이 필요할 때
&lt;/h2&gt;

&lt;p&gt;일대일 관계에서는 보통 한 테이블에 &lt;strong&gt;외래 키를 추가&lt;/strong&gt;하면 충분합니다.&lt;br&gt;&lt;br&gt;
그러나, &lt;strong&gt;특정한 경우에는 조인 테이블이 더 좋은 선택이 될 수 있습니다.&lt;/strong&gt;  &lt;/p&gt;
&lt;h3&gt;
  
  
  ✅ 3.1 보안 요구사항이 있을 때
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;민감한 정보를 별도로 저장해야 하는 경우&lt;/strong&gt;, 조인 테이블을 사용할 수 있습니다.
&lt;/li&gt;
&lt;li&gt;예를 들어, &lt;code&gt;사용자&lt;/code&gt; 테이블과 &lt;code&gt;재무 정보&lt;/code&gt; 테이블을 분리하면,

&lt;ul&gt;
&lt;li&gt;일반 사용자 데이터(&lt;code&gt;이름&lt;/code&gt;, &lt;code&gt;이메일&lt;/code&gt;)와
&lt;/li&gt;
&lt;li&gt;민감한 데이터(&lt;code&gt;급여&lt;/code&gt;, &lt;code&gt;계좌 번호&lt;/code&gt;)를 &lt;strong&gt;다른 접근 권한&lt;/strong&gt;으로 관리할 수 있습니다.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="err"&gt;사용자&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;이름&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="err"&gt;이메일&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;재무정보&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;급여&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="err"&gt;계좌&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;번호&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  ✅ 3.2 선택적 관계를 표현할 때
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;일부 데이터만 특정 관계를 가질 필요가 있는 경우,
&lt;/li&gt;
&lt;li&gt;테이블을 분리하면 &lt;strong&gt;불필요한 NULL 값을 줄일 수 있습니다.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어, 일부 고객만 &lt;strong&gt;프리미엄 멤버십&lt;/strong&gt;을 이용하는 경우:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="err"&gt;고객&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="err"&gt;고객&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;이름&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="err"&gt;고객&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;멤버십&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="err"&gt;고객&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;멤버십&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;시작일&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;멤버십&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;등급&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;고객&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="err"&gt;고객&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;고객&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→ 모든 고객이 멤버십 정보를 가질 필요가 없으므로 &lt;strong&gt;NULL 컬럼을 방지할 수 있습니다.&lt;/strong&gt;  &lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ 3.3 성능 최적화를 위해 테이블을 분할할 때
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;자주 조회하는 데이터와 &lt;strong&gt;드물게 사용되는 데이터&lt;/strong&gt;를 분리하면 성능이 향상될 수 있습니다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예를 들어, &lt;strong&gt;사용자 프로필을 분리하여 관리하는 경우:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="err"&gt;사용자&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;이름&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="err"&gt;이메일&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;프로필&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;주소&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="err"&gt;생년월일&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;사용자&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→ &lt;code&gt;사용자&lt;/code&gt; 테이블만 자주 조회되므로 &lt;strong&gt;쿼리 성능이 향상&lt;/strong&gt;될 가능성이 큽니다.  &lt;/p&gt;




&lt;h2&gt;
  
  
  4. 조인 테이블 사용 시 주의할 점
&lt;/h2&gt;

&lt;p&gt;조인 테이블은 유용하지만, &lt;strong&gt;무조건 좋은 것은 아닙니다.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
사용하기 전에 다음을 고려하세요.  &lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;쿼리 복잡성 증가&lt;/strong&gt; → JOIN이 많아질수록 쿼리가 어려워질 수 있음.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;성능 저하 가능성&lt;/strong&gt; → JOIN이 많아지면 조회 속도가 느려질 수 있음.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;무결성 관리 필요&lt;/strong&gt; → 외래 키 제약 조건을 신경 써야 함.  &lt;/p&gt;




&lt;h2&gt;
  
  
  5. 결론: 언제 조인 테이블을 사용할까?
&lt;/h2&gt;

&lt;p&gt;✅ 관계에 &lt;strong&gt;추가 정보(메타데이터)&lt;/strong&gt; 가 필요할 때&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;시간에 따른 이력 관리&lt;/strong&gt; 가 필요할 때&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;미래의 확장성을 고려&lt;/strong&gt; 해야 할 때&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;보안, 선택적 관계, 성능 최적화&lt;/strong&gt; 가 필요할 때  &lt;/p&gt;

&lt;p&gt;하지만, &lt;strong&gt;단순한 관계라면 외래 키만 사용하는 것이 더 효율적&lt;/strong&gt;일 수도 있습니다.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;요구사항을 꼼꼼히 검토하고, 정말 필요한 경우에만 사용하는 것이 중요합니다!&lt;/strong&gt; 🚀  &lt;/p&gt;




&lt;p&gt;이제 조인 테이블을 언제, 왜 사용해야 하는지 감이 잡히셨나요?&lt;br&gt;&lt;br&gt;
추가 질문이 있다면 언제든지 댓글 남겨주세요!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>JPA에서 복합 키(Composite Key)와 인덱스 순서 최적화: 성능 향상을 위한 실전 가이드</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Tue, 11 Mar 2025 04:02:55 +0000</pubDate>
      <link>https://dev.to/junghwan18/jpaeseo-boghab-kicomposite-keywa-indegseu-sunseo-coejeoghwa-seongneung-hyangsangeul-wihan-siljeon-gaideu-33pa</link>
      <guid>https://dev.to/junghwan18/jpaeseo-boghab-kicomposite-keywa-indegseu-sunseo-coejeoghwa-seongneung-hyangsangeul-wihan-siljeon-gaideu-33pa</guid>
      <description>&lt;p&gt;&lt;strong&gt;JPA에서 복합 키(Composite Key)와 인덱스 순서 최적화: 성능 향상을 위한 실전 가이드&lt;/strong&gt;  &lt;/p&gt;




&lt;h2&gt;
  
  
  JPA에서 복합 키와 인덱스 순서 최적화
&lt;/h2&gt;

&lt;p&gt;JPA를 활용한 엔티티 매핑에서 복합 키(Composite Key)의 인덱스 순서는 쿼리 성능에 직접적인 영향을 미친다. 본 글에서는 복합 키 인덱스의 동작 방식과 JPA에서 이를 효과적으로 관리하는 방법을 다룬다.  &lt;/p&gt;




&lt;h2&gt;
  
  
  1. 복합 키와 기본 키 인덱스
&lt;/h2&gt;

&lt;h3&gt;
  
  
  복합 키란?
&lt;/h3&gt;

&lt;p&gt;복합 키(Composite Key)는 두 개 이상의 컬럼을 조합하여 레코드를 고유하게 식별하는 키다. 예를 들어, &lt;code&gt;CHILD&lt;/code&gt; 테이블에서 &lt;code&gt;(PARENT_ID, CHILD_ID)&lt;/code&gt;를 복합 키로 설정하면 각 레코드가 고유하게 관리된다.  &lt;/p&gt;

&lt;h3&gt;
  
  
  기본 키 인덱스의 특징
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;고유성(Unique)&lt;/strong&gt;: 중복 값을 허용하지 않는다.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NOT NULL&lt;/strong&gt;: 기본 키는 &lt;code&gt;NULL&lt;/code&gt; 값을 허용하지 않는다.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;클러스터형 vs. 비클러스터형 인덱스&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MySQL(InnoDB), SQL Server&lt;/strong&gt; → &lt;strong&gt;클러스터형 인덱스&lt;/strong&gt;: 데이터가 기본 키 순서대로 물리적으로 저장됨.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oracle&lt;/strong&gt; → &lt;strong&gt;비클러스터형 인덱스&lt;/strong&gt;: 인덱스가 데이터의 물리적 저장 순서와 무관하게 동작함.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. 인덱스 순서가 성능에 미치는 영향
&lt;/h2&gt;

&lt;p&gt;B-트리 인덱스는 &lt;strong&gt;왼쪽에서 오른쪽으로 조건을 평가&lt;/strong&gt;하기 때문에 복합 키의 컬럼 순서는 성능에 큰 영향을 미친다.  &lt;/p&gt;

&lt;h3&gt;
  
  
  인덱스 컬럼 순서에 따른 성능 차이
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;CHILD&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;PARENT_ID&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;CHILD_ID&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;NAME&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CHILD_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;효율적인 쿼리 (PARENT_ID, CHILD_ID 순서)&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;CHILD&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;PARENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PARENT_ID&lt;/strong&gt;가 첫 번째 컬럼이므로, 인덱스를 빠르게 탐색 가능.
&lt;/li&gt;
&lt;li&gt;인덱스 스캔 범위가 제한되므로 성능이 향상됨.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;비효율적인 쿼리 (CHILD_ID, PARENT_ID 순서)&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;CHILD&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;PARENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;인덱스가 (CHILD_ID, PARENT_ID) 순서라면, &lt;code&gt;PARENT_ID&lt;/code&gt;만으로는 인덱스를 활용할 수 없음.
&lt;/li&gt;
&lt;li&gt;결국 &lt;strong&gt;풀 테이블 스캔(full table scan)&lt;/strong&gt; 이 발생할 가능성이 높음.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;인덱스 컬럼 배치 전략&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;카디널리티(Cardinality, 고유 값 개수)가 높은 컬럼을 앞에 배치&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;고유 값이 많으면 검색 범위를 빠르게 좁힐 수 있음.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;자주 &lt;code&gt;=&lt;/code&gt;(Equal) 조건으로 검색되는 컬럼을 앞에 배치&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;필터링을 우선적으로 수행하여 효율적인 인덱스 사용 가능.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;범위 조건(&lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;BETWEEN&lt;/code&gt;, &lt;code&gt;LIKE&lt;/code&gt;)이 포함된 컬럼은 뒤에 배치&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;앞쪽에 위치하면 인덱스 탐색이 비효율적일 수 있음.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  3. JPA에서 복합 키 매핑
&lt;/h2&gt;

&lt;p&gt;JPA에서 복합 키는 &lt;code&gt;@IdClass&lt;/code&gt;와 &lt;code&gt;@EmbeddedId&lt;/code&gt;를 사용해 정의할 수 있다.  &lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 &lt;code&gt;@IdClass&lt;/code&gt; 방식
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 식별자 클래스&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChildId&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Serializable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 첫 번째 필드 (PARENT_ID)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;childId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 두 번째 필드 (CHILD_ID)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 엔티티 클래스&lt;/span&gt;
&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@IdClass&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChildId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@ManyToOne&lt;/span&gt;
    &lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"PARENT_ID"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CHILD_ID"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;childId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;: 인덱스가 &lt;strong&gt;(PARENT_ID, CHILD_ID)&lt;/strong&gt; 순서로 생성됨.  &lt;/p&gt;




&lt;h3&gt;
  
  
  3.2 &lt;code&gt;@EmbeddedId&lt;/code&gt; 방식
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Embeddable&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChildId&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Serializable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"PARENT_ID"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 첫 번째 필드&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CHILD_ID"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;childId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 두 번째 필드&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@EmbeddedId&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ChildId&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;: 동일하게 &lt;strong&gt;(PARENT_ID, CHILD_ID)&lt;/strong&gt; 순서로 인덱스가 생성됨.  &lt;/p&gt;




&lt;h2&gt;
  
  
  4. 실전 예제: 복합 키와 쿼리 성능 최적화
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 자주 실행되는 쿼리
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;CHILD&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;PARENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;CHILD&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;PARENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;CHILD_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'B'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;CHILD&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;PARENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;CHILD_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.2 최적의 인덱스 순서: &lt;strong&gt;(PARENT_ID, CHILD_ID)&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PARENT_ID&lt;/code&gt;가 = 조건으로 사용되므로 첫 번째 컬럼.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CHILD_ID&lt;/code&gt;는 정렬 및 추가 필터링을 위해 두 번째 컬럼.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.3 비효율적인 순서: &lt;strong&gt;(CHILD_ID, PARENT_ID)&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PARENT_ID&lt;/code&gt;로 검색할 때 인덱스를 제대로 활용할 수 없음.
&lt;/li&gt;
&lt;li&gt;인덱스 전체를 스캔해야 할 가능성이 커짐.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. 데이터베이스별 인덱스 동작 차이
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;데이터베이스&lt;/th&gt;
&lt;th&gt;인덱스 유형&lt;/th&gt;
&lt;th&gt;성능 영향&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MySQL (InnoDB)&lt;/strong&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;strong&gt;SQL Server&lt;/strong&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;strong&gt;Oracle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;비클러스터형 인덱스&lt;/td&gt;
&lt;td&gt;물리적 저장과 무관, 인덱스 탐색 효율성이 핵심&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  6. 성능 최적화를 위한 설계 가이드
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;쿼리 패턴 분석&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;어떤 컬럼이 &lt;code&gt;=&lt;/code&gt; 조건으로 자주 사용되는지 확인.
&lt;/li&gt;
&lt;li&gt;범위 조건(&lt;code&gt;BETWEEN&lt;/code&gt;, &lt;code&gt;LIKE&lt;/code&gt;)을 포함하는 컬럼을 분석.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;카디널리티 고려&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;고유 값이 많은 컬럼을 앞에 배치하여 인덱스 효율 극대화.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;실행 계획(EXPLAIN) 확인&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;실제로 인덱스가 적절히 사용되는지 점검.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  7. 결론
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;복합 키의 인덱스 순서는 쿼리 성능 최적화의 핵심 요소&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;카디널리티가 높은 컬럼, 자주 사용되는 필터 컬럼을 먼저 배치&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JPA에서 &lt;code&gt;@IdClass&lt;/code&gt; 또는 &lt;code&gt;@EmbeddedId&lt;/code&gt; 사용 시 필드 순서 주의&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;실행 계획(EXPLAIN)을 활용하여 최적화된 인덱스 설계 필요&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 가이드를 통해 &lt;strong&gt;JPA에서 복합 키를 효과적으로 관리&lt;/strong&gt;하고 &lt;strong&gt;쿼리 성능을 최적화&lt;/strong&gt;해보자.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>JPA @GeneratedValue 전략 완벽 가이드: AUTO, IDENTITY, SEQUENCE, TABLE 차이점과 선택 기준</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Tue, 11 Mar 2025 04:00:47 +0000</pubDate>
      <link>https://dev.to/junghwan18/jpa-generatedvalue-jeonryag-wanbyeog-gaideu-auto-identity-sequence-table-caijeomgwa-seontaeg-gijun-nba</link>
      <guid>https://dev.to/junghwan18/jpa-generatedvalue-jeonryag-wanbyeog-gaideu-auto-identity-sequence-table-caijeomgwa-seontaeg-gijun-nba</guid>
      <description>&lt;p&gt;&lt;strong&gt;JPA @GeneratedValue 전략 완벽 가이드: AUTO, IDENTITY, SEQUENCE, TABLE 차이점과 선택 기준&lt;/strong&gt;  &lt;/p&gt;




&lt;h2&gt;
  
  
  JPA의 @GeneratedValue 전략별 동작 방식과 특징
&lt;/h2&gt;

&lt;p&gt;JPA를 사용하다 보면 엔티티의 기본 키(Primary Key)를 어떻게 생성할지 고민하게 된다. 특히 비식별 관계에서는 대리 키(Surrogate Key)를 기본 키로 사용하는 경우가 많다. JPA는 이를 위해 &lt;code&gt;@GeneratedValue&lt;/code&gt; 어노테이션을 제공하며, 다양한 키 생성 전략을 지원한다.  &lt;/p&gt;

&lt;p&gt;이번 글에서는 &lt;code&gt;@GeneratedValue&lt;/code&gt;의 동작 방식과 각 전략의 특징을 자세히 살펴본다.  &lt;/p&gt;




&lt;h2&gt;
  
  
  @GeneratedValue란?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@GeneratedValue&lt;/code&gt;는 JPA에서 엔티티의 기본 키 값을 자동으로 생성하도록 설정하는 어노테이션이다. 데이터베이스와 애플리케이션 레벨에서 협력하여 키를 생성하며, &lt;code&gt;strategy&lt;/code&gt; 속성을 통해 생성 방식을 지정할 수 있다.  &lt;/p&gt;

&lt;p&gt;지원되는 주요 전략은 &lt;code&gt;AUTO&lt;/code&gt;, &lt;code&gt;IDENTITY&lt;/code&gt;, &lt;code&gt;SEQUENCE&lt;/code&gt;, &lt;code&gt;TABLE&lt;/code&gt; 네 가지이며, 각각의 동작 방식과 장단점은 다음과 같다.  &lt;/p&gt;




&lt;h2&gt;
  
  
  1. GenerationType.AUTO: 데이터베이스에 맡기기
&lt;/h2&gt;

&lt;h3&gt;
  
  
  특징
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;JPA 구현체(예: Hibernate)가 사용하는 데이터베이스에 따라 전략을 자동 선택한다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  동작 방식
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;MySQL, PostgreSQL → &lt;code&gt;IDENTITY&lt;/code&gt; 방식 사용 (AUTO_INCREMENT 활용)
&lt;/li&gt;
&lt;li&gt;Oracle → &lt;code&gt;SEQUENCE&lt;/code&gt; 방식 사용
&lt;/li&gt;
&lt;li&gt;H2 → 설정에 따라 &lt;code&gt;IDENTITY&lt;/code&gt; 또는 &lt;code&gt;SEQUENCE&lt;/code&gt; 선택
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  장점
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;데이터베이스에 의존하지 않고 유연하게 사용할 수 있다.
&lt;/li&gt;
&lt;li&gt;개발 초기 단계나 DB 종류가 자주 바뀔 때 유용하다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  주의점
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;명확한 전략이 아니므로 운영 환경에서 의도와 다르게 동작할 가능성이 있다.
&lt;/li&gt;
&lt;li&gt;운영 환경에서는 명시적으로 지정하는 것이 좋다.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. GenerationType.IDENTITY: 데이터베이스의 자동 증가 기능 사용
&lt;/h2&gt;

&lt;h3&gt;
  
  
  특징
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;MySQL의 &lt;code&gt;AUTO_INCREMENT&lt;/code&gt;, SQL Server의 &lt;code&gt;IDENTITY&lt;/code&gt; 등 데이터베이스 자체 기능을 활용한다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  동작 방식
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;persist()&lt;/code&gt; 호출 시 즉시 &lt;code&gt;INSERT&lt;/code&gt; 쿼리를 실행한다.
&lt;/li&gt;
&lt;li&gt;데이터베이스가 &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; 또는 &lt;code&gt;IDENTITY&lt;/code&gt; 컬럼에서 키 값을 생성한다.
&lt;/li&gt;
&lt;li&gt;생성된 키 값을 JPA가 엔티티에 설정한다.
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  장점
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;데이터베이스가 키 생성을 담당하므로 구현이 간단하다.
&lt;/li&gt;
&lt;li&gt;직관적인 방식이며 추가 설정이 거의 필요 없다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  단점
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;persist()&lt;/code&gt; 시 즉시 &lt;code&gt;INSERT&lt;/code&gt;가 실행되므로 &lt;strong&gt;쓰기 지연(Transactional Write-Behind)&lt;/strong&gt; 이 불가능하다.
&lt;/li&gt;
&lt;li&gt;여러 엔티티를 한 번에 삽입하는 배치 작업에서 성능이 저하될 수 있다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  사용 예시 (MySQL)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Member&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. GenerationType.SEQUENCE: 시퀀스를 활용한 키 생성
&lt;/h2&gt;

&lt;h3&gt;
  
  
  특징
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Oracle, PostgreSQL 등에서 지원하는 시퀀스 객체를 활용한다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  동작 방식
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;persist()&lt;/code&gt; 호출 시 JPA가 시퀀스에서 다음 값을 가져온다.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="k"&gt;NEXT&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;MY_SEQUENCE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;가져온 값을 엔티티의 식별자에 설정한다.
&lt;/li&gt;
&lt;li&gt;트랜잭션 커밋 시점에 &lt;code&gt;INSERT&lt;/code&gt; 쿼리를 실행한다.
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  장점
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;allocationSize&lt;/code&gt;를 설정하면 시퀀스 값을 미리 할당받아 성능을 최적화할 수 있다.
&lt;/li&gt;
&lt;li&gt;쓰기 지연(Transactional Write-Behind)이 가능하여 배치 작업에서 성능이 뛰어나다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  주의점
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;데이터베이스에 시퀀스 객체가 미리 생성되어 있어야 한다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  사용 예시 (Oracle)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@SequenceGenerator&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MEMBER_SEQ_GENERATOR"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequenceName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MEMBER_SEQ"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allocationSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Member&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEQUENCE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MEMBER_SEQ_GENERATOR"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. GenerationType.TABLE: 키 생성 테이블 활용
&lt;/h2&gt;

&lt;h3&gt;
  
  
  특징
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;별도의 키 생성용 테이블을 만들어 기본 키를 관리한다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  동작 방식
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;JPA가 키 생성 테이블에서 값을 조회하고 업데이트하며 키를 생성한다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  장점
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;모든 데이터베이스에서 동작하기 때문에 이식성이 뛰어나다.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  단점
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;테이블 접근으로 인해 성능이 저하될 수 있으며 잘 사용되지 않는 방식이다.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  애플리케이션 vs. 데이터베이스: 키 생성 주체 비교
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;전략&lt;/th&gt;
&lt;th&gt;키 생성 주체&lt;/th&gt;
&lt;th&gt;주요 특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IDENTITY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;데이터베이스&lt;/td&gt;
&lt;td&gt;DB가 키를 생성하며, JPA는 결과만 받아옴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SEQUENCE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JPA&lt;/td&gt;
&lt;td&gt;JPA가 시퀀스를 호출해 키를 가져옴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TABLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JPA&lt;/td&gt;
&lt;td&gt;키 생성용 테이블을 활용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AUTO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;데이터베이스에 따라 다름&lt;/td&gt;
&lt;td&gt;유연하지만 운영 환경에서 주의 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  MySQL과 @GeneratedValue
&lt;/h2&gt;

&lt;p&gt;MySQL에서 &lt;code&gt;AUTO_INCREMENT&lt;/code&gt;를 사용할 때는 &lt;code&gt;GenerationType.IDENTITY&lt;/code&gt;를 선택하는 것이 일반적이다.  &lt;/p&gt;

&lt;p&gt;JPA는 &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; 컬럼을 비워둔 채 &lt;code&gt;INSERT&lt;/code&gt;를 수행하고, MySQL이 생성한 값을 받아 엔티티에 설정한다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;위 테이블과 함께 &lt;code&gt;GenerationType.IDENTITY&lt;/code&gt;를 사용하면 자연스럽게 동작한다.  &lt;/p&gt;




&lt;h2&gt;
  
  
  어떤 전략을 선택해야 할까?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MySQL / PostgreSQL&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;간단한 프로젝트라면 &lt;code&gt;IDENTITY&lt;/code&gt;로 충분
&lt;/li&gt;
&lt;li&gt;배치 작업이 많다면 &lt;code&gt;SEQUENCE&lt;/code&gt; 고려
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Oracle&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;시퀀스를 활용한 &lt;code&gt;SEQUENCE&lt;/code&gt;가 적합
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;배치 작업이 많다면?&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SEQUENCE&lt;/code&gt;의 쓰기 지연과 &lt;code&gt;allocationSize&lt;/code&gt; 최적화 활용
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;DB 독립성이 중요하다면?&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TABLE&lt;/code&gt; 고려 가능 (하지만 성능 저하 감수해야 함)
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  결론
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@GeneratedValue&lt;/code&gt;는 JPA에서 대리 키를 효율적으로 생성하는 강력한 도구다. 하지만 데이터베이스 종류, 성능 요구사항, 개발 편의성을 고려해 적절한 전략을 선택하는 것이 중요하다.  &lt;/p&gt;

&lt;p&gt;각 전략의 동작 방식과 특징을 이해하고 프로젝트에 맞는 방식을 선택하면 더욱 효과적인 JPA 활용이 가능할 것이다.&lt;/p&gt;

</description>
      <category>java</category>
      <category>webdev</category>
    </item>
    <item>
      <title>범위 한정될 때 유리한 계수 정렬</title>
      <dc:creator>junghwan</dc:creator>
      <pubDate>Tue, 11 Mar 2025 03:57:17 +0000</pubDate>
      <link>https://dev.to/junghwan18/susjayi-beomwiga-hanjeongdoeeo-isseul-ddaen-gyesujeongryeoldo-gominhaeboja-1j7h</link>
      <guid>https://dev.to/junghwan18/susjayi-beomwiga-hanjeongdoeeo-isseul-ddaen-gyesujeongryeoldo-gominhaeboja-1j7h</guid>
      <description>&lt;p&gt;계수 정렬(Counting Sort)은 비교 기반 정렬 알고리즘과 달리 데이터의 크기 범위가 한정되어 있을 때 매우 빠른 성능을 발휘하는 알고리즘입니다. 이 글에서는 계수 정렬의 원리, 구현 방법, 시간/공간 복잡도, 그리고 장단점에 대해 자세히 알아보겠습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  계수 정렬의 원리
&lt;/h2&gt;

&lt;p&gt;계수 정렬은 정렬 대상이 되는 데이터들이 모두 정수이며, 그 범위가 제한적일 경우 효과적입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;작동 원리&lt;/strong&gt;: 입력 배열에 존재하는 각 정수의 등장 횟수를 미리 세어 두고, 이 횟수를 이용하여 최종 정렬된 배열에서 각 숫자가 위치할 인덱스를 결정합니다.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;비교 연산이 없음&lt;/strong&gt;: 기존의 선택, 삽입, 퀵 정렬과 달리 서로 값을 비교하지 않고, 각 숫자가 몇 번 등장하는지를 기반으로 정렬을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  동작 과정
&lt;/h2&gt;

&lt;p&gt;계수 정렬의 전형적인 동작 과정은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;1. 최대값 및 최소값 확인&lt;/strong&gt;: 배열의 전체 범위를 파악하기 위해 최대값(또는 최소값) 정보를 추출합니다.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;2. 카운팅 배열 생성&lt;/strong&gt;: 입력 배열의 값들이 모두 들어갈 수 있는 크기의 새로운 배열(카운팅 배열)을 생성한 후, 각 인덱스에 대응하는 값이 등장하는 횟수를 저장합니다.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;3. 누적 합 계산&lt;/strong&gt;: 카운팅 배열의 각 원소에 대해 누적합을 계산하여, 해당 값이 최종 정렬 배열에서 몇 번째 인덱스부터 배치되어야 하는지를 결정합니다.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;4. 정렬 배열 생성&lt;/strong&gt;: 입력 배열을 뒤에서부터 순회하면서, 누적합에 따른 위치에 값을 배치하고, 배치한 후에는 해당 카운트 값을 감소시킵니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  코드 예제 (Python)
&lt;/h2&gt;

&lt;p&gt;아래는 파이썬으로 구현한 계수 정렬의 예시입니다. 이 코드는 입력 배열의 값들이 0 이상의 정수이고, 최대값이 비교적 작은 경우에 유용하게 동작합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;counting_sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;

    &lt;span class="n"&gt;max_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 카운팅 배열 생성 (0부터 max_val까지 모두 포함)
&lt;/span&gt;    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_val&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 각 원소의 등장 횟수를 기록
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="c1"&gt;# 누적 합 계산: 각 인덱스가 최종 배열에서 시작할 위치를 결정
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# 정렬된 결과를 저장할 배열 생성
&lt;/span&gt;    &lt;span class="n"&gt;sorted_arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 원본 배열을 뒤에서부터 순회하면 안정적인 정렬이 가능하다.
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sorted_arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sorted_arr&lt;/span&gt;

&lt;span class="c1"&gt;# 예제 실행
&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;정렬 전: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;정렬 후: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;counting_sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 코드는 먼저 각 수의 빈도수를 기록한 뒤, 누적합을 계산하여 정렬된 배열의 각 숫자 위치를 결정합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  시간 및 공간 복잡도
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;시간 복잡도&lt;/strong&gt;: 입력 배열의 크기를 n, 데이터의 범위를 k라고 할 때, 계수 정렬은 $$O(n + k)$$의 시간 복잡도를 가집니다. 따라서 k가 n에 비해 작은 경우 매우 빠르게 동작합니다.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;공간 복잡도&lt;/strong&gt;: 별도의 카운팅 배열을 필요로 하므로 $$O(k)$$의 추가 공간이 필요합니다. 이 점은 데이터의 범위가 클 경우 단점으로 작용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  장점과 단점
&lt;/h2&gt;

&lt;p&gt;아래 표는 계수 정렬의 장단점을 정리한 것입니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;항목&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;시간 효율성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;데이터 범위가 제한되어 있으면 매우 빠른 $$O(n)$$ 시간에 정렬 가능&lt;/td&gt;
&lt;td&gt;k가 크면 시간 복잡도가 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;안정성&lt;/strong&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;strong&gt;구현의 단순성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;알고리즘 구조가 단순하여 이해 및 구현이 용이&lt;/td&gt;
&lt;td&gt;추가 메모리 사용으로 인한 공간 낭비 가능성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;계수 정렬은 특히 학생 성적, 제한된 범위 내의 센서 데이터 등과 같이 데이터 범위가 제한된 경우에 탁월한 성능을 발휘합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  결론
&lt;/h2&gt;

&lt;p&gt;계수 정렬은 데이터의 범위가 제한되어 있는 경우에 매우 유리한 정렬 알고리즘입니다. 입력 데이터 내에 값의 분포가 한정되어 있다면, 비교 기반 정렬보다 훨씬 빠르게 정렬 작업을 수행할 수 있습니다. 하지만, 데이터 범위가 너무 넓거나 음수, 실수 등 정수 이외의 값이 포함될 경우에는 다른 알고리즘과의 병행 적용이나 변형이 필요합니다. 오늘 글에서 소개한 내용과 코드 예제를 참고하여, 자신만의 정렬 솔루션에 계수 정렬을 적용해보시기 바랍니다.&lt;/p&gt;

&lt;p&gt;이처럼 계수 정렬의 동작 원리와 효율성을 이해하면, 제한된 수 범위의 데이터 정렬 문제에서 적합한 해법을 찾는 데 큰 도움이 될 것입니다.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
