<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>kahnco</title>
    <link>https://kahnco.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 21 Jun 2026 09:49:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>kahnco</managingEditor>
    <image>
      <title>kahnco</title>
      <url>https://tistory1.daumcdn.net/tistory/5895694/attach/c15762881a0d4f7f94f5d8a6ddcb86b3</url>
      <link>https://kahnco.tistory.com</link>
    </image>
    <item>
      <title>[마이어스 수원 웨딩&amp;amp;파티] 드디어 계약 완료!</title>
      <link>https://kahnco.tistory.com/53</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2025-10-22-22-44-11 001.jpeg&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eo41bI/dJMb9WemRqF/yAmAJqVKfGl6fjg5V6Crq0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eo41bI/dJMb9WemRqF/yAmAJqVKfGl6fjg5V6Crq0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eo41bI/dJMb9WemRqF/yAmAJqVKfGl6fjg5V6Crq0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feo41bI%2FdJMb9WemRqF%2FyAmAJqVKfGl6fjg5V6Crq0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;667&quot; data-filename=&quot;KakaoTalk_Photo_2025-10-22-22-44-11 001.jpeg&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2025-10-22-22-44-21 002.jpeg&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dza7VJ/dJMb9WemRqk/iU4gJ4foIUspuTsPZXRKr0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dza7VJ/dJMb9WemRqk/iU4gJ4foIUspuTsPZXRKr0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dza7VJ/dJMb9WemRqk/iU4gJ4foIUspuTsPZXRKr0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdza7VJ%2FdJMb9WemRqk%2FiU4gJ4foIUspuTsPZXRKr0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;667&quot; data-filename=&quot;KakaoTalk_Photo_2025-10-22-22-44-21 002.jpeg&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2025-10-22-22-44-35 003.jpeg&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eFbhEC/dJMb81UiJlX/kltkyxmrSufCeSTH99GEik/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eFbhEC/dJMb81UiJlX/kltkyxmrSufCeSTH99GEik/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eFbhEC/dJMb81UiJlX/kltkyxmrSufCeSTH99GEik/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeFbhEC%2FdJMb81UiJlX%2FkltkyxmrSufCeSTH99GEik%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;667&quot; data-filename=&quot;KakaoTalk_Photo_2025-10-22-22-44-35 003.jpeg&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2025-10-22-22-45-05 004.jpeg&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc9Gch/dJMb9V7A8po/9nQz7iDfA5RwPlQKsprX60/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc9Gch/dJMb9V7A8po/9nQz7iDfA5RwPlQKsprX60/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc9Gch/dJMb9V7A8po/9nQz7iDfA5RwPlQKsprX60/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc9Gch%2FdJMb9V7A8po%2F9nQz7iDfA5RwPlQKsprX60%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;667&quot; data-filename=&quot;KakaoTalk_Photo_2025-10-22-22-45-05 004.jpeg&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;마이어스 수원 웨딩&amp;amp;파티] 드디어 계약 완료!  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;꺄! 드디어 마이어스 수원 웨딩홀 계약하고 왔어요! 너무 설레고 기분 좋아요 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;수많은 홀을 고민했지만, 저희 커플의 최종 선택은 역시 &amp;rsquo;마이어스&amp;lsquo;!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;  첫 번째 장점: 역시 접근성 짱!&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;수원 버스터미널 건물에 있어서 지방 하객분들 오시기 너무 편할 것 같더라고요. 터미널이랑 바로 이어져 있으니 길 헤맬 걱정 제로! 물론 주차도 백화점/마트랑 같이 써서 조금 신경 쓰이긴 하지만, 그래도 이 정도 위치면 합격이죠!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;  두 번째 장점: 말이 필요 없는 뷔페! (250가지라니.. )&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;결혼식에서 음식이 제일 중요하다고 생각하는 저희에게, 마이어스 뷔페의 명성은 이미 정평이 나 있잖아요? 250가지 다양한 메뉴라니! 하객분들께 맛있는 식사 대접할 생각에 벌써부터 뿌듯합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;  세 번째 장점: 완벽한 신부 동선!&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;투어하면서 가장 맘에 들었던 부분 중 하나! 바로 신부대기실이랑 홀이 연결되어 있다는 거! 동선이 너무 깔끔해서 좋았어요. 하객들 눈에 띄지 않게 신부가 바로 입장할 수 있다는 게 최고 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;  네 번째 장점: 우아한 홀 분위기!&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;생화 장식이랑 전체적인 인테리어가 정말 깔끔하고 고급지더라고요. 사진에서처럼 은은하고 우아한 분위기가 완전 제 취향저격이었답니다. 홀 자체도 어두운 분위기라 집중도도 높을 것 같고요. 샹들리에 조명도 너무 예뻤어요✨&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;  최종 소감 &amp;amp; 계약 꿀팁!&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;음식, 교통, 홀 분위기 삼박자가 딱 맞아서 고민 없이 당일 계약하고 왔답니다! 원하는 날짜와 시간까지 겟해서 더 만족스러워요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;상담도 친절하게 잘 해주셔서 기분 좋게 마무리했어요  결혼 준비하시는 예신, 예랑님들께 마이어스 수원 강력 추천합니다&lt;/span&gt;&lt;/p&gt;</description>
      <category>마이어스수원</category>
      <category>마이어스웨딩홀</category>
      <category>수원웨딩홀</category>
      <author>kahnco</author>
      <guid isPermaLink="true">https://kahnco.tistory.com/53</guid>
      <comments>https://kahnco.tistory.com/53#entry53comment</comments>
      <pubDate>Wed, 22 Oct 2025 22:50:08 +0900</pubDate>
    </item>
    <item>
      <title>[Database] PostgreSQL - 권한, 스키마, 상속, 파티셔닝</title>
      <link>https://kahnco.tistory.com/52</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이전 게시글에서는 &lt;b&gt;PostgreSQL&lt;/b&gt; 테이블의 기본 사항, 제약 조건을 통한 데이터 무결성 보장 방법, 기존 테이블 구조 수정 방법을 살펴보았습니다. 이번 게시글에서는 데이터베이스 관리 및 설계를 더욱 향상시키는 &lt;b&gt;PostgreSQL&lt;/b&gt;의 고급 데이터 정의 기능을 더 깊이 탐구합니다. 여기에는 사용자 접근을 제어하는 권한, 세분화된 접근 제어를 위한 행 수준 보안 정책, 데이터베이스 객체 구성을 위한 스키마, 코드 재사용을 위한 테이블 상속, 대규모 테이블 관리를 위한 파티셔닝, 외부 데이터 소스 접근, 그리고 객체 간의 종속성 추적 등이 포함됩니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 1: 권한을 통한 접근 관리&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 보안의 핵심은 사용자가 수행할 수 있는 작업을 제어하는 것입니다. &lt;b&gt;PostgreSQL&lt;/b&gt;은 강력한 권한 시스템을 통해 이를 관리합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1 권한 개념 및 관리&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PostgreSQL&lt;/b&gt;에서 권한은 특정 데이터베이스 객체(테이블, 스키마, 함수 등)에 대해 특정 역할(사용자 또는 그룹)이 수행할 수 있는 작업을 정의합니다. 객체 소유자(일반적으로 객체를 생성한 사용자) 또는 슈퍼유저는 이러한 권한을 부여하거나 취소할 수 있습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;GRANT 명령&lt;/b&gt;: 특정 객체에 대한 권한을 역할에 부여하는데 사용됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- joe 역할에게 accounts 테이블에 대한 UPDATE 권한 부여
GRANT UPDATE ON accounts TO joe;

-- 모든 역할(PUBLIC)에게 특정 스키마 사용 권한 부여
GRANT USAGE ON SCHEMA my_schema TO PUBLIC;&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;REVOKE 명령:&lt;/b&gt; 이전에 부여된 권한을 역할에서 제거하는 데 사용됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- joe 역할로부터 accounts 테이블에 대한 UPDATE 권한 취소
REVOKE UPDATE ON accounts FROM joe;

-- 특정 역할로부터 특정 테이블에 대한 모든 권한 취소
REVOKE ALL ON film FROM jim;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2 주요 권한 유형&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 다양한 객체 유형에 적용되는 여러 권한 유형을 제공합니다. 주요 권한 유형은 다음과 같습니다:&lt;/p&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;table style=&quot;border-collapse: collapse; width: 100%; height: 402px;&quot; border=&quot;1&quot; data-sourcepos=&quot;34:1-47:141&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt; 
   &lt;tbody&gt; 
    &lt;tr style=&quot;height: 38px;&quot;&gt; 
     &lt;td style=&quot;height: 38px; width: 11.0465%;&quot;&gt;권한&lt;/td&gt; 
     &lt;td style=&quot;height: 38px; width: 11.1628%;&quot;&gt;약어 (ACL)&lt;/td&gt; 
     &lt;td style=&quot;height: 38px; width: 22.093%;&quot;&gt;적용 대상 예시&lt;/td&gt; 
     &lt;td style=&quot;height: 38px; width: 55.6977%;&quot;&gt;설명&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 36px;&quot; data-sourcepos=&quot;36:1-36:84&quot;&gt; 
     &lt;td style=&quot;height: 36px; width: 11.0465%;&quot; data-sourcepos=&quot;36:1-36:10&quot;&gt;SELECT&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 11.1628%;&quot; data-sourcepos=&quot;36:12-36:25&quot;&gt;r (&quot;read&quot;)&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 22.093%;&quot; data-sourcepos=&quot;36:27-36:42&quot;&gt;테이블, 뷰, 시퀀스, 열&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 55.6977%;&quot; data-sourcepos=&quot;36:44-36:82&quot;&gt;객체에서 데이터를 읽을 수 있는 권한 (예: SELECT 명령)&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 36px;&quot; data-sourcepos=&quot;37:1-37:79&quot;&gt; 
     &lt;td style=&quot;height: 36px; width: 11.0465%;&quot; data-sourcepos=&quot;37:1-37:10&quot;&gt;INSERT&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 11.1628%;&quot; data-sourcepos=&quot;37:12-37:27&quot;&gt;a (&quot;append&quot;)&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 22.093%;&quot; data-sourcepos=&quot;37:29-37:36&quot;&gt;테이블, 열&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 55.6977%;&quot; data-sourcepos=&quot;37:38-37:77&quot;&gt;테이블에 새 행을 삽입할 수 있는 권한 (예: INSERT 명령)&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 36px;&quot; data-sourcepos=&quot;38:1-38:89&quot;&gt; 
     &lt;td style=&quot;height: 36px; width: 11.0465%;&quot; data-sourcepos=&quot;38:1-38:10&quot;&gt;UPDATE&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 11.1628%;&quot; data-sourcepos=&quot;38:12-38:26&quot;&gt;w (&quot;write&quot;)&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 22.093%;&quot; data-sourcepos=&quot;38:28-38:35&quot;&gt;테이블, 열&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 55.6977%;&quot; data-sourcepos=&quot;38:37-38:87&quot;&gt;테이블의 기존 행에 있는 열 값을 업데이트할 수 있는 권한 (예: UPDATE 명령)&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;39:1-39:64&quot;&gt; 
     &lt;td style=&quot;height: 20px; width: 11.0465%;&quot; data-sourcepos=&quot;39:1-39:10&quot;&gt;DELETE&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 11.1628%;&quot; data-sourcepos=&quot;39:12-39:16&quot;&gt;d&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 22.093%;&quot; data-sourcepos=&quot;39:18-39:22&quot;&gt;테이블&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 55.6977%;&quot; data-sourcepos=&quot;39:24-39:62&quot;&gt;테이블에서 행을 삭제할 수 있는 권한 (예: DELETE 명령)&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;40:1-40:74&quot;&gt; 
     &lt;td style=&quot;height: 20px; width: 11.0465%;&quot; data-sourcepos=&quot;40:1-40:12&quot;&gt;TRUNCATE&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 11.1628%;&quot; data-sourcepos=&quot;40:14-40:18&quot;&gt;t&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 22.093%;&quot; data-sourcepos=&quot;40:20-40:24&quot;&gt;테이블&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 55.6977%;&quot; data-sourcepos=&quot;40:26-40:72&quot;&gt;테이블의 모든 행을 빠르게 제거할 수 있는 권한 (예: TRUNCATE 명령)&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 40px;&quot; data-sourcepos=&quot;41:1-41:83&quot;&gt; 
     &lt;td style=&quot;height: 40px; width: 11.0465%;&quot; data-sourcepos=&quot;41:1-41:14&quot;&gt;REFERENCES&lt;/td&gt; 
     &lt;td style=&quot;height: 40px; width: 11.1628%;&quot; data-sourcepos=&quot;41:16-41:20&quot;&gt;x&lt;/td&gt; 
     &lt;td style=&quot;height: 40px; width: 22.093%;&quot; data-sourcepos=&quot;41:22-41:29&quot;&gt;테이블, 열&lt;/td&gt; 
     &lt;td style=&quot;height: 40px; width: 55.6977%;&quot; data-sourcepos=&quot;41:31-41:81&quot;&gt;외래 키 제약 조건을 생성할 수 있는 권한. 열 수준 또는 테이블 수준에서 부여해야 함.&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;42:1-42:50&quot;&gt; 
     &lt;td style=&quot;height: 20px; width: 11.0465%;&quot; data-sourcepos=&quot;42:1-42:11&quot;&gt;TRIGGER&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 11.1628%;&quot; data-sourcepos=&quot;42:13-42:17&quot;&gt;t&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 22.093%;&quot; data-sourcepos=&quot;42:19-42:23&quot;&gt;테이블&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 55.6977%;&quot; data-sourcepos=&quot;42:25-42:48&quot;&gt;테이블에 트리거를 생성할 수 있는 권한.&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 40px;&quot; data-sourcepos=&quot;43:1-43:130&quot;&gt; 
     &lt;td style=&quot;height: 40px; width: 11.0465%;&quot; data-sourcepos=&quot;43:1-43:10&quot;&gt;CREATE&lt;/td&gt; 
     &lt;td style=&quot;height: 40px; width: 11.1628%;&quot; data-sourcepos=&quot;43:12-43:16&quot;&gt;C&lt;/td&gt; 
     &lt;td style=&quot;height: 40px; width: 22.093%;&quot; data-sourcepos=&quot;43:18-43:39&quot;&gt;데이터베이스, 스키마, 테이블스페이스&lt;/td&gt; 
     &lt;td style=&quot;height: 40px; width: 55.6977%;&quot; data-sourcepos=&quot;43:41-43:128&quot;&gt;데이터베이스의 경우 새 스키마 생성을 허용. 스키마의 경우 스키마 내에 새 객체 생성을 허용. 테이블스페이스의 경우 테이블, 인덱스 등을 생성할 수 있음.&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;44:1-44:51&quot;&gt; 
     &lt;td style=&quot;height: 20px; width: 11.0465%;&quot; data-sourcepos=&quot;44:1-44:11&quot;&gt;CONNECT&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 11.1628%;&quot; data-sourcepos=&quot;44:13-44:17&quot;&gt;c&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 22.093%;&quot; data-sourcepos=&quot;44:19-44:26&quot;&gt;데이터베이스&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 55.6977%;&quot; data-sourcepos=&quot;44:28-44:49&quot;&gt;데이터베이스에 연결할 수 있는 권한.&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 36px;&quot; data-sourcepos=&quot;45:1-45:74&quot;&gt; 
     &lt;td style=&quot;height: 36px; width: 11.0465%;&quot; data-sourcepos=&quot;45:1-45:22&quot;&gt;TEMPORARY / TEMP&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 11.1628%;&quot; data-sourcepos=&quot;45:24-45:28&quot;&gt;T&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 22.093%;&quot; data-sourcepos=&quot;45:30-45:37&quot;&gt;데이터베이스&lt;/td&gt; 
     &lt;td style=&quot;height: 36px; width: 55.6977%;&quot; data-sourcepos=&quot;45:39-45:72&quot;&gt;데이터베이스 사용 시 임시 테이블을 생성할 수 있는 권한.&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;46:1-46:76&quot;&gt; 
     &lt;td style=&quot;height: 20px; width: 11.0465%;&quot; data-sourcepos=&quot;46:1-46:11&quot;&gt;EXECUTE&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 11.1628%;&quot; data-sourcepos=&quot;46:13-46:17&quot;&gt;X&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 22.093%;&quot; data-sourcepos=&quot;46:19-46:28&quot;&gt;함수, 프로시저&lt;/td&gt; 
     &lt;td style=&quot;height: 20px; width: 55.6977%;&quot; data-sourcepos=&quot;46:30-46:74&quot;&gt;함수 또는 프로시저를 실행하거나 정의에 지정된 연산자를 사용할 수 있는 권한.&lt;/td&gt; 
    &lt;/tr&gt; 
    &lt;tr style=&quot;height: 40px;&quot; data-sourcepos=&quot;47:1-47:141&quot;&gt; 
     &lt;td style=&quot;height: 40px; width: 11.0465%;&quot; data-sourcepos=&quot;47:1-47:9&quot;&gt;USAGE&lt;/td&gt; 
     &lt;td style=&quot;height: 40px; width: 11.1628%;&quot; data-sourcepos=&quot;47:11-47:15&quot;&gt;U&lt;/td&gt; 
     &lt;td style=&quot;height: 40px; width: 22.093%;&quot; data-sourcepos=&quot;47:17-47:56&quot;&gt;스키마, 시퀀스, 데이터 타입, 외부 데이터 래퍼, 외부 서버, 언어&lt;/td&gt; 
     &lt;td style=&quot;height: 40px; width: 55.6977%;&quot; data-sourcepos=&quot;47:58-47:139&quot;&gt;스키마의 경우 스키마 내 객체 접근 허용. 시퀀스의 경우 currval, nextval 사용 허용. 데이터 타입의 경우 타입 사용 허용.&lt;/td&gt; 
    &lt;/tr&gt; 
   &lt;/tbody&gt; 
  &lt;/table&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.3 PUBLIC 역할 및 기본 권한&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PUBLIC&lt;/b&gt;은 시스템의 모든 역할을 나타내는 특수 이름입니다. 객체 생성 시 일부 유형의 객체에는 기본적으로 PUBLIC에 특정 권한이 부여됩니다 (예: 데이터베이스에 대한 CONNECT 및 TEMPORARY, 함수에 대한 EXECUTE, 타입 및 언어에 대한 USAGE). 테이블, 열, 스키마 등에는 기본적으로 PUBLIC에 권한이 부여되지 않습니다. 객체 소유자는 REVOKE를 사용하여 이러한 기본 권한을 포함한 모든 권한을 취소할 수 있습니다. 보안을 강화하려면 객체 생성과 동일한 트랜잭션 내에서 불필요한 PUBLIC 권한을 취소하는 것이 좋습니다. ALTER DEFAULT PRIVILEGES 명령을 사용하여 기본 권한 설정을 변경할 수도 있습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.4 그랜트 옵션 (WITH GRANT OPTION)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;GRANT 명령에 WITH GRANT OPTION을 지정하면 권한을 받은 역할이 해당 권한을 다른 역할에게 다시 부여할 수 있습니다. 이 옵션이 없으면 권한을 받은 역할은 권한을 재부여할 수 없습니다. PUBLIC에는 그랜트 옵션을 부여할 수 없습니다. 만약 그랜트 옵션이 나중에 취소되면, 해당 역할을 통해 권한을 부여받은 모든 역할(직간접적으로)도 해당 권한을 잃게 됩니다 (CASCADE 옵션 사용 시).&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.5 소유권&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 수정하거나 삭제할 권리는 객체 소유자에게 내재되어 있으며, 일반적인 권한처럼 부여하거나 취소할 수 없습니다. 소유자는 항상 모든 그랜트 옵션을 보유한 것으로 간주되므로 자신의 권한을 취소했더라도 다시 부여할 수 있습니다. 객체 소유권은 ALTER... OWNER TO 명령을 통해 변경할 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 2: 행 수준 보안 정책을 통한 세분화된 접근 제어&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;표준 GRANT/REVOKE 권한 시스템은 객체 수준에서 접근을 제어하지만, 때로는 테이블 내의 특정 행에 대한 접근을 제어해야 할 필요가 있습니다. PostgreSQL은 이를 위해 &lt;b&gt;행 수준 보안(Row-Level Security, RLS)&lt;/b&gt; 기능을 제공합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 행 수준 보안 (RLS) 소개&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;RLS는 사용자가 테이블에 대해 쿼리를 실행할 때, 쿼리를 실행하는 사용자를 기반으로 반환되거나 수정될 수 있는 행을 제한하는 기능입니다. 이는 테이블에 적용되는 정책(Policy)을 통해 구현됩니다. 정책은 특정 조건(SQL 표현식)을 정의하며, 이 조건을 만족하는 행만 사용자에게 보이거나 수정 가능하게 됩니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 RLS 활성화 및 정책 생성&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;RLS를 사용하려면 먼저 대상 테이블에 대해 RLS를 활성화해야 합니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;그런 다음 CREATE POLICY 명령을 사용하여 하나 이상의 정책을 생성합니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;CREATE POLICY policy_name ON table_name
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- 정책 유형 (기본값: PERMISSIVE)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- 적용될 명령 (기본값: ALL)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- 적용될 역할 (기본값: PUBLIC)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- 기존 행에 대한 가시성/수정 가능성 조건
&amp;nbsp;&amp;nbsp; ; -- 새로 삽입/수정될 행에 대한 유효성 조건&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;USING (expression):&lt;/b&gt; 이 표현식은 기존 행에 대해 평가됩니다. SELECT, UPDATE, DELETE 명령 시 적용되며, 표현식이 true를 반환하는 행만 사용자에게 보이거나 수정/삭제 가능합니다. false 또는 null을 반환하는 행은 조용히 제외됩니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;WITH CHECK (expression):&lt;/b&gt; 이 표현식은 INSERT 또는 UPDATE 명령으로 생성되거나 수정될 행에 대해 평가됩니다. 표현식이 true를 반환하는 행만 삽입/수정이 허용되며, false 또는 null을 반환하면 오류가 발생합니다.&lt;/li&gt;&lt;li&gt;ALL 또는 UPDATE 정책에서 WITH CHECK가 생략되면 USING 표현식이 WITH CHECK 조건으로도 사용됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 허용(Permissive) vs 제한(Restrictive) 정책&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;정책은 두 가지 유형으로 나뉩니다:&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;PERMISSIVE (기본값):&lt;/b&gt; 허용 정책은 접근을 허용하는 조건을 정의합니다. 여러 허용 정책이 있는 경우, 행은 &lt;b&gt;하나라도&lt;/b&gt; 만족하면 접근이 허용됩니다 (논리적 OR).&lt;/li&gt;&lt;li&gt;&lt;b&gt;RESTRICTIVE:&lt;/b&gt; 제한 정책은 접근을 제한하는 조건을 정의합니다. 행에 접근하려면 &lt;b&gt;모든&lt;/b&gt; 제한 정책을 통과해야 합니다 (논리적 AND). 제한 정책은 최소한 하나의 허용 정책이 접근을 허용한 후에 추가적인 제한을 가하는 데 사용됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.4 RLS 우회 및 강제 적용&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 슈퍼유저, BYPASSRLS 속성을 가진 역할, 그리고 테이블 소유자는 RLS 정책을 우회합니다. 테이블 소유자에게도 RLS를 강제로 적용하려면 다음 명령을 사용합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.5 RLS 예시: 사용자별 데이터 접근&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;관리자만 자신의 부서 정보를 볼 수 있도록 하는 정책을 예로 들어 보겠습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 샘플 테이블 및 역할 생성 (간략화)
CREATE TABLE departments (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id serial primary key,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name VARCHAR(255) NOT NULL UNIQUE,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;manager VARCHAR(255) NOT NULL
);
INSERT INTO departments(name, manager) VALUES('Sales', 'alice'), ('Marketing', 'bob');
CREATE ROLE managers;
CREATE ROLE alice WITH LOGIN PASSWORD 'password' IN ROLE managers;
CREATE ROLE bob WITH LOGIN PASSWORD 'password' IN ROLE managers;
GRANT SELECT ON departments TO managers;

-- RLS 활성화
ALTER TABLE departments ENABLE ROW LEVEL SECURITY;

-- 정책 생성: 현재 사용자가 manager 열의 값과 일치하는 행만 볼 수 있도록 허용
CREATE POLICY department_managers ON departments
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FOR SELECT -- SELECT 명령에만 적용
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TO managers -- managers 역할에 적용
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;USING (manager = current_user); -- 현재 사용자와 manager가 같아야 함

-- 이제 'alice' 사용자로 로그인하여 SELECT * FROM departments; 를 실행하면
-- Sales 부서 정보만 보이고, 'bob' 사용자로 실행하면 Marketing 부서 정보만 보입니다.&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 3: 스키마를 이용한 객체 구성&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스가 커지면서 테이블, 뷰, 함수 등의 객체를 효율적으로 관리하는 것이 중요해집니다. PostgreSQL 스키마는 이러한 객체들을 논리적으로 그룹화하는 방법을 제공합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 스키마란 무엇인가?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;스키마는 데이터베이스 객체(테이블, 데이터 타입, 함수, 연산자 등)를 담는 네임스페이스입니다. 하나의 데이터베이스는 여러 스키마를 가질 수 있으며, 서로 다른 스키마 내에서는 동일한 이름의 객체를 가질 수 있습니다. 스키마는 다음과 같은 이점을 제공합니다:&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;사용자 격리:&lt;/b&gt; 여러 사용자가 서로 간섭 없이 동일한 데이터베이스를 사용할 수 있습니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;효율적인 데이터 구성:&lt;/b&gt; 관련 객체들을 논리적 그룹으로 묶어 관리 용이성을 높입니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;이름 충돌 방지:&lt;/b&gt; 특히 서드파티 애플리케이션을 통합할 때 객체 이름 충돌을 피할 수 있습니다.&lt;/li&gt;&lt;/ul&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 스키마 생성 (CREATE SCHEMA)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;CREATE SCHEMA 명령을 사용하여 새 스키마를 생성합니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 'myschema'라는 이름의 스키마 생성 (현재 사용자가 소유)
CREATE SCHEMA myschema;

-- 'joe' 사용자가 소유하는 'joe' 스키마 생성
CREATE SCHEMA AUTHORIZATION joe;

-- 'joe' 사용자가 소유하는 'test' 스키마 생성 (이미 존재하지 않는 경우에만)
CREATE SCHEMA IF NOT EXISTS test AUTHORIZATION joe;

-- 스키마 생성과 동시에 객체 생성
CREATE SCHEMA hollywood
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE TABLE films (title text, release date, awards text)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE VIEW winners AS SELECT title, release FROM films WHERE awards IS NOT NULL;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;스키마를 생성하려면 현재 데이터베이스에 대한 CREATE 권한이 필요합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 public 스키마&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;새 데이터베이스를 생성하면 기본적으로 public이라는 스키마가 함께 생성됩니다. 스키마 이름을 명시하지 않고 객체를 생성하면 해당 객체는 public 스키마에 저장됩니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.4 스키마 검색 경로 (search_path)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;스키마 이름을 명시하지 않고 객체(예: 테이블)를 참조할 때, PostgreSQL은 search_path 설정에 지정된 순서대로 스키마를 검색하여 해당 이름의 객체를 찾습니다. search_path는 쉼표로 구분된 스키마 이름 목록입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;현재 검색 경로 확인:&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SHOW search_path;
-- 기본값 예시: &quot;$user&quot;, public&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;$user:&lt;/b&gt; 현재 사용자와 동일한 이름의 스키마를 의미합니다. 기본적으로 가장 먼저 검색됩니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;pg_catalog:&lt;/b&gt; 시스템 카탈로그 스키마는 항상 검색되며, 명시적으로 지정되지 않으면 목록의 스키마들보다 먼저 검색됩니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;임시 테이블 스키마 (pg_temp)&lt;/b&gt;: 현재 세션의 임시 테이블 스키마도 존재하면 항상 검색되며, 명시적으로 지정되지 않으면 pg_catalog보다도 먼저 검색됩니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;검색 경로 설정:&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 현재 세션의 검색 경로를 'sales', 'public' 순으로 설정
SET search_path TO sales, public;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;객체를 생성할 때 스키마를 지정하지 않으면 search_path의 첫 번째 유효한 스키마에 생성됩니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.5 스키마와 권한&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;스키마 내의 객체에 접근하거나 스키마 내에 새 객체를 생성하려면 해당 스키마에 대한 특정 권한이 필요합니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;USAGE 권한:&lt;/b&gt; 스키마 내에 포함된 객체에 접근(조회)할 수 있게 합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;CREATE 권한:&lt;/b&gt; 스키마 내에 새 객체를 생성할 수 있게 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 모든 사용자는 public 스키마에 대해 CREATE 및 USAGE 권한을 갖습니다. 보안을 위해 이 기본 권한을 변경하는 것이 권장될 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.6 시스템 카탈로그 스키마 (pg_catalog)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pg_catalog&lt;/b&gt; 스키마는 PostgreSQL 시스템이 내부적으로 사용하는 특별한 스키마입니다. 여기에는 데이터베이스의 메타데이터(테이블, 열, 인덱스, 함수, 사용자, 권한 등에 대한 정보)를 저장하는 시스템 카탈로그 테이블과 뷰가 포함되어 있습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;역할:&lt;/b&gt; 데이터베이스 구조와 상태에 대한 정보를 저장하고 관리합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;접근:&lt;/b&gt; 일반적으로 사용자는 SQL 명령(예: CREATE TABLE, ALTER USER)을 통해 간접적으로 시스템 카탈로그와 상호작용하며, 직접 수정하는 것은 권장되지 않습니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;information_schema와의 관계:&lt;/b&gt; information_schema는 SQL 표준에 정의된 뷰 집합으로, pg_catalog에 저장된 메타데이터에 대한 표준화되고 이식성 있는 인터페이스를 제공합니다. pg_catalog은 PostgreSQL 특정 정보를 포함하지만, information_schema는 보다 일반적인 정보를 제공합니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.7 사용 패턴 및 이식성&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;스키마를 효과적으로 사용하는 몇 가지 패턴이 있습니다:&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;사용자별 개인 스키마:&lt;/b&gt; 각 사용자에게 자신의 이름과 동일한 스키마를 생성해주고 public 스키마의 CREATE 권한을 제거합니다. 사용자는 기본적으로 자신의 스키마에 객체를 생성하고 접근하게 됩니다. (PostgreSQL 15 이상 기본값)&lt;/li&gt;&lt;li&gt;&lt;b&gt;public 스키마 제거:&lt;/b&gt; search_path에서 public을 제거하고, 객체 참조 시 항상 스키마 이름을 명시하도록 합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;public 스키마 공유:&lt;/b&gt; 모든 사용자가 public 스키마를 공유합니다. 스키마가 없는 것처럼 동작하지만, 보안상 취약할 수 있습니다. (PostgreSQL 14 이하 기본값)&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이식성을 고려할 때, information_schema를 사용하여 메타데이터를 조회하는 것이 좋습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 4: 테이블 상속을 통한 코드 재사용&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 객체 지향 개념인 상속을 테이블 수준에서 지원합니다. 이를 통해 테이블 간에 구조(열)와 일부 제약 조건을 공유할 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 테이블 상속 개념&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 상속을 사용하면 하나 이상의 &quot;부모&quot; 테이블로부터 열과 특정 제약 조건을 상속받는 &quot;자식&quot; 테이블을 생성할 수 있습니다. 자식 테이블은 부모 테이블의 모든 열을 가지며, 자체적으로 추가 열을 정의할 수도 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;예시: 도시와 수도&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 부모 테이블: 도시 정보
CREATE TABLE cities (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name text,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;population real,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elevation int -- (in ft)
);

-- 자식 테이블: 수도 정보 (도시 정보 상속 + 주 정보 추가)
CREATE TABLE capitals (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;state char(2) UNIQUE NOT NULL
) INHERITS (cities); -- cities 테이블 상속&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이 경우 capitals 테이블은 name, population, elevation 열을 cities로부터 상속받고, 추가로 state 열을 갖습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 상속 작동 방식&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;열:&lt;/b&gt; 자식 테이블은 모든 부모 테이블의 열을 포함합니다. 여러 부모로부터 동일한 이름의 열을 상속받는 경우, 데이터 유형이 일치해야 하며 해당 열은 하나로 병합됩니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;제약 조건:&lt;/b&gt; CHECK 제약 조건과 NOT NULL 제약 조건은 자식 테이블로 상속됩니다. 동일한 이름의 CHECK 제약 조건은 병합되며, 조건이 다르면 오류가 발생합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;쿼리:&lt;/b&gt; 기본적으로 부모 테이블을 쿼리하면 부모 테이블 자체의 행과 모든 자식 테이블의 행이 함께 반환됩니다. 부모 테이블만 쿼리하려면 ONLY 키워드를 사용합니다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- cities 테이블과 모든 자식 테이블(capitals)의 모든 행 조회
SELECT name, elevation FROM cities WHERE elevation &amp;gt; 500;

-- cities 테이블 자체의 행만 조회
SELECT name, elevation FROM ONLY cities WHERE elevation &amp;gt; 500;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.3 사용 사례&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;데이터 모델링:&lt;/b&gt; 공통 속성을 가진 객체 계층을 모델링하는 데 사용될 수 있습니다 (예: 도시/수도, 다양한 유형의 장비).&lt;/li&gt;&lt;li&gt;&lt;b&gt;데이터 파티셔닝 (레거시):&lt;/b&gt; 과거에는 데이터를 작은 테이블로 나누는 파티셔닝 기법으로 사용되었습니다. 하지만 현재는 선언적 파티셔닝이 더 권장됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.4 주의 사항 및 제한 사항 (Caveats)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 상속은 유용할 수 있지만 몇 가지 중요한 제한 사항이 있습니다:&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;인덱스 및 고유/기본 키 제약 조건:&lt;/b&gt; 인덱스(UNIQUE, PRIMARY KEY 포함)는 자식 테이블로 상속되지 않습니다. 즉, 부모 테이블에 기본 키가 있어도 자식 테이블 간 또는 부모-자식 간의 고유성이 보장되지 않습니다. 필요한 경우 각 자식 테이블에 별도로 제약 조건을 추가해야 합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;외래 키 제약 조건:&lt;/b&gt; 외래 키 제약 조건 역시 상속되지 않습니다. 부모 테이블을 참조하는 외래 키는 자식 테이블의 행을 참조하지 않으며, 그 반대도 마찬가지입니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;데이터 라우팅:&lt;/b&gt; INSERT 또는 COPY 명령은 지정된 테이블에만 데이터를 삽입하며, 상속 계층 구조에 따라 자동으로 데이터를 자식 테이블로 분배하지 않습니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;ALTER TABLE 제한:&lt;/b&gt; 부모 테이블에서 상속된 열이나 제약 조건은 자식 테이블에서 직접 삭제하거나 변경할 수 없습니다. RENAME은 자식 테이블에 적용할 수 없습니다. 부모 테이블에서 DROP COLUMN을 사용해도 자식 테이블의 열은 특정 조건 하에서만 제거됩니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;권한:&lt;/b&gt; 상속 계층을 쿼리할 때 접근 권한은 부모 테이블에 대해서만 확인됩니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 제한 사항 때문에, 특히 파티셔닝 목적으로는 PostgreSQL 10부터 도입된 선언적 파티셔닝 기능이 일반적으로 더 선호됩니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 5: 대규모 테이블 관리를 위한 파티셔닝&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;매우 큰 테이블은 관리 및 쿼리 성능 측면에서 어려움을 겪을 수 있습니다. PostgreSQL의 테이블 파티셔닝은 큰 논리적 테이블을 파티션이라는 더 작고 관리하기 쉬운 물리적 테이블 조각으로 나누는 기술입니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1 파티셔닝 개요 및 이점&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파티셔닝은 큰 테이블을 지정된 규칙(파티션 키 기준)에 따라 여러 개의 작은 테이블(파티션)로 분할합니다. 애플리케이션은 여전히 단일 논리 테이블로 상호작용할 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;주요 이점은 다음과 같습니다:&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;쿼리 성능 향상:&lt;/b&gt; 쿼리 플래너가 관련 없는 파티션을 검색에서 제외(파티션 프루닝)하여 스캔 범위를 줄일 수 있습니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;대량 데이터 로드/삭제 용이성:&lt;/b&gt; 파티션 단위로 데이터를 빠르게 추가하거나 삭제할 수 있습니다 (예: 오래된 월별 데이터 파티션 삭제).&lt;/li&gt;&lt;li&gt;&lt;b&gt;유지 관리 용이성:&lt;/b&gt; 작은 파티션 단위로 인덱스 재구성, VACUUM 등의 작업을 수행할 수 있습니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;비용 효율적인 스토리지:&lt;/b&gt; 사용 빈도에 따라 파티션을 다른 스토리지 매체(예: 느리고 저렴한 스토리지)에 배치할 수 있습니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2 선언적 파티셔닝 (PostgreSQL 10+)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL 10부터 도입된 선언적 파티셔닝은 파티션된 테이블을 생성하고 관리하는 권장 방법입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;파티션된 테이블 생성:&lt;/b&gt; CREATE TABLE 문에 PARTITION BY 절을 사용하여 파티셔닝 방법과 파티션 키(분할 기준이 되는 열 또는 표현식)를 지정합니다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- logdate 열을 기준으로 범위 파티셔닝 설정
CREATE TABLE measurement (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;city_id int not null,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logdate date not null,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;peaktemp int,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;unitsales int
) PARTITION BY RANGE (logdate);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li data-sourcepos=&quot;299:1-302:141&quot;&gt;&lt;b&gt;파티셔닝 방법:&lt;/b&gt; 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;300:5-302:141&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li data-sourcepos=&quot;300:5-300:136&quot;&gt;&lt;b&gt;RANGE:&lt;/b&gt; 파티션 키 값의 범위를 기준으로 분할합니다. 각 파티션은 FOR VALUES FROM (하한값) TO (상한값)으로 정의되며, 하한은 포함되고 상한은 제외됩니다. 날짜나 연속적인 ID 범위에 적합합니다.&lt;/li&gt; 
   &lt;li data-sourcepos=&quot;301:5-301:136&quot;&gt;&lt;b&gt;LIST:&lt;/b&gt; 파티션 키 값이 각 파티션에 명시적으로 나열된 값 목록에 속하는지에 따라 분할합니다. FOR VALUES IN (값1, 값2,...)으로 정의됩니다. 지역 코드나 상태 코드처럼 불연속적인 값에 적합합니다.&lt;/li&gt; 
   &lt;li data-sourcepos=&quot;302:5-302:141&quot;&gt;&lt;b&gt;HASH:&lt;/b&gt; 파티션 키의 해시 값을 기준으로 분할합니다. 각 파티션은 FOR VALUES WITH (MODULUS 모듈러스, REMAINDER 나머지)로 정의됩니다. 데이터를 파티션 간에 균등하게 분배하고자 할 때 유용합니다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li data-sourcepos=&quot;303:1-317:36&quot;&gt;&lt;b&gt;파티션 생성:&lt;/b&gt; CREATE TABLE... PARTITION OF 문을 사용하여 각 파티션을 생성하고 해당 파티션이 포함할 값의 범위를 지정합니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- measurement 테이블의 2006년 2월 데이터 파티션 생성 (Range)
CREATE TABLE measurement_y2006m02 PARTITION OF measurement
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FOR VALUES FROM ('2006-02-01') TO ('2006-03-01');

-- customers 테이블의 'ACTIVE' 상태 파티션 생성 (List)
CREATE TABLE cust_active PARTITION OF customers
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FOR VALUES IN ('ACTIVE');

-- emp 테이블의 해시 파티션 생성 (Hash)
CREATE TABLE emp_0 PARTITION OF emp
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FOR VALUES WITH (MODULUS 3, REMAINDER 0);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;파티션 경계 조건에 대한 제약 조건은 자동으로 생성됩니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;하위 파티셔닝 (Sub-partitioning):&lt;/b&gt; 파티션 자체를 다시 파티션할 수 있습니다. 파티션 생성 시 PARTITION BY 절을 추가로 지정합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;인덱스:&lt;/b&gt; 파티션된 테이블에 인덱스를 생성하면 모든 파티션에 자동으로 해당 인덱스가 생성됩니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;파티션 프루닝:&lt;/b&gt; 쿼리 플래너가 쿼리의 WHERE 절을 분석하여 관련 없는 파티션을 스캔 대상에서 제외하는 최적화 기법입니다. enable_partition_pruning 설정이 활성화되어 있어야 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.3 파티셔닝 제한 사항&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;선언적 파티셔닝에도 몇 가지 제한 사항이 있습니다:&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;파티션된 테이블의 UNIQUE 또는 PRIMARY KEY 제약 조건은 반드시 파티션 키 열을 포함해야 합니다. (대안: 각 파티션에 개별적으로 고유 제약 조건 생성)&lt;/li&gt;&lt;li&gt;파티션된 테이블에는 BEFORE ROW 트리거를 정의할 수 없습니다. (대안: 각 파티션에 개별적으로 트리거 정의)&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.4 상속을 이용한 파티셔닝 (레거시)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL 10 이전에는 테이블 상속과 CHECK 제약 조건, 트리거를 조합하여 파티셔닝을 구현했습니다. 이 방법은 설정이 더 복잡하고 선언적 파티셔닝만큼 성능이 좋지 않거나 기능이 부족할 수 있어 현재는 잘 사용되지 않습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 6: 외부 데이터 접근 (Foreign Data Wrappers - FDW)&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 데이터베이스 외부(다른 PostgreSQL 서버, 다른 종류의 데이터베이스, 파일 등)에 저장된 데이터에 SQL을 통해 접근할 수 있는 기능을 제공합니다. 이를 Foreign Data Wrapper(FDW)라고 합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.1 FDW 개념&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;외부 데이터에 접근하기 위한 일반적인 단계는 다음과 같습니다:&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;FDW 확장 설치/생성 (CREATE EXTENSION / CREATE FOREIGN DATA WRAPPER):&lt;/b&gt; 사용할 FDW 확장을 설치하고(CREATE EXTENSION postgres_fdw;), 필요하다면 CREATE FOREIGN DATA WRAPPER 명령으로 FDW 객체를 정의합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;외부 서버 생성 (CREATE SERVER):&lt;/b&gt; 외부 데이터 소스에 대한 연결 정보를 정의하는 서버 객체를 생성합니다. FDW별 옵션(호스트, 포트, 데이터베이스 이름 등)을 지정합니다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 다른 PostgreSQL 서버를 위한 외부 서버 정의
CREATE SERVER foreign_server
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FOREIGN DATA WRAPPER postgres_fdw
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OPTIONS (host '192.83.123.89', port '5432', dbname 'foreign_db');&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;사용자 매핑 생성 (CREATE USER MAPPING):&lt;/b&gt; 로컬 PostgreSQL 역할이 외부 서버에 인증하는 데 사용할 자격 증명(예: 사용자 이름, 비밀번호)을 정의합니다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 로컬 사용자 'local_user'가 'foreign_server'에 연결할 때 사용할 원격 사용자 및 비밀번호 매핑
CREATE USER MAPPING FOR local_user
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SERVER foreign_server
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OPTIONS (user 'foreign_user', password 'password');&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;외부 테이블 생성 (CREATE FOREIGN TABLE):&lt;/b&gt; 로컬 PostgreSQL에서 외부 데이터의 구조를 정의하는 외부 테이블을 생성합니다. 일반 CREATE TABLE과 유사하지만, 데이터는 로컬에 저장되지 않습니다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 'foreign_server'의 'some_schema.some_table'을 참조하는 외부 테이블 생성
CREATE FOREIGN TABLE foreign_table (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id serial NOT NULL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data text
)
SERVER foreign_server
OPTIONS (schema_name 'some_schema', table_name 'some_table');&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.3 외부 테이블 사용 및 제약 조건&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;외부 테이블은 생성 후 일반 테이블처럼 쿼리(SELECT, INSERT, UPDATE, DELETE)할 수 있습니다. PostgreSQL은 해당 작업을 FDW를 통해 외부 소스로 전달합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;외부 테이블에 CHECK 또는 NOT NULL과 같은 제약 조건을 정의할 수는 있지만, PostgreSQL 코어나 대부분의 FDW는 이러한 제약 조건을 강제로 적용하지 &lt;b&gt;않습니다&lt;/b&gt;. 제약 조건은 단지 외부 데이터가 해당 조건을 만족한다고 가정하는 데 사용됩니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 7: 객체 종속성 이해&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 내의 객체들은 서로 연결되어 종속성을 형성합니다. 예를 들어, 뷰는 기반 테이블에 종속되고, 외래 키 제약 조건은 참조되는 테이블에 종속됩니다. PostgreSQL은 이러한 종속성을 추적하여 데이터베이스 구조의 무결성을 유지합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.1 종속성 개념 및 중요성&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;객체 종속성은 한 객체가 다른 객체의 존재나 정의에 의존하는 관계를 의미합니다. 예를 들어, 함수가 특정 사용자 정의 타입을 인수로 사용한다면, 해당 함수는 그 타입에 종속됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;종속성 추적은 객체를 삭제(DROP)하거나 수정(ALTER)할 때 중요합니다. PostgreSQL은 다른 객체가 여전히 의존하고 있는 객체를 실수로 삭제하는 것을 방지합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.2 pg_depend 시스템 카탈로그&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 pg_depend 시스템 카탈로그 테이블에 객체 간의 종속성 정보를 기록합니다. 이 테이블에는 의존하는 객체와 참조되는 객체의 식별자(OID), 그리고 종속성 유형을 나타내는 정보가 저장됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;주요 pg_depend 열:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;classid, objid, objsubid&lt;/b&gt;: 의존하는 객체의 카탈로그 OID, 객체 OID, 하위 ID (열 번호 등).&lt;/li&gt;&lt;li&gt;&lt;b&gt;refclassid, refobjid, refobjsubid&lt;/b&gt;: 참조되는 객체의 카탈로그 OID, 객체 OID, 하위 ID.&lt;/li&gt;&lt;li&gt;&lt;b&gt;deptype&lt;/b&gt;: 종속성 유형을 나타내는 코드.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 종속성 유형 (deptype):&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;DEPENDENCY_NORMAL ('n')&lt;/b&gt;: 일반적인 종속성. 의존 객체는 독립적으로 삭제될 수 있지만, 참조된 객체를 삭제하려면 CASCADE 옵션이 필요하며, 이 경우 의존 객체도 함께 삭제됩니다 (예: 열과 해당 데이터 타입).&lt;/li&gt;&lt;li&gt;&lt;b&gt;DEPENDENCY_AUTO ('a')&lt;/b&gt;: 자동 종속성. 의존 객체는 독립적으로 삭제될 수 있으며, 참조된 객체가 삭제되면 RESTRICT나 CASCADE에 관계없이 자동으로 함께 삭제됩니다 (예: 테이블과 해당 테이블의 명명된 제약 조건).&lt;/li&gt;&lt;li&gt;&lt;b&gt;DEPENDENCY_INTERNAL ('i')&lt;/b&gt;: 내부 종속성. 의존 객체는 참조된 객체의 내부 구현 일부이며 직접 삭제할 수 없습니다. 참조된 객체를 삭제하면 자동으로 함께 삭제됩니다 (예: 뷰와 해당 뷰의 ON SELECT 규칙).&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.3 DROP 명령과 종속성 (RESTRICT vs CASCADE)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 삭제하려고 할 때 다른 객체가 해당 객체에 의존하고 있다면, 기본적으로(RESTRICT 동작) PostgreSQL은 오류를 발생시키며 삭제를 거부합니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;DROP TABLE products;
-- ERROR: cannot drop table products because other objects depend on it
-- DETAIL: constraint orders_product_no_fkey on table orders depends on table products
-- HINT: Use DROP... CASCADE to drop the dependent objects too.&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;오류 메시지의 DETAIL과 HINT는 어떤 객체가 의존하고 있으며 어떻게 처리해야 하는지 알려줍니다. 의존하는 객체들을 먼저 수동으로 삭제하거나, CASCADE 옵션을 사용하여 의존하는 객체들까지 재귀적으로 함께 삭제할 수 있습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;DROP TABLE products CASCADE;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;CASCADE 옵션은 매우 강력하므로 사용 시 주의가 필요합니다. 의도치 않은 객체까지 삭제될 수 있으므로, 실행 전에 어떤 객체들이 영향을 받을지 확인하는 것이 좋습니다 (DROP을 CASCADE 없이 실행하여 DETAIL 확인).&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 8: 결론&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL의 고급 데이터 정의 기능들을 살펴보았습니다. 권한 시스템을 통한 접근 제어, 행 수준 보안 정책을 이용한 세밀한 데이터 접근 관리, 스키마를 활용한 효율적인 객체 구성, 테이블 상속의 개념과 한계, 대규모 데이터 관리를 위한 강력한 선언적 파티셔닝, 외부 데이터 소스와의 연동을 가능하게 하는 FDW, 그리고 데이터베이스 무결성 유지에 필수적인 객체 종속성 추적까지 다루었습니다.&lt;/p&gt;</description>
      <category>Database</category>
      <category>Grant</category>
      <category>Inherit</category>
      <category>partitioning</category>
      <category>PostgreSQL</category>
      <category>schema</category>
      <author>kahnco</author>
      <guid isPermaLink="true">https://kahnco.tistory.com/52</guid>
      <comments>https://kahnco.tistory.com/52#entry52comment</comments>
      <pubDate>Wed, 23 Apr 2025 23:41:46 +0900</pubDate>
    </item>
    <item>
      <title>[Database] PostgreSQL - Table Management (DDL)</title>
      <link>https://kahnco.tistory.com/51</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 게시글에서는 &lt;b&gt;PostgreSQL&lt;/b&gt; 데이터베이스 시스템 내에서의 테이블 정의, 제약 조건 및 수정에 대한 포괄적인 기술적 검토를 제공합니다. 테이블의 기본 구조, 기본값 및 생성된 값과 같은 특수 열 속성, 데이터 무결성 유지에 있어 제약 조건의 중요한 역할(표준 및 PostgreSQL 특정 유형 포함), 시스템 열의 특성, 그리고 &lt;b&gt;ALTER TABLE&lt;/b&gt; 명령을 사용하여 기존 테이블 구조를 변경하는 다양한 작업에 대해 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 1: 데이터베이스 테이블 기본 사항&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 관계형 데이터베이스의 핵심에는 데이터를 구성하고 저장하는 기본 구조인 테이블이 있습니다. 고급 기능을 살펴보기 전에 테이블의 기본 구성과 목적을 이해하는 것이 필수적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1 테이블 구조: 행과 열&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL과 같은 관계형 데이터베이스 시스템 내의 테이블은 종이나 스프레드시트의 익숙한 테이블 구조를 반영합니다. 즉, 행과 열로 구성됩니다.&lt;span&gt;&lt;/span&gt; 각 열은 특정 속성 또는 정보 조각을 나타내며, 각 행은 해당 속성에 대한 값으로 구성된 단일 레코드 또는 엔티티를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;생성 시 열의 수와 순서는 고정되며 각 열에는 해당 테이블 내에서 고유한 이름이 할당됩니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이 고정된 구조는 데이터 저장 및 검색의 일관성과 예측 가능성을 보장합니다. 반대로 행의 수는 가변적이며, 특정 시점에 저장된 데이터 양을 반영하고 레코드가 추가되거나 제거됨에 따라 테이블이 동적으로 증가하거나 축소될 수 있도록 합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 열(스키마 정의)의 고정된 특성과 행(데이터 인스턴스 보유)의 가변적 특성 사이의 이러한 본질적인 대조는 안정적인 구조적 프레임워크 내에서 진화하는 데이터 세트를 관리하는 관계형 모델의 능력에 기본이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;표준 SQL에 의해 정의된 기본 테이블 구조는 행이 저장되거나 검색되는 순서를 본질적으로 보장하지 않는다는 점을 인식하는 것이 중요합니다. 행은 삽입 시간이나 물리적 저장 위치에 따라 특정 순서로 나타날 수 있지만, 쿼리에서 &lt;b&gt;ORDER BY&lt;/b&gt; 절을 사용하여 명시적으로 요청하지 않는 한 이 순서는 보장되지 않습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 또한 특정 제약 조건이 적용되지 않는 한 테이블에는 여러 개의 동일한 행이 포함될 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이는 테이블의 주요 역할이 구조적 조직이며, 순서 지정 및 고유성과 같은 속성은 특정 SQL 명령이나 제약 조건을 통해 계층화된다는 점을 강조합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;1.2 데이터 구성에서 테이블의 목적&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 테이블의 근본적인 목적은 관련 데이터를 저장하기 위한 구조화된 메커니즘을 제공하는 것입니다.&lt;span&gt;&lt;/span&gt; 일반적인 관계형 데이터베이스 설계에서는 정보가 여러 테이블로 분리되며, 각 테이블은 고유한 엔티티 유형(예: customers, products, orders)을 나타냅니다. 이러한 구성은 효율적인 데이터 관리를 용이하게 하여 관계형 모델의 원칙에 따라 정보의 대상 지정 저장, 검색, 수정 및 삭제를 가능하게 합니다.&lt;span&gt;&lt;/span&gt; 데이터를 논리적으로 구조화함으로써 테이블은 복잡한 쿼리와 관계를 효과적으로 정의하고 관리할 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.3 데이터 유형의 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 정의의 초석은 각 열에 데이터 유형을 할당하는 것입니다.&lt;span&gt;&lt;/span&gt; 데이터 유형은 두 가지 주요 기능을 수행합니다. 열에 저장될 수 있는 가능한 값 집합을 제한하고, 저장된 데이터에 의미를 할당하여 조작 방법을 결정합니다.&lt;span&gt;&lt;/span&gt; 예를 들어, integer로 정의된 열은 정수만 허용하고 수학적 연산을 허용하는 반면, text 열은 연결과 같은 텍스트 조작에 적합한 문자열을 허용합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;25:1-25:457&quot; data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 숫자 유형(smallint, integer, bigint, numeric, real, double precision), 문자 유형(char(n), varchar(n), text), 날짜/시간 유형(date, time, timestamp, interval), 부울(boolean), 이진 데이터(bytea), 네트워크 주소(inet, cidr), 기하학적 유형(point, line, box, polygon), 고유 식별자(uuid), 문서 유형(json, jsonb, xml) 등 다양한 요구 사항을 충족하는 풍부한 내장 데이터 유형 세트를 자랑합니다.&lt;span&gt;&lt;/span&gt; 또한 사용자는 CREATE TYPE 명령을 사용하여 사용자 정의 데이터 유형을 생성하여 유형 시스템을 확장할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;25:1-25:457&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;27:1-27:44&quot; data-ke-size=&quot;size16&quot;&gt;다음은 PostgreSQL에서 자주 사용되는 몇 가지 데이터 유형입니다 &lt;span&gt;&lt;/span&gt;:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;29:1-39:29&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;데이터 유형 범주&lt;/td&gt;
&lt;td&gt;예시 유형&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;31:1-31:100&quot;&gt;
&lt;td data-sourcepos=&quot;31:1-31:4&quot;&gt;숫자&lt;/td&gt;
&lt;td data-sourcepos=&quot;31:6-31:71&quot;&gt;integer, bigint, numeric(p, s), real, double precision&lt;/td&gt;
&lt;td data-sourcepos=&quot;31:73-31:98&quot;&gt;정수, 임의 정밀도 숫자, 부동 소수점 숫자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;32:1-32:63&quot;&gt;
&lt;td data-sourcepos=&quot;32:1-32:4&quot;&gt;문자&lt;/td&gt;
&lt;td data-sourcepos=&quot;32:6-32:38&quot;&gt;char(n), varchar(n), text&lt;/td&gt;
&lt;td data-sourcepos=&quot;32:40-32:61&quot;&gt;고정 길이 문자열, 가변 길이 문자열&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;33:1-33:104&quot;&gt;
&lt;td data-sourcepos=&quot;33:1-33:7&quot;&gt;날짜/시간&lt;/td&gt;
&lt;td data-sourcepos=&quot;33:9-33:64&quot;&gt;date, time, timestamp, timestamptz, interval&lt;/td&gt;
&lt;td data-sourcepos=&quot;33:66-33:102&quot;&gt;날짜, 시간, 날짜 및 시간 (타임존 포함/미포함), 시간 간격&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;34:1-34:31&quot;&gt;
&lt;td data-sourcepos=&quot;34:1-34:4&quot;&gt;부울&lt;/td&gt;
&lt;td data-sourcepos=&quot;34:6-34:16&quot;&gt;boolean&lt;/td&gt;
&lt;td data-sourcepos=&quot;34:18-34:29&quot;&gt;논리적 참/거짓 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;35:1-35:36&quot;&gt;
&lt;td data-sourcepos=&quot;35:1-35:4&quot;&gt;이진&lt;/td&gt;
&lt;td data-sourcepos=&quot;35:6-35:14&quot;&gt;bytea&lt;/td&gt;
&lt;td data-sourcepos=&quot;35:16-35:34&quot;&gt;이진 데이터 (&quot;바이트 배열&quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;36:1-36:71&quot;&gt;
&lt;td data-sourcepos=&quot;36:1-36:4&quot;&gt;기하&lt;/td&gt;
&lt;td data-sourcepos=&quot;36:6-36:50&quot;&gt;point, line, box, polygon, circle&lt;/td&gt;
&lt;td data-sourcepos=&quot;36:52-36:69&quot;&gt;2차원 평면상의 기하학적 객체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;37:1-37:64&quot;&gt;
&lt;td data-sourcepos=&quot;37:1-37:9&quot;&gt;네트워크 주소&lt;/td&gt;
&lt;td data-sourcepos=&quot;37:11-37:37&quot;&gt;inet, cidr, macaddr&lt;/td&gt;
&lt;td data-sourcepos=&quot;37:39-37:62&quot;&gt;IP 주소, 네트워크 주소, MAC 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;38:1-38:54&quot;&gt;
&lt;td data-sourcepos=&quot;38:1-38:6&quot;&gt;JSON&lt;/td&gt;
&lt;td data-sourcepos=&quot;38:8-38:24&quot;&gt;json, jsonb&lt;/td&gt;
&lt;td data-sourcepos=&quot;38:26-38:52&quot;&gt;텍스트 JSON 데이터, 이진 JSON 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;39:1-39:29&quot;&gt;
&lt;td data-sourcepos=&quot;39:1-39:6&quot;&gt;UUID&lt;/td&gt;
&lt;td data-sourcepos=&quot;39:8-39:15&quot;&gt;uuid&lt;/td&gt;
&lt;td data-sourcepos=&quot;39:17-39:27&quot;&gt;범용 고유 식별자&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 유형 선택은 데이터 무결성을 보장하는 첫 번째 방어선으로, 근본적으로 잘못된 데이터(예: 숫자 필드의 텍스트)가 저장되는 것을 방지합니다. 또한 저장 효율성과 쿼리 성능에도 상당한 영향을 미칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 2: 특수 열 속성 및 정의&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 이름과 데이터 유형 외에도 열은 값 할당 또는 계산을 자동화하는 특수 속성을 가질 수 있어 애플리케이션 로직을 단순화하고 데이터 관리를 향상시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 기본값 할당 (DEFAULT)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DEFAULT&lt;/b&gt; 절은 &lt;b&gt;INSERT&lt;/b&gt; 작업 중에 해당 열에 대한 명시적 값이 제공되지 않은 경우 열에 기본값을 자동으로 할당하는 메커니즘을 제공합니다.&lt;span&gt;&lt;/span&gt; 이는 사용자가 지정하지 않은 경우에도 열에 합리적인 값이 있도록 보장하여 삽입 중에 누락된 값을 명시적으로 처리할 필요성을 줄이는 데 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구문은 열 정의 내에서 DEFAULT 키워드 다음에 기본 표현식(default_expr)을 지정하는 것을 포함합니다.&lt;span&gt;&lt;/span&gt; 이 표현식은 열의 데이터 유형과 호환되는 값을 생성해야 합니다.&lt;span&gt;&lt;/span&gt; 중요한 것은 표현식이 변수가 없어야 한다는 것입니다. 즉, 동일한 테이블 내의 다른 열을 참조할 수 없으며 하위 쿼리, 쿼리 매개변수, 집계 또는 창/분석 함수를 포함할 수 없습니다.&lt;span&gt;&lt;/span&gt; 그러나 비결정적일 수는 있습니다.&lt;span&gt;&lt;/span&gt; 일반적인 예로는 리터럴 값(예: DEFAULT 0, DEFAULT 'active'), 생성 시간을 기록하기 위한 current_timestamp와 같은 함수 또는 시퀀스 생성기에서 값을 가져오기 위한 nextval('sequence_name')이 있습니다.&lt;span&gt;&lt;/span&gt; 열 정의에 DEFAULT 절이 생략되면 기본값은 암시적으로 NULL입니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;예시:&lt;br /&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1745214674688&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE distributors (
    name      varchar(40) DEFAULT 'Luso Films', -- 문자열 리터럴 기본값
    did       integer DEFAULT nextval('distributors_serial'), -- 시퀀스 기본값
    modtime   timestamp DEFAULT current_timestamp -- 함수 기본값
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 distributors 테이블에 새 행을 삽입할 때 name을 지정하지 않으면 'Luso Films'가 기본값으로 사용됩니다. did 열은 distributors_serial 시퀀스에서 다음 값을 가져오고, modtime 열은 현재 타임스탬프를 기본값으로 갖습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2.2 ID 열: 자동 증가 키 (GENERATED AS IDENTITY, SERIAL)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 기본 키 열에 대한 일반적인 요구 사항은 각 새 행에 대해 고유하고 순차적인 숫자 식별자를 자동으로 생성하는 것입니다. &lt;b&gt;PostgreSQL&lt;/b&gt;은 이를 위해 두 가지 주요 접근 방식을 제공합니다. 전통적인 &lt;b&gt;PostgreSQL&lt;/b&gt; 특정 &lt;b&gt;SERIAL&lt;/b&gt; 의사 유형과 최신 SQL 표준 &lt;b&gt;GENERATED AS IDENTITY&lt;/b&gt; 절입니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;SERIAL 유형(smallserial, serial, bigserial, 각각 smallint, integer, bigint에 해당)&lt;/b&gt;은 약식으로 작동합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 열을 &lt;b&gt;SERIAL&lt;/b&gt;로 정의하면 자동으로 세 가지 작업이 수행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;1. 기본 시퀀스 생성기를 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;2. 열의 기본값을 nextval()을 사용하여 해당 시퀀스에서 다음 값을 검색하도록 설정&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;3. 열에 NOT NULL 제약 조건을 추가합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;편리하지만 &lt;b&gt;SERIAL&lt;/b&gt;은 &lt;b&gt;PostgreSQL&lt;/b&gt; 확장이며 SQL 표준의 일부가 아닙니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 기본 시퀀스 구성(예: 시작 값, 증분)은 테이블 생성 후 별도의 &lt;b&gt;ALTER SEQUENCE&lt;/b&gt; 명령이 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;SERIAL 예시:&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1745214911842&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE products (
    product_id SERIAL PRIMARY KEY, -- SERIAL을 사용한 자동 증가 기본 키
    product_name VARCHAR(100) NOT NULL
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 product_id는 SERIAL로 정의되어, 새 제품이 삽입될 때마다 자동으로 증가하는 정수 값이 할당됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PostgreSQL 버전 10&lt;/b&gt;에서 도입된 &lt;b&gt;GENERATED AS IDENTITY&lt;/b&gt; 절은 ID 열을 정의하는 표준 준수 방법을 제공합니다.&lt;span&gt;&lt;/span&gt; 암시적으로 생성된 시퀀스를 열과 명시적으로 연결합니다. SERIAL과 마찬가지로 ID 열은 암시적으로 NOT NULL입니다.&lt;span&gt;&lt;/span&gt; 구문은 두 가지 주요 동작을 제공합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-sourcepos=&quot;80:1-82:0&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;80:1-80:274&quot;&gt;&lt;b&gt;GENERATED ALWAYS AS IDENTITY&lt;/b&gt;: PostgreSQL에 시퀀스를 사용하여 열에 대한 값을 항상 생성하도록 지시합니다. INSERT 문에 OVERRIDING SYSTEM VALUE 절이 포함되지 않는 한 사용자가 INSERT 중에 값을 제공하려고 시도하면 오류가 발생합니다.&lt;span&gt;&lt;/span&gt; 마찬가지로 열 값을 변경하려는 UPDATE 명령도 거부됩니다.&lt;span&gt;&lt;/span&gt; 이는 엄격한 적용을 제공하여 데이터베이스가 ID 값을 제어하도록 보장합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;81:1-82:0&quot;&gt;&lt;b&gt;GENERATED BY DEFAULT AS IDENTITY&lt;/b&gt;: PostgreSQL이 사용자가 INSERT 중에 값을 제공하지 않는 경우에만 값을 생성해야 함을 지정합니다. 값이 제공되면 시퀀스 생성 값보다 우선합니다.&lt;span&gt;&lt;/span&gt; 이 동작은 SERIAL 열의 동작과 유사하며 때때로 수동 ID 할당이 필요할 수 있는 경우에 더 많은 유연성을 제공합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GENERATED AS IDENTITY 예시:&lt;/h4&gt;
&lt;pre id=&quot;code_1745215182394&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- GENERATED ALWAYS 예시
CREATE TABLE color (
    color_id INT GENERATED ALWAYS AS IDENTITY, -- 항상 자동 생성
    color_name VARCHAR NOT NULL
);

-- GENERATED BY DEFAULT 예시
CREATE TABLE distributors (
    did    integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, -- 기본적으로 자동 생성, 사용자 값 허용
    name   varchar(40) NOT NULL CHECK (name &amp;lt;&amp;gt; '')
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 예시(color 테이블)에서 color_id는 항상 데이터베이스에 의해 생성되며, 사용자가 값을 직접 삽입하려고 하면 오류가 발생합니다 (단, OVERRIDING SYSTEM VALUE 사용 시 제외).&lt;span&gt;&lt;/span&gt; 두 번째 예시(distributors 테이블)에서는 사용자가 did 값을 제공하면 해당 값이 사용되고, 제공하지 않으면 데이터베이스가 값을 생성합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;GENERATED AS IDENTITY 절은 CREATE SEQUENCE에서 사용 가능한 매개변수(예: INCREMENT BY, START WITH, MINVALUE, MAXVALUE, CYCLE)를 미러링하는 sequence_options를 사용하여 기본 시퀀스의 인라인 구성도 허용합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이는 SERIAL 접근 방식에 비해 시퀀스 관리를 테이블 정의와 더 긴밀하게 통합합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;SERIAL과 IDENTITY가 모두 존재하는 것은 이전 버전과의 호환성/편의성과 SQL 표준 준수 증가에 대한 PostgreSQL의 약속을 반영합니다. 개발자는 PostgreSQL 관용적 관행과 IDENTITY 열이 제공하는 표준 준수 및 명시적 제어의 이점 사이에서 균형을 맞추면서 자신의 요구에 가장 적합한 메커니즘을 선택할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 표는 주요 차이점을 요약합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;105:1-112:100&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;기능&lt;/td&gt;
&lt;td&gt;SERIAL / BIGSERIAL&lt;/td&gt;
&lt;td&gt;GENERATED BY DEFAULT AS IDENTITY&lt;/td&gt;
&lt;td&gt;GENERATED ALWAYS AS IDENTITY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;107:1-107:57&quot;&gt;
&lt;td data-sourcepos=&quot;107:1-107:11&quot;&gt;SQL 표준 준수&lt;/td&gt;
&lt;td data-sourcepos=&quot;107:13-107:37&quot;&gt;아니요 (PostgreSQL 특정) &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;107:39-107:46&quot;&gt;예 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;107:48-107:55&quot;&gt;예 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;108:1-108:96&quot;&gt;
&lt;td data-sourcepos=&quot;108:1-108:8&quot;&gt;명시적 구문&lt;/td&gt;
&lt;td data-sourcepos=&quot;108:10-108:24&quot;&gt;의사 유형 약식 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;108:26-108:59&quot;&gt;명시적 GENERATED...IDENTITY 절 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;108:61-108:94&quot;&gt;명시적 GENERATED...IDENTITY 절 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;109:1-109:112&quot;&gt;
&lt;td data-sourcepos=&quot;109:1-109:11&quot;&gt;사용자 입력 제어&lt;/td&gt;
&lt;td data-sourcepos=&quot;109:13-109:29&quot;&gt;사용자 제공 값 허용 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;109:31-109:54&quot;&gt;사용자 제공 값 허용 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;109:56-109:110&quot;&gt;사용자 제공 값 거부 (OVERRIDING SYSTEM VALUE 제외) &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;110:1-110:136&quot;&gt;
&lt;td data-sourcepos=&quot;110:1-110:8&quot;&gt;시퀀스 구성&lt;/td&gt;
&lt;td data-sourcepos=&quot;110:10-110:36&quot;&gt;별도의 ALTER SEQUENCE &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;110:38-110:85&quot;&gt;CREATE TABLE의 통합된 sequence_options &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;110:87-110:134&quot;&gt;CREATE TABLE의 통합된 sequence_options &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;111:1-111:87&quot;&gt;
&lt;td data-sourcepos=&quot;111:1-111:12&quot;&gt;Null 허용 여부&lt;/td&gt;
&lt;td data-sourcepos=&quot;111:14-111:39&quot;&gt;암시적으로 NOT NULL &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;111:41-111:62&quot;&gt;암시적으로 NOT NULL &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;111:64-111:85&quot;&gt;암시적으로 NOT NULL &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;112:1-112:100&quot;&gt;
&lt;td data-sourcepos=&quot;112:1-112:9&quot;&gt;기본 메커니즘&lt;/td&gt;
&lt;td data-sourcepos=&quot;112:11-112:50&quot;&gt;시퀀스 생성, DEFAULT nextval() 설정 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;112:52-112:74&quot;&gt;시퀀스 생성, 직접 연결 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;112:76-112:98&quot;&gt;시퀀스 생성, 직접 연결 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 생성된 열: 계산된 값 (GENERATED ALWAYS AS... STORED)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 열은 열의 값이 직접 삽입되거나 업데이트되지 않고 동일한 행 내의 다른 열을 포함하는 표현식을 기반으로 자동으로 계산되는 특수 범주입니다.&lt;span&gt;&lt;/span&gt; 개념적으로 뷰처럼 작동하지만 열 수준에서 작동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;PostgreSQL은 현재 &lt;/span&gt;&lt;b&gt;저장된(stored)&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 생성된 열 한 가지만 구현합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이는 행이 쓰여질 때(INSERT 또는 UPDATE를 통해) 값이 계산되고 결과가 다른 열 데이터와 함께 디스크에 물리적으로 저장됨을 의미합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이는 (현재 PostgreSQL에서 지원되지 않는) 가상 생성된 열과 대조됩니다. 가상 생성된 열은 열을 읽을 때만 값을 계산하고 추가 저장 공간을 차지하지 않습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;저장된 생성된 열을 정의하는 구문은 다음과 같습니다. &lt;b&gt;column_name data_type GENERATED ALWAYS AS (expression) STORED&lt;/b&gt; .&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 사례:&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장된 생성된 열은 파생된 값이 자주 쿼리되거나 인덱싱에 필요할 때 유용합니다. 결과를 미리 계산하고 저장함으로써 모든 쿼리에서 즉석에서 값을 계산하는 것과 비교하여 읽기 성능을 잠재적으로 향상시킬 수 있습니다.&lt;span&gt;&lt;/span&gt; 일반적인 예는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;126:1-129:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;126:1-126:60&quot;&gt;&lt;b&gt;first_name&lt;/b&gt; 및 &lt;b&gt;last_name&lt;/b&gt; 열에서 &lt;b&gt;full_name&lt;/b&gt; 열 생성.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;127:1-127:63&quot;&gt;&lt;b&gt;list_price, tax, discount&lt;/b&gt; 열을 기반으로 &lt;b&gt;net_price&lt;/b&gt; 계산.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;128:1-129:0&quot;&gt;파생된 기하학적 속성 또는 데이터의 표준화된 표현 저장.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시 1: 이름 연결&lt;/h4&gt;
&lt;pre id=&quot;code_1745215975365&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE contacts(
    id SERIAL PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    -- full_name은 first_name과 last_name을 연결하여 생성됨
    full_name VARCHAR(101) GENERATED ALWAYS AS (first_name |
| ' ' |
| last_name) STORED,
    email VARCHAR(300) UNIQUE
);

INSERT INTO contacts(first_name, last_name, email)
VALUES ('John', 'Doe', 'john.doe@example.com');

SELECT * FROM contacts;
-- 결과: id=1, first_name='John', last_name='Doe', full_name='John Doe', email='...'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 &lt;b&gt;full_name&lt;/b&gt; 열은 &lt;b&gt;first_name&lt;/b&gt; 과 &lt;b&gt;last_name&lt;/b&gt; 열의 값을 공백으로 연결하여 자동으로 계산되고 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시 2: 순 가격 계산&lt;/h4&gt;
&lt;pre id=&quot;code_1745216098959&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    list_price DECIMAL(10, 2) NOT NULL,
    tax DECIMAL(5, 2) DEFAULT 0,
    discount DECIMAL(5, 2) DEFAULT 0,
    -- net_price는 list_price, tax, discount를 사용하여 계산됨
    net_price DECIMAL(10, 2) GENERATED ALWAYS AS
        ((list_price + (list_price * tax / 100)) - (list_price * discount / 100)) STORED
);

INSERT INTO products (name, list_price, tax, discount)
VALUES ('Laptop', 1200.00, 10.00, 5.00);

SELECT name, net_price FROM products WHERE id = 1;
-- 결과: name='Laptop', net_price=1260.00&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 net_price 는 정가, 세금, 할인을 고려하여 계산된 최종 가격을 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;제한 사항:&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무결성과 효율적인 계산을 보장하기 위해 생성된 열에는 몇 가지 제한 사항이 적용됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;174:1-184:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;174:1-174:129&quot;&gt;&lt;b&gt;쓰기 제한:&lt;/b&gt; 생성된 열에는 직접 쓸 수 없습니다. INSERT 또는 UPDATE 문은 해당 열에 대한 값을 지정할 수 없지만, INSERT 중에 DEFAULT 키워드를 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;175:1-180:50&quot;&gt;&lt;b&gt;표현식 제약 조건:&lt;/b&gt; 생성 표현식에는 제한 사항이 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;176:5-180:50&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;176:5-176:143&quot;&gt;불변(immutable) 함수(동일한 입력에 대해 항상 동일한 결과를 반환하고 부작용이 없는 함수)만 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt; random() 또는 now()와 같은 휘발성 함수는 허용되지 않습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;177:5-177:38&quot;&gt;하위 쿼리를 포함할 수 없습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;178:5-178:59&quot;&gt;현재 행 외부의 어떤 것도 참조할 수 없습니다(예: 다른 테이블, 이전 행).&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;179:5-179:40&quot;&gt;동일한 테이블 내의 다른 생성된 열을 참조할 수 없습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;180:5-180:50&quot;&gt;tableoid를 제외한 시스템 열을 참조할 수 없습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;181:1-181:73&quot;&gt;&lt;b&gt;기타 정의:&lt;/b&gt; 생성된 열은 동시에 DEFAULT 절이나 IDENTITY 정의를 가질 수 없습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;182:1-182:54&quot;&gt;&lt;b&gt;파티셔닝:&lt;/b&gt; 생성된 열은 테이블의 파티션 키의 일부가 될 수 없습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;183:1-184:0&quot;&gt;&lt;b&gt;상속/트리거/복제:&lt;/b&gt; 상속 계층 및 파티션된 테이블에서의 동작에 대한 특정 규칙이 적용됩니다.&lt;span&gt;&lt;/span&gt; BEFORE 트리거에서는 액세스할 수 없지만, BEFORE 트리거에서 기본 열에 대한 변경 사항은 생성된 열의 계산에 반영됩니다.&lt;span&gt;&lt;/span&gt; 논리적 복제 중에도 건너뜁니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;185:1-185:355&quot; data-ke-size=&quot;size16&quot;&gt;저장된 생성된 열의 사용에는 장단점이 있습니다. 추가 디스크 공간을 소비하고 쓰기 작업(INSERT, UPDATE)에 계산 오버헤드를 추가합니다. 왜냐하면 표현식을 평가하고 결과를 저장해야 하기 때문입니다.&lt;span&gt;&lt;/span&gt; 그러나 자주 액세스되는 파생 데이터의 경우, 이 쓰기 시간 비용은 읽기 성능 향상으로 상쇄될 수 있습니다. SELECT 쿼리 중에 재계산할 필요 없이 값을 즉시 사용할 수 있기 때문입니다.&lt;span&gt;&lt;/span&gt; 생성 표현식에 대한 제한 사항은 결정론적 동작을 유지하고 쓰기 중 계산이 효율적이고 영향을 받는 행에 국한되도록 보장하여 복잡한 종속성이나 성능 문제를 방지하는 데 필수적입니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;185:1-185:355&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;185:1-185:355&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 3: 제약 조건을 통한 데이터 무결성 보장&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;185:1-185:355&quot; data-ke-size=&quot;size16&quot;&gt;데이터 유형은 기본적인 수준의 유효성 검사를 제공하지만, 제약 조건은 테이블에 저장된 데이터에 대한 규칙을 정의하고 시행하는 더 강력하고 세분화된 메커니즘을 제공하여 데이터의 정확성, 일관성 및 전반적인 무결성을 보장합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;185:1-185:355&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;185:1-185:355&quot; data-ke-size=&quot;size23&quot;&gt;3.1 제약 조건의 역할&lt;/h3&gt;
&lt;p data-sourcepos=&quot;185:1-185:355&quot; data-ke-size=&quot;size16&quot;&gt;제약 조건은 SQL을 사용하여 테이블 스키마의 일부로 정의된 공식적인 규칙으로, 삽입 또는 업데이트 작업이 성공하려면 새 데이터나 업데이트된 데이터가 만족해야 합니다.&lt;span&gt;&lt;/span&gt; 제약 조건은 데이터 품질의 수호자 역할을 하여 데이터베이스 내에서 일관성이 없거나 논리적으로 잘못된 상태로 이어질 수 있는 작업을 방지합니다.&lt;span&gt;&lt;/span&gt; 사용자가 정의된 제약 조건을 위반하는 INSERT 또는 UPDATE 작업을 시도하면 PostgreSQL은 오류를 발생시키고 작업이 중단되어 기존 데이터의 무결성을 보존합니다.&lt;span&gt;&lt;/span&gt; 제약 조건을 통해 데이터베이스 설계자는 비즈니스 규칙과 구조적 요구 사항을 데이터베이스 스키마 자체에 직접 인코딩할 수 있습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;185:1-185:355&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;185:1-185:355&quot; data-ke-size=&quot;size23&quot;&gt;3.2 열 제약 조건 대 테이블 제약 조건&lt;/h3&gt;
&lt;p data-sourcepos=&quot;185:1-185:355&quot; data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 구문적으로 두 가지 방식으로 제약 조건을 정의할 수 있습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-sourcepos=&quot;199:1-201:0&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;199:1-199:84&quot;&gt;&lt;b&gt;열 제약 조건:&lt;/b&gt; CREATE TABLE 문 내에서 개별 열 정의의 일부로 정의됩니다. 일반적으로 해당 특정 열에만 적용됩니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;200:1-201:0&quot;&gt;&lt;b&gt;테이블 제약 조건:&lt;/b&gt; CREATE TABLE 문 내에서 별도로 정의되며, 단일 열 정의에 직접 연결되지 않습니다. 여러 열에 걸친 제약 조건을 정의할 수 있습니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본질적으로 단일 열에만 영향을 미치는 제약 조건(예: NOT NULL 또는 단일 열에 적용되는 CHECK 또는 UNIQUE 제약 조건)의 경우, 열 제약 조건으로 정의하는 것은 종종 표기상의 편의 문제입니다.&lt;span&gt;&lt;/span&gt; 그러나 제약 조건이 여러 열을 포함하거나 참조해야 하는 경우(예: 다중 열 UNIQUE 키, 두 열을 비교하는 CHECK 제약 조건, 다중 열 FOREIGN KEY) 테이블 제약 조건이 필요합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;NOT NULL 제약 조건은 항상 열 제약 조건으로 작성해야 한다는 예외가 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 다른 일반적인 제약 조건(CHECK, UNIQUE, PRIMARY KEY, FOREIGN KEY)은 단일 열에 적용될 때 두 구문을 모두 사용하여 표현할 수 있지만, 다중 열 적용에는 테이블 제약 조건 구문이 필요합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이러한 구분은 CREATE TABLE 문이 구성되는 방식을 안내하며, 지역화된 규칙에는 열 제약 조건을 사용하고 더 광범위한 규칙이나 스타일 일관성을 위해서는 테이블 제약 조건을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;다음 표는 일반적인 제약 조건 유형에 대한 정의 범위를 요약합니다 &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;208:1-215:41&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;제약 조건 유형&lt;/td&gt;
&lt;td&gt;열 제약 조건 허용 여부&lt;/td&gt;
&lt;td&gt;테이블 제약 조건 허용 여부&lt;/td&gt;
&lt;td&gt;다중 열 가능 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;210:1-210:37&quot;&gt;
&lt;td data-sourcepos=&quot;210:1-210:9&quot;&gt;CHECK&lt;/td&gt;
&lt;td data-sourcepos=&quot;210:11-210:13&quot;&gt;예&lt;/td&gt;
&lt;td data-sourcepos=&quot;210:15-210:17&quot;&gt;예&lt;/td&gt;
&lt;td data-sourcepos=&quot;210:19-210:35&quot;&gt;예 (테이블 제약 조건으로)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;211:1-211:31&quot;&gt;
&lt;td data-sourcepos=&quot;211:1-211:12&quot;&gt;NOT NULL&lt;/td&gt;
&lt;td data-sourcepos=&quot;211:14-211:16&quot;&gt;예&lt;/td&gt;
&lt;td data-sourcepos=&quot;211:18-211:23&quot;&gt;아니요*&lt;/td&gt;
&lt;td data-sourcepos=&quot;211:25-211:29&quot;&gt;아니요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;212:1-212:38&quot;&gt;
&lt;td data-sourcepos=&quot;212:1-212:10&quot;&gt;UNIQUE&lt;/td&gt;
&lt;td data-sourcepos=&quot;212:12-212:14&quot;&gt;예&lt;/td&gt;
&lt;td data-sourcepos=&quot;212:16-212:18&quot;&gt;예&lt;/td&gt;
&lt;td data-sourcepos=&quot;212:20-212:36&quot;&gt;예 (테이블 제약 조건으로)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;213:1-213:43&quot;&gt;
&lt;td data-sourcepos=&quot;213:1-213:15&quot;&gt;PRIMARY KEY&lt;/td&gt;
&lt;td data-sourcepos=&quot;213:17-213:19&quot;&gt;예&lt;/td&gt;
&lt;td data-sourcepos=&quot;213:21-213:23&quot;&gt;예&lt;/td&gt;
&lt;td data-sourcepos=&quot;213:25-213:41&quot;&gt;예 (테이블 제약 조건으로)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;214:1-214:43&quot;&gt;
&lt;td data-sourcepos=&quot;214:1-214:15&quot;&gt;FOREIGN KEY&lt;/td&gt;
&lt;td data-sourcepos=&quot;214:17-214:19&quot;&gt;예&lt;/td&gt;
&lt;td data-sourcepos=&quot;214:21-214:23&quot;&gt;예&lt;/td&gt;
&lt;td data-sourcepos=&quot;214:25-214:41&quot;&gt;예 (테이블 제약 조건으로)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;215:1-215:41&quot;&gt;
&lt;td data-sourcepos=&quot;215:1-215:11&quot;&gt;EXCLUDE&lt;/td&gt;
&lt;td data-sourcepos=&quot;215:13-215:17&quot;&gt;아니요&lt;/td&gt;
&lt;td data-sourcepos=&quot;215:19-215:21&quot;&gt;예&lt;/td&gt;
&lt;td data-sourcepos=&quot;215:23-215:39&quot;&gt;예 (테이블 제약 조건으로)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-sourcepos=&quot;216:1-216:124&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;: NOT NULL 기능은 테이블 CHECK 제약 조건(예: CHECK (column IS NOT NULL))으로 모방할 수 있지만 &lt;span&gt;&lt;/span&gt;, 직접적인 NOT NULL 구문은 열 전용입니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 CHECK 제약 조건 (CHECK)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CHECK 제약 조건은 행 내의 데이터 값에 대한 임의의 조건을 적용하는 다목적 방법을 제공합니다.&lt;span&gt;&lt;/span&gt; INSERT 또는 UPDATE 작업이 성공하려면 TRUE 또는 UNKNOWN(null)으로 평가되어야 하는 부울 표현식을 지정합니다. 표현식이 FALSE로 평가되면 작업이 실패합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;예를 들어 price 열이 양수인지 확인(CHECK (price &amp;gt; 0)), end_date가 start_date 이후에 발생하는지 확인(CHECK (end_date &amp;gt; start_date)), 특정 형식 또는 값 범위 적용 등이 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 테이블 제약 조건으로 정의된 경우 표현식은 평가되는 행의 여러 열을 참조할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745216807442&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CHECK (price &amp;gt; 0), -- 열 CHECK 제약 조건
    discounted_price numeric,
    CHECK (price &amp;gt; discounted_price) -- 테이블 CHECK 제약 조건 (여러 열 참조)
);

-- 제약 조건 위반 시도
INSERT INTO products (product_no, name, price, discounted_price) VALUES (1, 'Test', 10, 15);
-- ERROR: new row for relation &quot;products&quot; violates check constraint &quot;products_check&quot;
-- DETAIL: Failing row contains (1, Test, 10, 15).&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 price 열에는 양수만 허용하는 열 제약 조건이 있고, 테이블 수준에서는 price가 discounted_price보다 커야 한다는 제약 조건이 있습니다.&lt;span&gt;&lt;/span&gt; 마지막 INSERT 문은 두 번째 제약 조건을 위반하여 실패합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;그러나 PostgreSQL의 CHECK 제약 조건에는 제한 사항이 있습니다. 표현식에는 하위 쿼리가 포함될 수 없으며, 삽입되거나 업데이트되는 현재 행의 열 이외의 변수를 참조할 수도 없습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 검사는 다른 테이블의 데이터를 참조할 수도 없습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 또한 표현식 내에서 사용되는 함수는 일관된 결과를 보장하기 위해 이상적으로는 불변(immutable)이어야 합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이러한 제한 사항은 CHECK 제약 조건을 수정되는 단일 행 내의 데이터를 기반으로 한 조건 유효성 검사로 제한합니다. 이 설계는 DML 작업 중 제약 조건 평가의 성능과 단순성을 우선시합니다. 다른 행, 다른 테이블 또는 집계 상태에 액세스해야 하는 더 복잡한 유효성 검사 논리는 일반적으로 트리거를 사용하거나 애플리케이션 수준에서 처리해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;CHECK 제약 조건은 표현식이 TRUE 또는 NULL(알 수 없음)으로 평가되면 만족된다는 점에 유의하는 것도 중요합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 따라서 간단한 CHECK (price &amp;gt; 0) 제약 조건은 price 열의 NULL 값을 방지하지 않습니다. null을 허용하지 않으려면 NOT NULL 제약 조건을 사용하거나 검사 표현식에서 명시적으로 null을 처리해야 합니다(예: CHECK (price IS NOT NULL AND price &amp;gt; 0)).&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.4 NOT NULL 제약 조건 (NOT NULL)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NOT NULL 제약 조건은 열이 NULL 값을 저장할 수 없도록 보장하는 가장 기본적인 데이터 무결성 규칙 중 하나입니다.&lt;span&gt;&lt;/span&gt; 필수 정보를 나타내는 열에 필수적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구문적으로 NOT NULL은 항상 데이터 유형 바로 뒤에 열 제약 조건으로 지정됩니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; CHECK (column_name IS NOT NULL)을 사용하여 동일한 논리적 규칙을 표현할 수 있지만, PostgreSQL은 전용 NOT NULL 제약 조건을 더 효율적으로 구현합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 다른 제약 조건 유형과 달리 이 방식으로 정의된 NOT NULL 제약 조건에는 명시적인 이름을 지정할 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;예시:&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1745216965682&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE users (
    user_id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL, -- NOT NULL 제약 조건
    email VARCHAR(255) UNIQUE NOT NULL, -- NOT NULL 제약 조건
    password VARCHAR(50) NOT NULL -- NOT NULL 제약 조건
);

-- 제약 조건 위반 시도
INSERT INTO users (username, email) VALUES ('testuser', 'test@example.com');
-- ERROR: null value in column &quot;password&quot; violates not-null constraint&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 username, email, password 열은 모두 NOT NULL로 정의되어 있어 해당 열에 값을 제공하지 않고 행을 삽입하려고 하면 오류가 발생합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.5 UNIQUE 제약 조건 (UNIQUE)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UNIQUE 제약 조건은 지정된 열(또는 열 그룹)의 값(또는 값 조합)이 테이블 내의 모든 행에서 고유함을 보장합니다.&lt;span&gt;&lt;/span&gt; 이는 식별자, 이메일 주소 또는 고유해야 하는 기타 속성에 대한 중복 항목을 방지하는 데 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;UNIQUE 제약 조건은 단일 열에 적용되는 경우 열 제약 조건으로 정의하거나, 하나 또는 여러 열에 적용되는 경우 테이블 제약 조건으로 정의할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 테이블 제약 조건 구문은 열 조합(예: UNIQUE (order_id, product_id))에 대한 고유성을 적용하는 데 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;270:1-270:180&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;270:1-270:180&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745217126880&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 단일 열 UNIQUE (열 제약 조건)
CREATE TABLE products (
    product_no integer UNIQUE, -- product_no는 고유해야 함
    name text,
    price numeric
);

-- 다중 열 UNIQUE (테이블 제약 조건)
CREATE TABLE example (
    a integer,
    b integer,
    c integer,
    UNIQUE (a, c) -- (a, c) 조합은 고유해야 함
);

-- 제약 조건 위반 시도 (products 테이블)
INSERT INTO products (product_no, name, price) VALUES (1, 'Cheese', 9.99);
INSERT INTO products (product_no, name, price) VALUES (1, 'Bread', 1.99);
-- ERROR: duplicate key value violates unique constraint &quot;products_product_no_key&quot;
-- DETAIL: Key (product_no)=(1) already exists.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 예시에서는 product_no 열에 UNIQUE 제약 조건이 적용됩니다. 두 번째 예시에서는 a와 c 열의 조합이 고유해야 합니다.&lt;span&gt;&lt;/span&gt; 마지막 INSERT 문은 products 테이블의 UNIQUE 제약 조건을 위반하여 실패합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;UNIQUE 제약 조건의 중요한 측면은 NULL 값 처리입니다. 기본적으로 (그리고 SQL 표준에 따라) NULL 값은 서로 같다고 간주되지 않습니다. 즉, UNIQUE 제약 조건이 있는 단일 열은 여러 행이 해당 열에 NULL을 포함하도록 허용합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; PostgreSQL은 NULLS NOT DISTINCT를 지정하여 이 동작을 변경할 수 있도록 허용합니다. 이는 제약 조건의 목적상 NULL 값을 동일하게 처리하여 제약된 열(들)에 하나의 NULL 값만 허용합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 기본 동작은 NULLS DISTINCT를 사용하여 명시적으로 지정할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;중요하게도, UNIQUE 제약 조건을 추가하면 백그라운드에서 제약된 열(들)에 대한 고유 B-트리 인덱스가 자동으로 생성됩니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이 인덱스는 데이터베이스가 INSERT 및 UPDATE 작업 중에 위반 사항을 효율적으로 확인하는 데 사용하는 메커니즘입니다. 따라서 동일한 열에 대해 별도의 고유 인덱스를 수동으로 생성하는 것은 중복됩니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이 자동 인덱스 생성은 논리적 데이터 무결성 규칙과 성능적인 적용에 필요한 물리적 인덱싱 구조 간의 긴밀한 통합을 강조합니다. 선택적 INCLUDE 절을 사용하면 인덱스에 저장할 추가 열을 지정하여 특정 쿼리에 대한 인덱스 전용 스캔을 가능하게 할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 4: 관계 및 고유성 설정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 제약 조건 유형 중에서 기본 키와 외래 키는 관계형 데이터베이스 설계의 기초를 형성하므로 레코드의 고유 식별과 테이블 간의 논리적 연결 적용을 가능하게 하는 특별한 지위를 갖습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 기본 키 제약 조건 (PRIMARY KEY)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PRIMARY KEY 제약 조건은 값이 테이블 내의 각 행을 고유하게 식별하는 열 또는 열 집합을 지정합니다.&lt;span&gt;&lt;/span&gt; 이는 레코드에 대한 최종 식별자를 제공하는 가장 중요한 제약 조건 유형이라고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;기능적으로 PRIMARY KEY 제약 조건은 지정된 열(들)에 대한 UNIQUE 제약 조건과 NOT NULL 제약 조건의 속성을 모두 결합합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 즉, 기본 키 값(들)은 모든 행에서 고유해야 하며 관련된 열 중 어느 것도 NULL 값을 포함할 수 없습니다. 테이블은 최대 하나의 기본 키만 정의하도록 제한되지만, 이 키는 여러 열에 걸쳐 있을 수 있습니다(복합 기본 키라고 함).&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745217348513&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 단일 열 PRIMARY KEY (열 제약 조건)
CREATE TABLE products (
    product_no integer PRIMARY KEY, -- product_no는 기본 키 (UNIQUE + NOT NULL)
    name text,
    price numeric
);

-- 복합 PRIMARY KEY (테이블 제약 조건)
CREATE TABLE example (
    a integer,
    b integer,
    c integer,
    PRIMARY KEY (a, c) -- (a, c) 조합이 기본 키
);

-- 제약 조건 위반 시도 (products 테이블)
INSERT INTO products (product_no, name, price) VALUES (NULL, 'Test', 1.00);
-- ERROR: null value in column &quot;product_no&quot; violates not-null constraint

INSERT INTO products (product_no, name, price) VALUES (1, 'Cheese', 9.99);
INSERT INTO products (product_no, name, price) VALUES (1, 'Bread', 1.99);
-- ERROR: duplicate key value violates unique constraint &quot;products_pkey&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 예시에서 product_no는 기본 키입니다. 두 번째 예시에서는 a와 c의 조합이 기본 키입니다.&lt;span&gt;&lt;/span&gt; 마지막 두 INSERT 문은 각각 NOT NULL 및 UNIQUE 속성을 위반하여 실패합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;UNIQUE 제약 조건과 유사하게, PRIMARY KEY를 정의하면 고유성 요구 사항을 효율적으로 적용하기 위해 키 열(들)에 대한 기본 고유 B-트리 인덱스가 자동으로 생성됩니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; INCLUDE 절은 기본 키와 함께 사용하여 이 인덱스에 키가 아닌 열을 추가할 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;338:1-338:171&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;338:1-338:171&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;기본 키는 여러 가지 중요한 목적을 수행합니다. 애플리케이션과 사용자가 특정 행을 참조하는 신뢰할 수 있는 방법을 제공하고, 테이블 구조를 문서화하는 데 기본이 되며, 다른 테이블이 이 테이블을 참조하는 외래 키 관계를 설정할 때 기본 대상 열 역할을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;340:1-340:149&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;340:1-340:149&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4.2 외래 키 제약 조건 (FOREIGN KEY)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-sourcepos=&quot;340:1-340:149&quot; data-ke-size=&quot;size16&quot;&gt;외래 키 제약 조건은 테이블 간의 관계를 설정하고 적용하여 참조 무결성을 보장하는 메커니즘입니다.&lt;span&gt;&lt;/span&gt; 한 테이블(참조하는 테이블)의 열(또는 열 집합)에 정의된 외래 키는 해당 열의 값이 다른 테이블(참조되는 테이블)의 해당 기본 키 또는 고유 제약 조건 열(들)에 있는 값과 일치해야 함을 요구합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;340:1-340:149&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;340:1-340:149&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이 제약 조건은 &quot;고아&quot; 레코드 생성을 방지합니다. 예를 들어, order 레코드는 관련 customer_id가 customers 테이블의 실제 레코드에 해당하지 않는 한 존재할 수 없도록 보장할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 외래 키를 정의하는 사용자는 참조되는 테이블에 대한 REFERENCES 권한을 보유해야 합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; REFERENCES 절에 참조되는 열이 명시적으로 지정되지 않은 경우 PostgreSQL은 참조가 참조되는 테이블의 기본 키에 대한 것이라고 가정합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 참조되는 열은 기본 키를 구성하거나 고유 제약 조건 또는 인덱스의 대상이어야 합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 필수는 아니지만, 참조하는 열을 인덱싱하는 것은 외래 키 관계를 기반으로 한 조인을 포함하는 쿼리의 성능에 종종 유익합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;340:1-340:149&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;340:1-340:149&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745217508560&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 먼저 참조될 테이블 생성
CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

-- 외래 키를 포함하는 테이블 생성
CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    shipping_address text,
    product_no integer REFERENCES products (product_no) -- 외래 키 (products.product_no 참조)
);

-- 다른 테이블의 UNIQUE 열 참조 예시
CREATE TABLE product_codes (
    code char(5) UNIQUE,
    description text
);

CREATE TABLE order_details (
    order_id integer REFERENCES orders(order_id),
    product_code char(5) REFERENCES product_codes (code), -- 외래 키 (product_codes.code 참조)
    quantity integer
);

-- 제약 조건 위반 시도 (orders 테이블)
INSERT INTO orders (order_id, shipping_address, product_no) VALUES (1, '123 Main St', 999);
-- ERROR: insert or update on table &quot;orders&quot; violates foreign key constraint &quot;orders_product_no_fkey&quot;
-- DETAIL: Key (product_no)=(999) is not present in table &quot;products&quot;.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;orders 테이블의 product_no 열은 products 테이블의 product_no (기본 키)를 참조합니다. order_details 테이블의 product_code 열은 product_codes 테이블의 code (UNIQUE 열)를 참조합니다.&lt;span&gt;&lt;/span&gt; 마지막 INSERT 문은 products 테이블에 product_no가 999인 행이 없기 때문에 외래 키 제약 조건을 위반하여 실패합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;외래 키는 지정된 MATCH 유형에 따라 참조하는 열의 NULL 값을 처리할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 기본값인 MATCH SIMPLE은 참조하는 열 중 하나라도 NULL이면 외래 키 제약 조건이 만족되도록 허용합니다. MATCH FULL은 참조하는 열 중 하나라도 NULL이 아닌 경우 모두 null이 아니어야 하며 조합이 참조된 행과 일치해야 합니다. 모든 참조하는 열이 NULL이면 제약 조건이 만족됩니다. MATCH PARTIAL은 SQL 표준에 정의되어 있지만 현재 PostgreSQL에서는 구현되지 않았습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 참조하는 열을 NOT NULL로 선언하면 null이 완전히 허용되지 않으므로 이러한 MATCH 규칙을 효과적으로 우회합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;기본 키와 외래 키는 함께 독립적인 테이블 모음을 응집력 있고 상호 연결된 데이터베이스로 변환합니다. 서로 다른 데이터 엔티티 간의 의도된 관계를 공식적으로 정의하고 데이터베이스 엔진이 이러한 관계의 일관성을 자동으로 적용하도록 하여 관계형 모델의 기반을 형성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;4.2.1 참조 작업 (ON DELETE, ON UPDATE)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외래 키 제약 조건은 참조되는 테이블의 행이 삭제되거나(ON DELETE) 참조된 키 값이 업데이트될 때(ON UPDATE) 데이터베이스가 어떻게 반응해야 하는지를 지시하는 참조 작업을 지정할 수도 있습니다.&lt;span&gt;&lt;/span&gt; 이러한 작업은 관련 테이블 간의 일관성을 유지하기 위한 선언적 규칙을 제공합니다. 사용 가능한 작업은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;391:1-396:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;391:1-391:148&quot;&gt;&lt;b&gt;NO ACTION&lt;/b&gt; (기본값): 제약 조건 검사가 발생할 때(제약 조건이 지연 가능한 경우 문 또는 트랜잭션 끝에 발생할 수 있음) 참조하는 행이 여전히 존재하는 경우, (참조되는 테이블에서의 삭제/업데이트) 작업은 오류를 발생시킵니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;392:1-392:107&quot;&gt;&lt;b&gt;RESTRICT&lt;/b&gt;: NO ACTION과 유사하지만, 검사는 항상 명령이 실행될 때 즉시 수행됩니다(지연될 수 없음). 참조하는 행이 존재하면 작업이 실패합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;393:1-393:114&quot;&gt;&lt;b&gt;CASCADE&lt;/b&gt;: 참조된 행이 삭제되면 해당 행을 참조하는 모든 행도 자동으로 삭제됩니다. 참조된 키가 업데이트되면 참조하는 열의 해당 값이 새 값으로 자동으로 업데이트됩니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;394:1-394:119&quot;&gt;&lt;b&gt;SET NULL&lt;/b&gt;: 참조된 행이 삭제되거나 참조된 키가 업데이트되면 참조하는 행의 참조하는 열(들)이 NULL로 설정됩니다. 이를 위해서는 참조하는 열이 null을 허용해야 합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;395:1-396:0&quot;&gt;&lt;b&gt;SET DEFAULT&lt;/b&gt;: 참조된 행이 삭제되거나 참조된 키가 업데이트되면 참조하는 행의 참조하는 열(들)이 정의된 기본값으로 설정됩니다. 이를 위해서는 참조하는 열에 적절한 기본값이 정의되어 있어야 합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;397:1-397:29&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시 (ON DELETE CASCADE):&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745217598394&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    shipping_address text,
    product_no integer REFERENCES products (product_no) ON DELETE CASCADE -- CASCADE 옵션 추가
);

INSERT INTO products VALUES (1, 'Toy Car', 5.00);
INSERT INTO orders VALUES (101, '456 Oak Ave', 1);
INSERT INTO orders VALUES (102, '789 Pine St', 1);

SELECT * FROM orders; -- 2개의 주문이 표시됨

DELETE FROM products WHERE product_no = 1; -- product_no=1인 제품 삭제

SELECT * FROM orders; -- 주문이 없음 (CASCADE로 인해 삭제됨)&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;p data-sourcepos=&quot;421:1-421:150&quot; data-ke-size=&quot;size16&quot;&gt;이 예시에서는 orders 테이블의 외래 키에 ON DELETE CASCADE가 지정되었습니다. products 테이블에서 product_no가 1인 행이 삭제되자, 해당 제품을 참조하던 orders 테이블의 모든 행도 자동으로 삭제되었습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;421:1-421:150&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;421:1-421:150&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이러한 참조 작업은 계단식 삭제 또는 업데이트와 같은 복잡한 일관성 논리를 스키마 정의에 따라 데이터베이스 엔진이 자동으로 처리하도록 허용합니다. 이는 책임을 애플리케이션 계층에서 이동시켜 잠재적으로 안정성을 높이고 애플리케이션 코드를 단순화합니다. 그러나 작업 선택, 특히 CASCADE는 스키마 설계 중에 신중하게 고려해야 합니다. 자동 수정은 제대로 계획되지 않으면 광범위하고 때로는 의도하지 않은 결과를 초래할 수 있기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;421:1-421:150&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;421:1-421:150&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 5: 고급 제약 조건 메커니즘&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;421:1-421:150&quot; data-ke-size=&quot;size16&quot;&gt;표준 SQL 제약 조건 외에도 PostgreSQL은 더 복잡한 데이터 무결성 규칙을 적용하기 위한 고유한 메커니즘을 제공합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;421:1-421:150&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;421:1-421:150&quot; data-ke-size=&quot;size23&quot;&gt;5.1 제외 제약 조건 (EXCLUDE)&lt;/h3&gt;
&lt;p data-sourcepos=&quot;431:1-431:294&quot; data-ke-size=&quot;size16&quot;&gt;제외 제약 조건은 고유성 개념을 크게 일반화하는 강력한 PostgreSQL 특정 기능입니다.&lt;span&gt;&lt;/span&gt; UNIQUE 제약 조건은 등호(=) 연산자를 기반으로 두 행이 동일하지 않도록 보장하는 반면, EXCLUDE 제약 조건은 지정된 열 또는 표현식에 대해 지정된 연산자를 사용하여 임의의 두 행을 비교할 때 모든 비교 결과가 TRUE가 되지 않도록 보장합니다.&lt;span&gt;&lt;/span&gt; 본질적으로, 단순한 등호를 훨씬 넘어서는 정의된 기준 집합을 기반으로 행이 &quot;충돌&quot;하는 것을 방지합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;431:1-431:294&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-sourcepos=&quot;431:1-431:294&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;5.1.1 연산자 및 사용 사례&lt;/span&gt;&lt;/h4&gt;
&lt;p data-sourcepos=&quot;435:1-435:229&quot; data-ke-size=&quot;size16&quot;&gt;제외 제약 조건은 &lt;b&gt;EXCLUDE USING index_method ( element WITH operator [,...] )&lt;/b&gt; 구문을 사용하여 정의되며, 선택적으로 인덱스 매개변수와 부분 제약 조건을 위한 WHERE 절이 뒤따릅니다.&lt;span&gt;&lt;/span&gt; index_method는 일반적으로 gist 또는 spgist입니다. 제약 조건의 핵심은 element WITH operator 쌍에 있습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;435:1-435:229&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;435:1-435:229&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;제외 제약 조건의 강력함은 선택한 index_method 및 관련 연산자 클래스와 호환되는 한 사용할 수 있는 다양한 operator에서 비롯됩니다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;438:1-443:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;438:1-438:91&quot;&gt;&lt;b&gt;= (같음):&lt;/b&gt; 표준 UNIQUE 제약 조건을 모방하여 두 행이 열에서 동일한 값을 갖지 않도록 보장하는 데 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;439:1-439:183&quot;&gt;&lt;b&gt;&amp;amp;&amp;amp; (겹침):&lt;/b&gt; 이것은 아마도 가장 일반적인 사용 사례일 것입니다. 범위 유형(예: tsrange, daterange, numrange) 또는 기하학적 유형(box, circle)에 적용되어 행이 겹치는 범위나 교차하는 기하학적 구조를 갖는 것을 방지합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;440:1-440:93&quot;&gt;&lt;b&gt;&amp;lt;&amp;gt; (같지 않음):&lt;/b&gt; 등록 값이 동일한 버스 ID에 속하지 않는 한 고유하도록 보장하는 것과 같은 더 복잡한 논리에 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;441:1-441:109&quot;&gt;&lt;b&gt;기타 범위/기하학적 연산자:&lt;/b&gt; @&amp;gt; (포함), &amp;lt;@ (포함됨), ~= (점/요소 포함)와 같은 연산자는 데이터 유형 및 원하는 논리에 따라 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;442:1-443:0&quot;&gt;&lt;b&gt;사용자 정의 연산자:&lt;/b&gt; 매우 특정한 제외 논리를 위해 사용자 정의 연산자를 정의하는 것이 이론적으로 가능하지만, 이는 PostgreSQL의 연산자 클래스 시스템과의 통합을 요구하는 상당한 복잡성을 수반합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;444:1-444:35&quot; data-ke-size=&quot;size16&quot;&gt;제외 제약 조건이 뛰어난 일반적인 사용 사례는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;445:1-449:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;445:1-445:124&quot;&gt;&lt;b&gt;자원 스케줄링/예약:&lt;/b&gt; 동일한 자원에 대한 시간 슬롯(tsrange 또는 daterange)이 겹치지 않도록(&amp;amp;&amp;amp;) 하여 회의실, 장비 또는 인력의 이중 예약을 방지합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;446:1-446:78&quot;&gt;&lt;b&gt;가격 유효성:&lt;/b&gt; 겹치는 유효 기간을 방지하여 특정 시점에 주어진 제품에 대해 하나의 가격 정의만 활성화되도록 보장합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;447:1-447:76&quot;&gt;&lt;b&gt;지리 공간 제약 조건:&lt;/b&gt; 테이블에 저장된 지리적 영역(polygon 또는 box)이 교차하는 것을 방지합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;448:1-449:0&quot;&gt;&lt;b&gt;복잡한 비즈니스 규칙:&lt;/b&gt; 고유성이 다른 열의 값에 따라 달라지는 버스 등록 예와 같은 규칙을 적용합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;450:1-450:24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시 1: 회의실 예약 (겹침 방지)&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1745217759390&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- GiST 인덱스를 사용하기 위해 btree_gist 확장 활성화 (필요한 경우)
CREATE EXTENSION IF NOT EXISTS btree_gist;

CREATE TABLE room_reservations (
    room_name text,
    reservation_period tsrange, -- 타임스탬프 범위 타입
    EXCLUDE USING gist (
        room_name WITH =, -- 방 이름은 같아야 하고
        reservation_period WITH &amp;amp;&amp;amp; -- 예약 기간은 겹치면 안 됨 (&amp;amp;&amp;amp; 연산자)
    )
);

-- 유효한 예약 삽입
INSERT INTO room_reservations VALUES ('Room A', '

**예시 2: 버스 등록 (조건부 고유성)**
```sql
CREATE EXTENSION IF NOT EXISTS btree_gist;

CREATE TABLE bus_register_ledger (
    bus_id text,
    registration text,
    driver text,
    -- registration은 같고 bus_id는 다른 경우를 제외함
    EXCLUDE USING gist (
        registration WITH =,
        bus_id WITH &amp;lt;&amp;gt;
    )
);

-- 초기 데이터 삽입
INSERT INTO bus_register_ledger(bus_id, registration, driver) VALUES ('bus-id-1', 'bussy1', 'jessica');
INSERT INTO bus_register_ledger(bus_id, registration, driver) VALUES ('bus-id-2', 'bussy2', 'john');

-- 같은 버스, 같은 등록번호 (운전자 변경 등) -&amp;gt; 허용됨
INSERT INTO bus_register_ledger(bus_id, registration, driver) VALUES ('bus-id-1', 'bussy1', 'adam');

-- 다른 버스, 이미 사용 중인 등록번호 -&amp;gt; 제약 조건 위반
INSERT INTO bus_register_ledger(bus_id, registration, driver) VALUES ('bus-id-3', 'bussy2', 'brendan');
-- ERROR: conflicting key value violates exclusion constraint &quot;unique_bus_id_registration_pair&quot;
-- DETAIL: Key (registration, bus_id)=(bussy2, bus-id-3) conflicts with existing key (registration, bus_id)=(bussy2, bus-id-2).&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 제약 조건은 registration 값이 같고(=) 동시에 bus_id 값이 다른(&amp;lt;&amp;gt;) 두 행이 존재하는 것을 방지합니다. 즉, 특정 등록 번호는 여러 버스에 할당될 수 없습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;5.1.2 GiST/SP-GiST 인덱스의 역할&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제외 제약 조건은 효율적인 적용을 위해 기본적으로 기본 인덱스에 의존합니다.&lt;span&gt;&lt;/span&gt; 일반적으로 등호 및 스칼라 유형의 범위 비교에 적합한 B-트리 인덱스를 사용하는 UNIQUE 및 PRIMARY KEY 제약 조건과 달리, 제외 제약 조건은 일반적으로 더 고급 인덱스 유형이 필요합니다: &lt;b&gt;GiST&lt;/b&gt; (일반화된 검색 트리) 또는 &lt;b&gt;SP-GiST&lt;/b&gt; (공간 분할 GiST).&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이러한 인덱스 구조는 다차원 데이터, 비 스칼라 유형(범위, 기하학적 모양, 전체 텍스트 검색 벡터 등) 및 제외 제약 조건에서 자주 사용되는 복잡한 비교 연산자(예: &amp;amp;&amp;amp;, @&amp;gt;, &amp;lt;&amp;gt;)를 처리하도록 설계되었습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 표준 B-트리 인덱스는 일반적으로 = 연산자만 사용하는 단순한 제외를 제외하고는 이러한 작업에 충분하지 않습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이러한 특수 인덱스 유형에 대한 요구 사항은 고급 제약 조건의 논리적 정의와 이를 효율적으로 지원하는 데 필요한 물리적 저장 및 인덱싱 메커니즘 간의 깊은 연관성을 강조합니다. EXCLUDE의 선언적 능력은 GiST 및 SP-GiST의 기능에 의해 직접적으로 가능해집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;504:1-504:274&quot; data-ke-size=&quot;size16&quot;&gt;또한, 제외 제약 조건이 동일한 GiST 인덱스 내에서 범위 또는 기하학적 유형과 함께 = 또는 &amp;lt;&amp;gt;와 같은 연산자를 사용하여 표준 데이터 유형(예: integer 또는 text)을 결합해야 하는 경우, 종종 btree_gist 확장을 설치해야 합니다(CREATE EXTENSION btree_gist;).&lt;span&gt;&lt;/span&gt; 이 확장은 표준 B-트리 비교 가능 유형에 대한 GiST 연산자 클래스를 제공하여 GiST 구조 내에서 효과적으로 인덱싱할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;504:1-504:274&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;504:1-504:274&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;매우 강력하지만, 고도로 사용자 정의되거나 조건부 제외 논리를 정의하는 것은 선언적 프레임워크 내에서 제한에 부딪힐 수 있습니다. 예를 들어, 다른 열의 값을 기반으로 제외 제약 조건을 조건부로 만들려고 시도하는 경우(예: status = 'SCHEDULED'인 경우에만 겹침 제외) 복잡한 사용자 정의 연산자를 사용하고 잠재적으로 내부 연산자 클래스를 수정하지 않고는 매우 어렵거나 불가능할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이러한 경우 개발자는 부분 제외 제약 조건( WHERE 절 사용)과 애플리케이션 수준 검사를 결합하거나 데이터베이스 트리거를 사용하여 절차적 유효성 검사 논리를 구현하는 등 대체 전략을 채택해야 할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;504:1-504:274&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;504:1-504:274&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 6: 시스템 열 이해&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;504:1-504:274&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 CREATE TABLE에서 명시적으로 정의한 열 외에도 PostgreSQL은 모든 테이블에 여러 숨겨진 시스템 열을 자동으로 포함합니다. 이러한 열은 데이터베이스의 내부 작동, 특히 저장 및 동시성 제어와 관련된 메타데이터를 제공합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;504:1-504:274&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;504:1-504:274&quot; data-ke-size=&quot;size23&quot;&gt;6.1 암시적으로 정의된 열&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 사용자 정의 테이블에는 PostgreSQL에서 관리하는 시스템 열이 암시적으로 포함됩니다.&lt;span&gt;&lt;/span&gt; 이러한 열의 이름(예: ctid, xmin, xmax)은 예약되어 있으며 사용자 정의 열의 식별자로 사용할 수 없습니다.&lt;span&gt;&lt;/span&gt; 일반적으로 표준 SELECT * 쿼리에서는 숨겨져 있지만, 필요한 경우 명시적으로 선택할 수 있으며, 종종 진단 목적, 관리 작업 또는 낮은 수준의 동작을 이해하는 데 사용됩니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.2 주요 시스템 열 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 시스템 열은 유용한 내부 정보를 제공합니다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;520:1-528:148&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;시스템 열&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;522:1-522:57&quot;&gt;
&lt;td data-sourcepos=&quot;522:1-522:7&quot;&gt;oid&lt;/td&gt;
&lt;td data-sourcepos=&quot;522:9-522:16&quot;&gt;객체 식별자&lt;/td&gt;
&lt;td data-sourcepos=&quot;522:18-522:55&quot;&gt;역사적으로 행의 고유 식별자로 사용되었으나 현재는 권장되지 않음.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;523:1-523:101&quot;&gt;
&lt;td data-sourcepos=&quot;523:1-523:12&quot;&gt;tableoid&lt;/td&gt;
&lt;td data-sourcepos=&quot;523:14-523:31&quot;&gt;행을 포함하는 테이블의 OID&lt;/td&gt;
&lt;td data-sourcepos=&quot;523:33-523:99&quot;&gt;상속/파티션된 테이블에서 행의 실제 출처 테이블 식별. pg_class.oid와 조인하여 테이블 이름 확인 가능.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;524:1-524:62&quot;&gt;
&lt;td data-sourcepos=&quot;524:1-524:8&quot;&gt;xmin&lt;/td&gt;
&lt;td data-sourcepos=&quot;524:10-524:27&quot;&gt;삽입 트랜잭션 ID (XID)&lt;/td&gt;
&lt;td data-sourcepos=&quot;524:29-524:60&quot;&gt;이 행 버전을 삽입한 트랜잭션 식별 (MVCC 핵심).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;525:1-525:59&quot;&gt;
&lt;td data-sourcepos=&quot;525:1-525:8&quot;&gt;cmin&lt;/td&gt;
&lt;td data-sourcepos=&quot;525:10-525:25&quot;&gt;삽입 명령 ID (CID)&lt;/td&gt;
&lt;td data-sourcepos=&quot;525:27-525:57&quot;&gt;삽입 트랜잭션 내의 특정 명령 식별 (0부터 시작).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;526:1-526:114&quot;&gt;
&lt;td data-sourcepos=&quot;526:1-526:8&quot;&gt;xmax&lt;/td&gt;
&lt;td data-sourcepos=&quot;526:10-526:27&quot;&gt;삭제 트랜잭션 ID (XID)&lt;/td&gt;
&lt;td data-sourcepos=&quot;526:29-526:112&quot;&gt;이 행 버전을 삭제한 트랜잭션 식별 (0이면 삭제되지 않음). 삭제 트랜잭션이 커밋되지 않았거나 롤백된 경우 0이 아닐 수 있음 (MVCC 핵심).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;527:1-527:64&quot;&gt;
&lt;td data-sourcepos=&quot;527:1-527:8&quot;&gt;cmax&lt;/td&gt;
&lt;td data-sourcepos=&quot;527:10-527:25&quot;&gt;삭제 명령 ID (CID)&lt;/td&gt;
&lt;td data-sourcepos=&quot;527:27-527:62&quot;&gt;삭제 트랜잭션 내의 특정 명령 식별 (0이면 삭제되지 않음).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;528:1-528:148&quot;&gt;
&lt;td data-sourcepos=&quot;528:1-528:8&quot;&gt;ctid&lt;/td&gt;
&lt;td data-sourcepos=&quot;528:10-528:34&quot;&gt;행 버전의 물리적 위치 (Tuple ID)&lt;/td&gt;
&lt;td data-sourcepos=&quot;528:36-528:146&quot;&gt;테이블 내 행 버전의 물리적 주소 ((페이지 번호, 페이지 내 아이템 인덱스)). &lt;b&gt;주의:&lt;/b&gt; UPDATE나 VACUUM FULL 등으로 변경될 수 있으므로 장기 식별자로 사용 불가.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시: 시스템 열 조회&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745217978147&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 'employees' 테이블에서 일부 시스템 열과 사용자 정의 열 조회
SELECT ctid, xmin, tableoid, employee_id, name
FROM employees
LIMIT 5;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 employees 테이블의 처음 5개 행에 대해 물리적 위치(ctid), 삽입 트랜잭션 ID(xmin), 테이블 OID(tableoid) 및 사용자 정의 열(employee_id, name)을 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 열, 특히 xmin 및 xmax는 PostgreSQL이 MVCC에 사용하는 기본 메커니즘을 노출합니다. 각 행 버전을 관리하는 트랜잭션 상태 및 가시성 규칙에 대한 창을 제공합니다. 애플리케이션은 일반적으로 데이터의 논리적 뷰(트랜잭션 스냅샷과 관련된 커밋된 행만 표시)에서 작동하지만, 이러한 열은 이 동시성 제어를 가능하게 하는 물리적 및 트랜잭션 현실을 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ctid(물리적 위치)와 PRIMARY KEY(논리적 식별자)의 뚜렷한 특성은 중요합니다. ctid의 불안정성은 업데이트 또는 유지 관리 작업으로 인해 행의 물리적 주소가 변경될 수 있음을 강조하는 반면, 기본 키는 물리적 저장 세부 정보와 독립적인 일정한 논리적 참조 지점을 제공합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;트랜잭션 및 명령 식별자(xmin, xmax, cmin, cmax)는 32비트 값입니다. 매우 활동적이고 장기 실행되는 데이터베이스에서는 트랜잭션 ID가 결국 랩어라운드될 수 있습니다. PostgreSQL에는 이를 정상적으로 처리하는 메커니즘(VACUUM 및 트랜잭션 ID 고정 포함)이 있지만, 매우 긴 기간(수십억 건의 트랜잭션) 동안 XID의 절대적인 고유성에 의존하는 것은 현명하지 않다는 점을 강조합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 7: 기존 테이블 수정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;547:1-547:181&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스 스키마는 거의 정적이지 않습니다. 요구 사항이 진화함에 따라 기존 테이블 구조를 변경해야 합니다. PostgreSQL은 이를 위해 다목적 ALTER TABLE 명령을 제공하여 열 추가에서 테이블 이름 변경에 이르기까지 다양한 수정을 허용합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;547:1-547:181&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;547:1-547:181&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;다양한 ALTER TABLE 하위 명령이 성능과 동시성에 매우 다른 영향을 미칠 수 있다는 점을 인지하는 것이 중요합니다. 일부 작업은 빠르고 메타데이터만 변경하는 반면, 다른 작업은 전체 테이블을 스캔하거나 다시 작성해야 할 수 있으며, 상당한 시간과 리소스를 소비하고 다른 작업을 차단하는 배타적 잠금이 필요할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;549:1-549:193&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;br /&gt;7.1 열 추가 (ADD COLUMN)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-sourcepos=&quot;549:1-549:193&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존 테이블에 새 열을 추가하려면 ADD COLUMN 절을 사용합니다. &lt;b&gt;ALTER TABLE table_name ADD COLUMN column_name data_type [column_constraint...];&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;549:1-549:193&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;549:1-549:193&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;예시:&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745218075790&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 'description' 열 추가 (기본값 NULL)
ALTER TABLE products ADD COLUMN description text;

-- 'active' 열 추가 (기본값 TRUE)
ALTER TABLE links ADD COLUMN active boolean DEFAULT TRUE;

-- 열이 존재하지 않을 경우에만 'status' 열 추가
ALTER TABLE orders ADD COLUMN IF NOT EXISTS status varchar(20) DEFAULT 'pending';&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 예시는 description 열을 추가합니다 . 두 번째 예시는 active 열을 boolean 타입으로 추가하고 기본값을 TRUE로 설정합니다 . 세 번째 예시는 status 열이 아직 없는 경우에만 추가합니다 .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;570:1-570:584&quot; data-ke-size=&quot;size16&quot;&gt;CHECK 또는 DEFAULT와 같은 제약 조건을 열 정의의 일부로 지정할 수 있습니다.&lt;span&gt;&lt;/span&gt; 중요한 성능 고려 사항은 DEFAULT 절과 관련이 있습니다. PostgreSQL 11부터 상수(비휘발성) 기본값(예: DEFAULT 0 또는 DEFAULT 'pending')으로 열을 추가하는 것은 매우 빠릅니다.&lt;span&gt;&lt;/span&gt; 데이터베이스는 기본값을 테이블의 메타데이터에 저장하고 행을 읽거나 결국 다시 작성할 때 지연 적용하여 모든 기존 행을 즉시 비용이 많이 드는 업데이트를 피합니다.&lt;span&gt;&lt;/span&gt; 그러나 기본값이 휘발성(예: DEFAULT clock_timestamp() 또는 DEFAULT random())인 경우 PostgreSQL은 ALTER TABLE 명령이 실행될 때 모든 기존 행에 대해 함수를 평가해야 하므로 전체 테이블 재작성이 필요하며, 이는 큰 테이블에서 매우 느릴 수 있습니다.&lt;span&gt;&lt;/span&gt; DEFAULT가 지정되지 않은 경우 기존 행은 새 열에 NULL을 갖게 됩니다. IF NOT EXISTS 옵션은 동일한 이름의 열이 이미 존재하는 경우 오류를 방지하는 데 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;570:1-570:584&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;570:1-570:584&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;7.2 열 제거 (DROP COLUMN)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-sourcepos=&quot;570:1-570:584&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존 열을 제거하려면 DROP COLUMN 절을 사용합니다. &lt;b&gt;ALTER TABLE table_name DROP COLUMN column_name;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;570:1-570:584&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;570:1-570:584&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;예시:&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745218157696&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 'description' 열 제거 (종속 객체 없으면 성공)
ALTER TABLE products DROP COLUMN description;

-- 'active' 열 제거 (종속 객체 있어도 강제 제거)
ALTER TABLE links DROP COLUMN active CASCADE;

-- 열이 존재하는 경우에만 'temp_notes' 열 제거
ALTER TABLE tasks DROP COLUMN IF EXISTS temp_notes;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 예시는 description 열을 제거합니다 . 두 번째 예시는 active 열과 이에 종속된 모든 객체(예: 뷰)를 함께 제거합니다 . 세 번째 예시는 temp_notes 열이 있을 경우에만 제거합니다 .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제된 열과 관련된 모든 인덱스 또는 테이블 제약 조건도 자동으로 제거됩니다.&lt;span&gt;&lt;/span&gt; RESTRICT 옵션(기본값)은 다른 데이터베이스 객체(예: 뷰 또는 다른 테이블의 외래 키 제약 조건)가 해당 열에 종속된 경우 열 삭제를 방지합니다.&lt;span&gt;&lt;/span&gt; CASCADE 옵션은 종속 객체를 재귀적으로 자동으로 제거하여 삭제를 진행하도록 허용합니다. 이는 극도의 주의를 기울여 사용해야 합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;열 삭제는 일반적으로 빠른 메타데이터 수준 작업으로, 후속 SQL 명령에 대해 열을 보이지 않게 만듭니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 그러나 이전 열 데이터가 차지했던 디스크 공간을 즉시 회수하지는 않습니다. 공간은 사용 가능한 것으로 표시되며 향후 UPDATE 또는 INSERT 작업에 의해 점진적으로 재사용되거나 VACUUM에 의해 회수됩니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; IF EXISTS 옵션은 열이 존재하지 않는 경우 오류를 방지합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;7.3 제약 조건 추가 (ADD CONSTRAINT)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CREATE TABLE에서 사용된 테이블 제약 조건 구문을 미러링하는 ADD CONSTRAINT 구문을 사용하여 기존 테이블에 새 제약 조건을 추가할 수 있습니다. ALTER TABLE table_name ADD CONSTRAINT constraint_name constraint_definition; .&lt;span&gt;&lt;/span&gt; 이는 CHECK, UNIQUE, PRIMARY KEY, FOREIGN KEY, EXCLUDE 제약 조건에 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;NOT NULL 제약 조건(기술적으로 열 제약 조건)을 추가하려면 다른 구문을 사용합니다. ALTER TABLE table_name ALTER COLUMN column_name SET NOT NULL;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745218238626&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- CHECK 제약 조건 추가
ALTER TABLE products ADD CONSTRAINT name_not_empty CHECK (name &amp;lt;&amp;gt; '');

-- UNIQUE 제약 조건 추가
ALTER TABLE products ADD CONSTRAINT unique_product_no UNIQUE (product_no);

-- FOREIGN KEY 제약 조건 추가 (초기 검증 없이)
ALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customers (customer_id) NOT VALID;
-- 나중에 검증 수행
ALTER TABLE orders VALIDATE CONSTRAINT fk_customer;

-- NOT NULL 제약 조건 추가
ALTER TABLE products ALTER COLUMN product_no SET NOT NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 두 예시는 각각 CHECK 및 UNIQUE 제약 조건을 추가합니다 . 세 번째 예시는 FOREIGN KEY 제약 조건을 NOT VALID 옵션과 함께 추가하여 즉각적인 테이블 스캔을 피하고, 나중에 VALIDATE CONSTRAINT를 사용하여 검증합니다.&lt;span&gt;&lt;/span&gt; 마지막 예시는 product_no 열에 NOT NULL 제약 조건을 추가합니다 .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;일반적으로 제약 조건을 추가하려면 PostgreSQL이 전체 테이블을 스캔하여 모든 기존 행이 새 규칙을 만족하는지 확인해야 합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이 스캔은 시간이 많이 걸릴 수 있으며 동시 쓰기를 차단하는 잠금이 필요할 수 있습니다. 이를 완화하기 위해 PostgreSQL은 CHECK 및 FOREIGN KEY 제약 조건에 대해 NOT VALID 옵션을 제공합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; NOT VALID가 지정되면 초기 유효성 검사 스캔이 건너뛰어져 ALTER TABLE 명령이 훨씬 빠르고 덜 방해가 됩니다. 제약 조건은 모든 새 행 또는 업데이트된 행에 대해 즉시 적용되지만 기존 행은 확인되지 않습니다. 제약 조건은 나중에 ALTER TABLE table_name VALIDATE CONSTRAINT constraint_name; 명령을 사용하여 기존 데이터에 대해 유효성을 검사할 수 있으며, 이는 테이블 스캔을 수행하지만 종종 덜 엄격한 잠금으로 수행될 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;7.4 제약 조건 제거 (DROP CONSTRAINT)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 제약 조건을 제거하려면 해당 이름을 알아야 합니다. 생성 중에 제약 조건을 명시적으로 명명한 경우 해당 이름을 사용합니다. 그렇지 않은 경우 시스템에서 생성된 이름(예: mytable_col_check)이 있으며, 이는 psql의 \d tablename 명령과 같은 도구를 사용하여 찾을 수 있습니다.&lt;span&gt;&lt;/span&gt; 구문은 다음과 같습니다. &lt;b&gt;ALTER TABLE table_name DROP CONSTRAINT constraint_name;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;NOT NULL 제약 조건(사용자가 액세스할 수 있는 이름이 없음)을 제거하려면 다음을 사용합니다. &lt;b&gt;ALTER TABLE table_name ALTER COLUMN column_name DROP NOT NULL;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;예시:&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745218308080&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 이름으로 제약 조건 제거
ALTER TABLE products DROP CONSTRAINT unique_product_no;

-- 제약 조건이 존재하는 경우에만 제거
ALTER TABLE orders DROP CONSTRAINT IF EXISTS fk_customer;

-- NOT NULL 제약 조건 제거
ALTER TABLE products ALTER COLUMN product_no DROP NOT NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 예시는 unique_product_no라는 이름의 제약 조건을 제거합니다 . 두 번째 예시는 fk_customer 제약 조건이 있을 경우에만 제거합니다 . 마지막 예시는 product_no 열에서 NOT NULL 제약 조건을 제거합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DROP COLUMN과 유사하게 RESTRICT(기본값)는 다른 객체가 제약 조건에 종속된 경우(예: 외래 키가 삭제되는 고유/기본 키 제약 조건에 종속됨) 삭제를 방지하는 반면, CASCADE는 종속 객체를 제거합니다.&lt;span&gt;&lt;/span&gt; 제약 조건을 지원하기 위해 자동으로 생성된 모든 인덱스(예: UNIQUE 또는 PRIMARY KEY의 경우)도 삭제됩니다.&lt;span&gt;&lt;/span&gt; PostgreSQL 9.4 이전에는 제약 조건 속성을 수정하려면 종종 제약 조건을 삭제하고 다시 추가해야 했습니다.&lt;span&gt;&lt;/span&gt; 9.4부터 ALTER TABLE... ALTER CONSTRAINT가 존재하지만, 주요 용도는 외래 키 제약 조건의 지연 가능성을 변경하는 것입니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;7.5 열의 기본값 변경 (ALTER COLUMN SET/DROP DEFAULT)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열과 연결된 기본값은 다음을 사용하여 변경하거나 제거할 수 있습니다. ALTER TABLE table_name ALTER COLUMN column_name SET DEFAULT expression; ALTER TABLE table_name ALTER COLUMN column_name DROP DEFAULT;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745218377247&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 'target' 열의 기본값을 '_blank'로 설정
ALTER TABLE links ALTER COLUMN target SET DEFAULT '_blank';

-- 'status' 열의 기본값 제거 (NULL로 설정됨)
ALTER TABLE orders ALTER COLUMN status DROP DEFAULT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 예시는 target 열의 기본값을 설정합니다 . 두 번째 예시는 status 열의 기본값을 제거합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 빠른 메타데이터 전용 작업입니다.&lt;span&gt;&lt;/span&gt; 열에 대한 값이 명시적으로 제공되지 않은 후속 INSERT 또는 UPDATE 명령에만 영향을 미칩니다. 기존 행에 이미 저장된 데이터를 변경하지 않습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.6 열의 데이터 유형 변경 (ALTER COLUMN TYPE)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 열의 데이터 유형 변경은 다음을 사용하여 가능합니다.&lt;b&gt; ALTER TABLE table_name ALTER COLUMN column_name TYPE new_data_type;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745218434122&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 'description' 열의 타입을 VARCHAR(255)로 변경
ALTER TABLE products ALTER COLUMN description TYPE VARCHAR(255);

-- 'price' 열의 타입을 INTEGER로 변경 (USING 절 사용)
ALTER TABLE products ALTER COLUMN price TYPE INTEGER USING price::integer;

-- 'col_name' 열의 타입을 INTEGER로 변경 (빈 문자열을 NULL로 처리)
ALTER TABLE tbl_name ALTER COLUMN col_name TYPE integer USING (NULLIF(col_name, '')::integer);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 예시는 단순 타입 변경입니다. 두 번째 예시는 USING 절을 사용하여 기존 numeric 값을 integer로 캐스팅합니다 . 세 번째 예시는 빈 문자열이 있는 varchar 열을 integer로 변경할 때 빈 문자열을 NULL로 변환한 후 캐스팅합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업은 복잡하고 잠재적으로 중단될 수 있습니다. 많은 경우 PostgreSQL은 기존 데이터를 새 유형으로 변환하기 위해 전체 테이블을 다시 작성해야 하며, 이는 매우 느리고 새 테이블 버전에 상당한 디스크 공간을 소비하며 ACCESS EXCLUSIVE 잠금이 필요하여 다른 모든 액세스를 차단할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;선택적 USING 절은 중요합니다. PostgreSQL이 이전 값에서 새 열 값을 계산하는 방법을 지정합니다. 이전 데이터 유형과 새 데이터 유형 간에 암시적 또는 할당 캐스트를 사용할 수 없는 경우 필수입니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; expression은 이전 열 값(종종 암시적으로 캐스트됨, 예: USING old_col::new_type) 또는 테이블의 다른 열을 참조할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 열과 관련된 인덱스 및 단순 제약 조건은 일반적으로 해당 정의를 다시 구문 분석하여 자동으로 변환됩니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 데이터 분포가 변경될 수 있으므로 열 통계가 삭제되며, 이후 테이블에서 ANALYZE를 실행하는 것이 좋습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;7.7 열 이름 변경 (RENAME COLUMN)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 열의 이름을 변경하는 것은 간단한 메타데이터 변경입니다. &lt;b&gt;ALTER TABLE table_name RENAME COLUMN column_name TO new_column_name;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745218510165&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 'title' 열의 이름을 'link_title'로 변경
ALTER TABLE links RENAME COLUMN title TO link_title;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업은 빠르며 저장된 데이터에 영향을 미치지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.8 테이블 이름 변경 (RENAME TO)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 전체 테이블의 이름을 변경하는 것은 메타데이터 전용 작업입니다.&lt;b&gt; ALTER TABLE table_name RENAME TO new_table_name;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745218556431&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 'links' 테이블의 이름을 'urls'로 변경
ALTER TABLE links RENAME TO urls;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르며 테이블 데이터를 수정하지 않습니다 . 그러나 이 명령은 인덱스, 제약 조건 또는 테이블 열이 소유한 시퀀스와 같은 관련 객체의 이름을 자동으로 변경하지 않는다는 점에 유의하는 것이 중요합니다. 원하는 경우 해당 ALTER 명령(예: ALTER INDEX, ALTER SEQUENCE)을 사용하여 별도로 이름을 변경해야 합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;ALTER TABLE에서 사용할 수 있는 광범위한 작업과 CASCADE, IF EXISTS, NOT VALID, USING과 같은 제어 옵션을 결합하면 관리자와 개발자에게 스키마 진화를 위한 강력한 도구를 제공합니다. 그러나 이러한 작업 전반에 걸친 성능 영향 및 잠금 동작의 상당한 변화는 특히 프로덕션 환경에서 큰 테이블을 수정할 때 신중한 계획과 기본 메커니즘에 대한 이해를 필요로 합니다. 기본값이 있는 열 추가 최적화와 같이 PostgreSQL에서 ALTER TABLE 기능의 지속적인 진화는 시간이 지남에 따라 스키마 수정을 덜 방해하고 더 관리하기 쉽게 만들기 위한 지속적인 노력을 보여줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 표는 일반적인 ALTER TABLE 작업과 주요 특징을 요약합니다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;722:1-731:147&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;작업&lt;/td&gt;
&lt;td&gt;구문 요약&lt;/td&gt;
&lt;td&gt;주요 옵션 / 절&lt;/td&gt;
&lt;td&gt;성능 / 잠금 고려 사항&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;724:1-724:213&quot;&gt;
&lt;td data-sourcepos=&quot;724:1-724:14&quot;&gt;ADD COLUMN&lt;/td&gt;
&lt;td data-sourcepos=&quot;724:16-724:71&quot;&gt;ALTER TABLE... ADD COLUMN name type [constraints...]&lt;/td&gt;
&lt;td data-sourcepos=&quot;724:73-724:100&quot;&gt;DEFAULT, IF NOT EXISTS&lt;/td&gt;
&lt;td data-sourcepos=&quot;724:102-724:211&quot;&gt;상수 DEFAULT 사용 시 빠름 (메타데이터만).&lt;span&gt;&lt;/span&gt; 휘발성 DEFAULT 사용 시 느림/재작성.&lt;span&gt;&lt;/span&gt; 그 외에는 최소 잠금. ACCESS EXCLUSIVE 잠금 잠시 필요 .&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;725:1-725:193&quot;&gt;
&lt;td data-sourcepos=&quot;725:1-725:15&quot;&gt;DROP COLUMN&lt;/td&gt;
&lt;td data-sourcepos=&quot;725:17-725:51&quot;&gt;ALTER TABLE... DROP COLUMN name&lt;/td&gt;
&lt;td data-sourcepos=&quot;725:53-725:88&quot;&gt;CASCADE, RESTRICT, IF EXISTS&lt;/td&gt;
&lt;td data-sourcepos=&quot;725:90-725:191&quot;&gt;빠름 (열을 보이지 않게 표시), 그러나 공간 즉시 회수 안 됨.&lt;span&gt;&lt;/span&gt; ACCESS EXCLUSIVE 잠금 필요 . CASCADE는 광범위한 영향을 미칠 수 있음.&lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;726:1-726:280&quot;&gt;
&lt;td data-sourcepos=&quot;726:1-726:18&quot;&gt;ADD CONSTRAINT&lt;/td&gt;
&lt;td data-sourcepos=&quot;726:20-726:55&quot;&gt;ALTER TABLE... ADD CONSTRAINT...&lt;/td&gt;
&lt;td data-sourcepos=&quot;726:57-726:87&quot;&gt;NOT VALID (CHECK, FK 용)&lt;/td&gt;
&lt;td data-sourcepos=&quot;726:89-726:278&quot;&gt;데이터 유효성 검사 스캔으로 인해 느리거나 잠금이 많이 발생할 수 있음.&lt;span&gt;&lt;/span&gt; NOT VALID는 스캔을 건너뛰고, 더 빠르며, 잠금이 적음 (SHARE UPDATE EXCLUSIVE), 나중에 VALIDATE 필요.&lt;span&gt;&lt;/span&gt; FK는 양쪽 테이블에 SHARE ROW EXCLUSIVE 잠금 필요 .&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;727:1-727:148&quot;&gt;
&lt;td data-sourcepos=&quot;727:1-727:19&quot;&gt;DROP CONSTRAINT&lt;/td&gt;
&lt;td data-sourcepos=&quot;727:21-727:59&quot;&gt;ALTER TABLE... DROP CONSTRAINT name&lt;/td&gt;
&lt;td data-sourcepos=&quot;727:61-727:96&quot;&gt;CASCADE, RESTRICT, IF EXISTS&lt;/td&gt;
&lt;td data-sourcepos=&quot;727:98-727:146&quot;&gt;일반적으로 빠름 (메타데이터 변경). ACCESS EXCLUSIVE 잠금 필요 .&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;728:1-728:167&quot;&gt;
&lt;td data-sourcepos=&quot;728:1-728:33&quot;&gt;ALTER COLUMN SET/DROP DEFAULT&lt;/td&gt;
&lt;td data-sourcepos=&quot;728:35-728:102&quot;&gt;ALTER TABLE... ALTER COLUMN name SET DEFAULT expr / DROP DEFAULT&lt;/td&gt;
&lt;td data-sourcepos=&quot;728:104-728:106&quot;&gt;-&lt;/td&gt;
&lt;td data-sourcepos=&quot;728:108-728:165&quot;&gt;빠름 (메타데이터만).&lt;span&gt;&lt;/span&gt; ACCESS SHARE UPDATE EXCLUSIVE 잠금 필요 .&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;729:1-729:174&quot;&gt;
&lt;td data-sourcepos=&quot;729:1-729:21&quot;&gt;ALTER COLUMN TYPE&lt;/td&gt;
&lt;td data-sourcepos=&quot;729:23-729:72&quot;&gt;ALTER TABLE... ALTER COLUMN name TYPE new_type&lt;/td&gt;
&lt;td data-sourcepos=&quot;729:74-729:90&quot;&gt;USING (종종 필요)&lt;/td&gt;
&lt;td data-sourcepos=&quot;729:92-729:172&quot;&gt;종종 느리고, 전체 테이블/인덱스 재작성 필요.&lt;span&gt;&lt;/span&gt; ACCESS EXCLUSIVE 잠금 필요 . USING 절 중요.&lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;730:1-730:127&quot;&gt;
&lt;td data-sourcepos=&quot;730:1-730:17&quot;&gt;RENAME COLUMN&lt;/td&gt;
&lt;td data-sourcepos=&quot;730:19-730:67&quot;&gt;ALTER TABLE... RENAME COLUMN name TO new_name&lt;/td&gt;
&lt;td data-sourcepos=&quot;730:69-730:71&quot;&gt;-&lt;/td&gt;
&lt;td data-sourcepos=&quot;730:73-730:125&quot;&gt;빠름 (메타데이터만).&lt;span&gt;&lt;/span&gt; ACCESS EXCLUSIVE 잠금 필요 .&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;731:1-731:147&quot;&gt;
&lt;td data-sourcepos=&quot;731:1-731:16&quot;&gt;RENAME TABLE&lt;/td&gt;
&lt;td data-sourcepos=&quot;731:18-731:56&quot;&gt;ALTER TABLE name RENAME TO new_name&lt;/td&gt;
&lt;td data-sourcepos=&quot;731:58-731:70&quot;&gt;IF EXISTS&lt;/td&gt;
&lt;td data-sourcepos=&quot;731:72-731:145&quot;&gt;빠름 (메타데이터만).&lt;span&gt;&lt;/span&gt; ACCESS EXCLUSIVE 잠금 필요 . 관련 객체 이름 변경 안 함.&lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;섹션 8: 결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 데이터베이스 테이블 구조를 정의, 관리 및 발전시키기 위한 강력하고 기능이 풍부한 환경을 제공합니다. 행, 열, 데이터 유형의 기본 개념부터 생성된 열 및 제외 제약 조건과 같은 고급 기능에 이르기까지 이 시스템은 데이터 구성 및 무결성 적용을 위한 광범위한 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DEFAULT 값과 같은 열 속성 및 SERIAL과 표준 GENERATED AS IDENTITY 절 간의 미묘한 차이에 대한 철저한 이해는 특히 기본 키 생성에 있어 효과적인 스키마 설계에 중요합니다. CHECK, NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY 및 PostgreSQL 특정 EXCLUDE와 같은 다양한 제약 조건 배열은 복잡한 비즈니스 규칙 구현과 데이터베이스 내에서 직접 참조 무결성 유지를 가능하게 하여 안정성과 일관성을 향상시킵니다. 시스템 열의 존재는 기본 MVCC 메커니즘에 대한 통찰력을 제공하는 반면, 포괄적인 ALTER TABLE 명령은 생성 후 스키마 수정을 위한 유연성을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL 테이블을 성공적으로 관리하려면 사용 가능한 구문에 대한 지식뿐만 아니라 다양한 작업의 의미에 대한 인식도 필요합니다. 제약 조건 추가 또는 데이터 유형 변경의 성능 영향, 다양한 ALTER TABLE 작업과 관련된 잠금 동작, CASCADE 및 NOT VALID와 같은 제어 옵션의 적절한 사용과 같은 요소는 특히 프로덕션 환경에서 중요한 고려 사항입니다. PostgreSQL의 강력한 기능을 신중하게 활용함으로써 개발자와 관리자는 효율적이고 안정적이며 변화하는 요구 사항에 적응할 수 있는 데이터베이스 스키마를 구축하고 유지 관리할 수 있습니다.&lt;/p&gt;</description>
      <category>Database</category>
      <category>DDL</category>
      <category>PostgreSQL</category>
      <author>kahnco</author>
      <guid isPermaLink="true">https://kahnco.tistory.com/51</guid>
      <comments>https://kahnco.tistory.com/51#entry51comment</comments>
      <pubDate>Mon, 21 Apr 2025 15:58:22 +0900</pubDate>
    </item>
    <item>
      <title>[Database] PostgreSQL - SQL Query</title>
      <link>https://kahnco.tistory.com/50</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;소개&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL에서 SQL 쿼리를 효과적으로 작성하고 이해하기 위해서는 SQL 구문의 기본적인 개념을 명확히 파악하는 것이 필수적입니다. SQL 구문은 데이터베이스와 소통하는 언어의 문법과 같아서, 이를 정확히 알아야 원하는 데이터를 올바르게 조회하고 조작할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;7:1-7:230&quot; data-ke-size=&quot;size16&quot;&gt;이 게시물에서는 PostgreSQL 17.4 공식 문서를 기준으로 SQL 구문의 세 가지 핵심 구성 요소인 &lt;b&gt;어휘 구조(Lexical Structure)&lt;/b&gt;, &lt;b&gt;값 표현식(Value Expressions)&lt;/b&gt;, 그리고 &lt;b&gt;함수 호출(Calling Functions)&lt;/b&gt;에 대해 자세히 알아보겠습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;7:1-7:230&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;7:1-7:230&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 어휘 구조 (Lexical Structure): SQL 구문의 기본 단위&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-sourcepos=&quot;7:1-7:230&quot; data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-sourcepos=&quot;7:1-7:230&quot; data-ke-size=&quot;size16&quot;&gt;모든 SQL 명령은 &lt;b&gt;토큰(Token) &lt;/b&gt;이라고 불리는 기본적인 구성 요소들의 연속으로 이루어집니다. 토큰은 SQL 언어에서 의미를 가지는 최소 단위이며, 키워드, 식별자, 상수, 연산자, 특수 문자 등이 있습니다. 토큰들은 일반적으로 공백(스페이스, 탭, 줄 바꿈)으로 구분되지만, 모호성이 없는 경우에는 공백 없이 붙여 쓸 수도 있습니다. 예를 들어, 특수 문자가 다른 유형의 토큰 옆에 오는 경우가 그렇습니다.&lt;span&gt;&lt;/span&gt; 주석은 토큰으로 취급되지 않으며 공백과 동일하게 처리됩니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;7:1-7:230&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;7:1-7:230&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;식별자(Identifiers)와 키워드(Keywords)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-sourcepos=&quot;7:1-7:230&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;17:1-17:135&quot;&gt;&lt;b&gt;식별자(Identifiers):&lt;/b&gt; 테이블, 뷰, 컬럼, 함수 등 데이터베이스 객체를 식별하는 데 사용되는 이름입니다. 예를 들어 users, order_id, calculate_total 등이 식별자에 해당합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;18:1-19:0&quot;&gt;&lt;b&gt;키워드(Keywords):&lt;/b&gt; SELECT, UPDATE, INSERT, WHERE, FROM 등과 같이 SQL 언어 자체에서 특별하고 고정된 의미를 가지는 단어들입니다.&lt;span&gt;&lt;/span&gt; 어떤 토큰이 식별자인지 키워드인지는 SQL 언어의 문맥을 이해해야 구분할 수 있습니다. PostgreSQL의 모든 키워드 목록은 다음 링크에서 확인할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/sql-keywords-appendix.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.postgresql.org/docs/current/sql-keywords-appendix.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744957114352&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Appendix&amp;nbsp;C.&amp;nbsp;SQL Key Words&quot; data-og-description=&quot;Appendix&amp;nbsp;C.&amp;nbsp;SQL Key Words Table&amp;nbsp;C.1 lists all tokens that are key words in the SQL standard and in PostgreSQL 17.4. Background &amp;hellip;&quot; data-og-host=&quot;www.postgresql.org&quot; data-og-source-url=&quot;https://www.postgresql.org/docs/current/sql-keywords-appendix.html&quot; data-og-url=&quot;https://www.postgresql.org/docs/17/sql-keywords-appendix.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/csDbKz/hyYHfEdqPl/TKwSx66GMr9P7fc79RkMp0/img.png?width=540&amp;amp;height=557&amp;amp;face=0_0_540_557&quot;&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/sql-keywords-appendix.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.postgresql.org/docs/current/sql-keywords-appendix.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/csDbKz/hyYHfEdqPl/TKwSx66GMr9P7fc79RkMp0/img.png?width=540&amp;amp;height=557&amp;amp;face=0_0_540_557');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Appendix&amp;nbsp;C.&amp;nbsp;SQL Key Words&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Appendix&amp;nbsp;C.&amp;nbsp;SQL Key Words Table&amp;nbsp;C.1 lists all tokens that are key words in the SQL standard and in PostgreSQL 17.4. Background &amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.postgresql.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;20:1-24:0&quot;&gt;&lt;b&gt;작성 규칙:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;21:5-24:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;21:5-21:82&quot;&gt;식별자와 키워드는 반드시 문자(a-z, 발음 구별 부호가 있는 문자, 비라틴 문자 포함) 또는 밑줄(_)로 시작해야 합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;22:5-22:110&quot;&gt;두 번째 문자부터는 문자, 밑줄, 숫자(0-9), 또는 달러 기호($)를 사용할 수 있습니다. 단, 달러 기호는 SQL 표준에서는 허용하지 않으므로 이식성을 고려해야 합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;23:5-24:0&quot;&gt;식별자의 최대 길이는 시스템 설정(NAMEDATALEN)에 따라 결정되며, 기본값은 64로 설정되어 있어 실제 사용 가능한 최대 길이는 63바이트입니다. 이보다 긴 이름은 시스템에 의해 자동으로 잘립니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;25:1-29:0&quot;&gt;&lt;b&gt;따옴표 없는 식별자 (Unquoted Identifiers):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;26:5-29:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;26:5-26:53&quot;&gt;일반적으로 사용하는 식별자입니다 (예: my_table, user_id).&lt;/li&gt;
&lt;li data-sourcepos=&quot;27:5-27:146&quot;&gt;&lt;b&gt;대소문자를 구분하지 않습니다.&lt;/b&gt; PostgreSQL은 내부적으로 따옴표 없는 식별자를 모두 소문자로 변환하여 처리합니다. 따라서 MY_TABLE, my_table, My_Table은 모두 동일한 mytable로 인식됩니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;28:5-29:0&quot;&gt;SQL 표준에서는 따옴표 없는 식별자를 대문자로 변환하도록 규정하고 있어, PostgreSQL의 방식과는 차이가 있습니다.&lt;span&gt;&lt;/span&gt; 이로 인해 다른 데이터베이스 시스템과의 이식성 문제가 발생할 수 있습니다. 따라서 프로젝트 내에서 일관된 명명 규칙(예: 항상 소문자와 밑줄 사용)을 따르거나, 대소문자 구분이 필요한 경우 항상 따옴표 있는 식별자를 사용하는 것이 권장됩니다.&lt;span&gt;&lt;/span&gt; 예를 들어, CREATE TABLE MyTable (...) 쿼리를 실행하면 실제로는 mytable 테이블이 생성됩니다. 이후 SELECT * FROM &quot;MyTable&quot; 로 조회하면 오류가 발생합니다. 대소문자를 유지하려면 &quot;MyTable&quot;로 생성해야 합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;30:1-37:0&quot;&gt;&lt;b&gt;따옴표 있는 식별자 (Quoted Identifiers):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;31:5-37:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;31:5-31:67&quot;&gt;큰따옴표(&quot;)로 묶어서 표현합니다 (예: &quot;My Table&quot;, &quot;user id&quot;). &lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;32:5-32:73&quot;&gt;&lt;b&gt;대소문자를 구분합니다.&lt;/b&gt; &quot;My_Table&quot;과 &quot;my_table&quot;은 서로 다른 식별자로 취급됩니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;33:5-33:73&quot;&gt;SQL 키워드도 따옴표로 묶으면 식별자로 사용할 수 있습니다 (예: &quot;select&quot;, &quot;table&quot;). &lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;34:5-34:70&quot;&gt;공백이나 SQL에서 특수한 의미를 가지는 문자(*, - 등)를 식별자에 포함시킬 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;35:5-35:60&quot;&gt;식별자 내부에 큰따옴표를 포함시키려면 두 개의 큰따옴표(&quot;&quot;)를 연속해서 사용합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;36:5-37:0&quot;&gt;유니코드 이스케이프를 사용하여 식별자를 정의할 수도 있습니다 (U&amp;amp;&quot;...&quot; 구문).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상수 (Constants)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수는 SQL 문 내에서 고정된 값을 나타냅니다. PostgreSQL은 다양한 타입의 상수를 지원합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;42:1-75:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;42:1-48:0&quot;&gt;&lt;b&gt;문자열 상수(String Constants):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;43:5-48:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;43:5-43:69&quot;&gt;작은따옴표(')로 묶어서 표현합니다. 예: 'Hello World', 'PostgreSQL'.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;44:5-44:105&quot;&gt;문자열 내부에 작은따옴표를 포함하려면 두 개의 작은따옴표('')를 사용합니다. 예: 'Dianne''s horse'는 Dianne's horse를 의미합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;45:5-45:218&quot;&gt;&lt;b&gt;C 스타일 이스케이프:&lt;/b&gt; 문자열 앞에 E를 붙여(E'...') C 언어 스타일의 백슬래시 이스케이프 시퀀스를 사용할 수 있습니다. 예를 들어, E'\n'은 줄 바꿈, E'\t'는 탭, E'\\'는 백슬래시 자체, E'\''는 작은따옴표를 나타냅니다. 8진수(\ooo)나 16진수(\xhh)로 바이트 값을 직접 지정할 수도 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;46:5-46:260&quot;&gt;&lt;b&gt;유니코드 이스케이프:&lt;/b&gt; 문자열 앞에 U&amp;amp;를 붙여(U&amp;amp;'...') 유니코드 문자를 코드 포인트로 지정할 수 있습니다. \xxxx (4자리 16진수) 또는 \+xxxxxx (6자리 16진수) 형식을 사용합니다. 예를 들어, U&amp;amp;'d\0061t\+000061'는 'data'와 동일합니다. UESCAPE 절을 사용하여 기본 이스케이프 문자인 백슬래시(\) 대신 다른 문자를 지정할 수도 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;47:5-48:0&quot;&gt;&lt;b&gt;달러-따옴표 문자열(Dollar-Quoted Strings):&lt;/b&gt; 복잡한 문자열, 특히 작은따옴표나 백슬래시가 많이 포함된 문자열(예: 함수 본문, XML/JSON 조각)을 쉽게 표현하는 방법입니다. $$문자열 내용$$ 형식이나, 태그를 사용하여 $tag$문자열 내용$tag$ 형식으로 작성합니다. 태그는 $ 기호로 시작하지 않는 임의의 식별자입니다. 달러-따옴표 내부의 모든 문자는 리터럴로 취급되므로, 이스케이프 처리가 전혀 필요 없습니다. 예를 들어, $$Dianne's horse$$는 'Dianne''s horse'와 동일합니다. 태그를 사용하면 달러-따옴표 문자열을 중첩하여 사용할 수도 있습니다.&lt;span&gt;&lt;/span&gt; 함수 본문처럼 작은따옴표가 많이 사용되는 텍스트에서는 달러-따옴표를 사용하는 것이 C 스타일 이스케이프보다 훨씬 가독성이 높고 유지보수가 용이합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;49:1-54:0&quot;&gt;&lt;b&gt;숫자 상수(Numeric Constants):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;50:5-54:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;50:5-50:127&quot;&gt;정수(예: 42, -100), 부동 소수점 수(예: 3.14159, -0.5, .001, 5e2 (5 * 10^2), 1.925e-3 (1.925 * 10^-3))를 포함합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;51:5-51:106&quot;&gt;16진수(0x), 8진수(0o), 2진수(0b) 접두사를 사용하여 비십진 정수 상수를 표현할 수 있습니다 (예: 0xFF, 0o77, 0b1111).&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;52:5-52:63&quot;&gt;가독성을 위해 숫자 사이에 밑줄(_)을 사용할 수 있습니다 (예: 1_000_000).&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;53:5-54:0&quot;&gt;타입 추론: 소수점이나 지수가 없는 숫자는 기본적으로 integer 타입으로 간주됩니다. 값이 integer 범위를 넘어서면 bigint로, bigint 범위를 넘어서면 numeric으로 처리됩니다. 소수점이나 지수가 있는 숫자는 numeric 타입으로 간주됩니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;55:1-57:0&quot;&gt;&lt;b&gt;불리언 상수(Boolean Constants):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;56:5-57:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;56:5-57:0&quot;&gt;참(True)은 TRUE, 거짓(False)은 FALSE, 알 수 없는 값은 NULL 키워드로 표현합니다. &lt;span&gt;&lt;/span&gt;` 예시를 통해 추론 가능)&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;58:1-61:0&quot;&gt;&lt;b&gt;비트 문자열 상수(Bit-String Constants):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;59:5-61:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;59:5-59:74&quot;&gt;이진수 형태는 B 다음에 0과 1로 구성된 문자열을 작은따옴표로 묶어 표현합니다 (예: B'1001'). &lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;60:5-61:0&quot;&gt;16진수 형태는 X 다음에 16진수 문자(0-9, A-F)열을 작은따옴표로 묶어 표현합니다 (예: X'1FF'). 각 16진수 문자는 4개의 비트를 나타냅니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;62:1-65:70&quot;&gt;&lt;b&gt;기타 타입 상수:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;63:5-65:70&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;63:5-65:70&quot;&gt;다른 특정 데이터 타입의 상수를 표현하려면 명시적인 &lt;b&gt;타입 캐스트(Type Cast)&lt;/b&gt;를 사용해야 합니다. PostgreSQL은 여러 캐스트 구문을 지원합니다 &lt;span&gt;&lt;/span&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;64:9-65:70&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;64:9-64:101&quot;&gt;type 'string' (표준 SQL 구문): 예) DATE '2024-07-15', INTERVAL '1 day', BOOLEAN 'true'&lt;/li&gt;
&lt;li data-sourcepos=&quot;65:9-65:70&quot;&gt;'string'::type (PostgreSQL 고유 구문): 예) '1.23'::REAL, `'&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;62:1-65:70&quot;&gt;&amp;nbsp;몇 가지 제약 조건이 있습니다:
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;67:5-69:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;67:5-67:54&quot;&gt;주석 시작 기호인 -- 와 /* 는 연산자 이름에 포함될 수 없습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;68:5-69:0&quot;&gt;여러 문자로 구성된 연산자 이름은 + 또는 - 로 끝날 수 없습니다. 단, 이름에 ~! @ # % ^ &amp;amp; | \?문자 중 하나 이상이 포함된 경우는 예외입니다 (예:@-는 허용되지만*-`는 허용되지 않음). 이는 SQL 호환 쿼리를 파싱할 때 토큰 사이에 공백이 없어도 모호성을 피하기 위함입니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;70:1-75:0&quot;&gt;&lt;b&gt;우선순위(Precedence):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;71:5-75:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;71:5-71:113&quot;&gt;연산자들은 고정된 우선순위(Precedence)와 결합성(Associativity) 규칙을 따릅니다. 이 규칙은 여러 연산자가 함께 사용될 때 어떤 연산이 먼저 수행될지를 결정합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;72:5-72:85&quot;&gt;예를 들어, 곱셈(*)은 덧셈(+)보다 우선순위가 높고, 대부분의 연산자는 왼쪽 결합성(left-associative)을 가집니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;73:5-73:107&quot;&gt;괄호 ()를 사용하여 연산의 우선순위를 명시적으로 지정하거나 기본 우선순위를 변경할 수 있습니다. 복잡한 표현식에서는 괄호를 사용하여 의도를 명확히 하는 것이 좋습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;74:5-75:0&quot;&gt;다음은 PostgreSQL 17의 연산자 우선순위 표입니다 (높은 순서에서 낮은 순서로) &lt;span&gt;&lt;/span&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 330px;&quot; border=&quot;1&quot; data-sourcepos=&quot;76:1-93:33&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;연산자/요소&lt;/td&gt;
&lt;td&gt;결합성&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;78:1-78:27&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;78:1-78:5&quot;&gt;.&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;78:7-78:9&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;78:11-78:25&quot;&gt;테이블/컬럼 이름 구분자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;79:1-79:36&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;79:1-79:6&quot;&gt;::&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;79:8-79:10&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;79:12-79:34&quot;&gt;PostgreSQL 스타일 타입 캐스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;80:1-80:26&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;80:1-80:9&quot;&gt;[ ]&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;80:11-80:13&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;80:15-80:24&quot;&gt;배열 요소 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;81:1-81:33&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;81:1-81:9&quot;&gt;+ -&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;81:11-81:13&quot;&gt;우&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;81:15-81:31&quot;&gt;단항 플러스, 단항 마이너스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;82:1-82:27&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;82:1-82:11&quot;&gt;COLLATE&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;82:13-82:15&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;82:17-82:25&quot;&gt;콜레이션 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;83:1-83:41&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;83:1-83:6&quot;&gt;AT&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;83:8-83:10&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;83:12-83:39&quot;&gt;AT TIME ZONE, AT LOCAL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;84:1-84:18&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;84:1-84:5&quot;&gt;^&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;84:7-84:9&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;84:11-84:16&quot;&gt;거듭제곱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;85:1-85:34&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;85:1-85:13&quot;&gt;* / %&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;85:15-85:17&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;85:19-85:32&quot;&gt;곱셈, 나눗셈, 나머지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;86:1-86:24&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;86:1-86:9&quot;&gt;+ -&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;86:11-86:13&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;86:15-86:22&quot;&gt;덧셈, 뺄셈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;87:1-87:43&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;87:1-87:13&quot;&gt;(다른 모든 연산자)&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;87:15-87:17&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;87:19-87:41&quot;&gt;다른 모든 내장 및 사용자 정의 연산자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;88:1-88:69&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;88:1-88:41&quot;&gt;BETWEEN IN LIKE ILIKE SIMILAR&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;88:43-88:43&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;88:45-88:67&quot;&gt;범위 포함, 집합 멤버십, 문자열 매칭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;89:1-89:50&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;89:1-89:37&quot;&gt;&amp;lt; &amp;gt; = &amp;lt;= &amp;gt;= &amp;lt;&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;89:39-89:39&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;89:41-89:48&quot;&gt;비교 연산자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;90:1-90:86&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;90:1-90:25&quot;&gt;IS ISNULL NOTNULL&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;90:27-90:27&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;90:29-90:84&quot;&gt;IS TRUE, IS FALSE, IS NULL, IS DISTINCT FROM 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;91:1-91:21&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;91:1-91:7&quot;&gt;NOT&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;91:9-91:11&quot;&gt;우&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;91:13-91:19&quot;&gt;논리 부정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;92:1-92:34&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;92:1-92:7&quot;&gt;AND&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;92:9-92:11&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;92:13-92:32&quot;&gt;논리 곱 (conjunction)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;93:1-93:33&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;93:1-93:6&quot;&gt;OR&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;93:8-93:10&quot;&gt;좌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;93:12-93:31&quot;&gt;논리 합 (disjunction)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;연산자 우선순위는 쿼리의 의미를 완전히 바꿀 수 있습니다. 예를 들어, `WHERE a = 1 AND b = 2 OR c = 3` 조건은 `AND`가 `OR`보다 우선순위가 높기 때문에 `(a = 1 AND b = 2) OR c = 3`으로 해석됩니다.[1] 만약 `a = 1 AND (b = 2 OR c = 3)`을 의도했다면 반드시 괄호를 사용해야 합니다. 사용자 정의 연산자도 내장 연산자와 동일한 이름 규칙 및 우선순위 규칙을 따르므로, 사용자 정의 연산자를 사용할 때는 이러한 상호작용을 신중하게 고려해야 합니다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특수 문자 (Special Characters)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산자 외에도 몇몇 비영문숫자 문자들은 SQL 구문에서 특별한 의미를 가집니다 &lt;span&gt;&lt;/span&gt;:&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;101:1-109:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;101:1-101:69&quot;&gt;&lt;b&gt;괄호 ():&lt;/b&gt; 표현식을 그룹화하고 우선순위를 지정하며, 특정 SQL 명령의 고정된 구문의 일부로 사용됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;102:1-102:59&quot;&gt;&lt;b&gt;대괄호 ``:&lt;/b&gt; 배열의 특정 요소를 선택하는 데 사용됩니다 (예: my_array).&lt;/li&gt;
&lt;li data-sourcepos=&quot;103:1-103:80&quot;&gt;&lt;b&gt;쉼표 ,:&lt;/b&gt; 리스트의 요소들을 구분하는 데 사용됩니다 (예: INSERT INTO table (col1, col2)...).&lt;/li&gt;
&lt;li data-sourcepos=&quot;104:1-104:83&quot;&gt;&lt;b&gt;세미콜론 ;:&lt;/b&gt; SQL 명령의 끝을 나타냅니다. 문자열 상수나 따옴표 있는 식별자 내부를 제외하고는 명령 중간에 나타날 수 없습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;105:1-105:118&quot;&gt;&lt;b&gt;콜론 ::&lt;/b&gt; 배열의 &quot;슬라이스(slice)&quot;를 선택하는 데 사용됩니다 (예: my_array[2:4]). 일부 SQL 방언(예: 임베디드 SQL)에서는 변수 이름 앞에 붙여 사용하기도 합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;106:1-106:88&quot;&gt;&lt;b&gt;별표 *:&lt;/b&gt; SELECT * 구문에서 테이블의 모든 컬럼을 나타내거나, COUNT(*)와 같이 집계 함수에서 특별한 의미로 사용됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;107:1-107:103&quot;&gt;&lt;b&gt;마침표 .:&lt;/b&gt; 숫자 상수에서 소수점을 나타내거나, 스키마, 테이블, 컬럼 이름을 구분하는 데 사용됩니다 (예: my_schema.my_table.my_column).&lt;/li&gt;
&lt;li data-sourcepos=&quot;108:1-109:0&quot;&gt;&lt;b&gt;달러 기호 $:&lt;/b&gt; 숫자와 함께 사용되어 함수 정의나 준비된 구문(prepared statement) 본문 내에서 위치 지정 파라미터(positional parameter)를 나타냅니다 (예: $1, $2). 다른 문맥에서는 식별자의 일부나 달러-따옴표 문자열 상수의 일부로 사용될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주석 (Comments)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 코드 내에 설명을 추가하기 위해 주석을 사용할 수 있습니다. 주석은 쿼리 실행에 영향을 주지 않으며, 파싱 전에 제거되어 공백처럼 취급됩니다.&lt;span&gt;&lt;/span&gt; PostgreSQL은 두 가지 형태의 주석을 지원합니다 &lt;span&gt;&lt;/span&gt;:&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;114:1-127:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;114:1-117:7&quot;&gt;&lt;b&gt;한 줄 주석:&lt;/b&gt; 두 개의 하이픈(--)으로 시작하며, 해당 줄의 끝까지 모든 텍스트가 주석으로 처리됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744957136282&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM users; -- 사용자의 모든 정보를 조회합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;블록 주석:&lt;/b&gt; /*로 시작하여 */로 끝납니다. 여러 줄에 걸쳐 작성할 수 있으며, 중첩하여 사용할 수도 있습니다 (C 언어와 달리 중첩이 가능합니다).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744957158391&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 이것은 여러 줄에 걸친
 블록 주석입니다.
 /* 중첩된 블록 주석도 가능합니다. */
*/
SELECT name FROM products WHERE price &amp;gt; 100;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 값 표현식 (Value Expressions): SQL에서 값 계산하기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;값 표현식(Value Expression)&lt;/b&gt; 또는 &lt;b&gt;스칼라 표현식(Scalar Expression) &lt;/b&gt;은 평가될 때 단일 값(예: 숫자, 문자열, 날짜, 불리언 등)을 결과로 내는 SQL 구문입니다. 이는 여러 행과 열로 구성된 테이블을 결과로 내는 테이블 표현식(Table Expression)과 대조됩니다. 값 표현식은 SQL 쿼리의 다양한 부분에서 사용됩니다. 예를 들어, SELECT 문의 결과 목록(target list)에서 출력될 값을 계산하거나, INSERT 또는 UPDATE 문에서 새로운 컬럼 값을 지정하거나, WHERE 절이나 HAVING 절에서 검색 조건을 정의하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;표현식 종류 카탈로그&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 다양한 종류의 값 표현식을 지원합니다 &lt;span&gt;&lt;/span&gt;:&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;138:1-160:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;138:1-138:110&quot;&gt;&lt;b&gt;상수/리터럴 (Constants/Literals):&lt;/b&gt; 100, 'Hello World', TRUE, DATE '2024-07-15' 와 같이 고정된 값 자체입니다.&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;139:1-139:184&quot;&gt;&lt;b&gt;컬럼 참조 (Column References):&lt;/b&gt; 테이블의 특정 컬럼 값을 나타냅니다. table_name.column_name 또는 alias.column_name 형태로 사용하며, 쿼리 내에서 컬럼 이름이 고유하다면 테이블 이름이나 별칭(alias)을 생략할 수 있습니다 (예: user_id).&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;140:1-140:139&quot;&gt;&lt;b&gt;위치 지정 파라미터 (Positional Parameters):&lt;/b&gt; 함수 정의나 준비된 구문(prepared statement) 내에서 외부로부터 전달되는 값을 참조하는 데 사용됩니다. $1, $2 와 같이 표현합니다.&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;141:1-141:182&quot;&gt;&lt;b&gt;연산자 (Operators):&lt;/b&gt; 하나 또는 두 개의 피연산자에 적용되어 새로운 값을 계산합니다. 산술 연산자 (+, -, *, /), 비교 연산자 (=, &amp;gt;, &amp;lt;, LIKE, BETWEEN), 논리 연산자 (AND, OR, NOT) 등이 있습니다.&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;142:1-142:137&quot;&gt;&lt;b&gt;함수 호출 (Function Calls):&lt;/b&gt; 내장 함수나 사용자 정의 함수를 호출하여 그 반환 값을 사용합니다. 예: sqrt(numeric_column), lower(text_column), now().&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;143:1-143:192&quot;&gt;&lt;b&gt;집계 표현식 (Aggregate Expressions):&lt;/b&gt; 여러 행의 값을 기반으로 단일 결과 값을 계산합니다. count(*), sum(amount), avg(score) 등이 있으며, GROUP BY 절과 함께 사용되는 경우가 많습니다. FILTER 절을 사용하여 집계 대상 행을 제한할 수도 있습니다.&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;144:1-144:224&quot;&gt;&lt;b&gt;윈도우 함수 호출 (Window Function Calls):&lt;/b&gt; 현재 행과 관련된 행들의 집합(윈도우)에 대해 계산을 수행합니다. 집계 함수와 유사하지만, 행들을 그룹으로 축약하지 않습니다. rank() OVER (ORDER BY score DESC), sum(salary) OVER (PARTITION BY department) 와 같이 OVER 절과 함께 사용됩니다.&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;145:1-145:184&quot;&gt;&lt;b&gt;타입 캐스트 (Type Casts):&lt;/b&gt; 한 데이터 타입의 값을 다른 데이터 타입으로 명시적으로 변환합니다. expression::type (예: count::float), CAST(expression AS type) (예: CAST(price AS numeric(10,2))) 구문을 사용합니다.&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;146:1-146:150&quot;&gt;&lt;b&gt;콜레이션 표현식 (Collation Expressions):&lt;/b&gt; 표현식의 정렬 규칙(collation)을 지정합니다. 주로 문자열 비교나 정렬 시 특정 로케일 규칙을 적용할 때 사용됩니다 (예: column_name COLLATE &quot;fr_FR&quot;).&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;147:1-147:188&quot;&gt;&lt;b&gt;스칼라 서브쿼리 (Scalar Subqueries):&lt;/b&gt; 단일 행, 단일 컬럼을 반환하는 SELECT 문을 괄호로 묶어 사용합니다. 서브쿼리의 결과 값이 표현식의 값이 됩니다. 만약 서브쿼리가 행을 반환하지 않으면 결과는 NULL이 됩니다. 예: (SELECT max(price) FROM products).&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;148:1-148:184&quot;&gt;&lt;b&gt;배열 생성자 (Array Constructors):&lt;/b&gt; 배열 값을 생성합니다. ARRAY[element1, element2,...] 또는 ARRAY(subquery) 구문을 사용합니다. 예: ARRAY, ARRAY(SELECT user_id FROM active_users).&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;149:1-149:173&quot;&gt;&lt;b&gt;배열 첨자 (Subscripts):&lt;/b&gt; 배열의 특정 요소나 부분(슬라이스)에 접근합니다. array_expression[index] 또는 array_expression[lower_index:upper_index] 구문을 사용합니다. 예: score , names[1:5]. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;150:1-150:204&quot;&gt;&lt;b&gt;로우 생성자 (Row Constructors):&lt;/b&gt; 로우(row) 또는 복합(composite) 타입의 값을 생성합니다. ROW(field1, field2,...) 또는 괄호만 사용하여 (field1, field2,...) 형태로 표현합니다. 예: ROW(1, 'apple', 1.50), (user_id, user_name). &lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;151:1-151:188&quot;&gt;&lt;b&gt;필드 선택 (Field Selection):&lt;/b&gt; 복합 타입 값에서 특정 필드의 값을 추출합니다. composite_value.field_name 구문을 사용합니다. 컬럼 참조(table.column)도 필드 선택의 한 형태입니다. table_alias.* 와 같이 사용하여 모든 필드를 선택할 수도 있습니다. &lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;152:1-152:128&quot;&gt;&lt;b&gt;괄호로 묶인 표현식 (Parenthesized Expressions):&lt;/b&gt; 표현식의 일부를 괄호로 묶어 평가 순서를 명시적으로 제어하거나 가독성을 높입니다. 예: (price + tax) * quantity.&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;153:1-160:0&quot;&gt;&lt;b&gt;조건 표현식 (Conditional Expressions):&lt;/b&gt; 조건에 따라 다른 값을 반환합니다.&lt;span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;154:5-160:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;154:5-154:71&quot;&gt;CASE WHEN condition THEN result [WHEN...] END: 가장 일반적인 조건 분기.&lt;/li&gt;
&lt;li data-sourcepos=&quot;155:5-155:82&quot;&gt;CASE expression WHEN value THEN result [WHEN...] END: 특정 값과 비교하는 간단한 형태.&lt;/li&gt;
&lt;li data-sourcepos=&quot;156:5-156:71&quot;&gt;COALESCE(value1, value2,...): 인자 목록 중 첫 번째로 NULL이 아닌 값을 반환.&lt;/li&gt;
&lt;li data-sourcepos=&quot;157:5-157:69&quot;&gt;NULLIF(value1, value2): 두 값이 같으면 NULL을, 다르면 value1을 반환.&lt;/li&gt;
&lt;li data-sourcepos=&quot;158:5-158:59&quot;&gt;GREATEST(value1, value2,...): 인자 목록 중 가장 큰 값을 반환.&lt;/li&gt;
&lt;li data-sourcepos=&quot;159:5-160:0&quot;&gt;LEAST(value1, value2,...): 인자 목록 중 가장 작은 값을 반환.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;표현식 평가 규칙 및 우선순위&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;163:1-168:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;163:1-163:72&quot;&gt;복잡한 표현식의 평가 순서는 &lt;b&gt;섹션 1&lt;/b&gt;&amp;nbsp;에서 설명한 &lt;b&gt;연산자 우선순위&lt;/b&gt; 규칙과 &lt;b&gt;결합성&lt;/b&gt;에 의해 결정됩니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;164:1-164:78&quot;&gt;예를 들어, a + b * c는 곱셈(*)이 덧셈(+)보다 우선순위가 높으므로 a + (b * c)와 같이 평가됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;165:1-165:117&quot;&gt;&lt;b&gt;괄호 ()&lt;/b&gt; 를 사용하면 이러한 기본 평가 순서를 변경할 수 있습니다. (a + b) * c는 덧셈을 먼저 수행합니다. 괄호는 또한 복잡한 표현식의 가독성을 높이는 데 중요합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;166:1-166:242&quot;&gt;값 표현식과 테이블 표현식의 구분은 매우 중요합니다. SELECT 목록이나 WHERE 절과 같이 스칼라 값을 기대하는 곳에서는 값 표현식을 사용해야 하고, FROM이나 JOIN 절과 같이 테이블(행 집합)을 기대하는 곳에서는 테이블 표현식을 사용해야 합니다.&lt;span&gt;&lt;/span&gt; 스칼라 서브쿼리는 테이블 표현식이지만 단일 스칼라 값을 반환하도록 설계되어 값 표현식이 필요한 곳에 사용할 수 있는 예외적인 경우입니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;167:1-168:0&quot;&gt;PostgreSQL은 종종 데이터 타입이 다른 피연산자 간의 연산을 위해 &lt;b&gt;암시적 타입 변환&lt;/b&gt;을 수행합니다 (예: integer + numeric). 하지만 이러한 암시적 변환에 의존하는 것은 잠재적인 오류나 성능 저하를 유발할 수 있습니다.&lt;span&gt;&lt;/span&gt; 예를 들어, 5 / 2는 정수 나눗셈으로 2를 반환하지만, 5::numeric / 2는 2.5를 반환합니다. 따라서 의도한 타입과 연산을 명확히 하기 위해 &lt;b&gt;명시적 타입 캐스트&lt;/b&gt;(:: 또는 CAST)를 사용하는 것이 좋습니다. 이는 코드의 정확성, 가독성 및 유지보수성을 향상시킵니다.&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 함수 및 프로시저 호출 (Calling Functions and Procedures)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수 호출 구문 개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL에서 함수를 호출하는 기본 구문은 다음과 같습니다 &lt;span&gt;&lt;/span&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1744957890510&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function_name ( [ argument [,...] )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 function_name은 호출할 함수의 이름이며, 괄호 안에는 0개 이상의 인자(argument)를 쉼표로 구분하여 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인자 전달 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 함수 인자를 전달하는 세 가지 표기법을 지원합니다 &lt;span&gt;&lt;/span&gt;:&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;185:1-205:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;185:1-190:0&quot;&gt;&lt;b&gt;위치 지정 표기법 (Positional Notation):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;186:5-190:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;186:5-186:56&quot;&gt;가장 전통적인 방식으로, 함수 선언 시 정의된 파라미터 순서대로 인자 값을 전달합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;187:5-187:58&quot;&gt;예: concat_lower_or_upper('Hello', 'World', true)&lt;/li&gt;
&lt;li data-sourcepos=&quot;188:5-188:99&quot;&gt;함수 정의 시 파라미터에 기본값(DEFAULT)이 설정되어 있다면, 오른쪽부터 순서대로 해당 인자를 생략할 수 있습니다. 생략된 파라미터는 기본값을 사용합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;189:5-190:0&quot;&gt;예: concat_lower_or_upper('Hello', 'World') (세 번째 파라미터 uppercase의 기본값 false 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;191:1-198:0&quot;&gt;&lt;b&gt;이름 지정 표기법 (Named Notation):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;192:5-198:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;192:5-192:64&quot;&gt;각 인자 값을 해당 파라미터 이름과 함께 param_name =&amp;gt; value 형식으로 전달합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;193:5-193:58&quot;&gt;인자의 순서는 중요하지 않으며, 어떤 파라미터에 어떤 값이 전달되는지 명확하게 보여줍니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;194:5-194:79&quot;&gt;기본값이 있는 파라미터는 자유롭게 생략할 수 있습니다. 이는 파라미터가 많거나 기본값이 많은 함수를 호출할 때 매우 유용합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;195:5-195:81&quot;&gt;예: concat_lower_or_upper(a =&amp;gt; 'Hello', uppercase =&amp;gt; true, b =&amp;gt; 'World')&lt;/li&gt;
&lt;li data-sourcepos=&quot;196:5-196:51&quot;&gt;하위 호환성을 위해 param_name := value 구문도 지원됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;197:5-198:0&quot;&gt;파라미터 수가 많거나, 동일 타입의 파라미터가 여러 개 있거나, 기본값을 활용해야 하는 경우, 이름 지정 표기법은 위치 지정 표기법보다 코드 가독성과 유지보수성을 크게 향상시킵니다.&lt;span&gt;&lt;/span&gt; 위치 지정 방식은 함수 시그니처 변경 시 오류를 유발하기 쉽지만, 이름 지정 방식은 파라미터 이름으로 명시적으로 연결되므로 이러한 변경에 더 강건합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;199:1-203:0&quot;&gt;&lt;b&gt;혼합 표기법 (Mixed Notation):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;200:5-203:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;200:5-200:39&quot;&gt;위치 지정 표기법과 이름 지정 표기법을 함께 사용합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;201:5-201:53&quot;&gt;단, &lt;b&gt;위치 지정 인자들이 반드시 이름 지정 인자들보다 먼저 나와야 합니다.&lt;/b&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;202:5-203:0&quot;&gt;예: concat_lower_or_upper('Hello', 'World', uppercase =&amp;gt; true) ('Hello'와 'World'는 위치로, uppercase =&amp;gt; true는 이름으로 전달)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;204:1-205:0&quot;&gt;&lt;b&gt;제한 사항:&lt;/b&gt; 현재 집계 함수(Aggregate Function)를 직접 호출할 때는 이름 지정 또는 혼합 표기법을 사용할 수 없습니다. 하지만 집계 함수를 윈도우 함수(Window Function)로 사용할 때는 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;함수 결정 (Function Resolution - 오버로딩)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; PostgreSQL에서는 함수의 이름이 같더라도 인자의 개수나 데이터 타입이 다르면 서로 다른 함수로 정의할 수 있습니다. 이를 &lt;b&gt;함수 오버로딩(Function Overloading)&lt;/b&gt;이라고 합니다. 특정 함수 호출이 있을 때, PostgreSQL은 어떤 함수를 실행할지 결정하기 위해 다음과 같은 &lt;b&gt;함수 결정(Function Resolution)&lt;/b&gt; 과정을 따릅니다 &lt;span&gt;&lt;/span&gt;:&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-sourcepos=&quot;210:1-222:0&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;210:1-210:199&quot;&gt;&lt;b&gt;후보 함수 선정:&lt;/b&gt; 호출된 함수 이름과 인자 개수에 맞는 함수들을 시스템 카탈로그(pg_proc)에서 찾습니다. 스키마가 지정되지 않았다면 현재 search_path 내의 함수들을 고려하며, 동일 시그니처 함수가 여러 스키마에 있다면 경로상 가장 먼저 나오는 함수만 고려합니다. 스키마가 지정되었다면 해당 스키마 내의 함수만 고려합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;211:1-211:126&quot;&gt;&lt;b&gt;VARIADIC 함수 처리:&lt;/b&gt; 가변 인자(VARIADIC) 함수가 후보에 있고, 호출 시 VARIADIC 키워드가 사용되지 않았다면, 가변 인자 파라미터를 실제 전달된 인자 타입들로 확장하여 고려합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;212:1-212:69&quot;&gt;&lt;b&gt;기본값 고려:&lt;/b&gt; 기본값이 있는 파라미터를 가진 함수들도 후보로 고려합니다 (호출 시 해당 인자가 생략된 경우).&lt;/li&gt;
&lt;li data-sourcepos=&quot;213:1-213:106&quot;&gt;&lt;b&gt;정확한 타입 일치 확인:&lt;/b&gt; 호출 시 전달된 인자들의 데이터 타입과 정확히 일치하는 시그니처를 가진 함수가 있는지 확인합니다. 있다면 해당 함수를 선택하고 결정 과정을 종료합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;214:1-214:117&quot;&gt;&lt;b&gt;특수 타입 변환 구문 확인:&lt;/b&gt; 정확한 일치가 없고, 함수 호출이 단일 인자를 가지며 함수 이름이 특정 데이터 타입의 이름과 같다면, 타입 캐스트(CAST) 구문으로 처리될 수 있는지 확인합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;215:1-220:108&quot;&gt;&lt;b&gt;최적 일치 찾기 (Best Match):&lt;/b&gt; 정확한 일치가 없다면, 다음 규칙에 따라 가장 적합한 함수를 찾습니다:
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;216:5-220:108&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;216:5-216:47&quot;&gt;입력 인자 타입으로 암시적 변환이 불가능한 함수는 후보에서 제외합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;217:5-217:33&quot;&gt;도메인 타입 인자는 기본 타입으로 간주합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;218:5-218:46&quot;&gt;입력 타입과 정확히 일치하는 파라미터가 가장 많은 후보를 선호합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;219:5-219:78&quot;&gt;타입 변환이 필요한 경우, 해당 타입 카테고리 내의 &quot;선호 타입(preferred type)&quot;을 받아들이는 후보를 선호합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;220:5-220:108&quot;&gt;unknown 타입 인자가 있다면, 다른 인자들의 타입이나 후보 함수들이 받아들이는 타입 카테고리(주로 string 선호)를 기반으로 타입을 추론하여 최적 후보를 좁힙니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;221:1-222:0&quot;&gt;&lt;b&gt;모호성 오류:&lt;/b&gt; 위의 과정을 거쳐도 유일한 최적 함수를 결정할 수 없다면, &quot;ambiguous function call&quot; 오류가 발생합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-sourcepos=&quot;223:1-223:404&quot; data-ke-size=&quot;size16&quot;&gt;함수 호출 시 &quot;function does not exist&quot; 또는 &quot;ambiguous function call&quot; 오류가 발생하는 경우, 이는 단순히 함수가 없어서가 아니라 제공된 인자 타입이 정확히 일치하는 시그니처가 없고, 암시적 변환 및 최적 일치 규칙으로도 고유한 후보를 찾지 못했기 때문일 가능성이 높습니다.&lt;span&gt;&lt;/span&gt; 예를 들어 my_func(123) 호출이 실패했다면, my_func(bigint)는 있지만 integer에서 bigint로의 암시적 변환이 해당 컨텍스트에서 허용되지 않거나 선호되지 않기 때문일 수 있습니다. 이 경우 my_func(123::bigint)와 같이 명시적으로 인자 타입을 캐스팅하면 문제를 해결하고 PostgreSQL이 정확한 함수를 찾도록 도울 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;223:1-223:404&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;223:1-223:404&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;프로시저 호출&lt;/span&gt;&lt;/h3&gt;
&lt;p data-sourcepos=&quot;223:1-223:404&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;227:1-227:119&quot;&gt;함수(CREATE FUNCTION)와 달리, 프로시저(CREATE PROCEDURE)는 값을 반환하지 않으며(물론 OUT 파라미터를 가질 수는 있음) 트랜잭션 제어에 대한 규칙이 다릅니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;228:1-232:0&quot;&gt;프로시저를 호출할 때는 SELECT 문이 아닌 CALL 명령을 사용해야 합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744958017078&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CALL my_procedure(argument1, argument2);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;235:1-235:256&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 PostgreSQL 17.4 SQL 구문의 세 가지 핵심 요소인 어휘 구조, 값 표현식, 함수 호출에 대해 살펴보았습니다. 어휘 구조는 SQL을 구성하는 가장 기본적인 벽돌(식별자, 키워드, 상수 등)이며, 값 표현식은 이러한 벽돌을 조합하여 의미 있는 값을 계산하는 방법입니다. 마지막으로 함수 호출은 강력한 재사용 가능 코드 블록을 실행하는 메커니즘을 제공하며, PostgreSQL의 오버로딩과 타입 시스템은 유연한 함수 사용을 가능하게 합니다.&lt;/p&gt;</description>
      <category>Database</category>
      <category>PostgreSQL</category>
      <category>SQL</category>
      <author>kahnco</author>
      <guid isPermaLink="true">https://kahnco.tistory.com/50</guid>
      <comments>https://kahnco.tistory.com/50#entry50comment</comments>
      <pubDate>Fri, 18 Apr 2025 15:34:09 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring Data - MongoDB</title>
      <link>https://kahnco.tistory.com/49</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 프레임워크 생태계에서 MongoDB와 같은 NoSQL 데이터베이스를 사용하는 Java 애플리케이션 개발을 단순화하는 것은 매우 중요합니다. Spring Data MongoDB는 MongoDB 문서 스타일 데이터 저장소를 사용하는 솔루션 개발에 핵심 Spring 개념을 적용하여 이를 가능하게 합니다. 문서를 저장하고 쿼리하기 위한 높은 수준의 추상화인 &quot;템플릿&quot;을 제공하며, 이는 Spring 프레임워크의 JDBC 지원과 유사점을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 게시글에서는 Spring Data MongoDB 버전 4.4.4에 초점을 맞춰, 개발자가 이 강력한 프레임워크를 효과적으로 활용하는 데 필요한 모든 것을 다룹니다. 프로젝트 설정부터 시작하여 핵심 개념, 문서 매핑, 기본적인 CRUD 작업, 다양한 쿼리 방법, 그리고 인덱싱, GridFS, Aggregation Framework, Reactive 지원과 같은 고급 기능까지 상세하게 살펴볼 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;소개&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Data MongoDB 란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data MongoDB는 더 큰 Spring Data 프로젝트의 일부로, Spring 기반 애플리케이션에서 MongoDB 데이터베이스와의 상호작용을 단순화하는 데 중점을 둡니다. 이는 Spring의 핵심 원칙인 IoC(Inversion of Control), 일관된 예외 변환 계층 등을 MongoDB 데이터 접근에 적용합니다. 개발자는 익숙한 Spring 개념을 사용하여 MongoDB의 문서 지향 데이터 모델과 상호작용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목표 및 이점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data MongoDB의 주요 목표는 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;보일러플레이트 코드 감소:&lt;/b&gt; 반복적인 데이터 접근 로직 작성을 최소화합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일관된 프로그래밍 모델 제공:&lt;/b&gt; 저장소별 특정 기능을 유지하면서도 친숙하고 일관된 Spring 기반 프로그래밍 모델을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발 생산성 향상:&lt;/b&gt; MongoDB 관련 개발 작업을 단순화하고 가속화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대상 버전 및 호환성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 가이드는 &lt;b&gt;Spring Data MongoDB 4.4.4&lt;/b&gt; 버전을 기준으로 작성되었습니다. Spring Data MongoDB 4.4.x 버전은 일반적으로 MongoDB 서버 4.4.x 버전 이상 및 호환되는 MongoDB Java 드라이버(예: 4.x 버전대)와 함께 사용됩니다. 특정 버전 호환성은 공식 문서를 참조하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 추상화: Repository 와 Template&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data MongoDB는 MongoDB와 상호작용하는 두 가지 주요 추상화 방법을 제공합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Repositories&lt;/b&gt;: MongoRepository와 같은 인터페이스를 정의하면 Spring Data가 런타임에 구현을 자동으로 제공합니다. 이는 CRUD 작업과 규약 기반 쿼리(Derived Queries)에 이상적입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Templates&lt;/b&gt;: MongoTemplate은 MongoDB 작업을 위한 더 낮은 수준의 추상화를 제공하여 복잡한 쿼리, 업데이트, 집계(aggregation) 작업 등에 대한 더 많은 제어권을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프로젝트 설정하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 애플리케이션에서 Spring Data MongoDB를 사용하기 위한 초기 설정 과정을 알아봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 설정 (Maven/Gradle)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 필요한 의존성을 프로젝트에 추가해야 합니다. Spring Boot에서는 spring-boot-starter-data-mongodb 스타터를 사용하는 것이 가장 일반적입니다. 이 스타터는 spring-data-mongodb, MongoDB Java 드라이버(동기 및/또는 리액티브), 그리고 필요한 다른 Spring Boot 의존성들을 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 프로젝트의 빌드 도구에 따라 다음 의존성을 추가합니다. 일반적으로 Spring Boot의 부모 POM이나 BOM(Bill of Materials)이 버전을 관리해주므로 명시적인 버전 지정이 필요 없을 수 있지만, 특정 버전(여기서는 4.4.4와 호환되는 Spring Boot 버전)을 사용해야 한다면 해당 버전에 맞는 스타터를 명시해야 합니다. (참고: 아래 예시는 버전 3.4.4를 사용하지만, 실제로는 프로젝트의 Spring Boot 버전에 맞는 spring-boot-starter-data-mongodb 버전을 사용해야 합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Maven (pom.xml):&lt;/h4&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-data-mongodb&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Gradle (build.gradle - Groovy DSL):&lt;/h4&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
// 버전은 Spring Boot 플러그인 또는 BOM에서 관리되므로 보통 생략 가능&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Gradle (build.gradle.kts - Kotlin DSL):&lt;/h4&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;implementation(&quot;org.springframework.boot:spring-boot-starter-data-mongodb&quot;)
// 버전은 Spring Boot 플러그인 또는 BOM에서 관리되므로 보통 생략 가능&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB 연결 설정 (application.properties / application.yml)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot는 application.properties 또는 application.yml 파일에 명시된 설정을 기반으로 MongoDB 연결을 자동으로 구성합니다. 별도의 설정이 없다면 기본적으로 mongodb://localhost:27017/test에 연결을 시도합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 설정 방법은 크게 두 가지입니다:&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 개별 속성 사용:&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트, 포트, 데이터베이스 이름, 사용자 이름, 비밀번호 등을 개별 속성으로 지정하는 방식입니다. 이는 구성이 명확하고 이해하기 쉽다는 장점이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;application.properties 예시:&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744952528386&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=mydatabase
spring.data.mongodb.username=myuser
spring.data.mongodb.password=mypassword
# spring.data.mongodb.authentication-database=admin # 인증 DB가 다를 경우 지정&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;application.yml 예시:&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744952554898&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      database: mydatabase
      username: myuser
      password: mypassword
      # authentication-database: admin # 인증 DB가 다를 경우 지정&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 연결 URI 사용:&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 uri 속성을 사용하여 모든 연결 정보를 한 번에 지정하는 방식입니다. 이는 특히 MongoDB Atlas와 같이 SRV 레코드를 사용하거나 복잡한 연결 옵션이 필요한 경우에 유용하며, 더 간결합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;application.properties 예시:&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744952620730&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 로컬 연결 예시
# spring.data.mongodb.uri=mongodb://myuser:mypassword@localhost:27017/mydatabase
# Atlas SRV 연결 예시
spring.data.mongodb.uri=mongodb+srv://myuser:mypassword@myatlascluster.mongodb.net/mydatabase?retryWrites=true&amp;amp;w=majority&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;application.yml 예시:&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744952628662&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  data:
    mongodb:
      # 로컬 연결 예시
      # uri: mongodb://myuser:mypassword@localhost:27017/mydatabase
      # Atlas SRV 연결 예시
      uri: mongodb+srv://myuser:mypassword@myatlascluster.mongodb.net/mydatabase?retryWrites=true&amp;amp;w=majority&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의&lt;/b&gt;: 개별 속성 (host, port 등)과 uri 속성은 동시에 사용할 수 없습니다. 함께 사용하면 설정이 모호해져 애플리케이션 시작 시 오류가 발생할 수 있습니다. 따라서 개발 환경(예: 로컬 MongoDB)에서는 개별 속성을 사용하여 명확성을 높이고, 클라우드 환경(예: MongoDB Atlas)이나 복제 세트(replica set) 등 고급 설정이 필요할 때는 uri 속성을 사용하는 것이 일반적입니다. 이는 Spring Boot가 단순성과 유연성 사이에서 균형을 맞추려는 설계 철학을 반영합니다. 개발자는 상황에 맞춰 더 적합한 방식을 선택해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 주요 MongoDB 연결 속성:&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 표는 application.properties 또는 application.yml에서 자주 사용되는 spring.data.mongodb.* 속성들을 요약한 것입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;127:1-139:99&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;속성&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;기본값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;129:1-129:126&quot;&gt;
&lt;td data-sourcepos=&quot;129:1-129:27&quot;&gt;spring.data.mongodb.uri&lt;/td&gt;
&lt;td data-sourcepos=&quot;129:29-129:95&quot;&gt;MongoDB 연결 URI. 설정 시 host, port, username, password 등 개별 속성은 무시됨.&lt;/td&gt;
&lt;td data-sourcepos=&quot;129:97-129:124&quot;&gt;mongodb://localhost/test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;130:1-130:89&quot;&gt;
&lt;td data-sourcepos=&quot;130:1-130:28&quot;&gt;spring.data.mongodb.host&lt;/td&gt;
&lt;td data-sourcepos=&quot;130:30-130:73&quot;&gt;MongoDB 서버 호스트 이름 또는 IP 주소. (uri 미설정 시 사용)&lt;/td&gt;
&lt;td data-sourcepos=&quot;130:75-130:87&quot;&gt;localhost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;131:1-131:75&quot;&gt;
&lt;td data-sourcepos=&quot;131:1-131:28&quot;&gt;spring.data.mongodb.port&lt;/td&gt;
&lt;td data-sourcepos=&quot;131:30-131:63&quot;&gt;MongoDB 서버 포트 번호. (uri 미설정 시 사용)&lt;/td&gt;
&lt;td data-sourcepos=&quot;131:65-131:73&quot;&gt;27017&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;132:1-132:71&quot;&gt;
&lt;td data-sourcepos=&quot;132:1-132:32&quot;&gt;spring.data.mongodb.database&lt;/td&gt;
&lt;td data-sourcepos=&quot;132:34-132:49&quot;&gt;연결할 데이터베이스 이름.&lt;/td&gt;
&lt;td data-sourcepos=&quot;132:51-132:69&quot;&gt;test (uri에서 파생)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;133:1-133:54&quot;&gt;
&lt;td data-sourcepos=&quot;133:1-133:32&quot;&gt;spring.data.mongodb.username&lt;/td&gt;
&lt;td data-sourcepos=&quot;133:34-133:45&quot;&gt;인증 사용자 이름.&lt;/td&gt;
&lt;td data-sourcepos=&quot;133:47-133:52&quot;&gt;(없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;134:1-134:52&quot;&gt;
&lt;td data-sourcepos=&quot;134:1-134:32&quot;&gt;spring.data.mongodb.password&lt;/td&gt;
&lt;td data-sourcepos=&quot;134:34-134:43&quot;&gt;인증 비밀번호.&lt;/td&gt;
&lt;td data-sourcepos=&quot;134:45-134:50&quot;&gt;(없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;135:1-135:106&quot;&gt;
&lt;td data-sourcepos=&quot;135:1-135:47&quot;&gt;spring.data.mongodb.authentication-database&lt;/td&gt;
&lt;td data-sourcepos=&quot;135:49-135:97&quot;&gt;인증에 사용할 데이터베이스 이름. 지정하지 않으면 database 속성값을 사용.&lt;/td&gt;
&lt;td data-sourcepos=&quot;135:99-135:104&quot;&gt;(없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;136:1-136:60&quot;&gt;
&lt;td data-sourcepos=&quot;136:1-136:35&quot;&gt;spring.data.mongodb.replica-set&lt;/td&gt;
&lt;td data-sourcepos=&quot;136:37-136:51&quot;&gt;연결할 복제 세트 이름.&lt;/td&gt;
&lt;td data-sourcepos=&quot;136:53-136:58&quot;&gt;(없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;137:1-137:150&quot;&gt;
&lt;td data-sourcepos=&quot;137:1-137:35&quot;&gt;spring.data.mongodb.ssl.enabled&lt;/td&gt;
&lt;td data-sourcepos=&quot;137:37-137:138&quot;&gt;SSL 연결 사용 여부. (Spring Boot 2.x에서는 spring.data.mongodb.ssl.enabled 대신 spring.data.mongodb.ssl 사용)&lt;/td&gt;
&lt;td data-sourcepos=&quot;137:140-137:148&quot;&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;138:1-138:118&quot;&gt;
&lt;td data-sourcepos=&quot;138:1-138:43&quot;&gt;spring.data.mongodb.auto-index-creation&lt;/td&gt;
&lt;td data-sourcepos=&quot;138:45-138:78&quot;&gt;애플리케이션 시작 시 엔티티 기반 인덱스 자동 생성 여부.&lt;/td&gt;
&lt;td data-sourcepos=&quot;138:80-138:116&quot;&gt;true (주의: 최신 버전에서는 false일 수 있음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;139:1-139:99&quot;&gt;
&lt;td data-sourcepos=&quot;139:1-139:40&quot;&gt;spring.data.mongodb.grid-fs-database&lt;/td&gt;
&lt;td data-sourcepos=&quot;139:42-139:90&quot;&gt;GridFS 작업을 위한 데이터베이스 이름. 지정하지 않으면 기본 데이터베이스 사용.&lt;/td&gt;
&lt;td data-sourcepos=&quot;139:92-139:97&quot;&gt;(없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java 기반 설정 (선택 사항)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 Spring Boot 애플리케이션에서는 속성 파일을 통한 설정으로 충분합니다. 하지만 더 복잡한 MongoClient 설정이나 커스터마이징이 필요한 경우, Java 기반 설정 클래스(예: AbstractMongoClientConfiguration 상속)를 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;핵심 개념 이해하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data MongoDB를 효과적으로 사용하기 위해 알아야 할 핵심 개념들을 살펴봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Document&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB는 데이터를 BSON(Binary JSON) 형식의 &lt;b&gt;문서(Document)&lt;/b&gt; 로 저장합니다.&lt;span&gt;&lt;/span&gt; 이는 관계형 데이터베이스의 행(row)과 유사하지만, 유연한 스키마를 가지며 중첩된 구조나 배열을 포함할 수 있습니다. Spring Data MongoDB에서는 일반적인 Java 객체(POJO, Plain Old Java Object)를 이러한 MongoDB 문서에 매핑하여 사용합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Repository&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소(Repository) 패턴은 데이터 접근 로직을 추상화하는 방법입니다.&lt;span&gt;&lt;/span&gt; Spring Data는 다양한 수준의 저장소 인터페이스를 제공하며, MongoRepository는 MongoDB에 특화된 기능을 제공합니다.&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;157:1-162:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;157:1-157:132&quot;&gt;&lt;b&gt;Repository&amp;lt;T, ID&amp;gt;:&lt;/b&gt; 가장 기본적인 마커 인터페이스로, Spring Data가 저장소 인터페이스를 식별하는 데 사용됩니다. 관리할 도메인 클래스(T)와 식별자 타입(ID)을 제네릭으로 받습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;158:1-158:199&quot;&gt;&lt;b&gt;CrudRepository&amp;lt;T, ID&amp;gt;:&lt;/b&gt; Repository를 상속하며 기본적인 CRUD(Create, Read, Update, Delete) 기능을 제공합니다. save(), findById(), findAll(), count(), delete(), existsById() 등의 메서드를 포함합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;159:1-159:136&quot;&gt;&lt;b&gt;PagingAndSortingRepository&amp;lt;T, ID&amp;gt;:&lt;/b&gt; CrudRepository를 상속하며 페이징(findAll(Pageable)) 및 정렬(findAll(Sort)) 기능을 추가로 제공합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;160:1-160:196&quot;&gt;&lt;b&gt;MongoRepository&amp;lt;T, ID&amp;gt;:&lt;/b&gt; PagingAndSortingRepository를 상속하며, CRUD, 페이징, 정렬 기능 외에도 MongoDB 관련 특정 기능(예: Example 쿼리, 특정 쿼리 메서드)을 제공합니다. 대부분의 MongoDB 애플리케이션에서 주로 사용되는 인터페이스입니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;161:1-162:0&quot;&gt;&lt;b&gt;ReactiveMongoRepository&amp;lt;T, ID&amp;gt;:&lt;/b&gt; 리액티브 프로그래밍 모델을 지원하는 저장소 인터페이스입니다. 메서드들은 Mono 또는 Flux와 같은 리액티브 타입을 반환하여 논블로킹(non-blocking) 데이터 접근을 가능하게 합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;163:1-163:240&quot; data-ke-size=&quot;size16&quot;&gt;이러한 계층적 구조는 Spring Data 프로젝트 전반에 걸쳐 일관성을 제공합니다. 개발자는 CrudRepository의 기본 메서드를 다른 Spring Data 프로젝트(예: Spring Data JPA)와 유사하게 사용할 수 있으며, 필요에 따라 MongoRepository가 제공하는 MongoDB 고유의 기능으로 확장할 수 있습니다. 이는 추상화와 특정 저장소의 강력한 기능 활용 사이의 균형을 제공합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;163:1-163:240&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;163:1-163:240&quot; data-ke-size=&quot;size23&quot;&gt;Template&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿(Template)은 저장소보다 더 낮은 수준의 추상화를 제공하며, MongoDB 데이터베이스와 직접 상호작용할 수 있는 다양한 메서드를 제공합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;169:1-171:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;169:1-169:235&quot;&gt;&lt;b&gt;MongoTemplate:&lt;/b&gt; 동기(synchronous) 방식의 MongoDB 작업을 위한 핵심 클래스입니다. Spring의 JdbcTemplate과 유사한 디자인을 가지며 &lt;span&gt;&lt;/span&gt;, 문서 저장, 업데이트, 삭제, 쿼리 실행, 집계(aggregation), 인덱스 관리 등 광범위한 기능을 제공합니다. 저장소 인터페이스만으로는 구현하기 어려운 복잡한 작업이나 세밀한 제어가 필요할 때 유용합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;170:1-171:0&quot;&gt;&lt;b&gt;ReactiveMongoTemplate:&lt;/b&gt; 리액티브 스택을 위한 MongoTemplate의 비동기, 논블로킹 버전입니다.&lt;span&gt;&lt;/span&gt; Mono와 Flux를 반환하여 리액티브 애플리케이션에서 MongoDB 작업을 수행하는 데 사용됩니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;172:1-172:221&quot; data-ke-size=&quot;size16&quot;&gt;저장소는 규약을 통한 단순성과 생산성에 초점을 맞추는 반면, 템플릿은 MongoDB의 기능을 최대한 활용하고 복잡한 작업을 수행할 수 있는 유연성과 제어력을 제공합니다. 많은 경우 저장소 인터페이스로 충분하지만, 특정 요구사항이나 성능 최적화를 위해서는 템플릿 사용이 필요할 수 있습니다. 이 두 가지 접근 방식은 상호 보완적이며, 필요에 따라 함께 사용할 수도 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;172:1-172:221&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;172:1-172:221&quot; data-ke-size=&quot;size23&quot;&gt;핵심 Spring Data MongoDB 인터페이스 요약:&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;176:1-184:114&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;인터페이스&lt;/td&gt;
&lt;td&gt;상속&lt;/td&gt;
&lt;td&gt;주요 목적 및 기능&lt;/td&gt;
&lt;td&gt;사용 시점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;178:1-178:53&quot;&gt;
&lt;td data-sourcepos=&quot;178:1-178:14&quot;&gt;Repository&lt;/td&gt;
&lt;td data-sourcepos=&quot;178:16-178:18&quot;&gt;-&lt;/td&gt;
&lt;td data-sourcepos=&quot;178:20-178:37&quot;&gt;마커 인터페이스, 저장소 식별&lt;/td&gt;
&lt;td data-sourcepos=&quot;178:39-178:51&quot;&gt;직접 사용 거의 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;179:1-179:118&quot;&gt;
&lt;td data-sourcepos=&quot;179:1-179:18&quot;&gt;CrudRepository&lt;/td&gt;
&lt;td data-sourcepos=&quot;179:20-179:33&quot;&gt;Repository&lt;/td&gt;
&lt;td data-sourcepos=&quot;179:35-179:95&quot;&gt;기본적인 CRUD 작업 (save, findById, findAll, delete 등) 제공&lt;/td&gt;
&lt;td data-sourcepos=&quot;179:97-179:116&quot;&gt;간단한 CRUD 기능만 필요할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;180:1-180:136&quot;&gt;
&lt;td data-sourcepos=&quot;180:1-180:29&quot;&gt;PagingAndSortingRepository&lt;/td&gt;
&lt;td data-sourcepos=&quot;180:31-180:48&quot;&gt;CrudRepository&lt;/td&gt;
&lt;td data-sourcepos=&quot;180:50-180:112&quot;&gt;CRUD + 페이징 (findAll(Pageable)) 및 정렬 (findAll(Sort)) 기능 제공&lt;/td&gt;
&lt;td data-sourcepos=&quot;180:114-180:134&quot;&gt;페이징 또는 정렬 기능이 필요할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;181:1-181:178&quot;&gt;
&lt;td data-sourcepos=&quot;181:1-181:19&quot;&gt;MongoRepository&lt;/td&gt;
&lt;td data-sourcepos=&quot;181:21-181:50&quot;&gt;PagingAndSortingRepository&lt;/td&gt;
&lt;td data-sourcepos=&quot;181:52-181:122&quot;&gt;CRUD, 페이징, 정렬 + MongoDB 특화 기능 (Derived Queries, Example Queries 등) 제공&lt;/td&gt;
&lt;td data-sourcepos=&quot;181:124-181:176&quot;&gt;대부분의 Spring Data MongoDB 애플리케이션에서 권장되는 기본 저장소 인터페이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;182:1-182:157&quot;&gt;
&lt;td data-sourcepos=&quot;182:1-182:27&quot;&gt;ReactiveMongoRepository&lt;/td&gt;
&lt;td data-sourcepos=&quot;182:29-182:83&quot;&gt;ReactiveCrudRepository, ReactiveSortingRepository&lt;/td&gt;
&lt;td data-sourcepos=&quot;182:85-182:130&quot;&gt;리액티브 CRUD, 페이징, 정렬 기능 제공 (Mono, Flux 반환)&lt;/td&gt;
&lt;td data-sourcepos=&quot;182:132-182:155&quot;&gt;논블로킹, 리액티브 애플리케이션 구축 시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;183:1-183:113&quot;&gt;
&lt;td data-sourcepos=&quot;183:1-183:17&quot;&gt;MongoTemplate&lt;/td&gt;
&lt;td data-sourcepos=&quot;183:19-183:21&quot;&gt;-&lt;/td&gt;
&lt;td data-sourcepos=&quot;183:23-183:74&quot;&gt;MongoDB 작업에 대한 저수준 동기 API 제공 (쿼리, 업데이트, 집계, 인덱싱 등)&lt;/td&gt;
&lt;td data-sourcepos=&quot;183:76-183:111&quot;&gt;복잡한 쿼리, 대량 업데이트, 집계, 세밀한 제어가 필요할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;184:1-184:114&quot;&gt;
&lt;td data-sourcepos=&quot;184:1-184:25&quot;&gt;ReactiveMongoTemplate&lt;/td&gt;
&lt;td data-sourcepos=&quot;184:27-184:29&quot;&gt;-&lt;/td&gt;
&lt;td data-sourcepos=&quot;184:31-184:82&quot;&gt;MongoDB 작업에 대한 저수준 리액티브 API 제공 (Mono, Flux 반환)&lt;/td&gt;
&lt;td data-sourcepos=&quot;184:84-184:112&quot;&gt;리액티브 애플리케이션에서 저수준 제어가 필요할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Document 와 Repository 정의하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 객체를 MongoDB 문서로 매핑하고, 이를 다루기 위한 Repository 인터페이스를 정의하는 방법을 알아봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;POJO 매핑 어노테이션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data MongoDB는 여러 어노테이션을 사용하여 POJO 클래스와 MongoDB 문서 간의 매핑을 정의합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;194:1-223:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;194:1-201:0&quot;&gt;&lt;b&gt;@Document:&lt;/b&gt; 클래스 레벨에서 사용되며, 해당 클래스가 MongoDB 문서임을 나타냅니다.&lt;span&gt;&lt;/span&gt; collection 속성을 사용하여 매핑될 컬렉션 이름을 명시적으로 지정할 수 있습니다. 지정하지 않으면 클래스 이름을 소문자로 변환한 이름이 기본값으로 사용됩니다.&lt;span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744953395750&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Document(collection = &quot;products&quot;)
public class Product {
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@Id&lt;/b&gt;: 필드 레벨에서 사용되며, 해당 필드가 문서의 고유 식별자(_id)임을 나타냅니다. 필드 이름이 id 또는 _id인 경우 자동으로 식별자로 간주되지만, 다른 이름의 필드를 식별자로 사용하려면 @Id 어노테이션을 명시해야 합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ObjectId 매핑:&lt;/b&gt; 필드 타입이 org.bson.types.ObjectId인 경우, MongoDB의 ObjectId 타입과 자동으로 매핑됩니다. 저장 시 필드 값이 null이면 MongoDB 드라이버 또는 Spring Data가 자동으로 ObjectId를 생성하여 할당할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;String 매핑:&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 필드 타입이 String인 경우, 문자열 값이 _id로 저장됩니다. 이 경우 애플리케이션에서 고유한 ID 값을 직접 생성하고 할당해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744953436168&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Id
private ObjectId internalId; // ObjectId 사용 예

// 또는

@Id
private String productCode; // String 사용 예&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@Field&lt;/b&gt;: 필드 레벨에서 사용되며, Java 필드 이름과 MongoDB 문서 내의 필드 이름이 다를 경우 매핑할 이름을 지정합니다. value 속성 (또는 속성 이름 생략)을 사용하여 MongoDB 필드 이름을 지정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744953461649&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Field(&quot;product_name&quot;)
private String productName;

@Field(&quot;unit_price&quot;)
private Double price;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 매핑 어노테이션 요약:&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;226:1-233:64&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;어노테이션&lt;/td&gt;
&lt;td&gt;레벨&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;주요 속성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;228:1-228:75&quot;&gt;
&lt;td data-sourcepos=&quot;228:1-228:13&quot;&gt;@Document&lt;/td&gt;
&lt;td data-sourcepos=&quot;228:15-228:19&quot;&gt;클래스&lt;/td&gt;
&lt;td data-sourcepos=&quot;228:21-228:58&quot;&gt;클래스를 MongoDB 문서로 매핑하고 컬렉션 이름을 지정합니다.&lt;/td&gt;
&lt;td data-sourcepos=&quot;228:60-228:73&quot;&gt;collection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;229:1-229:48&quot;&gt;
&lt;td data-sourcepos=&quot;229:1-229:7&quot;&gt;@Id&lt;/td&gt;
&lt;td data-sourcepos=&quot;229:9-229:12&quot;&gt;필드&lt;/td&gt;
&lt;td data-sourcepos=&quot;229:14-229:42&quot;&gt;필드를 문서의 기본 키(_id)로 지정합니다.&lt;/td&gt;
&lt;td data-sourcepos=&quot;229:44-229:46&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;230:1-230:77&quot;&gt;
&lt;td data-sourcepos=&quot;230:1-230:10&quot;&gt;@Field&lt;/td&gt;
&lt;td data-sourcepos=&quot;230:12-230:15&quot;&gt;필드&lt;/td&gt;
&lt;td data-sourcepos=&quot;230:17-230:56&quot;&gt;Java 필드를 MongoDB 문서의 특정 필드 이름으로 매핑합니다.&lt;/td&gt;
&lt;td data-sourcepos=&quot;230:58-230:75&quot;&gt;value (or name)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;231:1-231:111&quot;&gt;
&lt;td data-sourcepos=&quot;231:1-231:12&quot;&gt;@Indexed&lt;/td&gt;
&lt;td data-sourcepos=&quot;231:14-231:17&quot;&gt;필드&lt;/td&gt;
&lt;td data-sourcepos=&quot;231:19-231:53&quot;&gt;해당 필드에 대한 단일 필드 인덱스를 생성하도록 지정합니다.&lt;/td&gt;
&lt;td data-sourcepos=&quot;231:55-231:109&quot;&gt;unique, direction, sparse, expireAfterSeconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;232:1-232:105&quot;&gt;
&lt;td data-sourcepos=&quot;232:1-232:17&quot;&gt;@CompoundIndex&lt;/td&gt;
&lt;td data-sourcepos=&quot;232:19-232:23&quot;&gt;클래스&lt;/td&gt;
&lt;td data-sourcepos=&quot;232:25-232:67&quot;&gt;여러 필드를 포함하는 복합 인덱스를 생성하도록 클래스 레벨에서 지정합니다.&lt;/td&gt;
&lt;td data-sourcepos=&quot;232:69-232:103&quot;&gt;def, name, unique, sparse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;233:1-233:64&quot;&gt;
&lt;td data-sourcepos=&quot;233:1-233:14&quot;&gt;@Transient&lt;/td&gt;
&lt;td data-sourcepos=&quot;233:16-233:19&quot;&gt;필드&lt;/td&gt;
&lt;td data-sourcepos=&quot;233:21-233:58&quot;&gt;해당 필드를 영속성 대상에서 제외합니다 (DB에 저장되지 않음).&lt;/td&gt;
&lt;td data-sourcepos=&quot;233:60-233:62&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타입 매핑&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;237:1-237:92&quot;&gt;&lt;b&gt;기본 타입:&lt;/b&gt; String, Integer, Long, Double, Boolean 등 표준 Java 타입은 해당하는 BSON 타입으로 자동 매핑됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;238:1-238:115&quot;&gt;&lt;b&gt;ObjectId:&lt;/b&gt; @Id 어노테이션이 붙은 org.bson.types.ObjectId 타입 필드는 MongoDB의 ObjectId 타입으로 매핑되며, 자동 생성을 지원합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;239:1-240:0&quot;&gt;&lt;b&gt;날짜 및 시간 타입:&lt;/b&gt; java.util.Date 및 Java 8의 java.time 패키지 타입들(LocalDate, LocalDateTime, Instant 등)은 기본적으로 MongoDB의 Date 타입으로 매핑됩니다. Spring Data MongoDB는 이러한 타입들에 대한 내장 컨버터를 제공합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제: Domain Entity 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 @Document, @Id, @Field를 사용한 간단한 Product 엔티티 예제입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744953653893&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.myapp.domain;

import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.time.LocalDateTime;

@Document(collection = &quot;products&quot;) // &quot;products&quot; 컬렉션에 매핑
public class Product {

    @Id // 이 필드를 _id로 사용
    private ObjectId id;

    @Field(&quot;product_name&quot;) // MongoDB 필드 이름을 &quot;product_name&quot;으로 지정
    private String name;

    private String category; // 필드 이름 그대로 &quot;category&quot;로 매핑

    private double price; // 필드 이름 그대로 &quot;price&quot;로 매핑

    @Field(&quot;created_at&quot;)
    private LocalDateTime createdAt; // MongoDB Date 타입으로 매핑

    // 생성자, Getter, Setter 등 생략

    public Product(String name, String category, double price) {
        this.name = name;
        this.category = category;
        this.price = price;
        this.createdAt = LocalDateTime.now();
    }

    // Getters and Setters...
    public ObjectId getId() { return id; }
    public void setId(ObjectId id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getCategory() { return category; }
    public void setCategory(String category) { this.category = category; }
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }

    @Override
    public String toString() {
        return &quot;Product{&quot; +
               &quot;id=&quot; + id +
               &quot;, name='&quot; + name + '\'' +
               &quot;, category='&quot; + category + '\'' +
               &quot;, price=&quot; + price +
               &quot;, createdAt=&quot; + createdAt +
               '}';
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제: Repository 인터페이스 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Product 엔티티를 위한 Repository 인터페이스는 다음과 같이 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;동기 방식:&lt;/h4&gt;
&lt;pre id=&quot;code_1744953726695&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.myapp.repository;

import com.example.myapp.domain.Product;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ProductRepository extends MongoRepository&amp;lt;Product, ObjectId&amp;gt; {

    // 예시: 카테고리로 상품 목록 찾기 (Derived Query)
    List&amp;lt;Product&amp;gt; findByCategory(String category);

    // 예시: 특정 가격보다 비싼 상품 목록 찾기 (Derived Query)
    List&amp;lt;Product&amp;gt; findByPriceGreaterThan(double price);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;리액티브 방식:&lt;/h4&gt;
&lt;pre id=&quot;code_1744953744068&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.myapp.repository;

import com.example.myapp.domain.Product;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;

@Repository
public interface ReactiveProductRepository extends ReactiveMongoRepository&amp;lt;Product, ObjectId&amp;gt; {

    // 예시: 카테고리로 상품 목록 찾기 (Reactive Derived Query)
    Flux&amp;lt;Product&amp;gt; findByCategory(String category);

    // 예시: 특정 가격보다 비싼 상품 목록 찾기 (Reactive Derived Query)
    Flux&amp;lt;Product&amp;gt; findByPriceGreaterThan(double price);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;CRUD 작업 수행하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoRepository (또는 ReactiveMongoRepository) 인터페이스를 사용하여 MongoDB 문서에 대한 기본적인 생성(Create), 읽기(Read), 수정(Update), 삭제(Delete) 작업을 수행하는 방법을 알아봅니다. 이 인터페이스들은 CrudRepository를 상속하므로 표준 CRUD 메서드를 바로 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Repository 사용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 의존성 주입(Dependency Injection)을 사용하여 서비스나 컴포넌트에서 Repository 인스턴스를 주입받아 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744953787464&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class ProductService {

    private final ProductRepository productRepository;
    // 또는 private final ReactiveProductRepository productRepository;

    @Autowired
    public ProductService(ProductRepository productRepository) { // 또는 ReactiveProductRepository
        this.productRepository = productRepository;
    }

    // CRUD 메서드 구현...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 예제 (동기 방식 - MongoRepository)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 ProductRepository를 사용한 CRUD 작업 예제입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Create (생성) / Update (수정):&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;save() 메서드는 주어진 엔티티를 저장합니다. 만약 엔티티에 ID(@Id 필드) 값이 없거나 해당 ID를 가진 문서가 컬렉션에 없다면 새로운 문서를 삽입(Insert)합니다. 만약 ID 값이 있고 해당 ID의 문서가 이미 존재한다면 기존 문서를 덮어쓰는 방식으로 업데이트(Update)합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744953854364&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 새로운 Product 생성 및 저장
Product newProduct = new Product(&quot;Laptop&quot;, &quot;Electronics&quot;, 1200.00);
Product savedProduct = productRepository.save(newProduct);
System.out.println(&quot;Saved Product: &quot; + savedProduct);

// 기존 Product 조회 및 수정 후 저장 (Update)
ObjectId existingProductId = savedProduct.getId();
Optional&amp;lt;Product&amp;gt; optionalProduct = productRepository.findById(existingProductId);
if (optionalProduct.isPresent()) {
    Product productToUpdate = optionalProduct.get();
    productToUpdate.setPrice(1150.00);
    Product updatedProduct = productRepository.save(productToUpdate); // ID가 있으므로 Update 수행
    System.out.println(&quot;Updated Product: &quot; + updatedProduct);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Read (조회):&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ID로 조회:&lt;/b&gt; findById(id) 메서드는 주어진 ID에 해당하는 문서를 Optional&amp;lt;Entity&amp;gt; 형태로 반환합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744953964292&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ObjectId productId = /* 조회할 상품 ID */;
Optional&amp;lt;Product&amp;gt; productOptional = productRepository.findById(productId);
if (productOptional.isPresent()) {
    System.out.println(&quot;Found Product by ID: &quot; + productOptional.get());
} else {
    System.out.println(&quot;Product not found with ID: &quot; + productId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;전체 조회:&lt;/b&gt; findAll() 메서드는 컬렉션의 모든 문서를 List&amp;lt;Entity&amp;gt; (또는 Iterable&amp;lt;Entity&amp;gt;) 형태로 반환합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744953990794&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Product&amp;gt; allProducts = productRepository.findAll();
System.out.println(&quot;All Products:&quot;);
allProducts.forEach(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Delete (삭제):&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deleteById(id) 또는 delete(entity) 메서드를 사용하여 문서를 삭제할 수 있습니다. deleteAll()은 컬렉션의 모든 문서를 삭제합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744954098602&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ObjectId productIdToDelete = /* 삭제할 상품 ID */;
if (productRepository.existsById(productIdToDelete)) {
    productRepository.deleteById(productIdToDelete);
    System.out.println(&quot;Deleted Product with ID: &quot; + productIdToDelete);
}

// 또는 엔티티 객체로 삭제
// Optional&amp;lt;Product&amp;gt; productToDeleteOptional = productRepository.findById(productIdToDelete);
// productToDeleteOptional.ifPresent(productRepository::delete);

// 전체 삭제 (주의해서 사용)
// productRepository.deleteAll();
// System.out.println(&quot;All products deleted.&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 예제 (리액비트 방식 - ReactiveMongoRepository)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액티브 방식에서는 메서드들이 Mono (0 또는 1개의 결과) 또는 Flux (0개 이상의 결과)를 반환합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;Create / Update&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1744954139464&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Product newProduct = new Product(&quot;Keyboard&quot;, &quot;Accessories&quot;, 75.00);
Mono&amp;lt;Product&amp;gt; savedProductMono = reactiveProductRepository.save(newProduct);
savedProductMono.subscribe(saved -&amp;gt; System.out.println(&quot;Reactively Saved Product: &quot; + saved));

// Update (fetch, modify, save)
ObjectId existingId = /*... */;
reactiveProductRepository.findById(existingId)
   .flatMap(product -&amp;gt; {
        product.setPrice(70.00);
        return reactiveProductRepository.save(product);
    })
   .subscribe(updated -&amp;gt; System.out.println(&quot;Reactively Updated Product: &quot; + updated));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Read:&lt;/h4&gt;
&lt;pre id=&quot;code_1744954153460&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ID로 조회
ObjectId productId = /*... */;
Mono&amp;lt;Product&amp;gt; productMono = reactiveProductRepository.findById(productId);
productMono.subscribe(
    product -&amp;gt; System.out.println(&quot;Reactively Found Product by ID: &quot; + product),
    error -&amp;gt; System.err.println(&quot;Error finding product: &quot; + error),
    () -&amp;gt; System.out.println(&quot;Product lookup complete.&quot;) // Optional: onComplete
);

// 전체 조회
Flux&amp;lt;Product&amp;gt; allProductsFlux = reactiveProductRepository.findAll();
System.out.println(&quot;Reactively Found All Products:&quot;);
allProductsFlux.subscribe(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Delete:&lt;/h4&gt;
&lt;pre id=&quot;code_1744954176034&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ObjectId productIdToDelete = /*... */;
Mono&amp;lt;Void&amp;gt; deleteMono = reactiveProductRepository.deleteById(productIdToDelete);
deleteMono.subscribe(
    null, // onNext는 없음 (Void)
    error -&amp;gt; System.err.println(&quot;Error deleting product: &quot; + error),
    () -&amp;gt; System.out.println(&quot;Reactively Deleted Product with ID: &quot; + productIdToDelete) // onComplete
);

// 전체 삭제
// Mono&amp;lt;Void&amp;gt; deleteAllMono = reactiveProductRepository.deleteAll();
// deleteAllMono.subscribe(null, null, () -&amp;gt; System.out.println(&quot;All products reactively deleted.&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제들은 Spring Data MongoDB Repository를 사용하여 기본적인 데이터 관리 작업을 얼마나 간편하게 수행할 수 있는지 보여줍니다. 다음 섹션에서는 더 복잡한 데이터 조회 방법을 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MongoDB 쿼리하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data MongoDB는 데이터를 조회하는 다양한 방법을 제공합니다. 간단한 쿼리부터 복잡한 조건이나 집계가 필요한 쿼리까지, 상황에 맞는 방식을 선택할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Derived Queries (메서드 이름 기반 쿼리)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단한 방법은 Repository 인터페이스에 특정 명명 규칙을 따르는 메서드를 선언하는 것입니다. Spring Data MongoDB는 메서드 이름을 분석하여 자동으로 MongoDB 쿼리를 생성합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;509:1-509:144&quot;&gt;&lt;b&gt;기본 구조:&lt;/b&gt; find...By..., read...By..., query...By..., count...By..., get...By... 등의 접두사로 시작하고, By 뒤에 엔티티의 필드 이름을 조합하여 조건을 명시합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;510:1-514:7&quot;&gt;&lt;b&gt;조건 결합:&lt;/b&gt; And, Or 키워드를 사용하여 여러 필드 조건을 결합할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954233153&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Product&amp;gt; findByNameAndCategory(String name, String category);
List&amp;lt;Product&amp;gt; findByNameOrPriceLessThan(String name, double price);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비교 연산자&lt;/b&gt;: GreaterThan, LessThan, Between, Like, Containing, StartingWith, EndingWith, Exists, True, False, In, NotIn 등 다양한 키워드를 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954261068&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Product&amp;gt; findByPriceGreaterThan(double price);
List&amp;lt;Product&amp;gt; findByNameContainingIgnoreCase(String keyword); // 대소문자 무시 포함 검색
List&amp;lt;Product&amp;gt; findByCategoryIn(List&amp;lt;String&amp;gt; categories);
long countByCategory(String category); // 개수 세기
boolean existsByName(String name); // 존재 여부 확인&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정렬&lt;/b&gt;: OrderBy 키워드와 필드 이름, 그리고 Asc 또는 Desc를 사용하여 결과를 정렬할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954354355&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Product&amp;gt; findByCategoryOrderByPriceDesc(String category); // 카테고리로 찾고 가격 내림차순 정렬&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;페이징&lt;/b&gt;: 메서드 파라미터에 Pageable 인터페이스를 추가하면 페이징 처리가 가능합니다. Pageable 객체에는 페이지 번호, 페이지 크기, 정렬 정보가 포함될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1744954379587&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Page&amp;lt;Product&amp;gt; findByCategory(String category, Pageable pageable);

// 사용 예시
Pageable pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, &quot;name&quot;)); // 첫 페이지, 10개씩, 이름 오름차순
Page&amp;lt;Product&amp;gt; productPage = productRepository.findByCategory(&quot;Electronics&quot;, pageRequest);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Derived Queries는 간단하고 직관적이지만, 쿼리가 복잡해지면 메서드 이름이 너무 길어지고 가독성이 떨어질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Query 어노테이션 (JSON 기반 쿼리)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 이름만으로 표현하기 어려운 복잡한 쿼리나 MongoDB 고유의 연산자를 사용해야 할 경우, @Query 어노테이션을 사용하여 직접 쿼리 문자열을 정의할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;542:1-542:58&quot;&gt;&lt;b&gt;쿼리 정의:&lt;/b&gt; value 속성에 MongoDB의 JSON 기반 쿼리 문자열을 작성합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;543:1-550:7&quot;&gt;&lt;b&gt;파라미터 바인딩:&lt;/b&gt; 메서드 파라미터를 쿼리 내에서 사용하려면 위치 기반 플레이스홀더 (?0, ?1 등)를 사용합니다. ?0은 첫 번째 파라미터, ?1은 두 번째 파라미터를 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954419976&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Query(&quot;{ 'name' :?0 }&quot;)
List&amp;lt;Product&amp;gt; findProductsByName(String name);

@Query(&quot;{ 'category':?0, 'price': { $gte:?1, $lte:?2 } }&quot;)
List&amp;lt;Product&amp;gt; findByCategoryAndPriceRange(String category, double minPrice, double maxPrice);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;필드 선택 (Projection):&lt;/b&gt; fields 속성을 사용하여 반환될 문서에서 특정 필드만 선택하거나 제외할 수 있습니다. { 'fieldName': 1 }은 포함, { 'fieldName': 0 }은 제외를 의미합니다. _id는 기본적으로 포함되므로 제외하려면 명시적으로 { '_id': 0 }을 추가해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954436647&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Query(value = &quot;{ 'category' :?0 }&quot;, fields = &quot;{ 'name' : 1, 'price' : 1, '_id' : 0 }&quot;)
List&amp;lt;Product&amp;gt; findNameAndPriceByCategory(String category);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정렬:&lt;/b&gt; sort 속성을 사용하여 정렬 순서를 JSON 형식으로 지정할 수 있습니다. { 'fieldName': 1 }은 오름차순, { 'fieldName': -1 }은 내림차순입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954459709&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Query(value = &quot;{ 'category' :?0 }&quot;, sort = &quot;{ 'price' : -1 }&quot;)
List&amp;lt;Product&amp;gt; findByCategoryOrderByPriceDesc(String category);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;업데이트 쿼리:&lt;/b&gt; @Query는 조회뿐만 아니라 @Update 어노테이션과 함께 사용하여 업데이트 작업을 정의할 수도 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954485164&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Query(&quot;{ '_id' :?0 }&quot;)
@Update(&quot;{ '$set' : { 'price' :?1 } }&quot;)
UpdateResult updateProductPrice(ObjectId id, double newPrice); // UpdateResult 또는 long 반환 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Query는 Derived Query보다 유연하며 MongoDB의 다양한 연산자를 활용할 수 있게 해주지만, 쿼리 문자열을 직접 작성해야 하는 부담이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Programmatic Queries (Template &amp;amp; Criteria API)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoTemplate 또는 ReactiveMongoTemplate을 사용하면 Java 코드를 통해 동적으로 쿼리를 구성하고 실행할 수 있습니다. 이는 조건이 런타임 시점에 결정되거나 매우 복잡한 쿼리를 만들어야 할 때 유용합니다.&lt;span&gt;&lt;/span&gt; Criteria API는 쿼리 조건을 객체 지향적인 방식으로 안전하게 작성하도록 돕습니다.&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;574:1-578:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;574:1-574:85&quot;&gt;&lt;b&gt;Query 객체 생성:&lt;/b&gt; org.springframework.data.mongodb.core.query.Query 객체를 생성합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;575:1-575:220&quot;&gt;&lt;b&gt;Criteria 객체로 조건 추가:&lt;/b&gt; Criteria.where(&quot;fieldName&quot;)를 시작으로 .is(value), .lt(value), .gt(value), .regex(pattern), .in(collection), .and(&quot;otherField&quot;), .orOperator(...) 등 다양한 메서드를 체이닝하여 쿼리 조건을 만듭니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;576:1-576:128&quot;&gt;&lt;b&gt;쿼리 실행:&lt;/b&gt; MongoTemplate의 find(), findOne(), count(), exists() 등의 메서드에 Query 객체와 엔티티 클래스를 전달하여 쿼리를 실행합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;577:1-578:0&quot;&gt;&lt;b&gt;리액티브 실행:&lt;/b&gt; ReactiveMongoTemplate의 해당 메서드들은 Flux 또는 Mono를 반환합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954577073&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MongoTemplate 사용 예시
@Autowired
private MongoTemplate mongoTemplate;

public List&amp;lt;Product&amp;gt; findProductsDynamically(String name, Double maxPrice, String category) {
    Query query = new Query();
    List&amp;lt;Criteria&amp;gt; criteriaList = new ArrayList&amp;lt;&amp;gt;();

    if (name!= null &amp;amp;&amp;amp;!name.isEmpty()) {
        // 이름에 대한 정규식 검색 (대소문자 무시)
        criteriaList.add(Criteria.where(&quot;product_name&quot;).regex(name, &quot;i&quot;));
    }
    if (maxPrice!= null) {
        criteriaList.add(Criteria.where(&quot;price&quot;).lte(maxPrice));
    }
    if (category!= null &amp;amp;&amp;amp;!category.isEmpty()) {
        criteriaList.add(Criteria.where(&quot;category&quot;).is(category));
    }

    if (!criteriaList.isEmpty()) {
        query.addCriteria(new Criteria().andOperator(criteriaList.toArray(new Criteria)));
    }

    // 예시: 가격 오름차순 정렬 추가
    query.with(Sort.by(Sort.Direction.ASC, &quot;price&quot;));

    return mongoTemplate.find(query, Product.class);
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Template과 Criteria API는 가장 높은 수준의 유연성을 제공하지만, 다른 방법에 비해 코드가 더 장황해질 수 있습니다. Spring Data는 단순성을 위한 규약 기반 방식(findBy...), 선언적 사용자 정의를 위한 어노테이션(@Query), 그리고 완전한 프로그래밍 제어를 위한 API(MongoTemplate/Criteria)를 모두 제공하여 개발자가 상황에 맞는 최적의 도구를 선택할 수 있도록 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;쿼리 방법 비교:&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;614:1-621:70&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Derived Queries (메서드 이름)&lt;/td&gt;
&lt;td&gt;@Query 어노테이션&lt;/td&gt;
&lt;td&gt;Template + Criteria API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;616:1-616:64&quot;&gt;
&lt;td data-sourcepos=&quot;616:1-616:11&quot;&gt;&lt;b&gt;구현 방식&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;616:13-616:25&quot;&gt;메서드 시그니처 선언&lt;/td&gt;
&lt;td data-sourcepos=&quot;616:27-616:42&quot;&gt;메서드에 쿼리 문자열 정의&lt;/td&gt;
&lt;td data-sourcepos=&quot;616:44-616:62&quot;&gt;Java 코드로 쿼리 객체 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;617:1-617:29&quot;&gt;
&lt;td data-sourcepos=&quot;617:1-617:9&quot;&gt;&lt;b&gt;단순성&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;617:11-617:17&quot;&gt;매우 높음&lt;/td&gt;
&lt;td data-sourcepos=&quot;617:19-617:22&quot;&gt;중간&lt;/td&gt;
&lt;td data-sourcepos=&quot;617:24-617:27&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;618:1-618:29&quot;&gt;
&lt;td data-sourcepos=&quot;618:1-618:9&quot;&gt;&lt;b&gt;유연성&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;618:11-618:14&quot;&gt;낮음&lt;/td&gt;
&lt;td data-sourcepos=&quot;618:16-618:19&quot;&gt;중간&lt;/td&gt;
&lt;td data-sourcepos=&quot;618:21-618:27&quot;&gt;매우 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;619:1-619:57&quot;&gt;
&lt;td data-sourcepos=&quot;619:1-619:11&quot;&gt;&lt;b&gt;쿼리 복잡도&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;619:13-619:20&quot;&gt;간단한 쿼리&lt;/td&gt;
&lt;td data-sourcepos=&quot;619:22-619:37&quot;&gt;중간 ~ 복잡한 정적 쿼리&lt;/td&gt;
&lt;td data-sourcepos=&quot;619:39-619:55&quot;&gt;매우 복잡하거나 동적인 쿼리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;620:1-620:72&quot;&gt;
&lt;td data-sourcepos=&quot;620:1-620:11&quot;&gt;&lt;b&gt;타입 안전성&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;620:13-620:31&quot;&gt;컴파일 시점 (메서드 시그니처)&lt;/td&gt;
&lt;td data-sourcepos=&quot;620:33-620:45&quot;&gt;낮음 (문자열 기반)&lt;/td&gt;
&lt;td data-sourcepos=&quot;620:47-620:70&quot;&gt;높음 (Criteria API 사용 시)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;621:1-621:70&quot;&gt;
&lt;td data-sourcepos=&quot;621:1-621:12&quot;&gt;&lt;b&gt;주 사용 사례&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;621:14-621:29&quot;&gt;간단한 조회, 프로토타이핑&lt;/td&gt;
&lt;td data-sourcepos=&quot;621:31-621:49&quot;&gt;복잡한 정적 쿼리, 특정 연산자&lt;/td&gt;
&lt;td data-sourcepos=&quot;621:51-621:68&quot;&gt;동적 쿼리, 복잡한 조건 조합&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;고급 기능 살펴보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data MongoDB는 기본적인 CRUD 및 쿼리 기능 외에도 다양한 고급 기능을 제공하여 애플리케이션 개발을 더욱 효율적으로 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱싱 (@Indexed, @CompoundIndex)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB에서 쿼리 성능을 최적화하려면 인덱스를 적절하게 사용하는 것이 필수적입니다. Spring Data MongoDB는 어노테이션을 통해 엔티티 클래스에 인덱스를 선언적으로 정의할 수 있는 편리한 방법을 제공합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;631:1-664:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;631:1-649:0&quot;&gt;&lt;b&gt;@Indexed:&lt;/b&gt; 단일 필드 인덱스를 정의합니다. 필드 레벨에 적용합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;632:5-635:144&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;632:5-632:39&quot;&gt;&lt;b&gt;unique = true&lt;/b&gt;: 고유 인덱스를 생성합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;633:5-633:82&quot;&gt;&lt;b&gt;direction = IndexDirection.DESCENDING&lt;/b&gt;: 내림차순 인덱스를 생성합니다 (기본값은 ASCENDING).&lt;/li&gt;
&lt;li data-sourcepos=&quot;634:5-634:51&quot;&gt;&lt;b&gt;sparse = true&lt;/b&gt;: 해당 필드가 없는 문서는 인덱스에서 제외합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;635:5-635:144&quot;&gt;&lt;b&gt;expireAfterSeconds = 3600&lt;/b&gt;: TTL(Time-To-Live) 인덱스를 생성하여 3600초(1시간) 후에 문서가 자동으로 삭제되도록 합니다. (주의: TTL 인덱스는 Date 타입 필드나 Date 배열 필드에만 적용 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954721581&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Document(collection = &quot;users&quot;)
public class User {
    @Id private ObjectId id;
    @Indexed(unique = true) // email 필드에 고유 인덱스 생성
    private String email;
    @Indexed(direction = IndexDirection.DESCENDING) // age 필드에 내림차순 인덱스 생성
    private int age;
    @Indexed(expireAfterSeconds = 86400) // createdAt 필드에 TTL 인덱스 (1일 후 만료)
    private Date createdAt;
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@CompoundIndex:&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 여러 필드를 결합한 복합 인덱스를 정의합니다. 클래스 레벨에 적용하며, 여러 개를 정의할 수 있습니다 (@CompoundIndexes 사용).&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;def = &quot;{'lastName': 1, 'firstName': 1}&quot;&lt;/b&gt;: 인덱스 정의를 JSON 형식으로 지정합니다. 1은 오름차순, -1은 내림차순입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;name = &quot;user_name_idx&quot;&lt;/b&gt;: 인덱스 이름을 지정합니다.&lt;/li&gt;
&lt;li&gt;unique = true, sparse = true 등 @Indexed와 유사한 옵션을 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954760713&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Document(collection = &quot;users&quot;)
@CompoundIndex(name = &quot;user_name_idx&quot;, def = &quot;{'lastName': 1, 'firstName': 1}&quot;)
public class User {
    @Id private ObjectId id;
    private String firstName;
    private String lastName;
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인덱스 자동 생성:&lt;/b&gt; Spring Boot는 기본적으로 &lt;b&gt;spring.data.mongodb.auto-index-creation=true&lt;/b&gt; 설정을 통해 애플리케이션 시작 시 어노테이션 기반 인덱스를 자동으로 생성하려고 시도합니다.&lt;span&gt;&lt;/span&gt; 하지만 최신 버전에서는 이 기본값이 false일 수 있으며 &lt;span&gt;&lt;/span&gt;, 특히 운영 환경에서는 인덱스 생성 및 변경 작업이 리소스를 많이 소모하고 예기치 않은 성능 저하를 유발할 수 있으므로, 자동 생성에만 의존하기보다는 명시적인 인덱스 관리 전략(예: 배포 스크립트, Mongock &lt;span&gt;&lt;/span&gt; 같은 마이그레이션 도구 사용)을 고려하는 것이 좋습니다. 인덱스 전략은 애플리케이션 성능에 직접적인 영향을 미치므로 신중하게 계획하고 관리해야 합니다.&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;GridFS (대용량 파일 저장)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB의 BSON 문서 크기 제한(16MB)을 초과하는 대용량 파일(이미지, 비디오, 문서 등)을 저장하고 관리하기 위해 GridFS 명세를 사용합니다.&lt;span&gt;&lt;/span&gt; Spring Data MongoDB는 GridFsTemplate과 ReactiveGridFsTemplate을 통해 GridFS 기능을 쉽게 사용할 수 있도록 지원합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;671:1-703:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;671:1-696:0&quot;&gt;&lt;b&gt;GridFsTemplate (동기):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;672:5-675:53&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;672:5-672:161&quot;&gt;파일 저장: store(InputStream content, String filename, String contentType, Object metadata) 메서드 등을 사용하여 파일을 GridFS에 저장합니다. 파일은 여러 개의 청크(chunk)로 분할되어 저장됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;673:5-673:97&quot;&gt;파일 조회: findOne(Query query) 또는 find(Query query)를 사용하여 파일 메타데이터(GridFSFile)를 조회합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;674:5-674:106&quot;&gt;파일 내용 접근: 조회된 GridFSFile로부터 GridFsResource를 얻고, getInputStream()을 통해 파일 내용을 스트림으로 읽을 수 있습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;675:5-675:53&quot;&gt;파일 삭제: delete(Query query)를 사용하여 파일을 삭제합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954814840&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Autowired
private GridFsTemplate gridFsTemplate;

public ObjectId storeFile(InputStream inputStream, String filename, String contentType) {
    return gridFsTemplate.store(inputStream, filename, contentType);
}

public GridFsResource getFile(ObjectId id) {
    GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where(&quot;_id&quot;).is(id)));
    if (file!= null) {
        return gridFsTemplate.getResource(file);
    }
    return null;
}

public void deleteFile(ObjectId id) {
    gridFsTemplate.delete(Query.query(Criteria.where(&quot;_id&quot;).is(id)));
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;697:1-703:0&quot;&gt;&lt;b&gt;ReactiveGridFsTemplate (리액티브):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;698:5-703:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;698:5-698:71&quot;&gt;동기 버전과 유사한 기능을 제공하지만, Mono와 Flux를 반환하여 논블로킹 방식으로 작동합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;699:5-699:101&quot;&gt;파일 저장: store(Flux&amp;lt;DataBuffer&amp;gt; content, String filename,...)은 Mono&amp;lt;ObjectId&amp;gt;를 반환합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;700:5-700:112&quot;&gt;파일 조회: findOne(Query query)은 Mono&amp;lt;GridFSFile&amp;gt;, find(Query query)는 Flux&amp;lt;GridFSFile&amp;gt;을 반환합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;701:5-701:153&quot;&gt;파일 내용 접근: getResource(GridFSFile file)은 Mono&amp;lt;ReactiveGridFsResource&amp;gt;를 반환하고, 리소스에서 getDownloadStream()을 호출하면 Flux&amp;lt;DataBuffer&amp;gt;를 얻습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;702:5-703:0&quot;&gt;파일 삭제: delete(Query query)는 Mono&amp;lt;Void&amp;gt;를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;704:1-704:54&quot; data-ke-size=&quot;size16&quot;&gt;GridFS는 MongoDB 내에서 대용량 파일을 효율적으로 관리할 수 있는 강력한 솔루션입니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;704:1-704:54&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;704:1-704:54&quot; data-ke-size=&quot;size23&quot;&gt;Aggregation Framework (집계 프레임워크)&lt;/h3&gt;
&lt;p data-sourcepos=&quot;704:1-704:54&quot; data-ke-size=&quot;size16&quot;&gt;MongoDB의 Aggregation Framework는 여러 단계(stage)를 파이프라인으로 연결하여 데이터를 변환하고 집계하는 강력한 도구입니다.&lt;span&gt;&lt;/span&gt; Spring Data MongoDB는 이를 MongoTemplate (및 ReactiveMongoTemplate)과 @Aggregation 어노테이션을 통해 지원합니다.&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;710:1-761:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;710:1-742:0&quot;&gt;&lt;b&gt;MongoTemplate.aggregate():&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;711:5-712:164&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;711:5-711:162&quot;&gt;집계 파이프라인을 Aggregation 객체로 정의합니다. Aggregation 클래스는 $match, $group, $project, $sort, $limit, $lookup 등 다양한 정적 팩토리 메서드를 제공하여 파이프라인 단계를 구성합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;712:5-712:164&quot;&gt;mongoTemplate.aggregate(Aggregation aggregation, String inputCollectionName, Class&amp;lt;O&amp;gt; outputType) 메서드를 사용하여 집계를 실행합니다. outputType은 집계 결과가 매핑될 클래스입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954881586&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Autowired
private MongoTemplate mongoTemplate;

public List&amp;lt;CategoryStats&amp;gt; getCategoryPriceStats() {
    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.group(&quot;category&quot;) // 카테고리별 그룹화
           .avg(&quot;price&quot;).as(&quot;averagePrice&quot;) // 평균 가격 계산
           .sum(&quot;price&quot;).as(&quot;totalPrice&quot;)   // 총 가격 계산
           .count().as(&quot;productCount&quot;),    // 상품 개수 계산
        Aggregation.project(&quot;averagePrice&quot;, &quot;totalPrice&quot;, &quot;productCount&quot;) // 결과 필드 선택
           .and(&quot;category&quot;).previousOperation(), // 그룹 키(_id)를 category 필드로 변경
        Aggregation.sort(Sort.Direction.DESC, &quot;productCount&quot;) // 상품 개수 내림차순 정렬
    );

    AggregationResults&amp;lt;CategoryStats&amp;gt; results = mongoTemplate.aggregate(
        aggregation, &quot;products&quot;, CategoryStats.class // &quot;products&quot; 컬렉션 대상, CategoryStats 클래스로 결과 매핑
    );
    return results.getMappedResults();
}
// CategoryStats 클래스는 집계 결과를 담기 위한 DTO
public static class CategoryStats {
    String category;
    double averagePrice;
    double totalPrice;
    long productCount;
    // Getters, Setters...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@Aggregation 어노테이션:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;744:5-744:117&quot;&gt;Repository 메서드에 직접 집계 파이프라인을 JSON 문자열 배열 형태로 정의할 수 있습니다.&lt;span&gt;&lt;/span&gt; 플레이스홀더(?0, ?1 등)를 사용하여 메서드 파라미터를 바인딩할 수 있습니다.&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744954920399&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ProductRepository extends MongoRepository&amp;lt;Product, ObjectId&amp;gt; {
    @Aggregation(pipeline = {
        &quot;{ '$match': { 'category':?0 } }&quot;,
        &quot;{ '$group': { '_id': '$category', 'averagePrice': { '$avg': '$price' } } }&quot;,
        &quot;{ '$project': { 'category': '$_id', 'averagePrice': 1, '_id': 0 } }&quot;
    })
    List&amp;lt;CategoryAveragePrice&amp;gt; findAveragePriceByCategory(String category);
}
// CategoryAveragePrice는 결과를 담을 DTO 또는 인터페이스 기반 프로젝션
public static class CategoryAveragePrice {
    String category;
    double averagePrice;
    // Getters, Setters...
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;Aggregation Framework는 복잡한 데이터 분석 및 보고서 생성 요구사항을 효과적으로 처리할 수 있게 해줍니다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reactive 프로그래밍 모델 지원&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data MongoDB는 ReactiveMongoRepository와 ReactiveMongoTemplate을 통해 완전한 리액티브 프로그래밍 모델을 지원합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;768:1-771:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;768:1-768:160&quot;&gt;&lt;b&gt;논블로킹 I/O:&lt;/b&gt; 리액티브 드라이버를 사용하여 데이터베이스 작업을 수행하므로 스레드가 I/O 작업을 기다리며 블로킹되지 않습니다. 이는 적은 수의 스레드로도 높은 동시성(concurrency)을 처리할 수 있게 하여 애플리케이션의 확장성과 자원 효율성을 크게 향상시킵니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;769:1-769:165&quot;&gt;&lt;b&gt;리액티브 타입 반환:&lt;/b&gt; 모든 Repository 및 Template 메서드는 Project Reactor의 Mono (0-1개 결과) 또는 Flux (0-N개 결과) 타입을 반환합니다. 이를 통해 리액티브 파이프라인을 구성하고 데이터 스트림을 비동기적으로 처리할 수 있습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;770:1-771:0&quot;&gt;&lt;b&gt;아키텍처 영향:&lt;/b&gt; 리액티브 스택을 선택하는 것은 단순히 API 사용 방식을 바꾸는 것을 넘어, 애플리케이션 전체 아키텍처에 영향을 미칩니다.&lt;span&gt;&lt;/span&gt; 데이터베이스 접근뿐만 아니라 웹 계층(예: Spring WebFlux) 등 다른 부분도 리액티브 방식으로 구성해야 그 효과를 극대화할 수 있습니다. 이는 높은 처리량과 응답성이 중요한 마이크로서비스나 실시간 데이터 처리 애플리케이션에 특히 적합합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;772:1-772:90&quot; data-ke-size=&quot;size16&quot;&gt;리액티브 지원은 현대적인 고성능 애플리케이션 개발에 필수적인 요소로 자리 잡고 있으며, Spring Data MongoDB는 이를 위한 강력한 기반을 제공합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://spring.io/guides/gs/accessing-data-mongodb&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://spring.io/guides/gs/accessing-data-mongodb&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744955025914&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Getting Started | Accessing Data with MongoDB&quot; data-og-description=&quot;You can run the application from the command line with Gradle or Maven. You can also build a single executable JAR file that contains all the necessary dependencies, classes, and resources and run that. Building an executable jar makes it easy to ship, ver&quot; data-og-host=&quot;spring.io&quot; data-og-source-url=&quot;https://spring.io/guides/gs/accessing-data-mongodb&quot; data-og-url=&quot;https://spring.io/guides/gs/accessing-data-mongodb&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bAwD1Q/hyYH8Rumdq/GkOSKUalhVWD58XyNW73uk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/7m8IZ/hyYG70r3N6/Gxhqz2lGeMnJX2TTyHc9IK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://spring.io/guides/gs/accessing-data-mongodb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://spring.io/guides/gs/accessing-data-mongodb&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bAwD1Q/hyYH8Rumdq/GkOSKUalhVWD58XyNW73uk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/7m8IZ/hyYG70r3N6/Gxhqz2lGeMnJX2TTyHc9IK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Getting Started | Accessing Data with MongoDB&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;You can run the application from the command line with Gradle or Maven. You can also build a single executable JAR file that contains all the necessary dependencies, classes, and resources and run that. Building an executable jar makes it easy to ship, ver&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/mongodb/reference/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-data/mongodb/reference/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744955051501&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Data MongoDB :: Spring Data MongoDB&quot; data-og-description=&quot;Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-data/mongodb/reference/&quot; data-og-url=&quot;https://docs.spring.io/spring-data/mongodb/reference/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/mongodb/reference/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-data/mongodb/reference/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data MongoDB :: Spring Data MongoDB&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Spring</category>
      <category>MonGo</category>
      <category>mongoDB</category>
      <category>Spring</category>
      <category>spring data</category>
      <category>spring data mongodb</category>
      <author>kahnco</author>
      <guid isPermaLink="true">https://kahnco.tistory.com/49</guid>
      <comments>https://kahnco.tistory.com/49#entry49comment</comments>
      <pubDate>Fri, 18 Apr 2025 14:44:33 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring Data - JPA</title>
      <link>https://kahnco.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Framework 생태계에서 데이터 접근 계층을 개발하는 것은 종종 반복적이고 상용구 코드가 많이 필요한 작업이었습니다. 하지만 Spring Data JPA는 이러한 과정을 혁신적으로 단순화하여 개발자가 비즈니스 로직에 더 집중할 수 있도록 돕습니다. 이 글에서는 Spring Data JPA 3.4.4 버전을 기준으로, 프로젝트 설정부터 엔티티 및 관계 매핑, CRUD 작업, 다양한 쿼리 방법, 그리고 페이징, 프로젝션, 감사, 트랜잭션 관리와 같은 고급 기능까지 포괄적으로 살펴보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Spring Data JPA 소개: 목표와 핵심 개념&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA는 Jakarta Persistence API (JPA) 사양 기반의 리포지토리를 쉽게 구현할 수 있도록 지원하는 Spring Data 프로젝트의 하위 모듈입니다.&lt;span&gt;&lt;/span&gt; JPA는 Java 애플리케이션에서 관계형 데이터를 관리하기 위한 표준 명세이며, Hibernate, EclipseLink 등이 대표적인 구현체입니다.&lt;span&gt;&lt;/span&gt; Spring Data JPA는 이러한 JPA 구현체 위에 추상화 계층을 제공하여 데이터 접근 로직 개발을 간소화하는 것을 목표로 합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;주요 목표:&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;11:1-11:82&quot;&gt;&lt;b&gt;상용구 코드 감소:&lt;/b&gt; 반복적인 CRUD 코드, EntityManager 관리, 트랜잭션 처리 등의 상용구 코드를 대폭 줄여줍니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;12:1-12:89&quot;&gt;&lt;b&gt;일관된 프로그래밍 모델:&lt;/b&gt; 다양한 JPA 구현체나 데이터 저장소 기술에 대해 일관된 방식으로 데이터 접근 계층을 개발할 수 있도록 지원합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;13:1-14:0&quot;&gt;&lt;b&gt;단순화된 데이터 접근:&lt;/b&gt; 리포지토리 인터페이스 정의만으로 구현체를 자동 생성하여 기본적인 데이터 조작을 쉽게 만듭니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 개념:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;17:1-17:125&quot;&gt;&lt;b&gt;ORM (Object-Relational Mapping):&lt;/b&gt; 객체 지향 프로그래밍 언어의 객체와 관계형 데이터베이스의 테이블 간의 매핑을 관리하는 기술입니다. JPA는 ORM을 위한 표준 명세를 제공합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;18:1-18:105&quot;&gt;&lt;b&gt;EntityManager:&lt;/b&gt; JPA의 핵심 인터페이스로, 엔티티의 영속성(Persistence)을 관리합니다. 엔티티의 저장, 조회, 수정, 삭제 등의 작업을 수행합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;19:1-19:181&quot;&gt;&lt;b&gt;영속성 컨텍스트 (Persistence Context):&lt;/b&gt; 엔티티 객체들을 관리하는 환경입니다. EntityManager를 통해 접근하며, 엔티티의 상태 변화를 추적하고 데이터베이스와 동기화하는 역할을 합니다. 1차 캐시, 쓰기 지연, 변경 감지(Dirty Checking), 지연 로딩 등의 기능을 제공합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;20:1-21:0&quot;&gt;&lt;b&gt;JPA와 Spring Data JPA의 관계:&lt;/b&gt; Spring Data JPA는 JPA 명세를 기반으로 만들어졌습니다. 개발자는 Spring Data JPA가 제공하는 리포지토리 인터페이스를 사용하여 데이터 접근 로직을 작성하고, Spring Data JPA는 내부적으로 JPA의 EntityManager를 사용하여 실제 데이터베이스 작업을 처리합니다.&lt;span&gt;&lt;/span&gt; 즉, Spring Data JPA는 JPA를 더 쉽고 편리하게 사용하기 위한 추상화 계층입니다.&lt;span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 인터페이스:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data는 여러 리포지토리 인터페이스를 제공하며, 필요에 따라 선택하여 확장할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;26:1-29:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;26:1-26:202&quot;&gt;&lt;b&gt;CrudRepository&amp;lt;T, ID&amp;gt;&lt;/b&gt;: 가장 기본적인 인터페이스로, 엔티티에 대한 기본적인 CRUD(Create, Read, Update, Delete) 연산을 위한 메서드(save, findById, findAll, count, delete, existsById 등)를 제공합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;27:1-27:171&quot;&gt;&lt;b&gt;PagingAndSortingRepository&amp;lt;T, ID&amp;gt;&lt;/b&gt;: CrudRepository를 확장하며, 페이징(findAll(Pageable)) 및 정렬(findAll(Sort)) 기능을 위한 메서드를 추가로 제공합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;28:1-29:0&quot;&gt;&lt;b&gt;JpaRepository&amp;lt;T, ID&amp;gt;&lt;/b&gt;: PagingAndSortingRepository를 확장하며, JPA에 특화된 기능(예: flush(), saveAndFlush(), deleteAllInBatch(), getReferenceById())을 추가로 제공합니다.&lt;span&gt;&lt;/span&gt; 대부분의 JPA 기반 프로젝트에서는 JpaRepository를 사용하는 것이 권장됩니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프로젝트 설정: 의존성 및 구성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA를 사용하기 위한 기본적인 프로젝트 설정 방법을 알아봅니다. Spring Boot를 사용하면 많은 부분이 자동 구성되어 설정이 매우 간편해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 추가 (Maven/Gradle)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 프로젝트에서 Spring Data JPA를 사용하려면 &lt;b&gt;spring-boot-starter-data-jpa&lt;/b&gt; 의존성을 추가해야 합니다.&lt;span&gt;&lt;/span&gt; 이 스타터는 Spring Data JPA, JPA API(Jakarta Persistence), 기본 JPA 구현체인 Hibernate, JDBC, 트랜잭션 관리 등 필요한 라이브러리들을 포함하고 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring Boot 3.4.4&lt;/b&gt; 버전에 해당하는 spring-boot-starter-data-jpa 3.4.4 버전을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Maven (pom.xml):&lt;/h4&gt;
&lt;pre id=&quot;code_1744940603606&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-data-jpa&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.4.4&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.mysql&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;mysql-connector-j&amp;lt;/artifactId&amp;gt;
    &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Gradle (build.gradle - Groovy DSL):&lt;/h4&gt;
&lt;pre id=&quot;code_1744940636105&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id 'org.springframework.boot' version '3.4.4'
    id 'io.spring.dependency-management' version '1.1.7' // Spring Boot 플러그인이 관리
    id 'java'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    // 사용할 데이터베이스 드라이버 추가 (예: MySQL)
    runtimeOnly 'com.mysql:mysql-connector-j'

    // H2 데이터베이스 사용 시
    // runtimeOnly 'com.h2database:h2'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot의 의존성 관리 기능을 사용하면 &lt;b&gt;spring-boot-starter-data-jpa &lt;/b&gt;의 버전을 명시하지 않아도 spring-boot-starter-parent 또는 spring-boot-dependencies BOM(Bill of Materials)에 정의된 호환 버전을 자동으로 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터베이스 및 JPA 설정 (application.properties / application.yml)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot는 application.properties 또는 application.yml 파일을 통해 데이터 소스 및 JPA 설정을 관리합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;데이터 소스 설정 (spring.datasource.*)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 연결을 위한 기본 정보(URL, 사용자 이름, 비밀번호, 드라이버 클래스)를 설정합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744940726446&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Example for MySQL
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase?useSSL=false&amp;amp;serverTimezone=UTC&amp;amp;allowPublicKeyRetrieval=true
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Example for H2 (In-Memory)
# spring.datasource.url=jdbc:h2:mem:testdb
# spring.datasource.driver-class-name=org.h2.Driver
# spring.datasource.username=sa
# spring.datasource.password=&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot는 클래스패스에 H2, HSQLDB, Derby와 같은 임베디드 데이터베이스 드라이버가 있고 명시적인 데이터 소스 설정이 없는 경우, 자동으로 인메모리 데이터베이스를 구성합니다.&lt;span&gt;&lt;/span&gt; 또한, Spring Boot 2.x 버전부터는 HikariCP가 기본 커넥션 풀로 사용되며, spring.datasource.hikari.* 속성을 통해 커넥션 풀의 상세 설정을 조정할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;JPA 프로바이더 설정 (spring.jpa.*)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;JPA 동작 방식을 제어하는 속성들을 설정합니다. Spring Boot 는 Hibernate 를 기본 JPA 프로바이더로 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744940817639&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Hibernate가 실행하는 SQL 문을 로그로 출력 (개발 시 유용)
# 콘솔 출력 대신 로깅 프레임워크를 통해 제어하려면 logging.level.org.hibernate.SQL=DEBUG 사용 권장
spring.jpa.show-sql=true

# Hibernate의 DDL(Data Definition Language) 자동 생성 전략 설정
# none: DDL 생성 안 함 (운영 환경 권장)
# validate: 엔티티와 테이블 스키마 검증
# update: 변경된 부분만 스키마 업데이트 (개발 시 편리하나 주의 필요)
# create: 애플리케이션 시작 시 스키마 삭제 후 재생성
# create-drop: 시작 시 생성, 종료 시 삭제 (테스트 또는 개발 초기 유용)
# 임베디드 DB가 아니고 스키마 관리 도구(Flyway, Liquibase)가 없으면 기본값은 none
spring.jpa.hibernate.ddl-auto=update

# 사용할 Hibernate Dialect 지정 (대부분 자동 감지됨)
# spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
# 또는 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

# 기타 Hibernate 네이티브 속성 설정 (예: SQL 포맷팅)
spring.jpa.properties.hibernate.format_sql=true
# spring.jpa.properties.hibernate.default_schema=myschema&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot의 자동 구성(Auto-configuration) 덕분에, 대부분의 경우 개발자는 데이터 소스 연결 정보만 application.properties에 제공하면 됩니다.&lt;span&gt;&lt;/span&gt; EntityManagerFactory, DataSource, TransactionManager와 같은 핵심 빈들은 Spring Boot가 자동으로 구성해주므로, 특별한 커스터마이징이 필요하지 않다면 수동으로 빈을 정의할 필요가 없습니다.&lt;span&gt;&lt;/span&gt; 이는 Spring Boot가 설정의 복잡성을 크게 줄여주는 강력한 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;데이터 모델링: 엔티티와 관계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 를 사용하여 데이터베이스 테이블과 매핑될 Java 객체, 즉 엔티티(Entity)를 정의하고 엔티티 간의 관계를 설정하는 방법을 알아봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JPA 엔티티 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티는 데이터베이스 테이블의 한 행(row)에 해당하며, 일반적인 POJO(Plain Old Java Object) 클래스에 JPA 어노테이션을 추가하여 정의합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;149:1-149:211&quot;&gt;&lt;b&gt;@Entity&lt;/b&gt;: 해당 클래스가 JPA 엔티티임을 나타냅니다.&lt;span&gt;&lt;/span&gt; 엔티티 이름은 기본적으로 클래스 이름과 동일하지만 name 속성으로 변경할 수 있습니다.&lt;span&gt;&lt;/span&gt; JPA 구현체가 프록시 객체를 생성할 수 있도록 final 클래스로 선언해서는 안 되며, 인자 없는(no-arg) 기본 생성자가 필요합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;150:1-150:177&quot;&gt;&lt;b&gt;@Table&lt;/b&gt;: 엔티티와 매핑될 데이터베이스 테이블의 이름(name), 스키마(schema), 유니크 제약조건(uniqueConstraints) 등을 지정합니다.&lt;span&gt;&lt;/span&gt; 생략하면 엔티티 이름(클래스 이름)을 테이블 이름으로 사용합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;151:1-151:114&quot;&gt;&lt;b&gt;@Id&lt;/b&gt;: 엔티티의 기본 키(Primary Key) 필드를 지정합니다.&lt;span&gt;&lt;/span&gt; 모든 엔티티는 @Id 필드를 가져야 합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;152:1-157:83&quot;&gt;&lt;b&gt;@GeneratedValue&lt;/b&gt;: 기본 키 값을 자동으로 생성하는 전략을 지정합니다.&lt;span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;153:5-157:83&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;153:5-153:120&quot;&gt;&lt;b&gt;GenerationType.IDENTITY&lt;/b&gt;: 데이터베이스의 자동 증가(auto-increment) 컬럼을 사용합니다 (MySQL, PostgreSQL, SQL Server 등 지원).&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;154:5-154:225&quot;&gt;&lt;b&gt;GenerationType.SEQUENCE&lt;/b&gt;: 데이터베이스 시퀀스 객체를 사용하여 키를 생성합니다 (Oracle, PostgreSQL 등 지원).&lt;span&gt;&lt;/span&gt; @SequenceGenerator와 함께 사용하여 시퀀스 이름, 할당 크기 등을 지정할 수 있습니다.&lt;span&gt;&lt;/span&gt; Hibernate와 함께 사용할 때 JDBC 배치 처리에 유리하여 성능상 이점이 있을 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;155:5-155:109&quot;&gt;&lt;b&gt;GenerationType.TABLE&lt;/b&gt;: 키 생성 전용 테이블을 사용합니다. 모든 데이터베이스에서 동작하지만 성능 오버헤드가 있을 수 있어 권장되지 않습니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;156:5-156:129&quot;&gt;&lt;b&gt;GenerationType.AUTO&lt;/b&gt;: JPA 구현체(주로 Hibernate)가 데이터베이스 종류에 따라 IDENTITY, SEQUENCE, TABLE 중 적절한 전략을 자동으로 선택합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;157:5-157:83&quot;&gt;&lt;b&gt;GenerationType.UUID&lt;/b&gt;: UUID (Universally Unique Identifier)를 생성합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;158:1-158:218&quot;&gt;&lt;b&gt;@Column&lt;/b&gt;: 엔티티 필드와 매핑될 데이터베이스 컬럼의 상세 정보(이름 name, 길이 length, null 허용 여부 nullable, 유니크 여부 unique, 정밀도 precision, 스케일 scale 등)를 지정합니다.&lt;span&gt;&lt;/span&gt; 생략하면 필드 이름을 컬럼 이름으로 사용합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;159:1-159:88&quot;&gt;&lt;b&gt;@Transient&lt;/b&gt;: 해당 필드를 데이터베이스 컬럼에 매핑하지 않도록 지정합니다. 즉, 영속성 컨텍스트에서 관리되지 않습니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;160:1-161:0&quot;&gt;&lt;b&gt;기본 데이터 타입 매핑:&lt;/b&gt; String, Integer, Long, Double, Boolean 등 표준 Java 타입과 java.time 패키지의 날짜/시간 타입(LocalDate, LocalDateTime, Instant &lt;span&gt;&lt;/span&gt;)은 대부분 자동으로 적절한 데이터베이스 컬럼 타입으로 매핑됩니다. 레거시 java.util.Date 타입에는 @Temporal 어노테이션을 사용하여 DATE, TIME, TIMESTAMP 중 어떤 타입으로 매핑할지 지정할 수 있습니다.&lt;span&gt;&lt;/span&gt; Enum 타입은 @Enumerated를 사용하여 ORDINAL(순서, 기본값) 또는 STRING(이름)으로 저장 방식을 지정할 수 있습니다.&lt;span&gt;&lt;/span&gt; 큰 데이터(CLOB, BLOB)는 @Lob 어노테이션을 사용합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;엔티티 예시:&lt;/h4&gt;
&lt;pre id=&quot;code_1744941067880&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import jakarta.persistence.*;
import java.time.LocalDate;
import java.math.BigDecimal; // 가격에 BigDecimal 사용 권장

@Entity // 이 클래스가 JPA 엔티티임을 선언
@Table(name = &quot;products&quot;) // 데이터베이스 테이블 이름을 'products'로 지정
public class Product {

    @Id // 이 필드가 기본 키임을 나타냄
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = &quot;product_seq&quot;) // 시퀀스 전략 사용
    @SequenceGenerator(name = &quot;product_seq&quot;, sequenceName = &quot;product_id_seq&quot;, allocationSize = 1) // 시퀀스 생성기 설정
    private Long id;

    @Column(name = &quot;product_name&quot;, nullable = false, length = 100) // 컬럼명, not null, 길이 100 지정
    private String name;

    @Column(precision = 10, scale = 2) // 총 10자리, 소수점 이하 2자리 정밀도
    private BigDecimal price; // 가격은 부동소수점 오류 방지를 위해 BigDecimal 권장

    private LocalDate manufacturingDate; // LocalDate 타입은 자동으로 매핑됨

    @Transient // 이 필드는 데이터베이스에 저장되지 않음
    private String derivedInfo;

    // JPA는 기본 생성자를 요구함 (protected 권장)
    protected Product() {}

    // 인스턴스 생성을 위한 생성자
    public Product(String name, BigDecimal price, LocalDate manufacturingDate) {
        this.name = name;
        this.price = price;
        this.manufacturingDate = manufacturingDate;
    }

    // Getters and setters...
    // (Lombok @Getter, @Setter, @NoArgsConstructor, @AllArgsConstructor 사용 가능)

    public Long getId() { return id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public BigDecimal getPrice() { return price; }
    public void setPrice(BigDecimal price) { this.price = price; }
    public LocalDate getManufacturingDate() { return manufacturingDate; }
    public void setManufacturingDate(LocalDate manufacturingDate) { this.manufacturingDate = manufacturingDate; }
    public String getDerivedInfo() { return derivedInfo; }
    public void setDerivedInfo(String derivedInfo) { this.derivedInfo = derivedInfo; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관계 매핑&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티 간의 연관 관계(Association)를 매핑하는 방법을 알아봅니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;218:1-226:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;220:5-220:144&quot;&gt;&lt;b&gt;@OneToOne (일대일):&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;하나의 엔티티가 다른 엔티티 하나와 관계를 맺습니다 (예: User-Address). 매핑 전략으로는 외래 키(Foreign Key), 공유 기본 키(Shared Primary Key), 조인 테이블(Join Table) 방식이 있습니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;외래 키 방식&lt;/b&gt;: 한 테이블이 다른 테이블의 기본 키를 참조하는 외래 키 컬럼을 가집니다. 가장 일반적인 방식입니다. 외래 키를 가진 쪽(Owning Side)에 @JoinColumn을 사용하여 외래 키 컬럼을 명시하고, 반대쪽(Non-owning Side)에는 @OneToOne(mappedBy = &quot;...&quot;)을 사용하여 양방향 관계를 설정합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;220:5-220:144&quot;&gt;&lt;b&gt;공유 기본 키 방식:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;두 엔티티가 동일한 기본 키 값을 공유합니다. 한 엔티티의 기본 키가 다른 엔티티의 기본 키이자 외래 키 역할을 합니다. @MapsId와 @PrimaryKeyJoinColumn 어노테이션을 사용합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;220:5-220:144&quot;&gt;&lt;b&gt;조인 테이블 방식:&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;중간의 조인 테이블을 사용하여 관계를 관리합니다. @JoinTable 어노테이션을 사용하여 조인 테이블과 관련 컬럼들을 명시합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;222:1-224:191&quot;&gt;&lt;b&gt;@OneToMany / @ManyToOne (일대다 / 다대일):&lt;/b&gt; 가장 흔한 관계 유형입니다 (예: Department-Employees). 단방향 또는 양방향으로 매핑할 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;222:1-224:191&quot;&gt;&lt;b&gt;양방향 매핑 권장 사항:&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;@ManyToOne 쪽(자식, '다' 쪽)이 관계의 주인(Owner)이 되어 외래 키 컬럼을 관리하도록 설정하는 것이 가장 효율적입니다. @ManyToOne 어노테이션과 함께 @JoinColumn을 사용하여 외래 키 컬럼을 명시합니다. @OneToMany 쪽(부모, '일' 쪽)에는 mappedBy 속성을 사용하여 관계의 주인이 아님을 명시합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;224:5-224:191&quot;&gt;&lt;b&gt;양방향 관계 동기화:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;양방향 관계에서는 양쪽의 상태를 일관성 있게 유지하는 것이 중요합니다. 부모 엔티티에서 자식 엔티티를 추가하거나 제거할 때, 자식 엔티티의 부모 참조도 함께 설정/해제하는 유틸리티 메서드(예: addEmployee, removeEmployee)를 부모 엔티티에 구현하는 것이 좋습니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;225:1-226:0&quot;&gt;&lt;b&gt;@ManyToMany (다대다):&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;양쪽 엔티티 모두 여러 개의 상대 엔티티와 관계를 맺습니다 (예: Student-Course). 일반적으로 중간에 관계 테이블(Join Table)이 필요하며, @JoinTable 어노테이션을 사용하여 매핑합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;만약 관계 테이블에 추가적인 컬럼(예: 등록일)이 있다면, 관계 테이블 자체를 별도의 엔티티로 만들고 두 개의 @ManyToOne 관계로 매핑하는 것이 더 유연할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Fetch Types (데이터 로딩 전략):&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연관된 엔티티를 언제 데이터베이스에서 로드할지 결정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;FetchType.LAZY (지연 로딩)&lt;/b&gt;: 연관된 엔티티는 실제로 접근(getter 호출 등) 될 때 데이터베이스에서 로드됩니다. 컬렉션(@OneToMany, @ManyToMany)의 기본값입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;FetchType.EAGER (즉시 로딩)&lt;/b&gt;: 주 엔티티를 로드할 때 연관된 엔티티도 함께 데이터베이스에서 로드됩니다. 단일 연관 관계(@ManyToONe, @OneToOne)의 기본값 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시 로딩(Eager fetching)은 연관된 엔티티가 많거나 깊은 관계 그래프를 가질 경우, 필요하지 않은 데이터까지 모두 조회하여 성능 문제를 일으키는 N+1 쿼리 문제 등을 유발하기 쉽습니다.&lt;span&gt;&lt;/span&gt; 따라서 &lt;b&gt;지연 로딩(FetchType.LAZY)을 기본 전략으로 사용&lt;/b&gt;하는 것이 일반적으로 권장됩니다.&lt;span&gt;&lt;/span&gt; 필요한 경우, JPQL의 JOIN FETCH나 JPA Entity Graph 기능을 사용하여 특정 쿼리에서 필요한 연관 엔티티만 명시적으로 함께 로드하는 것이 성능 관리에 유리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cascade Types (영속성 전이):&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 엔티티에 대한 영속성 작업(저장, 수정, 삭제 등)이 자식 엔티티에게 어떻게 전파될지를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;240:1-240:81&quot;&gt;&lt;b&gt;CascadeType.ALL&lt;/b&gt;: 모든 영속성 작업(PERSIST, MERGE, REMOVE, REFRESH, DETACH)을 전파합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;241:1-241:56&quot;&gt;&lt;b&gt;CascadeType.PERSIST&lt;/b&gt;: 부모 엔티티 저장 시 자식 엔티티도 함께 저장됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;242:1-242:54&quot;&gt;&lt;b&gt;CascadeType.MERGE&lt;/b&gt;: 부모 엔티티 병합 시 자식 엔티티도 함께 병합됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;243:1-243:55&quot;&gt;&lt;b&gt;CascadeType.REMOVE&lt;/b&gt;: 부모 엔티티 삭제 시 자식 엔티티도 함께 삭제됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;244:1-245:0&quot;&gt;&lt;b&gt;orphanRemoval = true&lt;/b&gt;: @OneToMany 관계에서 사용되며, 부모 엔티티의 컬렉션에서 자식 엔티티가 제거되면 해당 자식 엔티티를 데이터베이스에서도 삭제합니다. CascadeType.REMOVE와 유사하지만, 참조가 끊어졌을 때 동작한다는 차이가 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cascade 옵션은 편리하지만, 특히 REMOVE나 ALL은 의도치 않은 데이터 삭제를 유발할 수 있으므로 신중하게 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;양방향 @OneToMany / @ManyToOne 예시:&lt;/h4&gt;
&lt;pre id=&quot;code_1744941914445&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 부모 엔티티 (예: Department)
@Entity
public class Department {
    @Id @GeneratedValue
    private Long id;
    private String name;

    // 'mappedBy'는 Employee 엔티티의 'department' 필드를 가리킴
    // cascade = CascadeType.ALL: Department 저장/수정/삭제 시 Employee도 영향 받음
    // orphanRemoval = true: Department의 employees 컬렉션에서 Employee 제거 시 DB에서도 삭제
    // fetch = FetchType.LAZY: employees는 필요할 때 로드 (권장)
    @OneToMany(mappedBy = &quot;department&quot;, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    private List&amp;lt;Employee&amp;gt; employees = new ArrayList&amp;lt;&amp;gt;();

    // 양방향 관계 동기화를 위한 유틸리티 메서드
    public void addEmployee(Employee employee) {
        employees.add(employee);
        employee.setDepartment(this); // Employee 쪽의 참조도 설정
    }

    public void removeEmployee(Employee employee) {
        employees.remove(employee);
        employee.setDepartment(null); // Employee 쪽의 참조도 제거
    }

    // 기본 생성자, 다른 getter/setter...
    protected Department() {}
    public Department(String name) { this.name = name; }
    public Long getId() { return id; }
    public String getName() { return name; }
    public List&amp;lt;Employee&amp;gt; getEmployees() { return employees; }
}

// 자식 엔티티 (예: Employee)
@Entity
public class Employee {
    @Id @GeneratedValue
    private Long id;
    private String name;

    // 관계의 주인 (Owning side)
    // fetch = FetchType.LAZY: Department는 필요할 때 로드 (ManyToOne 기본은 EAGER이나 LAZY 권장)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;department_id&quot;) // Employee 테이블에 생성될 외래 키 컬럼명
    private Department department;

    // 기본 생성자, getter/setter...
    protected Employee() {}
    public Employee(String name) { this.name = name; }
    public Long getId() { return id; }
    public String getName() { return name; }
    public Department getDepartment() { return department; }
    public void setDepartment(Department department) { this.department = department; }

    // 양방향 관계 및 컬렉션 사용 시 equals() 및 hashCode() 재정의 권장 (ID 기반) [71]
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Employee )) return false;
        Employee employee = (Employee) o;
        // 아직 영속화되지 않은 엔티티는 ID가 null일 수 있으므로 주의
        return id!= null &amp;amp;&amp;amp; id.equals(employee.getId());
    }

    @Override
    public int hashCode() {
        // ID 기반 해시코드 또는 클래스 기반 기본 해시코드
        return getClass().hashCode();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;리포지토리를 이용한 데이터 접근: CRUD 작업&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA의 핵심은 리포지토리 인터페이스를 통해 데이터 접근 로직을 추상화하는 것입니다. 개발자는 인터페이스만 정의하면, Spring이 런타임에 해당 인터페이스의 구현체를 자동으로 생성해줍니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;리포지토리 인터페이스 정의&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 접근을 위한 리포지토리 인터페이스는 Spring Data가 제공하는 기본 인터페이스(CrudRepository, PagingAndSortingRepository, JpaRepository 등) 중 하나를 확장하여 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744942059816&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import java.util.List;
import java.time.LocalDate;

@Repository // Spring 컴포넌트 스캔 대상으로 지정 (선택 사항이지만 권장)
public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt; {
    // 여기에 커스텀 쿼리 메서드를 추가할 수 있습니다.
    // 예: 이름으로 상품 찾기 (대소문자 무시)
    List&amp;lt;Product&amp;gt; findByNameIgnoreCase(String name);

    // 예: 특정 제조일 이후 상품 찾기
    List&amp;lt;Product&amp;gt; findByManufacturingDateAfter(LocalDate date);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서 ProductRepository는 JpaRepository를 확장하여 Product 엔티티(ID 타입은 Long)에 대한 모든 기본적인 CRUD, 페이징, 정렬 및 JPA 관련 메서드를 상속받습니다. @Repository 어노테이션은 이 인터페이스가 데이터 접근 계층의 컴포넌트임을 명시적으로 나타내며, 예외 변환 등의 추가적인 기능을 활성화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 CRUD 작업 수행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의된 리포지토리 인터페이스는 서비스 계층이나 테스트 코드에서 @Autowired 등을 통해 주입받아 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;생성(Create) 및 수정(Update): save()&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;save() 메서드는 새로운 엔티티를 데이터베이스에 저장하거나 기존 엔티티의 상태를 업데이트합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744942182734&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Autowired
ProductRepository repository;

// 새로운 Product 생성 및 저장
Product newProduct = new Product(&quot;Smart TV&quot;, BigDecimal.valueOf(1500.00), LocalDate.now());
Product savedProduct = repository.save(newProduct); // 저장된 엔티티 반환 (ID 포함)
System.out.println(&quot;Saved Product ID: &quot; + savedProduct.getId());

// 기존 Product 수정
Optional&amp;lt;Product&amp;gt; optionalProduct = repository.findById(savedProduct.getId());
if (optionalProduct.isPresent()) {
    Product existingProduct = optionalProduct.get();
    existingProduct.setPrice(BigDecimal.valueOf(1450.00));
    repository.save(existingProduct); // ID가 존재하므로 UPDATE 실행
    System.out.println(&quot;Product updated.&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA의 save() 메서드는 내부적으로 엔티티의 ID 존재 여부(entityInformation.isNew(entity))를 확인하여 EntityManager.persist() (ID가 없을 때, 새로운 엔티티) 또는 EntityManager.merge() (ID가 있을 때, 기존 엔티티 또는 detached 엔티티)를 호출하는 &quot;upsert&quot; 방식으로 동작합니다.&lt;span&gt;&lt;/span&gt; merge()는 데이터베이스에 해당 ID의 레코드가 있는지 확인하기 위해 SELECT 쿼리를 먼저 실행할 수 있으며, 이는 특히 ID를 직접 할당하는 경우나 대량 작업 시 성능에 영향을 줄 수 있습니다.&lt;span&gt;&lt;/span&gt; 또한, 트랜잭션 내에서 이미 영속 상태(managed)인 엔티티의 필드를 변경한 경우, 명시적으로 save()를 호출하지 않아도 트랜잭션 커밋 시점에 Hibernate의 변경 감지(dirty checking) 메커니즘에 의해 자동으로 UPDATE 쿼리가 실행됩니다. 따라서 이러한 경우 save() 호출은 불필요할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;조회(Read): findById(), findAll()&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;findById()는 ID를 기준으로 특정 엔티티를 조회하며, Optional&amp;lt;T&amp;gt;을 반환하여 결과가 없을 경우를 안전하게 처리합니다.&lt;span&gt;&lt;/span&gt; findAll()은 해당 타입의 모든 엔티티를 조회합니다 (JpaRepository는 List&amp;lt;T&amp;gt;를 반환).&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744942266023&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ID로 조회
Long productIdToFind = 1L;
Optional&amp;lt;Product&amp;gt; foundProduct = repository.findById(productIdToFind);
foundProduct.ifPresentOrElse(
    product -&amp;gt; System.out.println(&quot;Found Product: &quot; + product.getName()),
    () -&amp;gt; System.out.println(&quot;Product with ID &quot; + productIdToFind + &quot; not found.&quot;)
);

// 전체 조회 (주의: 테이블 크기가 클 경우 성능 문제 발생 가능)
List&amp;lt;Product&amp;gt; allProducts = repository.findAll();
System.out.println(&quot;Total products found: &quot; + allProducts.size());
allProducts.forEach(p -&amp;gt; System.out.println(&quot; - &quot; + p.getName()));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블에 데이터가 많을 경우 findAll() 은 성능 문제를 야기할 수 있으므로, 페이징 처리를 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;삭제(Delete): deleteById(), delete()&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;deleteById()는 ID를 기준으로 엔티티를 삭제하고, delete()는 엔티티 인스턴스를 받아 삭제합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744942386757&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ID로 삭제
Long productIdToDelete = 1L;
if (repository.existsById(productIdToDelete)) { // 삭제 전 존재 여부 확인 가능
    repository.deleteById(productIdToDelete);
    System.out.println(&quot;Product with ID &quot; + productIdToDelete + &quot; deleted.&quot;);
}

// 엔티티 인스턴스로 삭제
Optional&amp;lt;Product&amp;gt; productToDeleteOpt = repository.findById(2L);
productToDeleteOpt.ifPresent(product -&amp;gt; {
    repository.delete(product);
    System.out.println(&quot;Product '&quot; + product.getName() + &quot;' deleted.&quot;);
});

// 전체 삭제 (주의!)
// repository.deleteAll();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 연관 관계나 Cascade 설정이 있는 경우, 단순 delete() 호출이 예상대로 동작하지 않을 수 있습니다.&lt;span&gt;&lt;/span&gt; 예를 들어, 관계의 무결성 제약 조건 위반이나 잘못된 Cascade 설정으로 인해 삭제가 실패하거나, 양방향 관계에서 반대쪽 참조를 제거하지 않아 문제가 발생할 수 있습니다. 이런 경우에는 관계의 주인을 명확히 하고 영속성 전이 설정을 확인하거나, @Modifying 어노테이션을 사용한 커스텀 삭제 쿼리를 고려해야 할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;효과적인 데이터 쿼리 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA 는 기본적인 CRUD 외에도 데이터를 조회하는 다양한 방법을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파생 쿼리 (Derived Queries)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 이름을 분석하여 JPQL 쿼리를 자동으로 생성하는 기능입니다.&lt;span&gt;&lt;/span&gt; &lt;b&gt;find...By, read...By, get...By, query...By, count...By&lt;/b&gt; 등의 접두사로 시작하고, 엔티티 속성 이름과 조건 키워드를 조합하여 메서드 이름을 작성합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;430:1-441:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;430:1-430:84&quot;&gt;&lt;b&gt;구조:&lt;/b&gt; &amp;lt;접두사&amp;gt;By&amp;lt;속성명&amp;gt;&amp;lt;조건키워드&amp;gt;[&amp;lt;논리연산자&amp;gt;&amp;lt;속성명&amp;gt;&amp;lt;조건키워드&amp;gt;...] &lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;431:1-431:177&quot;&gt;&lt;b&gt;속성 표현식:&lt;/b&gt; 점(.)이나 언더스코어(_)를 사용하여 연관된 엔티티의 속성에 접근할 수 있습니다 (예: findByAddressCity(...) 또는 findByAddress_City(...)).&lt;span&gt;&lt;/span&gt; Spring Data는 이름 충돌이 발생할 경우 해결 규칙을 따릅니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;432:1-441:0&quot;&gt;&lt;b&gt;주요 키워드:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;433:5-441:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;433:5-433:53&quot;&gt;&lt;b&gt;기본 조건:&lt;/b&gt; And, Or, Is, Equals, Not&lt;/li&gt;
&lt;li data-sourcepos=&quot;434:5-434:108&quot;&gt;&lt;b&gt;비교:&lt;/b&gt; Between, LessThan, GreaterThan, LessThanEqual, GreaterThanEqual, After, Before&lt;/li&gt;
&lt;li data-sourcepos=&quot;435:5-435:65&quot;&gt;&lt;b&gt;Null 확인:&lt;/b&gt; IsNull, IsNotNull (또는 Null, NotNull)&lt;/li&gt;
&lt;li data-sourcepos=&quot;436:5-436:80&quot;&gt;&lt;b&gt;패턴 매칭:&lt;/b&gt; Like, NotLike, StartingWith, EndingWith, Containing&lt;/li&gt;
&lt;li data-sourcepos=&quot;437:5-437:33&quot;&gt;&lt;b&gt;컬렉션 조건:&lt;/b&gt; In, NotIn&lt;/li&gt;
&lt;li data-sourcepos=&quot;438:5-438:36&quot;&gt;&lt;b&gt;Boolean:&lt;/b&gt; True, False&lt;/li&gt;
&lt;li data-sourcepos=&quot;439:5-439:97&quot;&gt;&lt;b&gt;수식어:&lt;/b&gt; IgnoreCase (문자열 비교 시 대소문자 무시), OrderBy...Asc/Desc (정렬), Distinct (중복 제거)&lt;/li&gt;
&lt;li data-sourcepos=&quot;440:5-441:0&quot;&gt;&lt;b&gt;결과 제한:&lt;/b&gt; findFirst..., findTop...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;파생 쿼리 키워드 예시:&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 352px;&quot; border=&quot;1&quot; data-sourcepos=&quot;444:1-463:103&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;키워드&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;샘플 메서드&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;JPQL 조각 (예시)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;446:1-446:89&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;446:1-446:7&quot;&gt;And&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;446:9-446:38&quot;&gt;findByLastnameAndFirstname&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;446:40-446:87&quot;&gt;... where x.lastname =?1 and x.firstname =?2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;447:1-447:86&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;447:1-447:6&quot;&gt;Or&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;447:8-447:36&quot;&gt;findByLastnameOrFirstname&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;447:38-447:84&quot;&gt;... where x.lastname =?1 or x.firstname =?2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;448:1-448:108&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;448:1-448:16&quot;&gt;Is, Equals&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;448:18-448:57&quot;&gt;findByFirstname, findByFirstnameIs&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;448:59-448:106&quot;&gt;... where x.firstname =?1 (null이면 IS NULL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;449:1-449:82&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;449:1-449:11&quot;&gt;Between&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;449:13-449:38&quot;&gt;findByStartDateBetween&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;449:40-449:80&quot;&gt;... where x.startDate between?1 and?2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;450:1-450:63&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;450:1-450:12&quot;&gt;LessThan&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;450:14-450:34&quot;&gt;findByAgeLessThan&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;450:36-450:61&quot;&gt;... where x.age &amp;lt;?1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;451:1-451:77&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;451:1-451:20&quot;&gt;GreaterThanEqual&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;451:22-451:50&quot;&gt;findByAgeGreaterThanEqual&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;451:52-451:75&quot;&gt;... where x.age &amp;gt;=?1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;452:1-452:66&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;452:1-452:9&quot;&gt;After&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;452:11-452:34&quot;&gt;findByStartDateAfter&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;452:36-452:64&quot;&gt;... where x.startDate &amp;gt;?1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;453:1-453:60&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;453:1-453:10&quot;&gt;IsNull&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;453:12-453:30&quot;&gt;findByAgeIsNull&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;453:32-453:58&quot;&gt;... where x.age is null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;454:1-454:67&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;454:1-454:8&quot;&gt;Like&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;454:10-454:32&quot;&gt;findByFirstnameLike&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;454:34-454:65&quot;&gt;... where x.firstname like?1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;455:1-455:100&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;455:1-455:16&quot;&gt;StartingWith&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;455:18-455:48&quot;&gt;findByFirstnameStartingWith&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;455:50-455:98&quot;&gt;... where x.firstname like?1 (?1 뒤에 % 추가)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;456:1-456:96&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;456:1-456:14&quot;&gt;EndingWith&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;456:16-456:44&quot;&gt;findByFirstnameEndingWith&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;456:46-456:94&quot;&gt;... where x.firstname like?1 (?1 앞에 % 추가)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot; data-sourcepos=&quot;457:1-457:97&quot;&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;457:1-457:14&quot;&gt;Containing&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;457:16-457:44&quot;&gt;findByFirstnameContaining&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot; data-sourcepos=&quot;457:46-457:95&quot;&gt;... where x.firstname like?1 (?1 양쪽에 % 추가)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;458:1-458:102&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;458:1-458:18&quot;&gt;OrderBy...Desc&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;458:20-458:51&quot;&gt;findByAgeOrderByLastnameDesc&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;458:53-458:100&quot;&gt;... where x.age =?1 order by x.lastname desc&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;459:1-459:64&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;459:1-459:7&quot;&gt;Not&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;459:9-459:29&quot;&gt;findByLastnameNot&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;459:31-459:62&quot;&gt;... where x.lastname &amp;lt;&amp;gt;?1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;460:1-460:74&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;460:1-460:6&quot;&gt;In&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;460:8-460:47&quot;&gt;findByAgeIn(Collection&amp;lt;Age&amp;gt; ages)&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;460:49-460:72&quot;&gt;... where x.age in?1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;461:1-461:63&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;461:1-461:8&quot;&gt;True&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;461:10-461:31&quot;&gt;findByActiveTrue()&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;461:33-461:61&quot;&gt;... where x.active = true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;462:1-462:91&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;462:1-462:14&quot;&gt;IgnoreCase&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;462:16-462:44&quot;&gt;findByFirstnameIgnoreCase&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;462:46-462:89&quot;&gt;... where UPPER(x.firstname) = UPPER(?1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-sourcepos=&quot;463:1-463:103&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;463:1-463:12&quot;&gt;Distinct&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;463:14-463:51&quot;&gt;findDistinctByLastnameAndFirstname&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-sourcepos=&quot;463:53-463:101&quot;&gt;select distinct &amp;hellip; where x.lastname =?1 and...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;예시:&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1744942668271&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt; {
    // 이름으로 상품 찾기 (대소문자 무시)
    List&amp;lt;Product&amp;gt; findByNameIgnoreCase(String name);

    // 최소, 최대 가격 사이의 상품을 이름 내림차순으로 정렬하여 찾기
    List&amp;lt;Product&amp;gt; findByPriceBetweenOrderByNameDesc(BigDecimal minPrice, BigDecimal maxPrice);

    // 특정 제조일 이후 생산된 상위 5개 상품 찾기
    List&amp;lt;Product&amp;gt; findTop5ByManufacturingDateAfter(LocalDate date);

    // 이름에 특정 문자열을 포함하는 상품 수 세기
    long countByNameContaining(String searchTerm);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JPQL 을 이용한 커스텀 쿼리 (@Query)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파생 쿼리만으로 표현하기 어렵거나 복잡한 쿼리는 JPQL(Java Persistence Query Language)을 사용하여 직접 작성할 수 있습니다.&lt;span&gt;&lt;/span&gt; JPQL은 SQL과 유사하지만 테이블과 컬럼 대신 엔티티와 속성을 대상으로 쿼리합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;487:1-511:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;487:1-487:104&quot;&gt;&lt;b&gt;사용법:&lt;/b&gt; 리포지토리 메서드에 @Query 어노테이션을 추가하고 value 속성에 JPQL 쿼리 문자열을 작성합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;488:1-498:11&quot;&gt;&lt;b&gt;파라미터 바인딩:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;489:5-498:11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;489:5-493:11&quot;&gt;&lt;b&gt;위치 기반 파라미터:&lt;/b&gt; ?1, ?2 와 같이 1부터 시작하는 인덱스를 사용하며, 메서드 파라미터 순서대로 바인딩됩니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;489:5-493:11&quot;&gt;&lt;b&gt;이름 기반 파라미터:&lt;/b&gt; :paramName 형식을 사용하며, 메서드 파라미터에 @Param(&quot;paramName&quot;) 어노테이션을 붙여 바인딩합니다. 가독성이 높아 권장됩니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744942736118&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 위치 기반 파라미터 예시
@Query(&quot;SELECT p FROM Product p WHERE p.price &amp;gt;?1 AND p.manufacturingDate &amp;lt;?2&quot;)
List&amp;lt;Product&amp;gt; findByPriceGreaterThanAndDateBefore(BigDecimal minPrice, LocalDate maxDate);

// 이름 기반 파라미터 예시
@Query(&quot;SELECT p FROM Product p WHERE p.name = :name AND p.price &amp;lt; :maxPrice&quot;)
List&amp;lt;Product&amp;gt; findByNameAndPriceLessThan(@Param(&quot;name&quot;) String productName, @Param(&quot;maxPrice&quot;) BigDecimal priceLimit);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;수정 쿼리 (@Modifying):&lt;/b&gt; UPDATE 또는 DELETE JPQL 쿼리를 실행하려면 @Modifying 어노테이션을 함께 사용해야 합니다.&lt;span&gt;&lt;/span&gt; 이 메서드는 보통 int (영향받은 행 수) 또는 void를 반환하며, 트랜잭션 컨텍스트 내에서 실행되어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744942826944&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Modifying
@Transactional // 서비스 계층 또는 여기에 @Transactional 추가 필요
@Query(&quot;UPDATE Product p SET p.price = p.price * :discountRate WHERE p.manufacturingDate &amp;lt; :cutoffDate&quot;)
int applyDiscountForOldProducts(@Param(&quot;discountRate&quot;) BigDecimal rate, @Param(&quot;cutoffDate&quot;) LocalDate date);

@Modifying
@Transactional
@Query(&quot;DELETE FROM Product p WHERE p.id = :productId&quot;)
void deleteProductByIdCustom(@Param(&quot;productId&quot;) Long id);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네이티브 SQL 쿼리 (@Query(nativeQuery = true))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 고유의 기능을 사용하거나 JPQL로 표현하기 매우 복잡한 쿼리는 네이티브 SQL을 직접 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;516:1-529:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;516:1-516:106&quot;&gt;&lt;b&gt;사용법:&lt;/b&gt; @Query 어노테이션에 nativeQuery = true 속성을 추가하고 value 속성에 네이티브 SQL 쿼리 문자열을 작성합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;517:1-522:7&quot;&gt;&lt;b&gt;파라미터 바인딩:&lt;/b&gt; JPQL과 동일하게 위치 기반(?1) 또는 이름 기반(:paramName + @Param) 파라미터를 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744942879015&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MySQL의 FIND_IN_SET 함수 사용 예시
@Query(value = &quot;SELECT * FROM products p WHERE FIND_IN_SET(p.id, :ids) &amp;gt; 0&quot;, nativeQuery = true)
List&amp;lt;Product&amp;gt; findProductsByIdsNative(@Param(&quot;ids&quot;) String idList); // idList는 &quot;1,2,3&quot; 형태의 문자열&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;516:1-529:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;523:1-524:0&quot;&gt;&lt;b&gt;수정 쿼리 (@Modifying):&lt;/b&gt; 네이티브 SQL을 이용한 INSERT, UPDATE, DELETE 문에도 @Modifying 어노테이션을 사용합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;525:1-529:0&quot;&gt;&lt;b&gt;제한 사항:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;526:5-529:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;526:5-526:75&quot;&gt;&lt;b&gt;데이터베이스 종속성:&lt;/b&gt; 작성된 SQL은 특정 데이터베이스 벤더에 종속되어 이식성이 떨어집니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;527:5-527:131&quot;&gt;&lt;b&gt;페이징:&lt;/b&gt; Pageable 파라미터를 사용한 자동 페이징을 위해서는 별도의 countQuery 속성을 통해 전체 개수를 반환하는 네이티브 SQL 카운트 쿼리를 명시적으로 제공해야 합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;528:5-529:0&quot;&gt;&lt;b&gt;동적 정렬:&lt;/b&gt; Sort 파라미터를 이용한 동적 정렬 기능은 네이티브 쿼리에서 지원되지 않습니다.&lt;span&gt;&lt;/span&gt; 정렬이 필요하면 SQL 쿼리 내에 ORDER BY 절을 직접 포함해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;네이티브 쿼리는 강력한 기능을 제공하지만, JPA의 추상화 이점을 활용하지 못하고 특정 데이터베이스에 종속되며 페이징/정렬에 제약이 따릅니다. 따라서 JPQL이나 파생 쿼리로 해결할 수 없는 경우에 한해 신중하게 사용해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Criteria API 소개&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Criteria API는 JPQL 문자열 대신 Java 코드를 사용하여 프로그래밍 방식으로 쿼리를 구축하는 타입-세이프(type-safe)한 방법입니다.&lt;span&gt;&lt;/span&gt; 동적 쿼리(조건이 런타임에 변경되는 쿼리)를 구축하는 데 유용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;536:1-536:91&quot;&gt;&lt;b&gt;주요 인터페이스/클래스:&lt;/b&gt; CriteriaBuilder, CriteriaQuery, Root, Predicate.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;537:1-586:0&quot;&gt;&lt;b&gt;Spring Data JPA 통합:&lt;/b&gt; JpaSpecificationExecutor&amp;lt;T&amp;gt; 인터페이스를 리포지토리에 추가하고 Specification&amp;lt;T&amp;gt; 인터페이스를 구현하여 Criteria API 기반의 조건을 정의하고 조합할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744942985734&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. Repository에 JpaSpecificationExecutor 확장
public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt;, JpaSpecificationExecutor&amp;lt;Product&amp;gt; {}

// 2. Specification 정의 (별도 클래스 또는 람다 사용)
public class ProductSpecifications {
    public static Specification&amp;lt;Product&amp;gt; hasName(String name) {
        // root: 쿼리 대상 엔티티(Product)
        // query: 쿼리 자체 (여기서는 사용 안 함)
        // criteriaBuilder: 조건(Predicate) 생성 도구
        return (root, query, criteriaBuilder) -&amp;gt;
            criteriaBuilder.equal(root.get(&quot;name&quot;), name); // Product 엔티티의 'name' 속성 사용
    }

    public static Specification&amp;lt;Product&amp;gt; priceGreaterThan(BigDecimal price) {
        return (root, query, criteriaBuilder) -&amp;gt;
            criteriaBuilder.greaterThan(root.get(&quot;price&quot;), price); // 'price' 속성 사용
    }

    public static Specification&amp;lt;Product&amp;gt; manufacturedBefore(LocalDate date) {
         return (root, query, criteriaBuilder) -&amp;gt;
            criteriaBuilder.lessThan(root.get(&quot;manufacturingDate&quot;), date); // 'manufacturingDate' 속성 사용
    }
}

// 3. Service에서 Specification 사용
@Service
public class ProductSearchService {
    @Autowired ProductRepository repository;

    public List&amp;lt;Product&amp;gt; findProductsDynamically(String name, BigDecimal minPrice, LocalDate maxDate) {
        Specification&amp;lt;Product&amp;gt; spec = Specification.where(null); // 초기 Specification

        if (name!= null &amp;amp;&amp;amp;!name.isEmpty()) {
            spec = spec.and(ProductSpecifications.hasName(name));
        }
        if (minPrice!= null) {
            spec = spec.and(ProductSpecifications.priceGreaterThan(minPrice));
        }
        if (maxDate!= null) {
            spec = spec.and(ProductSpecifications.manufacturedBefore(maxDate));
        }

        return repository.findAll(spec); // Specification을 사용하여 쿼리 실행
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Criteria API 는 타입 안정성과 동적 쿼리 구성 능력을 제공하지만, 코드가 JPQL 이나 파생 쿼리에 비해 장황해질 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;고급 기능 및 모범 사례&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA는 기본적인 CRUD와 쿼리 기능 외에도 다양한 고급 기능을 제공하여 개발 생산성을 높여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;페이징 및 정렬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량 데이터를 효율적으로 처리하고 사용자 인터페이스에 표시하기 위해 페이징과 정렬 기능은 필수적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;597:1-601:127&quot;&gt;&lt;b&gt;핵심 인터페이스:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;598:5-601:127&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;598:5-598:93&quot;&gt;&lt;b&gt;Pageable&lt;/b&gt;: 페이징 정보(페이지 번호, 페이지 크기)와 정렬 정보를 함께 담는 인터페이스입니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;599:5-599:88&quot;&gt;&lt;b&gt;Sort&lt;/b&gt;: 정렬 기준(속성명, 정렬 방향 - 오름차순/내림차순)을 정의하는 인터페이스입니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;600:5-600:171&quot;&gt;&lt;b&gt;Page&amp;lt;T&amp;gt;&lt;/b&gt;: 페이징된 데이터 조각(현재 페이지의 엔티티 목록)과 함께 전체 데이터 개수, 전체 페이지 수 등의 추가 정보를 포함하는 인터페이스입니다.&lt;span&gt;&lt;/span&gt; Page 객체를 얻기 위해서는 추가적인 count 쿼리가 실행될 수 있습니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;601:5-601:127&quot;&gt;&lt;b&gt;Slice&amp;lt;T&amp;gt;&lt;/b&gt;: Page&amp;lt;T&amp;gt;와 유사하지만 전체 개수 정보를 포함하지 않고, 다음 페이지 존재 여부만 알려줍니다. Count 쿼리 오버헤드를 피하고 싶을 때 사용합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;602:1-606:0&quot;&gt;&lt;b&gt;사용법:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;603:5-606:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;603:5-603:113&quot;&gt;리포지토리 메서드(기본 findAll 또는 커스텀 메서드)의 파라미터로 Pageable 또는 Sort 객체를 전달합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;604:5-604:129&quot;&gt;PageRequest.of(int page, int size, Sort sort)를 사용하여 Pageable 객체를 생성합니다. 페이지 번호는 0부터 시작합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;605:5-606:0&quot;&gt;Sort.by(&quot;propertyName&quot;).ascending() 또는 .descending()을 사용하여 Sort 객체를 생성합니다. 여러 속성을 기준으로 정렬하려면 .and()를 사용하여 연결합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시:&lt;/h4&gt;
&lt;pre id=&quot;code_1744943281302&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class ProductPagingService {
    @Autowired ProductRepository repository;

    public Page&amp;lt;Product&amp;gt; findProductsPaginatedAndSorted(int pageNum, int pageSize, String sortBy, String sortDir) {
        // 정렬 방향 결정
        Sort.Direction direction = sortDir.equalsIgnoreCase(&quot;desc&quot;)? Sort.Direction.DESC : Sort.Direction.ASC;
        // 정렬 객체 생성 (예: 'price' 필드 기준)
        Sort sort = Sort.by(direction, sortBy);

        // Pageable 객체 생성 (페이지 번호는 0부터 시작)
        Pageable pageable = PageRequest.of(pageNum, pageSize, sort);

        // 페이징 및 정렬 적용하여 조회
        Page&amp;lt;Product&amp;gt; productPage = repository.findAll(pageable);

        // 결과 사용
        System.out.println(&quot;Total Elements: &quot; + productPage.getTotalElements());
        System.out.println(&quot;Total Pages: &quot; + productPage.getTotalPages());
        System.out.println(&quot;Products on Page &quot; + pageNum + &quot;:&quot;);
        productPage.getContent().forEach(p -&amp;gt; System.out.println(&quot; - &quot; + p.getName() + &quot; (&quot; + p.getPrice() + &quot;)&quot;));

        return productPage;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring MVC 환경에서는 Spring Data Web 지원을 통해 컨트롤러 메서드의 파라미터로 Pageable 또는 Sort를 선언하면, HTTP 요청 파라미터(예: page, size, sort=property,direction)로부터 자동으로 해당 객체를 생성하여 주입받을 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝션 (Projections)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에서 엔티티의 전체 필드가 아닌 필요한 일부 필드만 선택적으로 조회하는 기능입니다.&lt;span&gt;&lt;/span&gt; 이는 네트워크 트래픽을 줄이고, 애플리케이션 메모리 사용량을 최적화하며, 성능을 향상시키는 데 도움이 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;643:1-721:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;643:1-674:0&quot;&gt;&lt;b&gt;인터페이스 기반 프로젝션 (Interface-based Projections):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;644:5-648:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;644:5-644:152&quot;&gt;조회하려는 속성에 대한 getter 메서드만 포함하는 인터페이스를 정의합니다.&lt;span&gt;&lt;/span&gt; Spring Data JPA는 이 인터페이스의 프록시 구현체를 런타임에 생성합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;645:5-645:124&quot;&gt;&lt;b&gt;Closed Projections:&lt;/b&gt; 인터페이스의 getter 메서드 이름이 엔티티 속성 이름과 정확히 일치하는 경우입니다. JPA는 필요한 컬럼만 선택하는 최적화된 쿼리를 생성할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;646:5-646:227&quot;&gt;&lt;b&gt;Open Projections:&lt;/b&gt; @Value 어노테이션(SpEL 표현식 사용)이나 default 메서드를 사용하여 계산된 값을 반환하는 메서드를 인터페이스에 정의할 수 있습니다.&lt;span&gt;&lt;/span&gt; 이 경우 Spring Data는 전체 엔티티를 로드한 후 값을 계산해야 할 수 있어 쿼리 최적화가 제한될 수 있으므로 주의해야 합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;647:5-648:0&quot;&gt;중첩 프로젝션(Nested Projections)도 가능합니다. 즉, 프로젝션 인터페이스의 getter가 다른 프로젝션 인터페이스 타입을 반환할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744943373357&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 프로젝션 인터페이스 정의
public interface ProductNameAndPrice {
    String getName(); // Closed projection getter
    BigDecimal getPrice(); // Closed projection getter

    // Open projection getter (SpEL 사용)
    @Value(&quot;#{target.name + ' - Price: $' + target.price}&quot;)
    String getDisplayInfo();

    // 중첩 프로젝션 예시 (Address 엔티티가 있다고 가정)
    // AddressSummary getAddress();
}
/*
public interface AddressSummary {
    String getCity();
    String getZipCode();
}
*/

// 리포지토리 메서드에서 프로젝션 인터페이스 사용
public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt; {
    List&amp;lt;ProductNameAndPrice&amp;gt; findByManufacturingDateBefore(LocalDate date);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp;클래스 기반 프로젝션 (DTOs - Data Transfer Objects):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조회하려는 속성을 필드로 가지고, 해당 속성들을 초기화하는 생성자를 가진 클래스(DTO)를 정의합니다. 생성자의 파라미터 이름이 엔티티 속성 이름과 일치해야 합니다. Java Record는 불변 DTO를 정의하는 데 이상적입니다.&lt;/li&gt;
&lt;li&gt;인터페이스 기반과 달리 프록시를 사용하지 않으며, DTO 구조를 통한 직접적인 중첩 프로젝션은 지원하지 않습니다 (쿼리에서 중첩 구조를 만들어야 함).&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;파생 쿼리에서 DTO 타입을 반환 타입으로 지정하면 Spring Data JPA가 자동으로 생성자 표현식(Constructor Expression)을 사용하는 JPQL 쿼리를 생성합니다.&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; @Query를 사용할 경우, JPQL 쿼리 내에서 SELECT new com.example.YourDto(p.name, p.price)...와 같이 생성자 표현식을 명시적으로 사용해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744943784795&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// DTO 클래스 정의 (Java Record 사용)
public record ProductSummary(String name, BigDecimal price) {}

// 리포지토리 메서드 (파생 쿼리)
public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt; {
    List&amp;lt;ProductSummary&amp;gt; findByPriceGreaterThan(BigDecimal minPrice);
}

// 리포지토리 메서드 (@Query 사용)
public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt; {
    @Query(&quot;SELECT new com.example.yourpackage.ProductSummary(p.name, p.price) FROM Product p WHERE p.id = :id&quot;)
    Optional&amp;lt;ProductSummary&amp;gt; findSummaryById(@Param(&quot;id&quot;) Long id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동적 프로젝션 (Dynamic Projections):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;697:5-698:0&quot;&gt;하나의 리포지토리 메서드가 런타임에 지정된 타입(엔티티 원본 또는 특정 프로젝션 타입)으로 결과를 반환하도록 할 수 있습니다.&lt;span&gt;&lt;/span&gt; 메서드 시그니처에 제네릭 타입 파라미터 &amp;lt;T&amp;gt;와 Class&amp;lt;T&amp;gt; type 파라미터를 추가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744943827236&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt; {
    &amp;lt;T&amp;gt; List&amp;lt;T&amp;gt; findByNameContainingIgnoreCase(String namePart, Class&amp;lt;T&amp;gt; type);
}

// 서비스 계층에서의 사용 예시
@Service
public class ProductFinderService {
    @Autowired ProductRepository repository;

    public void searchProducts(String query) {
        // 전체 Product 엔티티 조회
        List&amp;lt;Product&amp;gt; fullProducts = repository.findByNameContainingIgnoreCase(query, Product.class);

        // ProductNameAndPrice 프로젝션으로 조회
        List&amp;lt;ProductNameAndPrice&amp;gt; nameAndPriceList = repository.findByNameContainingIgnoreCase(query, ProductNameAndPrice.class);

        // ProductSummary DTO로 조회
        List&amp;lt;ProductSummary&amp;gt; summaryList = repository.findByNameContainingIgnoreCase(query, ProductSummary.class);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝션은 특히 읽기 전용(read-only) 작업에서 성능을 최적화하는 데 매우 유용합니다. 인터페이스 기반은 간단한 필드 매핑에 편리하고, 클래스 기반 DTO는 커스텀 로직 추가나 불변 객체 구현에 유리합니다. 동적 프로젝션은 여러 뷰가 필요할 때 리포지토리 메서드의 중복을 줄여줍니다. 단, 클래스 기반 DTO 프로젝션은 네이티브 쿼리와 함께 사용할 때 @SqlResultSetMapping 같은 추가적인 매핑 설정 없이는 자동으로 동작하지 않는다는 점에 유의해야 합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;감사 (Auditing)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티가 언제, 누구에 의해 생성되고 수정되었는지 자동으로 추적하는 기능입니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;728:1-739:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;728:1-734:229&quot;&gt;&lt;b&gt;주요 어노테이션:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;729:5-734:229&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;729:5-729:40&quot;&gt;@CreatedBy: 엔티티 생성자 정보 필드에 사용.&lt;/li&gt;
&lt;li data-sourcepos=&quot;730:5-730:48&quot;&gt;@LastModifiedBy: 엔티티 최종 수정자 정보 필드에 사용.&lt;/li&gt;
&lt;li data-sourcepos=&quot;731:5-731:44&quot;&gt;@CreatedDate: 엔티티 생성 시각 정보 필드에 사용.&lt;/li&gt;
&lt;li data-sourcepos=&quot;732:5-734:229&quot;&gt;@LastModifiedDate: 엔티티 최종 수정 시각 정보 필드에 사용. &lt;span&gt;&lt;/span&gt; 이 어노테이션들은 엔티티 클래스 또는 @MappedSuperclass로 지정된 공통 부모 클래스의 필드에 적용할 수 있습니다.&lt;span&gt;&lt;/span&gt; 사용자 정보 필드는 String, Long 또는 커스텀 User 타입 등을 사용할 수 있고, 시각 정보 필드는 JDK 8의 java.time 타입, long, Date, Calendar 등을 지원합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;735:1-739:0&quot;&gt;&lt;b&gt;설정:&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-sourcepos=&quot;736:5-739:0&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;736:5-736:131&quot;&gt;&lt;b&gt;Auditing 활성화:&lt;/b&gt; Spring 설정 클래스(보통 메인 애플리케이션 클래스)에 @EnableJpaAuditing 어노테이션을 추가합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;737:5-737:271&quot;&gt;&lt;b&gt;AuditorAware 빈 등록:&lt;/b&gt; @CreatedBy, @LastModifiedBy를 사용하려면 현재 사용자 정보를 제공하는 AuditorAware&amp;lt;T&amp;gt; 인터페이스 구현체를 Spring 빈으로 등록해야 합니다. Spring Security와 통합하여 현재 인증된 사용자 정보를 반환하도록 구현하는 것이 일반적입니다.&lt;span&gt;&lt;/span&gt; Spring Data JPA는 자동으로 이 빈을 감지하여 사용합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;738:5-739:0&quot;&gt;&lt;b&gt;AuditingEntityListener 등록:&lt;/b&gt; 감사 대상 엔티티 클래스 또는 @MappedSuperclass에 @EntityListeners(AuditingEntityListener.class) 어노테이션을 추가하여 JPA 생명주기 이벤트 발생 시 감사 정보가 캡처되도록 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;AuditorAware 구현 예시 (Spring Security 사용):&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1744943904582&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Optional;

@Configuration
@EnableJpaAuditing(auditorAwareRef = &quot;auditorProvider&quot;) // 빈 이름 명시 (선택 사항)
public class AuditingConfig {

    @Bean
    public AuditorAware&amp;lt;String&amp;gt; auditorProvider() {
        // Spring Security 컨텍스트에서 현재 사용자 이름(String)을 반환하는 람다식
        return () -&amp;gt; {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication == null ||!authentication.isAuthenticated() |
| &quot;anonymousUser&quot;.equals(authentication.getPrincipal())) {
                // 인증되지 않았거나 익명 사용자인 경우 &quot;SYSTEM&quot; 또는 null 반환
                return Optional.of(&quot;SYSTEM&quot;);
            }
            // 인증된 사용자 이름 반환
            return Optional.of(authentication.getName());
        };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;@MappedSuperclass 를 이용한 공통 감사 필드 정의 예시:&lt;/h4&gt;
&lt;pre id=&quot;code_1744943985454&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import jakarta.persistence.*;
import org.springframework.data.annotation.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;

@MappedSuperclass // 이 클래스는 엔티티가 아니지만, 속성 매핑 정보는 하위 엔티티에 상속됨
@EntityListeners(AuditingEntityListener.class) // 감사 리스너 등록
public abstract class Auditable&amp;lt;U&amp;gt; { // U는 감사자(Auditor)의 타입 (예: String, Long)

    @CreatedBy // 생성자 정보
    @Column(updatable = false) // 생성자는 수정되지 않도록 설정
    protected U createdBy;

    @CreatedDate // 생성 시각 정보
    @Column(updatable = false) // 생성 시각은 수정되지 않도록 설정
    protected LocalDateTime createdDate;

    @LastModifiedBy // 최종 수정자 정보
    protected U lastModifiedBy;

    @LastModifiedDate // 최종 수정 시각 정보
    protected LocalDateTime lastModifiedDate;

    // Getters and setters...
    public U getCreatedBy() { return createdBy; }
    public LocalDateTime getCreatedDate() { return createdDate; }
    public U getLastModifiedBy() { return lastModifiedBy; }
    public LocalDateTime getLastModifiedDate() { return lastModifiedDate; }
}

// Auditable을 상속받는 Product 엔티티
@Entity
public class Product extends Auditable&amp;lt;String&amp;gt; { // 감사자 타입을 String으로 지정

    @Id @GeneratedValue
    private Long id;
    private String name;
    private BigDecimal price;
    private LocalDate manufacturingDate;

    // 기본 생성자, 다른 필드, getter/setter...
    protected Product() {}
    public Product(String name, BigDecimal price, LocalDate manufacturingDate) {
        this.name = name;
        this.price = price;
        this.manufacturingDate = manufacturingDate;
    }
    public Long getId() { return id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public BigDecimal getPrice() { return price; }
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜젝션 관리 (@Transactional)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 작업의 원자성(Atomicity), 일관성(Consistency), 고립성(Isolation), 지속성(Durability) - ACID 속성을 보장하기 위해 트랜잭션 관리는 필수적입니다.&lt;span&gt;&lt;/span&gt; Spring은 @Transactional 어노테이션을 통해 선언적 트랜잭션 관리를 지원합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;834:1-842:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;834:1-834:321&quot;&gt;&lt;b&gt;적용 위치:&lt;/b&gt; @Transactional은 &lt;b&gt;서비스 계층(Service Layer)의 메서드&lt;/b&gt;에 적용하는 것이 가장 일반적인 모범 사례입니다.&lt;span&gt;&lt;/span&gt; 서비스 계층은 일반적으로 비즈니스 로직의 단위(Unit of Work)를 정의하며, 이 단위는 여러 리포지토리 호출을 포함할 수 있습니다.&lt;span&gt;&lt;/span&gt; 컨트롤러 계층에 적용하는 것은 관심사의 분리 원칙에 어긋나고 &lt;span&gt;&lt;/span&gt;, 리포지토리 계층에만 적용하는 것은 전체 비즈니스 트랜잭션을 포괄하지 못할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;835:1-835:159&quot;&gt;&lt;b&gt;Spring Boot 자동 설정:&lt;/b&gt; spring-boot-starter-data-jpa 의존성이 있으면 Spring Boot가 자동으로 트랜잭션 관리자를 설정하므로, 별도로 @EnableTransactionManagement를 명시할 필요는 거의 없습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;836:1-840:242&quot;&gt;&lt;b&gt;주요 속성:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;837:5-840:242&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;837:5-837:240&quot;&gt;&lt;b&gt;readOnly&lt;/b&gt;: true로 설정하면 해당 트랜잭션이 읽기 전용임을 나타내는 힌트를 JPA 프로바이더에게 전달합니다. Hibernate의 경우, Flush 모드를 NEVER로 설정하여 변경 감지(dirty checking)를 생략하는 등 성능 최적화를 수행할 수 있습니다.&lt;span&gt;&lt;/span&gt; 조회 메서드에는 readOnly = true를 사용하는 것이 좋습니다. 기본값은 false입니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;838:5-838:151&quot;&gt;&lt;b&gt;propagation&lt;/b&gt;: 트랜잭션 전파 속성을 정의합니다. 예를 들어, 이미 진행 중인 트랜잭션이 있을 때 어떻게 동작할지 결정합니다 (REQUIRED(기본값), REQUIRES_NEW, SUPPORTS, NEVER 등).&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;839:5-839:115&quot;&gt;&lt;b&gt;isolation&lt;/b&gt;: 트랜잭션 격리 수준을 설정합니다 (READ_COMMITTED, SERIALIZABLE 등). 기본값은 데이터베이스의 기본 격리 수준을 따릅니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;840:5-840:242&quot;&gt;&lt;b&gt;rollbackFor / noRollbackFor&lt;/b&gt;: 특정 예외 타입이 발생했을 때 롤백을 수행할지(rollbackFor) 또는 수행하지 않을지(noRollbackFor) 지정합니다.&lt;span&gt;&lt;/span&gt; 기본적으로 Spring은 RuntimeException과 Error에 대해서만 롤백을 수행하고, 체크 예외(Checked Exception)에 대해서는 롤백하지 않습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;841:1-842:0&quot;&gt;&lt;b&gt;주의사항 (프록시 기반 동작):&lt;/b&gt; @Transactional은 Spring AOP 프록시를 통해 동작합니다. 따라서 같은 클래스 내의 다른 @Transactional 메서드를 this 참조로 호출하는 **자기 호출(self-invocation)**의 경우, 기본적으로 프록시를 거치지 않기 때문에 호출된 메서드의 트랜잭션 설정(예: propagation = REQUIRES_NEW)이 적용되지 않을 수 있습니다.&lt;span&gt;&lt;/span&gt; 이는 트랜잭션 경계를 설계할 때 고려해야 할 중요한 점이며, 필요하다면 별도의 서비스 빈으로 분리하거나 TransactionTemplate을 사용하는 등의 대안을 고려해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시:&lt;/h4&gt;
&lt;pre id=&quot;code_1744944425406&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class ProductService {
    @Autowired private ProductRepository productRepository;
    @Autowired private InventoryService inventoryService; // 다른 서비스

    // 쓰기 트랜잭션 (기본 설정: REQUIRED, readOnly=false)
    @Transactional
    public Product createProductAndUpdateInventory(Product product, int initialStock) {
        Product savedProduct = productRepository.save(product);
        // inventoryService의 updateStock 메서드가 실패하면(RuntimeException 발생 시)
        // productRepository.save() 작업도 롤백됨
        inventoryService.updateStock(savedProduct.getId(), initialStock);
        return savedProduct;
    }

    // 읽기 전용 트랜잭션 (최적화 가능)
    @Transactional(readOnly = true)
    public Optional&amp;lt;Product&amp;gt; findProductById(Long id) {
        return productRepository.findById(id);
    }

    // 특정 체크 예외 발생 시 롤백하도록 설정
    @Transactional(rollbackFor = InventoryUpdateException.class)
    public void updateProductPrice(Long id, BigDecimal newPrice) throws InventoryUpdateException {
        Optional&amp;lt;Product&amp;gt; productOpt = productRepository.findById(id);
        if (productOpt.isPresent()) {
            Product product = productOpt.get();
            product.setPrice(newPrice);
            productRepository.save(product);
            // 재고 관련 로직 수행 중 예외 발생 가능성
            inventoryService.checkAndUpdateRelatedInventory(product);
        }
    }
}

// 사용자 정의 체크 예외
public class InventoryUpdateException extends Exception {
    public InventoryUpdateException(String message) {
        super(message);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;락 (Locking)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 여러 트랜잭션이 같은 데이터에 접근하여 수정하려고 할 때 발생할 수 있는 문제(예: Lost Update)를 방지하기 위해 락 메커니즘을 사용할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;893:1-937:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;893:1-913:0&quot;&gt;&lt;b&gt;낙관적 락 (Optimistic Locking):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;894:5-899:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;894:5-894:128&quot;&gt;&lt;b&gt;개념:&lt;/b&gt; 데이터베이스 레코드 자체에 락을 걸지 않고, 엔티티에 버전(version) 컬럼을 두어 데이터 변경 시 버전을 확인하는 방식입니다. 충돌이 드물게 발생할 것이라고 가정합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;895:5-895:226&quot;&gt;&lt;b&gt;구현:&lt;/b&gt; 엔티티 클래스에 @Version 어노테이션이 붙은 필드(주로 Long, Integer, Timestamp 타입)를 추가합니다.&lt;span&gt;&lt;/span&gt; Hibernate는 엔티티 업데이트 시 이 버전 필드를 자동으로 증가시키고, UPDATE 쿼리의 WHERE 절에 version = &amp;lt;읽었던 버전&amp;gt; 조건을 추가합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;896:5-896:209&quot;&gt;&lt;b&gt;동작:&lt;/b&gt; 만약 다른 트랜잭션이 먼저 커밋하여 버전이 변경되었다면, 현재 트랜잭션의 UPDATE 문은 0개의 행에 영향을 미치게 됩니다. 이때 JPA는 OptimisticLockException을 발생시켜 충돌이 감지되었음을 알립니다.&lt;span&gt;&lt;/span&gt; 애플리케이션은 이 예외를 처리하여 재시도하거나 사용자에게 알려야 합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;897:5-897:108&quot;&gt;&lt;b&gt;장점:&lt;/b&gt; 데이터베이스 락을 사용하지 않으므로 동시성이 높고 성능 및 확장성 면에서 유리합니다. 특히 읽기 작업이 많은 시스템에 적합합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;898:5-899:0&quot;&gt;&lt;b&gt;단점:&lt;/b&gt; 충돌 발생 시 애플리케이션 레벨에서 예외 처리 및 재시도 로직이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744947924122&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Product {
    @Id @GeneratedValue private Long id;
    private String name;
    private BigDecimal price;

    @Version // 낙관적 락을 위한 버전 필드
    private Long version;

    // Constructors, getters, setters...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비관적 락 (Pessimistic Locking):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;915:5-915:144&quot;&gt;&lt;b&gt;개념:&lt;/b&gt; 데이터베이스 레벨의 실제 락(예: SELECT... FOR UPDATE 또는 FOR SHARE)을 사용하여 데이터에 대한 동시 접근을 제어합니다. 충돌이 자주 발생할 것이라고 가정합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;915:5-915:144&quot;&gt;&lt;b&gt;구현:&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 리포지토리 메서드에 @Lock 어노테이션과 LockModeType을 지정하여 사용합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; EntityManager의 lock() 또는 find() 메서드를 통해서도 직접 락을 설정할 수 있습니다.&lt;/span&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;915:5-915:144&quot;&gt;&lt;b&gt;LockModeType.PESSIMISTIC_READ&lt;/b&gt;: 공유 락(Shared Lock). 다른 트랜잭션이 읽는 것은 허용하지만, 수정/삭제는 방지합니다.&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;915:5-915:144&quot;&gt;&lt;b&gt;LockModeType.PESSIMISTIC_WRITE&lt;/b&gt;: 배타 락(Exclusive Lock). 다른 트랜잭션의 읽기, 수정, 삭제를 모두 방지합니다.&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;915:5-915:144&quot;&gt;&lt;b&gt;LockModeType.PESSIMISTIC_FORCE_INCREMENT&lt;/b&gt;: PESSIMISTIC_WRITE와 유사하게 배타 락을 걸지만, 추가로 @Version 필드의 값을 강제로 증가시킵니다.&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;915:5-915:144&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;장점:&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;데이터 충돌을 원천적으로 방지하여 데이터 무결성을 강력하게 보장합니다. 충돌이 빈번한 쓰기 중심의 시스템에 적합할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;921:5-922:0&quot;&gt;&lt;b&gt;단점:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;데이터베이스 락으로 인해 트랜잭션이 대기(blocking) 상태에 빠질 수 있어 동시성 및 성능 저하를 유발할 수 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;데드락(Deadlock) 발생 가능성도 존재합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744948020859&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt; {

    // ID로 조회 시 쓰기 락(배타 락) 획득
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query(&quot;SELECT p FROM Product p WHERE p.id = :id&quot;)
    Optional&amp;lt;Product&amp;gt; findByIdForUpdate(@Param(&quot;id&quot;) Long id);

    // 기본 findById 메서드를 오버라이드하여 읽기 락(공유 락) 적용
    @Override
    @Lock(LockModeType.PESSIMISTIC_READ)
    Optional&amp;lt;Product&amp;gt; findById(Long id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 JPA/Hibernate 환경에서는 &lt;b&gt;낙관적 락이 확장성 측면에서 더 선호되는 방식&lt;/b&gt;입니다.&lt;span&gt;&lt;/span&gt; 비관적 락은 강력한 일관성을 보장하지만 성능 저하와 데드락 위험을 감수해야 하므로, 충돌 가능성이 매우 높고 즉각적인 일관성이 반드시 필요한 특정 유스케이스에 한해 사용하는 것이 좋습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA 3.4.4는 Java 애플리케이션에서 데이터 접근 계층을 구축하는 작업을 놀랍도록 간소화시켜주는 강력한 프레임워크입니다. 리포지토리 인터페이스 기반의 프로그래밍 모델, 파생 쿼리, @Query 어노테이션을 통한 유연한 쿼리 작성 기능은 개발자가 상용구 코드 작성에서 벗어나 핵심 비즈니스 로직에 집중할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;944:1-944:212&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 기본적인 설정과 의존성 관리부터 시작하여, @Entity, @Id, @Table, @Column 등의 어노테이션을 사용한 엔티티 및 관계 매핑 방법을 살펴보았습니다. 특히, 양방향 @OneToMany 관계 매핑 시 @ManyToOne 쪽에서 관계를 관리하고 유틸리티 메서드를 사용하는 모범 사례와 FetchType 선택의 중요성을 강조했습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;944:1-944:212&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;946:1-946:190&quot; data-ke-size=&quot;size16&quot;&gt;또한, JpaRepository를 활용한 기본적인 CRUD 작업 수행 방법과 save(), delete() 메서드의 내부 동작 및 주의점에 대해 알아보았습니다. 파생 쿼리, JPQL, 네이티브 SQL, 그리고 Criteria API까지 다양한 쿼리 작성 방법을 비교하며 각각의 장단점과 사용 시기를 이해하는 데 중점을 두었습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;946:1-946:190&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;948:1-948:192&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 페이징 및 정렬, 프로젝션(인터페이스 기반, 클래스 기반 DTO, 동적), 감사 기능, @Transactional을 이용한 선언적 트랜잭션 관리, 그리고 낙관적/비관적 락과 같은 고급 기능들을 통해 Spring Data JPA가 제공하는 풍부한 기능을 활용하여 더욱 견고하고 효율적인 애플리케이션을 개발할 수 있음을 확인했습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;948:1-948:192&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;950:1-950:297&quot; data-ke-size=&quot;size16&quot;&gt;더 자세한 정보는 아래의 공식 문서를 참고하시기 바랍니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;950:1-950:297&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/jpa/reference/index.html&quot;&gt;https://docs.spring.io/spring-data/jpa/reference/index.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744948105740&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Data JPA :: Spring Data JPA&quot; data-og-description=&quot;Oliver Gierke, Thomas Darimont, Christoph Strobl, Mark Paluch, Jay Bryant, Greg Turnquist Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-data/jpa/reference/index.html&quot; data-og-url=&quot;https://docs.spring.io/spring-data/jpa/reference/index.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/jpa/reference/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-data/jpa/reference/index.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data JPA :: Spring Data JPA&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Oliver Gierke, Thomas Darimont, Christoph Strobl, Mark Paluch, Jay Bryant, Greg Turnquist Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>JPA</category>
      <category>Spring</category>
      <category>spring data</category>
      <author>kahnco</author>
      <guid isPermaLink="true">https://kahnco.tistory.com/47</guid>
      <comments>https://kahnco.tistory.com/47#entry47comment</comments>
      <pubDate>Fri, 18 Apr 2025 12:48:58 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring Data - JDBC</title>
      <link>https://kahnco.tistory.com/46</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계형 데이터베이스와의 상호작용은 많은 애플리케이션 개발의 핵심 요소입니다. Spring 프레임워크 생태계 내에서 개발자들은 주로 Spring Data JPA를 사용하여 객체-관계 매핑(ORM)의 편리함을 누려왔습니다. 하지만 모든 상황에 완전한 ORM 프레임워크가 필요한 것은 아닙니다. 때로는 더 단순하고, 예측 가능하며, SQL에 대한 직접적인 제어를 제공하는 솔루션이 더 적합할 수 있습니다. 바로 이 지점에서 Spring Data JDBC가 등장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;5:1-5:338&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data JDBC는 Spring Data 프로젝트의 일부로, 도메인 주도 설계(Domain-Driven Design, DDD) 원칙에 맞춰 JDBC 기반 데이터 접근 솔루션을 개발하는 데 중점을 둡니다.&lt;span&gt;&lt;/span&gt; 완전한 ORM 기능(캐싱, 지연 로딩, 쓰기 지연 등)을 제공하는 대신, 애그리거트(Aggregate)라는 핵심 개념을 중심으로 데이터베이스 테이블과 Java 객체 간의 간단한 매핑을 제공합니다.&lt;span&gt;&lt;/span&gt; 이를 통해 개발자는 ORM의 복잡성 없이 관계형 데이터베이스와 상호작용할 수 있으며, 실행되는 SQL에 대한 더 나은 가시성과 제어권을 가질 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;7:1-7:184&quot; data-ke-size=&quot;size16&quot;&gt;이 게시물에서는 &lt;b&gt;Spring Data JDBC 3.4.4&lt;/b&gt; 버전을 심층적으로 살펴봅니다. 프로젝트 설정부터 애그리거트 및 리포지토리 정의, CRUD 작업 수행, 다양한 쿼리 방법, 생명주기 이벤트 처리, 그리고 Spring Data JPA와의 비교를 통해 언제 Spring Data JDBC를 선택하는 것이 유리한지 알아보겠습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;7:1-7:184&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;7:1-7:184&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;시작하기: 설정 및 구성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;7:1-7:184&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data JDBC를 사용하기 위한 첫 단계는 프로젝트 의존성을 설정하고 데이터베이스 연결을 구성하는 것입니다. Spring Boot를 사용하면 이 과정이 매우 간소화됩니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;7:1-7:184&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;7:1-7:184&quot; data-ke-size=&quot;size23&quot;&gt;의존성 설정&lt;/h3&gt;
&lt;p data-sourcepos=&quot;7:1-7:184&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring Data JDBC 3.4.4&lt;/b&gt;는 &lt;b&gt;Spring Framework 6.2.4&lt;/b&gt; 이상 버전이 필요합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;7:1-7:184&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-sourcepos=&quot;7:1-7:184&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;Maven 사용 시 (pom.xml)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-sourcepos=&quot;7:1-7:184&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 프로젝트에서는 spring-boot-starter-data-jdbc 스타터를 추가하는 것이 가장 일반적입니다. 이 스타터는 Spring Data JDBC 코어, spring-jdbc, 트랜잭션 관리 등 필요한 의존성을 포함합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744867160394&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-data-jdbc&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.4.4&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.h2database&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;h2&amp;lt;/artifactId&amp;gt;
    &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring-boot-starter-data-jdbc는 특정 JDBC 드라이버를 포함하지 않으므로, 사용하려는 데이터베이스에 맞는 드라이버 의존성을 별도로 추가해야 합니다.&lt;span&gt;&lt;/span&gt; 이는 프레임워크가 특정 데이터베이스 기술에 종속되지 않고 개발자에게 선택권을 부여하려는 철학을 반영합니다. 기존 JDBC 설정과 유연하게 통합될 수 있도록 설계된 것입니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;36:1-36:73&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot를 사용하지 않는 경우, spring-data-jdbc 아티팩트를 직접 추가해야 합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744867193872&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.data&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-data-jdbc&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.4.4&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-jdbc&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;6.2.4&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Gradle 사용 시 (build.gradle)&lt;/h4&gt;
&lt;pre id=&quot;code_1744867233166&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id 'org.springframework.boot' version '3.4.4' // Spring Boot 플러그인 버전
    id 'io.spring.dependency-management' version '1.1.7' // 의존성 관리 플러그인
    id 'java'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17) // Spring Boot 3.x는 Java 17 이상 필요 [13]
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

    // 사용하는 데이터베이스의 JDBC 드라이버 추가
    runtimeOnly 'com.h2database:h2'
    // runtimeOnly 'org.postgresql:postgresql'
    // runtimeOnly 'com.mysql:mysql-connector-j'

    // Lombok (선택 사항)
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle에서도 implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'를 추가하고, 필요한 JDBC 드라이버를 runtimeOnly 스코프로 포함시킵니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터베이스 연결 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot에서는 application.properties 또는 application.yml 파일을 통해 데이터베이스 연결 정보를 간단하게 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;application.properties 예시 (H2 인메모리 데이터베이스):&lt;/h4&gt;
&lt;pre id=&quot;code_1744867436073&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Database Connection Settings
spring.datasource.url=jdbc:h2:mem:testdb # H2 인메모리 DB URL
spring.datasource.username=sa
spring.datasource.password=password
# spring.datasource.driver-class-name=org.h2.Driver # Boot가 URL 기반으로 자동 감지하는 경우가 많음 [6, 15]

# H2 Console (개발 시 유용)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# Spring Data JDBC 설정 (선택 사항)
# spring.data.jdbc.repositories.enabled=true # 기본값 true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정만으로 Spring Boot는 DataSource 빈을 자동으로 구성합니다. spring-boot-starter-data-jdbc가 클래스패스에 존재하고 DataSource 빈이 발견되면, Spring Boot는 NamedParameterJdbcOperations, TransactionManager 등 Spring Data JDBC에 필요한 핵심 컴포넌트들을 자동으로 구성하고 리포지토리를 활성화합니다.&lt;span&gt;&lt;/span&gt; 따라서 대부분의 Spring Boot 애플리케이션에서는 명시적인 Java 구성 클래스가 필요하지 않습니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;108:1-108:304&quot; data-ke-size=&quot;size16&quot;&gt;이러한 강력한 자동 구성 기능은 Spring Boot 사용자의 진입 장벽을 크게 낮춰줍니다. 하지만 Spring Boot 없이 Spring Framework만 사용하는 경우, 개발자는 DataSource, NamedParameterJdbcOperations, PlatformTransactionManager 등의 빈을 수동으로 설정하고 @EnableJdbcRepositories 어노테이션을 사용하여 리포지토리를 활성화하는 방법을 이해해야 합니다.&lt;span&gt;&lt;/span&gt; 이는 비-Boot 환경에서의 초기 설정 복잡성을 증가시킬 수 있습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;108:1-108:304&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-sourcepos=&quot;108:1-108:304&quot; data-ke-size=&quot;size20&quot;&gt;커넥션 풀 설정:&lt;/h4&gt;
&lt;p data-sourcepos=&quot;108:1-108:304&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot는 기본적으로 HikariCP를 커넥션 풀로 사용합니다. application.properties를 통해 HikariCP의 설정을 조정할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744867749160&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# HikariCP 설정 예시
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000 # 30초&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;121:1-121:43&quot; data-ke-size=&quot;size23&quot;&gt;Java 구성 (수동 설정 - 비-Boot 환경 또는 커스터마이징 시)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot를 사용하지 않거나 더 세밀한 제어가 필요한 경우, Java 구성을 통해 Spring Data JDBC를 설정할 수 있습니다. @Configuration 클래스에서 AbstractJdbcConfiguration을 상속받고 필요한 빈들을 정의합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744867783332&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration
@EnableJdbcRepositories // (1) JDBC 리포지토리 활성화
public class ApplicationConfig extends AbstractJdbcConfiguration { // (2) 기본 빈 제공

    @Bean
    DataSource dataSource() { // (3) DataSource 빈 정의
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder.setType(EmbeddedDatabaseType.HSQL).build(); // 예: HSQL 임베디드 DB
    }

    @Bean
    NamedParameterJdbcOperations namedParameterJdbcOperations(DataSource dataSource) { // (4) NamedParameterJdbcOperations 빈 정의
        return new NamedParameterJdbcTemplate(dataSource);
    }

    @Bean
    PlatformTransactionManager transactionManager(DataSource dataSource) { // (5) 트랜잭션 관리자 빈 정의
        return new DataSourceTransactionManager(dataSource);
    }

    // 필요 시 Dialect 등 추가 빈 정의
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터베이스 Dialect&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JDBC는 다양한 데이터베이스 벤더 간의 SQL 차이를 추상화하기 위해 Dialect 인터페이스 구현체를 사용합니다.&lt;span&gt;&lt;/span&gt; AbstractJdbcConfiguration은 기본적으로 DataSource 연결을 통해 Dialect를 자동으로 감지하려고 시도합니다.&lt;span&gt;&lt;/span&gt; 지원되는 데이터베이스에는 H2, HSQLDB, MySQL, PostgreSQL, Oracle, SQL Server, DB2 등이 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;167:1-167:192&quot; data-ke-size=&quot;size16&quot;&gt;자동 감지가 실패하거나 특정 Dialect를 강제해야 하는 경우, AbstractJdbcConfiguration의 jdbcDialect(NamedParameterJdbcOperations) 메서드를 오버라이드하여 커스텀 Dialect 빈을 등록하거나, 특정 프로퍼티를 통해 Dialect Provider를 지정할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;167:1-167:192&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;167:1-167:192&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;엔티티와 리포지토리 정의: 애그리거트 모델링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;167:1-167:192&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data JDBC의 핵심 철학은 도메인 주도 설계(DDD)의 애그리거트(Aggregate) 개념에 기반합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;167:1-167:192&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;167:1-167:192&quot; data-ke-size=&quot;size23&quot;&gt;애그리거트 루트 (Aggregate Root)&lt;/h3&gt;
&lt;p data-sourcepos=&quot;167:1-167:192&quot; data-ke-size=&quot;size16&quot;&gt;애그리거트는 함께 로드되고 저장되는 관련된 객체들의 묶음입니다. 애그리거트 루트는 이 묶음의 대표 엔티티이며, 리포지토리를 통해 관리되는 기본 단위입니다.&lt;span&gt;&lt;/span&gt; 모든 데이터 접근(저장, 조회, 삭제)은 애그리거트 루트를 위한 리포지토리를 통해 수행됩니다. 애그리거트 경계 내의 다른 엔티티들은 루트를 통해서만 접근하고 관리됩니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;167:1-167:192&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;167:1-167:192&quot; data-ke-size=&quot;size23&quot;&gt;어노테이션을 이용한 엔티티 매핑&lt;/h3&gt;
&lt;p data-sourcepos=&quot;167:1-167:192&quot; data-ke-size=&quot;size16&quot;&gt;Java 객체(POJO)를 데이터베이스 테이블에 매핑하기 위해 다음과 같은 어노테이션을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;181:1-185:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;181:1-181:104&quot;&gt;@Table: 엔티티가 매핑될 테이블 이름을 명시적으로 지정합니다. 생략하면 클래스 이름을 기반으로 (기본적으로 snake_case 변환) 테이블 이름이 결정됩니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;182:1-182:168&quot;&gt;@Id: 엔티티의 기본 키(Primary Key)를 나타내는 필드를 지정합니다. 모든 애그리거트 루트는 @Id 필드를 가져야 합니다.&lt;span&gt;&lt;/span&gt; 데이터베이스의 자동 증가(auto-increment) 컬럼과 연동될 수 있으며, ID 생성 전략에 대한 고려가 필요할 수 있습니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;183:1-183:101&quot;&gt;@Column: 필드가 매핑될 컬럼 이름을 명시적으로 지정합니다. 생략하면 필드 이름을 기반으로 (기본적으로 snake_case 변환) 컬럼 이름이 결정됩니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;184:1-185:0&quot;&gt;@MappedCollection: 일대다(one-to-many) 관계에서 자식 엔티티 컬렉션을 매핑할 때 사용됩니다. 자식 테이블의 외래 키 컬럼(idColumn)과 루트 테이블의 키 컬럼(keyColumn, 주로 ID)을 지정해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 엔티티 예시:&lt;/h4&gt;
&lt;pre id=&quot;code_1744867917934&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.mapping.Column;

@Table(&quot;CUSTOMERS&quot;) // 선택 사항, 기본값은 &quot;customer&quot; 테이블
public class Customer {

    @Id
    private Long id; // ID 필드는 필수

    @Column(&quot;first_name&quot;) // 선택 사항, 기본값은 &quot;first_name&quot; 컬럼
    private String firstName;

    private String lastName; // 기본적으로 &quot;last_name&quot; 컬럼에 매핑됨

    // 생성자, Getter, Setter 등
    // 불변성(Immutability) 고려 가능 [3] - 생성자 주입과 final 필드 사용
    public Customer(Long id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Getters... (Setter는 불변 객체일 경우 불필요)
    public Long getId() { return id; }
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관계 매핑 (애그리거트 내부)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JDBC 는 애그리거트 내부의 관계와 애그리거트 간의 참조를 구분합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;일대다 (One-to-Many):&lt;/b&gt; 애그리거트 루트가 소유한 자식 엔티티들의 컬렉션(주로 Set&amp;lt;RelatedEntity&amp;gt; 또는 List&amp;lt;RelatedEntity&amp;gt;)으로 표현됩니다.&lt;span&gt;&lt;/span&gt; 이때 @MappedCollection 어노테이션이 필수적이며, 자식 테이블에서 부모를 참조하는 외래 키 컬럼(idColumn)과 부모 테이블의 ID 컬럼(keyColumn)을 명시해야 합니다. 이는 JPA의 @OneToMany가 종종 컬럼을 추론하는 것과 달리, Spring Data JDBC는 더 명시적인 설정을 요구함을 보여줍니다. 개발자는 데이터베이스 스키마에 대한 정확한 이해를 바탕으로 매핑해야 하며, 설정 오류는 런타임 에러나 예상치 못한 데이터 불일치를 초래할 수 있습니다.&lt;span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744868080668&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Order (Aggregate Root) 내부
@Table(&quot;ORDERS&quot;)
public class Order {
    @Id private Long id;
    private String orderNumber;

    // OrderLineItem 테이블의 &quot;order_id&quot; 컬럼이 이 Order의 &quot;id&quot; 컬럼을 참조함
    @MappedCollection(idColumn = &quot;ORDER_ID&quot;, keyColumn = &quot;ID&quot;)
    private Set&amp;lt;OrderLineItem&amp;gt; items = new HashSet&amp;lt;&amp;gt;();

    // 생성자, Getter 등
}

// OrderLineItem (Order 애그리거트의 일부)
@Table(&quot;ORDER_LINE_ITEMS&quot;)
public class OrderLineItem {
    // 자식 엔티티는 독립적인 @Id를 가질 수도, 안 가질 수도 있음 (복합 키 등)
    private String productCode;
    private int quantity;
    // ORDER_ID 컬럼이 DB에 존재해야 함 (idColumn에 명시됨)

    // 생성자, Getter 등
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;일대일 (One-to-One):&lt;/b&gt; 애그리거트 내의 다른 엔티티 타입 필드로 직접 참조하여 모델링할 수 있습니다. 만약 참조된 엔티티가 별도의 테이블을 가진다면 외래 키 관계가 암시됩니다. 또는 @Embedded 어노테이션을 사용하여 값 객체(Value Object)를 루트 엔티티와 동일한 테이블의 컬럼들에 매핑할 수도 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744868104495&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Customer (Aggregate Root) 내부
public class Customer {
    @Id private Long id;
    private String name;

    // Address가 Customer 애그리거트의 일부라고 가정
    private Address shippingAddress;

    // 생성자, Getter 등
}

// Address 클래스 (별도 테이블 또는 @Embeddable 가능)
public class Address {
    private String street;
    private String city;
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다른 애그리거트에 대한 참조:&lt;/b&gt; 서로 다른 애그리거트 간의 직접적인 객체 참조는 권장되지 않습니다. 대신, 참조하려는 다른 애그리거트 루트의 ID 값만 저장합니다. 해당 애그리거트의 전체 정보가 필요하면, 그 애그리거트를 담당하는 별도의 리포지토리를 사용하여 ID로 조회해야 합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 방식은 애그리거트 경계를 명확히 하고, 관련 없는 큰 객체 그래프가 실수로 로드되는 것을 방지합니다. 이는 DDD의 핵심 원칙을 강제하며, JPA의 지연/즉시 로딩을 통해 객체 그래프를 자유롭게 탐색하던 개발자에게는 새로운 제약 조건이 될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Naming Strategy&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Spring Data JDBC는 Java의 camelCase 필드/클래스 이름을 데이터베이스의 snake_case 컬럼/테이블 이름으로 변환하는 NamingStrategy를 사용합니다.&lt;span&gt;&lt;/span&gt; 필요한 경우, 커스텀 NamingStrategy 빈을 등록하여 이 동작을 변경할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리포지토리 인터페이스 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 접근 로직을 추상화하기 위해 리포지토리 인터페이스를 정의합니다. Spring Data는 이 인터페이스의 구현체를 자동으로 생성해줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;283:1-283:149&quot;&gt;CrudRepository&amp;lt;EntityType, IdType&amp;gt;: 기본적인 CRUD(Create, Read, Update, Delete) 메서드를 제공합니다.&lt;span&gt;&lt;/span&gt; findAll()은 Iterable&amp;lt;T&amp;gt;을 반환합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;284:1-284:194&quot;&gt;ListCrudRepository&amp;lt;EntityType, IdType&amp;gt;: CrudRepository와 유사하지만, findAll(), findAllById() 등이 Iterable 대신 List&amp;lt;T&amp;gt;를 반환하여 사용 편의성을 높입니다. (Spring Data Commons의 일부로 3.4.4에서도 사용 가능)&lt;/li&gt;
&lt;li data-sourcepos=&quot;285:1-286:0&quot;&gt;PagingAndSortingRepository&amp;lt;EntityType, IdType&amp;gt;: 페이징 및 정렬 기능을 위한 메서드(findAll(Pageable), findAll(Sort))를 추가로 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 리포지토리 예시:&lt;/h4&gt;
&lt;pre id=&quot;code_1744868277047&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.data.repository.CrudRepository;
// 또는 import org.springframework.data.repository.ListCrudRepository;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository // 선택 사항이지만 가독성을 위해 권장
public interface CustomerRepository extends CrudRepository&amp;lt;Customer, Long&amp;gt; {
// public interface CustomerRepository extends ListCrudRepository&amp;lt;Customer, Long&amp;gt; { // List 반환을 원할 경우

    // 사용자 정의 쿼리 메서드 추가 가능 (5번 섹션 참조)
    List&amp;lt;Customer&amp;gt; findByLastName(String lastName);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;CRUD 작업 수행: 데이터 상호작용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리포지토리 인터페이스를 정의했다면, 이를 서비스나 컨트롤러 계층에 주입하여 데이터베이스 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리포지토리 주입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Autowired 어노테이션을 사용하여 리포지토리 인스턴스를 주입받습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744868435689&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CustomerService {

    private final CustomerRepository customerRepository;

    @Autowired
    public CustomerService(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    // CRUD 메서드 구현...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생성/수정 (Save)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CrudRepository의 save(AggregateRoot entity) 메서드는 엔티티 생성과 수정을 모두 처리합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;333:1-335:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;333:1-333:83&quot;&gt;엔티티의 ID가 null이거나, Persistable 인터페이스 구현을 통해 '새로운 엔티티'로 판단되면 INSERT SQL이 실행됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;334:1-335:0&quot;&gt;엔티티의 ID가 존재하면 UPDATE SQL이 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;336:1-336:221&quot; data-ke-size=&quot;size16&quot;&gt;중요한 점은 save 연산이 애그리거트 루트에 대해 호출되면, 해당 애그리거트에 속한 모든 엔티티(예: @MappedCollection으로 매핑된 컬렉션 내 아이템)에 대한 변경 사항이 함께 반영된다는 것입니다. Spring Data JDBC는 전달된 애그리거트 객체의 상태를 분석하여 관련된 자식 엔티티에 대한 INSERT, UPDATE, DELETE 문을 자동으로 결정하고 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744868658611&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CustomerService 내부

public Customer createCustomer(String firstName, String lastName) {
    Customer newCustomer = new Customer(null, firstName, lastName); // ID가 null이면 INSERT
    // 필요 시 관련된 자식 엔티티 추가 (예: newCustomer.addItem(newItem))
    return customerRepository.save(newCustomer);
}

public Customer updateCustomerName(Long id, String newFirstName, String newLastName) {
    // 기존 고객 조회 (Optional 처리 필요)
    Customer existingCustomer = customerRepository.findById(id)
           .orElseThrow(() -&amp;gt; new RuntimeException(&quot;Customer not found&quot;));

    // 수정된 정보로 새 객체 생성 (불변 객체 스타일) 또는 기존 객체 수정
    Customer updatedCustomer = new Customer(existingCustomer.getId(), newFirstName, newLastName);
    // 자식 엔티티 컬렉션 등도 필요 시 업데이트

    return customerRepository.save(updatedCustomer); // ID가 존재하므로 UPDATE
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 애그리거트 수준의 영속성 작업(save, delete)은 복잡한 객체 구조를 한 번에 저장하거나 삭제할 때 매우 편리합니다. 하지만 큰 애그리거트의 일부만 변경하는 경우에도 전체 애그리거트를 로드하고 저장하는 방식으로 동작할 수 있어 잠재적인 비효율이 발생할 수 있습니다. 이는 불필요한 데이터 전송을 유발하고, 낙관적 락킹(Optimistic Locking) &lt;span&gt;&lt;/span&gt;을 사용하지 않으면 동시성 문제로 인해 다른 변경 사항을 덮어쓸 위험도 내포합니다. 따라서 부분 업데이트가 빈번하다면, 애그리거트 전체 save 대신 @Query를 이용한 맞춤형 UPDATE 문 사용을 고려해야 할 수 있습니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;362:1-362:147&quot; data-ke-size=&quot;size16&quot;&gt;또한, save 시 애그리거트 경계 내의 모든 변경이 자동으로 전파되므로, 개발자는 애그리거트 경계를 명확하게 정의하는 것이 중요합니다. 실수로 관련 없는 엔티티를 애그리거트에 포함시키면, 루트 저장 시 의도치 않은 데이터 수정이나 삭제가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;362:1-362:147&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;362:1-362:147&quot; data-ke-size=&quot;size23&quot;&gt;조회 (Read / Find)&lt;/h3&gt;
&lt;p data-sourcepos=&quot;362:1-362:147&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;366:1-366:85&quot;&gt;&lt;b&gt;findById(ID id)&lt;/b&gt;: 주어진 ID에 해당하는 애그리거트 루트를 Optional&amp;lt;AggregateRoot&amp;gt; 형태로 반환합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;367:1-367:137&quot;&gt;&lt;b&gt;findAll()&lt;/b&gt;: 리포지토리가 관리하는 모든 애그리거트 루트를 Iterable&amp;lt;AggregateRoot&amp;gt; (또는 ListCrudRepository 사용 시 List&amp;lt;AggregateRoot&amp;gt;) 형태로 반환합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;368:1-369:0&quot;&gt;&lt;b&gt;findAllById(Iterable&amp;lt;ID&amp;gt; ids)&lt;/b&gt;: 여러 ID에 해당하는 엔티티들을 조회합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744868846995&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CustomerService 내부

public Optional&amp;lt;Customer&amp;gt; getCustomerById(Long id) {
    return customerRepository.findById(id);
}

public List&amp;lt;Customer&amp;gt; getAllCustomers() {
    // CrudRepository 사용 시 캐스팅 필요, ListCrudRepository는 불필요
    // return (List&amp;lt;Customer&amp;gt;) customerRepository.findAll();
    return customerRepository.findAll(); // ListCrudRepository 사용 시
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;삭제 (Delete)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;386:1-386:65&quot;&gt;deleteById(ID id): 주어진 ID의 애그리거트 루트와 소유된 관련 엔티티들을 함께 삭제합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;387:1-387:63&quot;&gt;delete(AggregateRoot entity): 주어진 엔티티 객체의 ID를 사용하여 삭제합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;388:1-389:0&quot;&gt;deleteAll(): 리포지토리가 관리하는 모든 애그리거트를 삭제합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744868941682&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CustomerService 내부

public void deleteCustomer(Long id) {
    customerRepository.deleteById(id); // 관련 자식 엔티티도 함께 삭제됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;데이터 쿼리: 기본 CRUD 를 넘어서&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JDBC 는 기본적인 CRUD 외에도 다양한 데이터 조회 방법을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파생 쿼리 (Derived Queries / Query Creation)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리포지토리 인터페이스에 특정 명명 규칙을 따르는 메서드를 선언하면, Spring Data 가 메서드 이름을 분석하여 자동으로 SQL 쿼리를 생성해줍니다. 이는 간단한 조회 조건을 위한 코드를 크게 줄여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시:&lt;/h4&gt;
&lt;pre id=&quot;code_1744869381809&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface CustomerRepository extends ListCrudRepository&amp;lt;Customer, Long&amp;gt; { // List 반환으로 변경

    // SELECT * FROM customers WHERE last_name =?
    List&amp;lt;Customer&amp;gt; findByLastName(String lastName);

    // SELECT * FROM customers WHERE first_name =? AND last_name =?
    Optional&amp;lt;Customer&amp;gt; findByFirstNameAndLastName(String firstName, String lastName);

    // SELECT COUNT(*) FROM customers WHERE last_name =?
    long countByLastName(String lastName); // [2] 예시

    // DELETE FROM customers WHERE last_name =? (반환값: 삭제된 행 수)
    long deleteByLastName(String lastName); // [2] 예시

    // DELETE FROM customers WHERE last_name =? (반환값: 삭제된 엔티티 목록)
    List&amp;lt;Customer&amp;gt; removeByLastName(String lastName); // [2] 예시 (대안적 삭제)

    // SELECT * FROM customers WHERE last_name =? ORDER BY first_name ASC
    List&amp;lt;Customer&amp;gt; findByLastNameOrderByFirstNameAsc(String lastName);

    // SELECT * FROM customers WHERE last_name LIKE?
    List&amp;lt;Customer&amp;gt; findByLastNameStartingWith(String prefix);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IgnoreCase, Containing, Between, In, NotNull 등 다양한 키워드를 조합하여 복잡한 쿼리 조건을 메서드 이름으로 표현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;436:1-436:113&quot; data-ke-size=&quot;size16&quot;&gt;파생 쿼리는 편리하지만, 생성되는 실제 SQL을 직접 제어하기 어렵다는 단점이 있습니다. 복잡한 조인이나 데이터베이스 특정 함수 사용, 성능 최적화가 필요한 경우에는 명시적인 쿼리 작성이 더 적합합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;436:1-436:113&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;438:1-438:27&quot; data-ke-size=&quot;size23&quot;&gt;@Query를 이용한 사용자 정의 쿼리&lt;/h3&gt;
&lt;p data-sourcepos=&quot;440:1-440:195&quot; data-ke-size=&quot;size16&quot;&gt;메서드 이름만으로 표현하기 어렵거나, 특정 SQL을 직접 작성하고 싶을 때 @Query 어노테이션을 사용합니다.&lt;span&gt;&lt;/span&gt; 값으로는 실행될 SQL 문을 직접 작성합니다. 파라미터 바인딩은 메서드 파라미터 이름과 일치하는 콜론(:) 접두사(예: :paramName)를 사용하거나 @Param 어노테이션으로 명시적으로 지정할 수 있습니다.&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744869425284&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.query.Param;
import java.time.LocalDate;
import java.util.List;

public interface CustomerRepository extends ListCrudRepository&amp;lt;Customer, Long&amp;gt; {

    @Query(&quot;SELECT * FROM customers WHERE last_name = :lastName ORDER BY first_name ASC&quot;)
    List&amp;lt;Customer&amp;gt; findCustomersByLastNameSorted(@Param(&quot;lastName&quot;) String name);

    @Query(&quot;SELECT c.* FROM customers c JOIN customer_orders co ON c.id = co.customer_id WHERE co.order_date &amp;gt; :cutoffDate&quot;)
    List&amp;lt;Customer&amp;gt; findCustomersWithRecentOrders(@Param(&quot;cutoffDate&quot;) LocalDate cutoffDate);

    // 네이티브 SQL 사용 가능 (데이터베이스 특정 기능 활용)
    @Query(&quot;SELECT * FROM customers WHERE UPPER(last_name) = UPPER(:lastName)&quot;)
    List&amp;lt;Customer&amp;gt; findByLastNameIgnoreCaseCustom(@Param(&quot;lastName&quot;) String lastName);
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Query를 사용하면 SQL에 대한 완전한 제어권을 가지므로, 복잡한 로직 구현이나 성능 튜닝에 유리합니다. 이는 Spring Data JDBC가 지향하는 'SQL에 더 가까운' 철학과도 일맥상통합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Modifying을 이용한 수정 쿼리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Query를 사용하여 UPDATE 또는 DELETE 문을 실행하는 경우, 해당 메서드에 @Modifying 어노테이션을 추가해야 합니다.&lt;span&gt;&lt;/span&gt; 이는 해당 메서드가 데이터를 변경하는 작업임을 명시적으로 나타냅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744869477640&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.query.Param;

public interface CustomerRepository extends ListCrudRepository&amp;lt;Customer, Long&amp;gt; {

    @Modifying
    @Query(&quot;UPDATE customers SET last_name = :newLastName WHERE id = :id&quot;)
    boolean updateLastName(@Param(&quot;id&quot;) Long id, @Param(&quot;newLastName&quot;) String newLastName); // [4] 예시 기반

    @Modifying
    @Query(&quot;DELETE FROM customers WHERE last_login_date &amp;lt; :cutoffDate&quot;)
    int deleteInactiveCustomers(@Param(&quot;cutoffDate&quot;) LocalDate cutoffDate); // 삭제된 행 수 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환 타입은 주로 변경된 행의 수를 나타내는 int 또는 long, 혹은 변경 성공 여부를 나타내는 boolean입니다. @Modifying 어노테이션은 데이터를 읽기만 하는 쿼리와 변경하는 쿼리를 명확히 구분하여, 실수로 데이터가 변경되는 것을 방지하는 안전장치 역할을 합니다. 프레임워크는 이 어노테이션을 통해 트랜잭션 처리 등 실행 컨텍스트를 적절히 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;페이징 및 정렬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PagingAndSortingRepository 인터페이스를 확장하면, Pageable (페이지 번호, 페이지 크기, 정렬 정보 포함) 및 Sort 객체를 파라미터로 받는 메서드를 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744869520907&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;

public interface CustomerRepository extends PagingAndSortingRepository&amp;lt;Customer, Long&amp;gt; {

    // last_name으로 검색하고 결과를 페이징 처리하여 반환
    Page&amp;lt;Customer&amp;gt; findByLastName(String lastName, Pageable pageable);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;생명주기 이벤트 처리: 영속성 과정 개입&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JDBC는 애그리거트의 영속성 생명주기(저장, 삭제, 로드 등) 동안 특정 시점에 개입하여 사용자 정의 로직을 실행할 수 있는 메커니즘을 제공합니다.&lt;span&gt;&lt;/span&gt; 이는 Spring의 ApplicationEvent 또는 특정 Callback 인터페이스를 구현하는 빈(Bean)을 통해 이루어집니다.&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;주요 콜백 인터페이스&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;511:1-511:87&quot;&gt;&lt;b&gt;BeforeConvertCallback&amp;lt;T&amp;gt;&lt;/b&gt;: 엔티티가 영속성을 위해 내부 표현으로 변환되기 전에 호출됩니다. 기본값 설정 등에 유용합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;512:1-512:102&quot;&gt;&lt;b&gt;BeforeSaveCallback&amp;lt;T&amp;gt;&lt;/b&gt;: 애그리거트 루트가 저장(INSERT 또는 UPDATE)되기 직전에 호출됩니다. 유효성 검사나 최종 수정에 사용할 수 있습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;513:1-513:60&quot;&gt;&lt;b&gt;AfterSaveCallback&amp;lt;T&amp;gt;&lt;/b&gt;: 애그리거트 루트가 성공적으로 저장된 후에 호출됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;514:1-514:62&quot;&gt;&lt;b&gt;BeforeDeleteCallback&amp;lt;T, ID&amp;gt;&lt;/b&gt;: 애그리거트 루트가 삭제되기 전에 호출됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;515:1-515:66&quot;&gt;&lt;b&gt;AfterDeleteCallback&amp;lt;T, ID&amp;gt;&lt;/b&gt;: 애그리거트 루트가 성공적으로 삭제된 후에 호출됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;516:1-517:0&quot;&gt;&lt;b&gt;AfterLoadCallback&amp;lt;T&amp;gt;&lt;/b&gt;: 애그리거트 루트가 데이터베이스에서 로드된 후에 호출됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현 예시 (개념)&lt;/h3&gt;
&lt;pre id=&quot;code_1744869772563&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
import java.time.LocalDateTime;

@Configuration
public class JdbcCallbackConfig {

    // Customer 엔티티 저장 전 호출될 콜백 빈 등록
    @Bean
    BeforeSaveCallback&amp;lt;Customer&amp;gt; customerBeforeSaveCallback() {
        return (customer, aggregateChange) -&amp;gt; {
            // 예: 생성/수정 시간 설정 (Auditing [3] 구현의 일부)
            // if (customer.getCreatedAt() == null) {
            //     customer.setCreatedAt(LocalDateTime.now());
            // }
            // customer.setUpdatedAt(LocalDateTime.now());

            System.out.println(&quot;Saving customer: &quot; + customer.getId());
            return customer; // 변경된 엔티티 또는 원본 엔티티 반환 필수
        };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 콜백 메커니즘은 감사(Auditing) 정보 기록, 타임스탬프 설정 등 영속성과 관련된 횡단 관심사(cross-cutting concerns)를 엔티티나 리포지토리 코드와 분리하여 깔끔하게 구현할 수 있게 해줍니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;548:1-548:253&quot; data-ke-size=&quot;size16&quot;&gt;JPA의 @EntityListeners나 @PrePersist, @PostLoad 같은 엔티티 직접 어노테이션 방식과 달리, Spring Data JDBC는 Spring의 이벤트/콜백 빈 메커니즘을 사용합니다. 이는 생명주기 로직이 도메인 객체 자체보다는 Spring 컨테이너 구성에 더 가깝게 연결됨을 의미합니다. 이는 관심사 분리 측면에서는 장점일 수 있으나, 엔티티 코드만 봐서는 관련 로직을 파악하기 어려울 수 있다는 단점도 가집니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;548:1-548:253&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;548:1-548:253&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Spring Data JDBC vs Spring Data JPA: 올바른 도구 선택&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;548:1-548:253&quot; data-ke-size=&quot;size16&quot;&gt;Spring 생태계에서 관계형 데이터 접근을 위한 두 가지 주요 선택지인 Spring Data JDBC와 Spring Data JPA는 근본적인 철학과 기능 범위에서 차이가 있습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;548:1-548:253&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;548:1-548:253&quot; data-ke-size=&quot;size23&quot;&gt;핵심 차이점: ORM vs 간결한 매퍼&lt;/h3&gt;
&lt;p data-sourcepos=&quot;548:1-548:253&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;556:1-556:231&quot;&gt;&lt;b&gt;Spring Data JPA:&lt;/b&gt; JPA(Java Persistence API) 명세를 구현한 ORM(Object-Relational Mapper) 프레임워크(주로 Hibernate 사용)입니다. 엔티티 관리, 1차/2차 캐시, 지연 로딩(Lazy Loading), 변경 감지(Dirty Checking), JPQL/Criteria API 등 풍부한 기능을 제공하여 개발 생산성을 높여줍니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;557:1-558:0&quot;&gt;&lt;b&gt;Spring Data JDBC:&lt;/b&gt; ORM이 아닙니다. 애그리거트 중심의 단순한 객체 매퍼로, ORM의 복잡한 기능들을 제공하지 않는 대신 &lt;span&gt;&lt;/span&gt;, SQL에 대한 직접적인 제어와 예측 가능한 동작을 강조합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 차이점 비교표&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;561:1-573:100&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;기능&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Spring Data JDBC 3.4.4&lt;/td&gt;
&lt;td&gt;Spring Data JPA (Hibernate)&lt;/td&gt;
&lt;td&gt;중요성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;563:1-563:81&quot;&gt;
&lt;td data-sourcepos=&quot;563:1-563:10&quot;&gt;&lt;b&gt;패러다임&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;563:12-563:33&quot;&gt;간결한 객체 매퍼 (애그리거트 중심)&lt;/td&gt;
&lt;td data-sourcepos=&quot;563:35-563:52&quot;&gt;완전한 ORM (엔티티 중심)&lt;/td&gt;
&lt;td data-sourcepos=&quot;563:54-563:79&quot;&gt;복잡성, 추상화 수준, 제공 기능 범위 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;564:1-564:69&quot;&gt;
&lt;td data-sourcepos=&quot;564:1-564:12&quot;&gt;&lt;b&gt;핵심 추상화&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;564:14-564:36&quot;&gt;애그리거트 루트 (DDD) &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;564:38-564:42&quot;&gt;엔티티&lt;/td&gt;
&lt;td data-sourcepos=&quot;564:44-564:67&quot;&gt;데이터 모델링 및 관계 처리 방식에 영향&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;565:1-565:93&quot;&gt;
&lt;td data-sourcepos=&quot;565:1-565:8&quot;&gt;&lt;b&gt;캐싱&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;565:10-565:26&quot;&gt;내장 캐시 없음 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;565:28-565:49&quot;&gt;1차 캐시(필수), 2차 캐시(선택)&lt;/td&gt;
&lt;td data-sourcepos=&quot;565:51-565:91&quot;&gt;성능 영향 (예측 가능성 vs 잠재적 속도 향상), 복잡성 증가 가능성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;566:1-566:83&quot;&gt;
&lt;td data-sourcepos=&quot;566:1-566:11&quot;&gt;&lt;b&gt;지연 로딩&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;566:13-566:23&quot;&gt;없음 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;566:25-566:36&quot;&gt;지원 (설정 가능)&lt;/td&gt;
&lt;td data-sourcepos=&quot;566:38-566:81&quot;&gt;성능 (N+1 문제 가능성 vs 필요한 데이터만 로딩), 복잡성 증가 가능성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;567:1-567:84&quot;&gt;
&lt;td data-sourcepos=&quot;567:1-567:11&quot;&gt;&lt;b&gt;쓰기 지연&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;567:13-567:20&quot;&gt;없음 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;567:22-567:45&quot;&gt;지원 (세션 플러시 시 변경 사항 반영)&lt;/td&gt;
&lt;td data-sourcepos=&quot;567:47-567:82&quot;&gt;SQL 업데이트 실행 시점 및 방식, 트랜잭션 동작 방식 차이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;568:1-568:79&quot;&gt;
&lt;td data-sourcepos=&quot;568:1-568:11&quot;&gt;&lt;b&gt;변경 감지&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;568:13-568:16&quot;&gt;없음&lt;/td&gt;
&lt;td data-sourcepos=&quot;568:18-568:32&quot;&gt;지원 (자동 변경 감지)&lt;/td&gt;
&lt;td data-sourcepos=&quot;568:34-568:77&quot;&gt;단순성 vs 자동 업데이트 편의성. JDBC는 명시적 save 호출 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;569:1-569:129&quot;&gt;
&lt;td data-sourcepos=&quot;569:1-569:11&quot;&gt;&lt;b&gt;쿼리 언어&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;569:13-569:42&quot;&gt;SQL (@Query), 파생 쿼리 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;569:44-569:82&quot;&gt;JPQL, Criteria API, Native SQL, 파생 쿼리&lt;/td&gt;
&lt;td data-sourcepos=&quot;569:84-569:127&quot;&gt;이식성(JPQL/Criteria) vs DB 특정 기능 활용(SQL) 용이성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;570:1-570:115&quot;&gt;
&lt;td data-sourcepos=&quot;570:1-570:12&quot;&gt;&lt;b&gt;스키마 관리&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;570:14-570:51&quot;&gt;없음 (Flyway/Liquibase 등 외부 도구 필요) &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;570:53-570:86&quot;&gt;지원 (예: hibernate.hbm2ddl.auto)&lt;/td&gt;
&lt;td data-sourcepos=&quot;570:88-570:113&quot;&gt;편의성 vs 스키마 변경에 대한 명시적 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;571:1-571:71&quot;&gt;
&lt;td data-sourcepos=&quot;571:1-571:9&quot;&gt;&lt;b&gt;복잡성&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;571:11-571:24&quot;&gt;낮음 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;571:26-571:29&quot;&gt;높음&lt;/td&gt;
&lt;td data-sourcepos=&quot;571:31-571:69&quot;&gt;학습 곡선, 프레임워크 내부 동작의 &quot;마법&quot; 같은 요소 존재 가능성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;572:1-572:70&quot;&gt;
&lt;td data-sourcepos=&quot;572:1-572:9&quot;&gt;&lt;b&gt;제어권&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;572:11-572:29&quot;&gt;더 직접적인 SQL 제어 &lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;572:31-572:43&quot;&gt;더 높은 추상화 수준&lt;/td&gt;
&lt;td data-sourcepos=&quot;572:45-572:68&quot;&gt;투명성 vs 프레임워크가 세부 사항 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;573:1-573:100&quot;&gt;
&lt;td data-sourcepos=&quot;573:1-573:8&quot;&gt;&lt;b&gt;성능&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;573:10-573:35&quot;&gt;예측 가능성 높음, 수동 최적화 필요 가능성&lt;/td&gt;
&lt;td data-sourcepos=&quot;573:37-573:74&quot;&gt;캐싱 활용 시 고성능 가능, 부주의 시 N+1 등 이슈 발생 위험&lt;/td&gt;
&lt;td data-sourcepos=&quot;573:76-573:98&quot;&gt;사용 사례 및 구성에 따라 크게 달라짐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Spring Data JDBC 선택 시점&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 상황에서 Spring Data JDBC가 좋은 선택이 될 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;579:1-585:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;579:1-579:35&quot;&gt;ORM의 복잡성을 피하고 단순함을 선호할 때.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;580:1-580:149&quot;&gt;도메인 모델에 명확한 애그리거트 경계가 존재하고 DDD 원칙을 따르고자 할 때.&lt;span&gt;&lt;/span&gt; Spring Data JDBC의 핵심 추상화인 애그리거트는 DDD 패턴과 직접적으로 일치하여, DDD를 명시적으로 따르는 프로젝트에 개념적으로 더 잘 맞을 수 있습니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;581:1-581:35&quot;&gt;실행되는 SQL에 대한 직접적인 제어가 중요할 때.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;582:1-582:66&quot;&gt;캐싱이나 지연 로딩으로 인한 부작용 없이 예측 가능한 성능 특성을 선호할 때 (수동 최적화가 필요할 수 있음).&lt;/li&gt;
&lt;li data-sourcepos=&quot;583:1-583:41&quot;&gt;레거시 데이터베이스 스키마와 작업하거나 ORM 매핑이 복잡한 경우.&lt;/li&gt;
&lt;li data-sourcepos=&quot;584:1-585:0&quot;&gt;팀이 프레임워크의 암묵적인 동작보다는 명시적인 작업을 선호할 때.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Data JPA 선택 시점&lt;/h3&gt;
&lt;div&gt;
&lt;p data-sourcepos=&quot;588:1-588:46&quot; data-ke-size=&quot;size16&quot;&gt;반면, 다음과 같은 경우에는 Spring Data JPA가 더 적합할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;590:1-595:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;590:1-590:31&quot;&gt;복잡한 엔티티 그래프를 자주 탐색해야 하는 경우.&lt;/li&gt;
&lt;li data-sourcepos=&quot;591:1-591:53&quot;&gt;읽기 작업이 많은 시나리오에서 내장 캐시 및 지연 로딩을 통한 성능 향상이 필요한 경우.&lt;/li&gt;
&lt;li data-sourcepos=&quot;592:1-592:58&quot;&gt;초기 개발 단계에서의 자동 스키마 생성이나 자동 변경 감지 기능이 개발 속도에 도움이 되는 경우.&lt;/li&gt;
&lt;li data-sourcepos=&quot;593:1-593:54&quot;&gt;JPQL 또는 Criteria API를 통한 데이터베이스 이식성이 중요한 요구사항인 경우.&lt;/li&gt;
&lt;li data-sourcepos=&quot;594:1-595:0&quot;&gt;팀이 이미 JPA/Hibernate에 익숙하고 관련 경험이 풍부한 경우.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;596:1-596:188&quot; data-ke-size=&quot;size16&quot;&gt;궁극적으로 Spring Data JDBC와 JPA 사이의 선택은 원하는 추상화 수준과 제어 수준 사이의 트레이드오프입니다. JDBC는 낮은 추상화 수준에서 더 많은 제어권과 예측 가능성을 제공하는 반면, JPA는 높은 추상화 수준에서 다양한 편의 기능을 제공하지만 복잡성이 증가하고 내부 동작을 이해해야 하는 부담이 따릅니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;596:1-596:188&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론: Spring Data JDBC로 단순성 수용하기&lt;/b&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p data-sourcepos=&quot;600:1-600:169&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data JDBC 3.4.4는 Spring 생태계 내에서 관계형 데이터베이스와 상호작용하는 강력하면서도 간결한 방법을 제공합니다. ORM의 복잡성을 제거하고 애그리거트라는 명확한 개념에 집중함으로써, 개발자는 실행되는 SQL에 대한 더 나은 제어권과 예측 가능한 동작을 확보할 수 있습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;600:1-600:169&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;602:1-602:209&quot; data-ke-size=&quot;size16&quot;&gt;특히 도메인 주도 설계를 적용하거나, 단순성을 중시하거나, SQL에 대한 직접적인 제어가 필요한 프로젝트에서 Spring Data JDBC는 Spring Data JPA의 훌륭한 대안이 될 수 있습니다. 설정의 용이성(특히 Spring Boot 환경에서), 명시적인 관계 매핑, 사용자 정의 쿼리 지원 등은 개발자가 데이터 접근 계층을 효과적으로 구축하는 데 도움을 줍니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;602:1-602:209&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;604:1-604:268&quot; data-ke-size=&quot;size16&quot;&gt;물론 캐싱, 지연 로딩과 같은 고급 ORM 기능이 필요한 경우에는 여전히 Spring Data JPA가 더 적합할 수 있습니다. 하지만 프로젝트의 요구사항과 팀의 선호도에 따라 Spring Data JDBC 3.4.4가 제공하는 단순성과 제어의 균형이 더 매력적인 선택지가 될 수 있음을 기억해야 합니다. Spring Data 프로젝트는 지속적으로 발전하고 있으며 &lt;span&gt;&lt;/span&gt;, Spring Data JDBC는 현대적인 애플리케이션 개발에서 중요한 역할을 계속 수행할 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>jdbc</category>
      <category>Spring</category>
      <category>Spring Boot</category>
      <category>spring data jdbc</category>
      <author>kahnco</author>
      <guid isPermaLink="true">https://kahnco.tistory.com/46</guid>
      <comments>https://kahnco.tistory.com/46#entry46comment</comments>
      <pubDate>Thu, 17 Apr 2025 15:08:18 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Annotation 기반 Bean 생명주기</title>
      <link>https://kahnco.tistory.com/45</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;서론: Spring 의 Ioc 컨테이너와 의존성 주입 (DI)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 프레임워크의 핵심 가치는 제어의 역전(Inversion of Control, IoC) 컨테이너와 의존성 주입(Dependency Injection, DI) 메커니즘에 있습니다.&lt;span&gt; &lt;/span&gt;이러한 개념은 최신 엔터프라이즈 애플리케이션 개발에서 느슨한 결합(Loose Coupling)과 높은 테스트 용이성을 달성하기 위한 기반을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;제어의 역전 (IoC):&lt;/b&gt; 전통적인 프로그래밍에서는 객체가 자신이 사용할 다른 객체를 직접 생성하거나 찾는 반면, IoC에서는 객체 생성, 생명주기 관리, 의존성 연결 등의 제어권이 개발자의 코드에서 외부 컨테이너(Spring IoC 컨테이너)로 이전됩니다.&lt;span&gt;&lt;/span&gt; 이는 객체 간의 결합도를 낮추고 코드의 유연성과 재사용성을 높이는 핵심 원리입니다. 컨테이너는 설정 메타데이터(XML, 어노테이션, Java 코드)를 사용하여 객체(빈)를 관리합니다.&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;의존성 주입 (DI):&lt;/b&gt; DI는 IoC를 구현하는 구체적인 디자인 패턴입니다. 객체가 필요로 하는 의존성(다른 객체)을 직접 생성하거나 찾는 대신, 외부(컨테이너)에서 해당 의존성을 객체에 주입(전달)해주는 방식입니다. 의존성은 주로 생성자 인수, 팩토리 메소드 인수, 또는 객체 인스턴스가 생성된 후 설정되는 속성(setter 메소드)을 통해 주입됩니다.&lt;span&gt; &lt;/span&gt;DI를 통해 코드는 더욱 명확해지고, 의존하는 객체의 구체적인 구현이나 위치를 알 필요가 없어 효과적인 분리가 가능해집니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;ApplicationContext:&lt;/b&gt; ApplicationContext는 Spring의 중심적인 IoC 컨테이너 인터페이스입니다.&lt;span&gt;&lt;/span&gt; 기본적인 빈 관리 기능을 제공하는 BeanFactory 인터페이스를 확장하여, 트랜잭션 관리, 국제화(i18n) 지원, 이벤트 발행 등 다양한 엔터프라이즈급 기능을 추가로 제공합니다.&lt;span&gt;&lt;/span&gt; 대부분의 Spring 애플리케이션에서는 ApplicationContext 구현체(예: AnnotationConfigApplicationContext, ClassPathXmlApplicationContext)를 IoC 컨테이너로 사용합니다. 이는 설정 메타데이터를 로드하고, 빈 정의를 해석하며, 빈 인스턴스를 생성하고 의존성을 주입하는 전체 과정을 관리합니다.&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;빈 (Beans):&lt;/b&gt; 빈은 Spring IoC 컨테이너에 의해 인스턴스화되고, 조립되고, 관리되는 객체들을 의미합니다.&lt;span&gt;&lt;/span&gt; 컨테이너는 설정 메타데이터(빈 정의)에 기술된 정보를 바탕으로 빈을 생성하고 관리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 문서는 Spring Framework &lt;b&gt;6.2.5 버전&lt;/b&gt;을 기준으로 어노테이션 기반 빈 관리 메커니즘을 상세히 설명합니다.&lt;span&gt;&lt;/span&gt; 이 버전까지 도입된 개선 사항과 변경점들을 반영하여, 빈이 어떻게 발견되고, 정의되고, 인스턴스화되며, 의존성이 주입되어 관리되는지 전체 생명주기를 다룹니다. Spring의 핵심 설계 원칙 중 하나는 설정 메타데이터와 애플리케이션 로직의 분리이며, IoC와 DI는 이를 가능하게 하는 기반 기술입니다. 따라서 컨테이너가 빈을 관리하는 방식을 이해하는 것은 Spring 프레임워크를 효과적으로 활용하는 데 필수적입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1단계: 컴포넌트 스캔을 통한 빈 발견&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션 기반 설정에서 Spring은 모든 빈을 XML이나 Java &lt;b&gt;@Bean&lt;/b&gt; 메소드로 명시적으로 정의하는 대신, 특정 어노테이션이 붙은 클래스를 자동으로 찾아 빈으로 등록하는 메커니즘을 제공합니다. 이것이 바로 컴포넌트 스캔(Component Scan)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;22:1-22:26&quot; data-ke-size=&quot;size23&quot;&gt;@ComponentScan 어노테이션&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;24:1-32:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;24:1-24:164&quot;&gt;&lt;b&gt;목적:&lt;/b&gt; @ComponentScan은 주로 @Configuration 어노테이션이 붙은 클래스에 사용하여 컴포넌트 스캔을 활성화하고 설정하는 역할을 합니다.&lt;span&gt;&lt;/span&gt; 이 어노테이션을 통해 Spring 컨테이너는 지정된 패키지 내에서 빈으로 관리할 후보 클래스들을 찾습니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;25:1-25:276&quot;&gt;&lt;b&gt;기본 동작:&lt;/b&gt; @ComponentScan에 아무런 인수를 지정하지 않으면, 해당 어노테이션이 선언된 @Configuration 클래스가 위치한 패키지와 그 하위 패키지 전체를 재귀적으로 스캔합니다.&lt;span&gt;&lt;/span&gt; 이러한 기본 동작은 프로젝트의 구성을 논리적으로 구조화하도록 유도합니다. 예를 들어, 애플리케이션의 메인 설정 클래스가 com.example.app 패키지에 있다면, Spring은 com.example.app 및 그 아래 모든 패키지에서 컴포넌트를 찾습니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;26:1-30:236&quot;&gt;&lt;b&gt;설정 속성:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;27:5-30:236&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;27:5-27:195&quot;&gt;basePackages / value: 스캔할 기본 패키지를 문자열 배열로 명시적으로 지정합니다. value는 basePackages의 별칭(alias)입니다.&lt;span&gt;&lt;/span&gt; 예: @ComponentScan(basePackages = {&quot;com.example.service&quot;, &quot;com.example.repository&quot;}).&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;28:5-28:253&quot;&gt;basePackageClasses: 문자열 기반 패키지 이름 대신, 타입-안전(type-safe)한 방식으로 스캔할 패키지를 지정합니다. 여기에 명시된 클래스가 속한 패키지를 스캔 대상으로 삼습니다.&lt;span&gt;&lt;/span&gt; 예: @ComponentScan(basePackageClasses = {UserService.class, ProductRepository.class}). 이는 리팩토링 시 패키지 이름 변경에 더 안전한 대안이 될 수 있습니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;29:5-29:265&quot;&gt;includeFilters / excludeFilters: 기본 스캔 대상 외에 추가적으로 포함하거나 제외할 타입을 지정하는 필터 규칙을 정의할 수 있습니다. 필터 타입으로는 어노테이션, 상속 가능한 타입(assignable type), AspectJ 표현식, 정규 표현식 등이 사용될 수 있어 매우 유연한 제어가 가능합니다.&lt;span&gt;&lt;/span&gt; 예를 들어, 특정 커스텀 어노테이션이 붙은 클래스만 포함하거나, 특정 인터페이스를 구현하는 클래스를 제외할 수 있습니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;30:5-30:236&quot;&gt;useDefaultFilters: 기본 필터(스테레오타입 어노테이션 대상)의 사용 여부를 제어합니다. 기본값은 true이며, 이 경우 @Component, @Repository, @Service, @Controller 등의 어노테이션이 붙은 클래스를 자동으로 감지합니다.&lt;span&gt;&lt;/span&gt; false로 설정하면 기본 필터를 사용하지 않고 includeFilters에 명시된 규칙만 적용됩니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;31:1-32:0&quot;&gt;&lt;b&gt;반복 가능한 어노테이션:&lt;/b&gt; Java 8 및 Spring 4.3부터 @ComponentScan은 반복 가능한(repeatable) 어노테이션이 되었습니다. 따라서 동일한 설정 클래스에 여러 개의 @ComponentScan을 선언할 수 있으며, 이는 내부적으로 @ComponentScans 컨테이너 어노테이션으로 처리됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;후보 컴포넌트 식별&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;35:1-38:131&quot;&gt;&lt;b&gt;스테레오타입 어노테이션:&lt;/b&gt; useDefaultFilters=true (기본값)일 때, Spring은 지정된 패키지 내에서 &lt;b&gt;@Component&lt;/b&gt; 어노테이션이나 &lt;b&gt;@Component&lt;/b&gt;를 메타 어노테이션으로 가지는 어노테이션이 붙은 클래스를 찾습니다.&lt;span&gt;&lt;/span&gt; 대표적인 스테레오타입 어노테이션은 다음과 같습니다:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;36:5-36:111&quot;&gt;&lt;b&gt;@Repository&lt;/b&gt;: 데이터 접근 계층(DAO)의 빈을 나타냅니다. 데이터 접근 관련 예외를 Spring의 DataAccessException으로 변환해주는 기능도 포함합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;36:5-36:111&quot;&gt;&lt;b&gt;@Service&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;: 서비스 계층(비즈니스 로직)의 빈을 나타냅니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;36:5-36:111&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;@Controller / @RestController&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;: 프레젠테이션 계층(웹 컨트롤러)의 빈을 나타냅니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;@RestController&lt;/b&gt;는&amp;nbsp;&lt;b&gt;@Controller&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;@ResponseBody&lt;/b&gt;를 합친 것입니다.&lt;/span&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;39:1-40:0&quot;&gt;&lt;b&gt;커스텀 어노테이션:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;@Component를 메타 어노테이션으로 사용하여 특정 목적을 가지는 커스텀 스테레오타입 어노테이션을 만들 수 있습니다. 예를 들어, @UseCase 어노테이션을 만들고 @Component를 붙이면, @UseCase가 붙은 클래스도 컴포넌트 스캔 대상이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스캔 프로세스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring은 설정된 패키지 내의 클래스패스(classpath)를 스캔하여 .class 파일을 찾습니다. 그런 다음 리플렉션(reflection)이나 더 효율적인 ASM 라이브러리를 사용하여 클래스 메타데이터(특히 어노테이션 정보)를 읽어 후보 컴포넌트인지 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring 6.2 특이사항 및 고려사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;47:1-48:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;47:1-48:0&quot;&gt;&lt;b&gt;더 엄격해진 규칙:&lt;/b&gt; Spring Framework 6.2부터 컴포넌트 스캔은 BeanFactory 초기화 과정에서 비교적 일찍 수행됩니다. 따라서, 빈 팩토리의 후반 단계에서 평가되는 조건(예: 다른 빈의 존재 여부에 따라 결정되는 Spring Boot의 @ConditionalOnBean)을 사용하여 @ComponentScan이 포함된 @Configuration 클래스의 활성화 여부를 제어하려고 하면 컨텍스트 로딩이 실패할 수 있습니다.&lt;span&gt;&lt;/span&gt; 이는 스캔이 필요한 시점에는 해당 조건을 안정적으로 평가할 수 없기 때문입니다. 이 변화는 애플리케이션 컨텍스트 생명주기 단계에 대한 더 엄격한 적용을 반영하며, 예측 가능하고 오류 발생 가능성이 적은 부트스트랩 과정을 지향함을 시사합니다. 개발자는 스캔 활성화/비활성화를 위한 조건부 설계를 할 때, 컨텍스트 시작 초기에 사용 가능한 정보(예: 환경 속성, 클래스 존재 여부)에 기반한 조건을 사용해야 합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;49:1-49:145&quot; data-ke-size=&quot;size16&quot;&gt;컴포넌트 스캔은 명시적인 빈 설정을 크게 줄여주며, 어떤 애플리케이션 구성 요소가 Spring에 의해 관리될지를 결정하는 핵심 메커니즘입니다. basePackages나 필터와 같은 설정 옵션을 이해하는 것은 애플리케이션의 빈 관리를 제어하는 데 중요합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;49:1-49:145&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2단계: 빈 정의(Bean Definition 생성)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;49:1-49:145&quot; data-ke-size=&quot;size16&quot;&gt;컴포넌트 스캔을 통해 후보 클래스를 찾는 것은 첫 단계일 뿐입니다. Spring은 즉시 해당 클래스의 인스턴스를 생성하지 않고, 대신 각 후보 컴포넌트에 대한 BeanDefinition 객체를 생성합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;49:1-49:145&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;49:1-49:145&quot; data-ke-size=&quot;size23&quot;&gt;Bean Definition&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;57:1-57:212&quot;&gt;&lt;b&gt;목적:&lt;/b&gt; BeanDefinition은 빈 인스턴스를 생성하는 데 필요한 모든 메타데이터를 담고 있는 일종의 '레시피' 또는 '청사진'입니다.&lt;span&gt;&lt;/span&gt; 이는 빈이 실제로 인스턴스화되기 전에 컨테이너 내부에 존재하는 빈의 표현입니다. BeanDefinition은 빈 생성의 구체적인 방법을 추상화하여 컨테이너가 빈의 생명주기를 일관되게 관리할 수 있도록 합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;58:1-68:0&quot;&gt;&lt;b&gt;주요 저장 메타데이터:&lt;/b&gt; 어노테이션으로부터 추출되어 BeanDefinition에 저장되는 주요 정보는 다음과 같습니다:
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;59:5-68:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;59:5-59:64&quot;&gt;&lt;b&gt;빈 클래스 이름:&lt;/b&gt; 인스턴스화될 클래스의 정규화된 이름(fully qualified name).&lt;/li&gt;
&lt;li data-sourcepos=&quot;60:5-60:171&quot;&gt;&lt;b&gt;빈 이름:&lt;/b&gt; 클래스 이름을 기반으로 생성되거나(단순 클래스 이름의 첫 글자를 소문자로 변경) @Component(&quot;myBeanName&quot;)과 같이 명시적으로 지정된 이름.&lt;span&gt;&lt;/span&gt; BeanNameGenerator 인터페이스 구현체를 통해 이름 생성 전략을 커스터마이징할 수도 있습니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;61:5-61:190&quot;&gt;&lt;b&gt;스코프(Scope):&lt;/b&gt; 빈 인스턴스의 생명주기와 공유 범위를 결정합니다 (예: singleton, prototype, request, session). 기본값은 singleton입니다. @Scope 어노테이션을 통해 지정되며, ScopeMetadataResolver에 의해 해석됩니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;62:5-62:63&quot;&gt;&lt;b&gt;생성자 인수 / 속성 값:&lt;/b&gt; 의존성 주입에 필요한 정보 (실제 의존성 해결은 나중에 수행됨).&lt;/li&gt;
&lt;li data-sourcepos=&quot;63:5-63:132&quot;&gt;&lt;b&gt;지연 초기화(Lazy Initialization):&lt;/b&gt; 빈을 컨테이너 시작 시 즉시 생성할지(false, 기본값), 아니면 처음 요청될 때 생성할지(true) 여부. @Lazy 어노테이션으로 제어됩니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;64:5-64:111&quot;&gt;&lt;b&gt;Primary 상태:&lt;/b&gt; 동일 타입의 여러 빈 후보 중 자동 와이어링(autowiring) 시 우선적으로 선택될지 여부. @Primary 어노테이션으로 지정됩니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;65:5-65:284&quot;&gt;&lt;b&gt;Fallback 상태 (6.2 신규):&lt;/b&gt; 동일 타입의 다른 빈(Primary 또는 일반 빈)이 없을 경우에만 사용될 후보임을 나타냅니다. @Fallback 어노테이션으로 지정됩니다.&lt;span&gt;&lt;/span&gt; 이는 기본 구현을 제공하되 사용자가 특정 구현을 제공하면 그것을 우선시하는 시나리오를 단순화합니다. 이전에는 사용자가 자신의 빈에 @Primary를 붙여 기본 구현을 덮어써야 했지만, @Fallback은 기본 구현 자체에 '후보'임을 명시적으로 표시하여 의도를 더 명확하게 합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;66:5-66:90&quot;&gt;&lt;b&gt;Depends On:&lt;/b&gt; 해당 빈이 초기화되기 전에 먼저 초기화되어야 하는 다른 빈들의 이름. @DependsOn 어노테이션으로 지정됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;67:5-68:0&quot;&gt;&lt;b&gt;초기화/소멸 메소드:&lt;/b&gt; 빈 인스턴스화 후 또는 소멸 전에 호출될 메소드. @PostConstruct, @PreDestroy (JSR-250) 어노테이션 등으로 지정됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 BeanDefinition 객체들은 컨테이너의 BeanDefinitionRegistry (주로 ApplicationContext 자체가 구현)에 등록됩니다. 이 레지스트리는 컨테이너가 관리하는 모든 빈의 정의를 보관하는 중앙 저장소 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;73:1-73:139&quot; data-ke-size=&quot;size16&quot;&gt;BeanDefinition 단계는 컴포넌트 발견과 실제 생성 사이의 중요한 중간 과정입니다. 이를 통해 컨테이너는 전체 설정을 검증하고, 빈 간의 의존성을 파악하며, 인스턴스를 만들기 전에 복잡한 생명주기를 관리할 준비를 할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;73:1-73:139&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3단계: 빈 인스턴스화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;73:1-73:139&quot; data-ke-size=&quot;size16&quot;&gt;BeanDefinition이 등록된 후, 컨테이너는 이 정보를 바탕으로 실제 빈 객체 인스턴스를 생성하는 단계로 진행합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;73:1-73:139&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;73:1-73:139&quot; data-ke-size=&quot;size23&quot;&gt;인스턴스화 트리거&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;81:1-81:177&quot;&gt;&lt;b&gt;기본 동작 (Singleton):&lt;/b&gt; 기본 스코프인 싱글톤(singleton) 빈은 일반적으로 ApplicationContext가 리프레시되거나 시작될 때 미리 인스턴스화됩니다(pre-instantiation).&lt;span&gt;&lt;/span&gt; 이는 애플리케이션 시작 시 필요한 빈들을 준비시켜 런타임 성능을 확보하기 위함입니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;82:1-82:93&quot;&gt;&lt;b&gt;지연 초기화 (Lazy):&lt;/b&gt; @Lazy 어노테이션이 붙은 싱글톤 빈은 컨테이너 시작 시 생성되지 않고, 해당 빈이 처음으로 요청될 때 인스턴스화됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;83:1-84:0&quot;&gt;&lt;b&gt;프로토타입 (Prototype):&lt;/b&gt; 프로토타입 스코프의 빈은 컨테이너 시작 시 생성되지 않으며, 요청될 때마다 새로운 인스턴스가 생성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인스턴스화 프로세스&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;87:1-87:79&quot;&gt;&lt;b&gt;BeanDefinition 조회:&lt;/b&gt; 컨테이너는 생성할 빈에 해당하는 BeanDefinition을 레지스트리에서 조회합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;88:1-92:334&quot;&gt;&lt;b&gt;생성자 선택:&lt;/b&gt; 의존성을 생성자를 통해 주입해야 하는 경우, Spring은 사용할 생성자를 결정해야 합니다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot; data-sourcepos=&quot;88:1-92:334&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;li data-sourcepos=&quot;88:1-92:334&quot;&gt;&lt;b&gt;단일 생성자:&lt;/b&gt;&amp;nbsp;클래스에 생성자가 하나만 정의되어 있다면, Spring은 그 생성자를 사용합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;88:1-92:334&quot;&gt;&lt;b&gt;@Autowired 명시:&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;여러 생성자가 있고 그중 하나에 @Autowired 어노테이션이 명시되어 있다면, Spring은 해당 생성자를 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;88:1-92:334&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;b&gt;다중 생성자 (암시적 선택 주의):&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;여러 생성자가 있는데 @Autowired가 명시되지 않은 경우, 과거 버전의 Spring이나 특정 상황에서는 의존성을 가장 많이 만족시킬 수 있는 '가장 탐욕스러운(greediest)' 생성자를 찾으려는 시도를 할 수 있었습니다. 하지만 이 동작에 의존하는 것은 권장되지 않습니다. 명확성을 위해, 특히 여러 생성자가 있는 경우에는 사용할 생성자에 @Autowired를 명시하는 것이 좋습니다. Spring 6.x 버전부터는 일반적으로 의존성 주입을 위한 단일 생성자를 권장합니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;88:1-92:334&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;b&gt;파라미터 이름 해결:&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Spring 6.x부터는 기본적으로 바이트코드를 파싱하여 생성자 파라미터 이름을 추론하지 않습니다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만약 파라미터 이름을 기반으로 한 생성자 주입(예: @Qualifier 없이 이름으로 매칭)을 사용하려면, 컴파일 시 Java 컴파일러 옵션 -parameters 플래그를 반드시 추가해야 합니다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 플래그를 사용하면 파라미터 이름이 바이트코드에 저장되어 런타임에 리플렉션을 통해 접근할 수 있습니다. 이는 잠재적으로 불안정할 수 있는 바이트코드 분석 대신 더 명시적인 설정(어노테이션 또는 빌드 구성)을 선호하는 경향을 반영합니다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;93:1-93:119&quot;&gt;&lt;b&gt;팩토리 메소드 사용:&lt;/b&gt; BeanDefinition에 생성자 대신 정적(static) 또는 인스턴스 팩토리 메소드가 지정되어 있다면, 컨테이너는 해당 팩토리 메소드를 호출하여 빈 인스턴스를 얻습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;94:1-95:0&quot;&gt;&lt;b&gt;객체 생성:&lt;/b&gt; 선택된 생성자나 팩토리 메소드가 리플렉션(reflection)을 통해 호출되어 실제 new 연산이 실행되거나 팩토리 메소드가 실행됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;초기 상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에서 '원시(raw)' 빈 인스턴스가 메모리에 생성됩니다. 하지만 생성자 주입을 사용하지 않았다면, 아직 세터(setter)나 필드(field)를 통한 의존성 주입은 이루어지지 않았으며, @PostConstruct와 같은 커스텀 초기화 메소드도 호출되지 않은 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;100:1-100:125&quot; data-ke-size=&quot;size16&quot;&gt;인스턴스화는 BeanDefinition이라는 청사진이 실제 객체로 구체화되는 단계입니다. 컨테이너가 올바른 생성자나 팩토리 메소드를 선택하는 로직은, 특히 여러 선택지가 있을 때, 정확한 빈 생성을 위해 매우 중요합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;100:1-100:125&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;100:1-100:125&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4단계: 의존성 주입 (속성 채우기)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원시 빈 인스턴스가 생성된 후, 컨테이너는 세터 주입이나 필드 주입 방식을 사용하는 경우 해당 빈의 의존성을 주입(populate)하는 과정을 진행합니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;100:1-100:125&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;100:1-100:125&quot; data-ke-size=&quot;size23&quot;&gt;DI 전략 및 Spring 권장 사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;108:1-111:85&quot;&gt;&lt;b&gt;생성자 주입 (Constructor Injection):&lt;/b&gt; 의존성이 생성자의 인수로 제공되며, 빈 인스턴스화 시점에 주입이 완료됩니다 (3단계에서 처리됨). &lt;b&gt;Spring 팀에서 일반적으로 권장하는 방식&lt;/b&gt;입니다.&lt;span&gt;&lt;/span&gt; 그 이유는 다음과 같습니다:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;108:1-111:85&quot;&gt;&lt;b&gt;필수 의존성 강제:&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체 생성 시점에 필수 의존성이 반드시 제공되어야 하므로, null 상태의 의존성을 가질 가능성이 줄어듭니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;108:1-111:85&quot;&gt;&lt;b&gt;불변성(Immutability) 확보:&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;final 키워드를 사용하여 필드를 선언하고 생성자에서 초기화함으로써 불변 객체를 만들 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;108:1-111:85&quot;&gt;&lt;b&gt;완전한 초기화 상태:&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;생성자 주입을 사용하는 컴포넌트는 클라이언트 코드에 반환될 때 항상 완전히 초기화된 상태임을 보장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;112:1-112:328&quot;&gt;&lt;b&gt;세터 주입 (Setter Injection):&lt;/b&gt; 빈 인스턴스가 생성된 후, 컨테이너가 세터(setter) 메소드를 호출하여 의존성을 주입합니다.&lt;span&gt;&lt;/span&gt; 이 방식은 &lt;b&gt;선택적(optional) 의존성&lt;/b&gt;에 주로 사용되며, 해당 의존성이 주입되지 않더라도 클래스 내에서 합리적인 기본값을 할당할 수 있는 경우에 적합합니다.&lt;span&gt;&lt;/span&gt; 만약 주입된 의존성이 null일 가능성이 있다면, 해당 의존성을 사용하는 모든 코드 위치에서 null 검사를 수행해야 합니다 (단, Spring은 빈 초기화 완료 전에 세터 주입을 수행하므로 일반적으로 사용 시점에는 주입이 완료된 상태입니다).&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;113:1-117:0&quot;&gt;&lt;b&gt;필드 주입 (Field Injection):&lt;/b&gt; 리플렉션을 사용하여 필드(private 포함)에 직접 의존성을 주입합니다. &lt;b&gt;일반적으로 권장되지 않는 방식&lt;/b&gt;입니다. 그 이유는 다음과 같습니다:
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;114:5-117:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;114:5-114:84&quot;&gt;&lt;b&gt;테스트 어려움:&lt;/b&gt; 단위 테스트 시 목(mock) 객체를 주입하기 위해 리플렉션을 사용해야 하므로 테스트 코드 작성이 번거로워집니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;115:5-115:83&quot;&gt;&lt;b&gt;의존성 숨김:&lt;/b&gt; 의존성이 생성자나 세터 메소드 시그니처에 드러나지 않아 클래스가 어떤 의존성을 필요로 하는지 파악하기 어렵습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;116:5-117:0&quot;&gt;&lt;b&gt;불변성 위반 가능성:&lt;/b&gt; final 필드에 주입할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어노테이션 기반 주입 메커니즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션은 의존성 주입 프로세스를 트리거하며, 이 작업은 주로 BeanPostProcessor(6단계에서 설명)에 의해 처리됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;122:1-131:164&quot;&gt;&lt;b&gt;@Autowired (Spring 전용):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;123:5-131:164&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;123:5-123:122&quot;&gt;&lt;b&gt;메커니즘:&lt;/b&gt; 생성자, 필드, 세터 메소드, 또는 설정 메소드(@Bean 메소드의 파라미터 등)에 표시하여 Spring DI 컨테이너에 의한 자동 와이어링(autowiring) 대상으로 지정합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;124:5-131:164&quot;&gt;&lt;b&gt;해결 전략:&lt;/b&gt; 주로 &lt;b&gt;타입(Type)&lt;/b&gt;을 기준으로 의존성을 찾습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;125:9-131:164&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;125:9-125:59&quot;&gt;&lt;b&gt;단일 후보:&lt;/b&gt; 해당 타입의 빈이 컨테이너에 하나만 존재하면 그 빈이 주입됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;126:9-130:266&quot;&gt;&lt;b&gt;다중 후보:&lt;/b&gt; 동일 타입의 빈이 여러 개 존재하면 추가적인 기준을 사용하여 후보를 좁힙니다:
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;127:13-130:266&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;127:13-127:113&quot;&gt;@Qualifier: 빈 이름이나 커스텀 한정자(qualifier) 값을 사용하여 특정 빈을 지정합니다. 예: @Qualifier(&quot;specificBeanName&quot;).&lt;/li&gt;
&lt;li data-sourcepos=&quot;128:13-128:72&quot;&gt;@Primary: 여러 후보 중 @Primary 어노테이션이 붙은 빈이 우선적으로 선택됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;129:13-129:368&quot;&gt;&lt;b&gt;Spring 6.2 개선 사항:&lt;/b&gt; 여러 후보 중 모호성을 해결할 때, 파라미터 이름 매칭과 @Qualifier 매칭이 @jakarta.annotation.Priority 랭킹보다 우선적으로 고려됩니다.&lt;span&gt;&lt;/span&gt; (이전 버전에서는 @Priority가 먼저 체크될 수 있었습니다). @Primary는 여전히 가장 높은 우선순위를 가집니다. 이 변경은 단일 후보를 선택할 때, @Priority의 일반적인 순위 메커니즘보다는 더 명시적이고 직접적인 한정자(Qualifier)나 이름 매칭을 선호하는 설계 의도를 반영합니다. @Priority는 주로 컬렉션에 주입되는 여러 빈의 순서를 정하는 데 더 적합합니다.&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;130:13-130:266&quot;&gt;&lt;b&gt;Spring 6.2 깊어진 제네릭 매칭:&lt;/b&gt; Spring 6.2는 더 깊어진 제네릭 타입 매칭 기능을 제공합니다.&lt;span&gt;&lt;/span&gt; 이전 버전에서는 관대하게 매칭되던 제네릭 시그니처가 6.2에서는 더 엄격하게 검사되어 매칭되지 않을 수 있습니다. 이는 주입 지점(예: 생성자 인수)과 빈 정의(예: @Bean 메소드의 반환 타입) 간의 제네릭 시그니처가 더 정확하게 일치해야 함을 의미하며, 타입 안정성을 강화하려는 움직임으로 볼 수 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;131:9-131:164&quot;&gt;&lt;b&gt;필수 여부:&lt;/b&gt; 매칭되는 빈이 없으면 기본적으로 예외가 발생합니다. @Autowired(required=false)로 설정하면 해당 의존성을 선택적으로 만들 수 있으며, 이 경우 매칭되는 빈이 없으면 null (또는 컬렉션/맵의 경우 빈 인스턴스)이 주입됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;132:1-137:101&quot;&gt;&lt;b&gt;@Resource (Jakarta EE / JSR-250):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;133:5-137:101&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;133:5-133:145&quot;&gt;&lt;b&gt;메커니즘:&lt;/b&gt; 의존성 주입을 위한 표준 Java 어노테이션입니다. 필드나 세터 메소드에 적용될 수 있습니다. Spring 6.x 및 Jakarta EE 9+ 환경에서는 jakarta.annotation.Resource를 사용합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;134:5-137:101&quot;&gt;&lt;b&gt;해결 전략:&lt;/b&gt; 주로 &lt;b&gt;이름(Name)&lt;/b&gt;을 기준으로 의존성을 찾습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;134:5-137:101&quot;&gt;&lt;b&gt;이름 우선:&lt;/b&gt; 먼저 필드 이름이나 세터 메소드의 프로퍼티 이름과 일치하는 빈 이름을 찾습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;134:5-137:101&quot;&gt;&lt;b&gt;타입 폴백:&lt;/b&gt; 이름으로 매칭되는 빈을 찾지 못하면, &lt;b&gt;타입&lt;/b&gt;을 기준으로 다시 매칭을 시도합니다 (이때 @Autowired와 유사하게 동작).&lt;/li&gt;
&lt;li data-sourcepos=&quot;134:5-137:101&quot;&gt;&lt;b&gt;명시적 이름 지정:&lt;/b&gt; @Resource(name=&quot;specificBeanName&quot;) 속성을 사용하여 주입할 빈의 이름을 명시적으로 지정할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;138:1-142:0&quot;&gt;&lt;b&gt;@Inject (Jakarta DI / JSR-330):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;139:5-142:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;139:5-139:199&quot;&gt;&lt;b&gt;메커니즘:&lt;/b&gt; 의존성 주입을 위한 또 다른 표준 Java 어노테이션으로, Jakarta Dependency Injection 사양(구 JSR-330)의 일부입니다. @Autowired와 매우 유사하게 동작합니다. Spring 6.x 및 Jakarta EE 9+ 환경에서는 jakarta.inject.Inject를 사용합니다.&lt;span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;140:5-142:0&quot;&gt;&lt;b&gt;해결 전략:&lt;/b&gt; 주로 &lt;b&gt;타입(Type)&lt;/b&gt;을 기준으로 의존성을 찾습니다.&lt;span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;141:9-142:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;141:9-142:0&quot;&gt;&lt;b&gt;모호성 해결:&lt;/b&gt; 동일 타입의 빈이 여러 개 있을 경우, JSR-330 표준의 @Qualifier (특히 @Named 어노테이션)나 프로바이더(Spring) 고유의 메커니즘(예: Spring의 @Primary, @Qualifier)을 사용하여 해결합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 해결 프로세스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너는 주입 지점(생성자 파라미터, 세터 메소드 파라미터, 필드)에서 요구하는 타입 및/또는 이름(그리고 한정자)과 일치하는 빈을 찾기 위해 자신의 BeanDefinition 레지스트리를 검색합니다.&lt;span&gt;&lt;/span&gt; 적절한 빈을 찾으면 해당 빈의 인스턴스(또는 인스턴스에 대한 참조)를 주입 지점에 전달합니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;147:1-147:165&quot; data-ke-size=&quot;size16&quot;&gt;어떤 DI 전략(생성자, 세터, 필드)과 어노테이션(@Autowired, @Resource, @Inject)을 선택하는지는 코드의 명확성, 테스트 용이성, 디자인 원칙 준수에 영향을 미칩니다. 필수 의존성에 대해 생성자 주입을 권장하는 Spring의 가이드라인은 강력한 지침이 됩니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;147:1-147:165&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;147:1-147:165&quot; data-ke-size=&quot;size23&quot;&gt;DI 어노테이션 비교&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;151:1-157:90&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr data-sourcepos=&quot;151:1-151:102&quot;&gt;
&lt;td&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;@Autowired (Spring)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;@Resource (Jakarta EE / JSR-250)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;@Inject (Jakarta DI / JSR-330)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;153:1-153:80&quot;&gt;
&lt;td data-sourcepos=&quot;153:1-153:8&quot;&gt;&lt;b&gt;표준&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;153:10-153:26&quot;&gt;Spring 프레임워크 고유&lt;/td&gt;
&lt;td data-sourcepos=&quot;153:28-153:52&quot;&gt;Jakarta EE 표준 (JSR-250)&lt;/td&gt;
&lt;td data-sourcepos=&quot;153:54-153:78&quot;&gt;Jakarta DI 표준 (JSR-330)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;154:1-154:64&quot;&gt;
&lt;td data-sourcepos=&quot;154:1-154:14&quot;&gt;&lt;b&gt;주요 해결 방식&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;154:16-154:30&quot;&gt;&lt;b&gt;타입 (Type)&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;154:32-154:46&quot;&gt;&lt;b&gt;이름 (Name)&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;154:48-154:62&quot;&gt;&lt;b&gt;타입 (Type)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;155:1-155:150&quot;&gt;
&lt;td data-sourcepos=&quot;155:1-155:14&quot;&gt;&lt;b&gt;폴백 해결 방식&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;155:16-155:60&quot;&gt;이름 (파라미터/필드 이름), @Qualifier, @Primary 등&lt;/td&gt;
&lt;td data-sourcepos=&quot;155:62-155:72&quot;&gt;타입 (Type)&lt;/td&gt;
&lt;td data-sourcepos=&quot;155:74-155:148&quot;&gt;@Named (표준 Qualifier), 프로바이더별 메커니즘 (Spring의 @Qualifier, @Primary 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;156:1-156:104&quot;&gt;
&lt;td data-sourcepos=&quot;156:1-156:14&quot;&gt;&lt;b&gt;필수 여부 처리&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;156:16-156:36&quot;&gt;required=false 속성&lt;/td&gt;
&lt;td data-sourcepos=&quot;156:38-156:79&quot;&gt;(표준에는 없음, Spring은 @Autowired와 유사하게 처리)&lt;/td&gt;
&lt;td data-sourcepos=&quot;156:81-156:102&quot;&gt;(표준에는 없음, 프로바이더별 처리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;157:1-157:90&quot;&gt;
&lt;td data-sourcepos=&quot;157:1-157:21&quot;&gt;&lt;b&gt;한정자 (Qualifier)&lt;/b&gt;&lt;/td&gt;
&lt;td data-sourcepos=&quot;157:23-157:36&quot;&gt;@Qualifier&lt;/td&gt;
&lt;td data-sourcepos=&quot;157:38-157:54&quot;&gt;name=&quot;...&quot; 속성&lt;/td&gt;
&lt;td data-sourcepos=&quot;157:56-157:88&quot;&gt;@Named, 프로바이더별 @Qualifier 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&lt;span&gt; &lt;/span&gt;표는&lt;span&gt; &lt;/span&gt;개발자가&lt;span&gt; &lt;/span&gt;프로젝트의&lt;span&gt; &lt;/span&gt;요구사항과&lt;span&gt; &lt;/span&gt;표준&lt;span&gt; &lt;/span&gt;준수&lt;span&gt; &lt;/span&gt;여부에&lt;span&gt; &lt;/span&gt;따라&lt;span&gt; &lt;/span&gt;적절한&lt;span&gt; DI &lt;/span&gt;어노테이션을&lt;span&gt; &lt;/span&gt;선택하는&lt;span&gt; &lt;/span&gt;데&lt;span&gt; &lt;/span&gt;도움을&lt;span&gt; &lt;/span&gt;줄&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;. @Autowired&lt;/span&gt;는&lt;span&gt; Spring &lt;/span&gt;생태계&lt;span&gt; &lt;/span&gt;내에서&lt;span&gt; &lt;/span&gt;가장&lt;span&gt; &lt;/span&gt;널리&lt;span&gt; &lt;/span&gt;사용되지만&lt;span&gt;, &lt;/span&gt;표준&lt;span&gt; &lt;/span&gt;어노테이션인&lt;span&gt; @Resource&lt;/span&gt;와&lt;span&gt; @Inject&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;이해하는&lt;span&gt; &lt;/span&gt;것은&lt;span&gt; Jakarta EE &lt;/span&gt;환경과의&lt;span&gt; &lt;/span&gt;상호&lt;span&gt; &lt;/span&gt;운용성&lt;span&gt; &lt;/span&gt;및&lt;span&gt; &lt;/span&gt;표준&lt;span&gt; &lt;/span&gt;준수를&lt;span&gt; &lt;/span&gt;위해&lt;span&gt; &lt;/span&gt;중요합니다&lt;span&gt;. &lt;/span&gt;특히&lt;span&gt; &lt;/span&gt;이름&lt;span&gt; &lt;/span&gt;기반&lt;span&gt; &lt;/span&gt;매칭이&lt;span&gt; &lt;/span&gt;필요할&lt;span&gt; &lt;/span&gt;때는&lt;span&gt; @Resource&lt;/span&gt;가&lt;span&gt; &lt;/span&gt;유용할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;5단계: 빈 후처리기 (Bean Post-Processing)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;빈 인스턴스화 및 기본적인 의존성 주입 이후, Spring은 빈의 생명주기에 개입하여 추가적인 처리를 수행할 수 있는 강력한 확장 메커니즘을 제공합니다. 이것이 바로 BeanPostProcessor 인터페이스입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;빈 생명주기 확장&lt;/span&gt;&lt;/h3&gt;
&lt;p data-sourcepos=&quot;167:1-167:261&quot; data-ke-size=&quot;size16&quot;&gt;BeanPostProcessor 인터페이스는 컨테이너의 빈 생명주기에 사용자 정의 로직을 연결(plug in)할 수 있게 해주는 콜백 인터페이스입니다. 이 후처리기들은 빈 인스턴스가 생성된 후에, 그리고 커스텀 초기화 메소드(@PostConstruct 등)가 호출되기 전 (postProcessBeforeInitialization 메소드)과 후 (postProcessAfterInitialization 메소드)에 실행될 기회를 가집니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;167:1-167:261&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;167:1-167:261&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;동작 방식&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;171:1-171:132&quot;&gt;&lt;b&gt;등록:&lt;/b&gt; BeanPostProcessor 자체도 Spring 컨테이너에 의해 관리되는 빈입니다. 컨테이너는 시작 시점에 자신의 설정 내에 정의된 모든 BeanPostProcessor 빈들을 감지하고 특별히 등록합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;172:1-175:0&quot;&gt;&lt;b&gt;호출:&lt;/b&gt; 컨테이너가 일반 빈을 생성하는 과정에서, 등록된 모든 BeanPostProcessor들이 순서대로 해당 빈 인스턴스에 대해 호출됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;173:5-175:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;173:5-173:230&quot;&gt;postProcessBeforeInitialization(Object bean, String beanName): 빈의 초기화 콜백(예: @PostConstruct 메소드, InitializingBean의 afterPropertiesSet)이 호출되기 직전에 실행됩니다. 원본 빈 인스턴스를 반환하거나, 필요하다면 래핑(wrapping)된 인스턴스(예: 프록시)를 반환할 수 있습니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;174:5-175:0&quot;&gt;postProcessAfterInitialization(Object bean, String beanName): 빈의 초기화 콜백이 호출된 직후에 실행됩니다. 마찬가지로 원본 또는 래핑된 인스턴스를 반환할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어노테이션 처리를 위한 주요 구현체&lt;/h3&gt;
&lt;p data-sourcepos=&quot;178:1-178:126&quot; data-ke-size=&quot;size16&quot;&gt;앞서 설명한 @Autowired, @Resource, @PostConstruct, @PreDestroy 등의 어노테이션 기반 기능들은 실제로는 특정 BeanPostProcessor 구현체들에 의해 처리됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;180:1-182:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;180:1-180:198&quot;&gt;&lt;b&gt;AutowiredAnnotationBeanPostProcessor:&lt;/b&gt; @Autowired와 @Value 어노테이션 (그리고 JSR-330의 @Inject도 처리 가능)을 감지하고 처리하는 핵심 후처리기입니다. 이 프로세서는 해당 어노테이션이 붙은 필드나 메소드를 찾아 리플렉션을 사용하여 의존성 조회 및 주입 로직을 수행합니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;181:1-182:0&quot;&gt;&lt;b&gt;CommonAnnotationBeanPostProcessor:&lt;/b&gt; JSR-250 표준 어노테이션인 @Resource, @PostConstruct, @PreDestroy를 처리합니다. @Resource 어노테이션에 대한 의존성 주입을 수행하고, @PostConstruct와 @PreDestroy로 지정된 생명주기 콜백 메소드를 적절한 시점에 호출하는 역할을 담당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 순서&lt;/h3&gt;
&lt;p data-sourcepos=&quot;185:1-185:154&quot; data-ke-size=&quot;size16&quot;&gt;여러 BeanPostProcessor가 등록된 경우, 실행 순서가 중요할 수 있습니다. Spring은 Ordered 인터페이스를 구현하거나 @Order 어노테이션을 사용하여 후처리기들의 실행 순서를 제어할 수 있도록 지원합니다. 순서 값이 낮을수록 먼저 실행됩니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;185:1-185:154&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;187:1-187:212&quot; data-ke-size=&quot;size16&quot;&gt;BeanPostProcessor는 Spring의 어노테이션 기반 기능들이 실제로 동작하게 만드는 '마법' 뒤의 일꾼들입니다. 이들은 어노테이션 처리 로직을 핵심 컨테이너 로직과 분리하여 프레임워크의 높은 확장성을 가능하게 합니다. 이들의 역할을 이해하면 @Autowired와 같은 어노테이션이 어떻게 의존성 주입을 실제로 수행하는지 그 내부 메커니즘을 파악할 수 있습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;187:1-187:212&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;187:1-187:212&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고: AOP 프록시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;191:1-191:105&quot; data-ke-size=&quot;size16&quot;&gt;빈의 생명주기 동안, 특히 빈 후처리 단계에서, Aspect-Oriented Programming (AOP) 설정에 따라 원본 빈 인스턴스가 프록시(Proxy) 객체로 대체될 수 있습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;191:1-191:105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;191:1-191:105&quot; data-ke-size=&quot;size23&quot;&gt;AOP 통합&lt;/h3&gt;
&lt;p data-sourcepos=&quot;191:1-191:105&quot; data-ke-size=&quot;size16&quot;&gt;Spring AOP는 선언적 트랜잭션 관리(@Transactional), 보안(@Secured 등), 로깅 등과 같은 횡단 관심사(cross-cutting concerns)를 모듈화하는 데 사용됩니다.&lt;span&gt;&lt;/span&gt; AOP가 적용되도록 설정된 빈의 경우, Spring은 해당 빈의 메소드 호출을 가로채서 부가 기능(Advice)을 적용하기 위해 원본 빈 객체를 감싸는 프록시 객체를 생성할 필요가 있습니다.&lt;span&gt;&lt;/span&gt; Spring AOP는 기본적으로 프록시 패턴 기반으로 동작합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;191:1-191:105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;191:1-191:105&quot; data-ke-size=&quot;size23&quot;&gt;프록시 생성 시점&lt;/h3&gt;
&lt;p data-sourcepos=&quot;191:1-191:105&quot; data-ke-size=&quot;size16&quot;&gt;이러한 프록시 객체 생성은 일반적으로 빈 후처리 단계(5단계) 에서 이루어집니다. AbstractAutoProxyCreator와 같은 특수한 BeanPostProcessor 구현체들이 이 역할을 담당하는 경우가 많습니다.&lt;span&gt;&lt;/span&gt; 구체적으로는, 원본 빈 인스턴스가 생성되고 기본적인 의존성 주입이 완료된 후, 초기화 콜백(@PostConstruct 등)이 호출되기 전이나 후에 프록시가 생성될 수 있습니다 (주로 postProcessAfterInitialization 단계에서 발생).&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;191:1-191:105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;191:1-191:105&quot; data-ke-size=&quot;size23&quot;&gt;영향&lt;/h3&gt;
&lt;p data-sourcepos=&quot;203:1-203:176&quot; data-ke-size=&quot;size16&quot;&gt;AOP 프록시가 생성되면, 다른 빈에 의존성으로 주입되는 것은 원본 빈 인스턴스가 아니라 이 프록시 객체입니다. 따라서 해당 빈의 메소드를 호출하면 실제로는 프록시 객체를 통해 호출이 이루어지며, 이때 AOP 어드바이스(예: 트랜잭션 시작/커밋/롤백 로직)가 먼저 실행된 후 원본 빈의 메소드가 호출될 수 있습니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;203:1-203:176&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;205:1-205:138&quot; data-ke-size=&quot;size16&quot;&gt;AOP는 빈 생명주기에 매끄럽게 통합되어, 주입되는 객체 참조가 원본 객체가 아닐 수도 있다는 점을 인지하는 것이 중요합니다. 이는 특히 트랜잭션이나 보안과 같은 AOP 기능이 적용된 빈을 디버깅하거나 동작을 이해할 때 중요한 맥락 정보가 됩니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;205:1-205:138&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;187:1-187:212&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론: Spring 6.2.5 의 어노테이션 기반 빈 생명주기 요약&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;209:1-209:69&quot; data-ke-size=&quot;size16&quot;&gt;Spring Framework 6.2.5에서 어노테이션 기반으로 관리되는 빈은 다음과 같은 단계를 거쳐 생성되고 준비됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-sourcepos=&quot;211:1-218:0&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;211:1-211:243&quot;&gt;&lt;b&gt;스캔 (Scan):&lt;/b&gt; @Configuration 클래스에 선언된 @ComponentScan 설정에 따라, 지정된 패키지 내에서 @Component 및 관련 스테레오타입 어노테이션(@Service, @Repository, @Controller 등)이 붙은 클래스를 탐색합니다.&lt;span&gt;&lt;/span&gt; (Spring 6.2부터 특정 후행 조건과 함께 @ComponentScan 사용 시 제약 강화 &lt;span&gt;&lt;/span&gt;)&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;212:1-212:196&quot;&gt;&lt;b&gt;정의 (Define):&lt;/b&gt; 스캔된 각 후보 클래스에 대해, 빈의 생성 및 관리에 필요한 모든 메타데이터(클래스 정보, 스코프, 이름, 의존성 정보, 생명주기 콜백 등)를 포함하는 BeanDefinition 객체를 생성하여 컨테이너의 레지스트리에 등록합니다.&lt;span&gt;&lt;/span&gt; (Spring 6.2에 @Fallback 빈 개념 도입 &lt;span&gt;&lt;/span&gt;)&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;213:1-213:166&quot;&gt;&lt;b&gt;인스턴스화 (Instantiate):&lt;/b&gt; BeanDefinition 정보를 바탕으로, 적절한 생성자나 팩토리 메소드를 호출하여 빈의 '원시' 인스턴스를 생성합니다.&lt;span&gt;&lt;/span&gt; (Spring 6.x는 생성자 파라미터 이름 해결을 위해 -parameters 컴파일러 플래그 권장 &lt;span&gt;&lt;/span&gt;)&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;214:1-214:315&quot;&gt;&lt;b&gt;속성 채우기 (Populate):&lt;/b&gt; BeanPostProcessor (예: AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor)가 동작하여 @Autowired, @Resource, @Inject 등의 어노테이션을 처리하고, 정의된 의존성을 빈 인스턴스에 주입합니다.&lt;span&gt;&lt;/span&gt; (Spring 6.2에서 @Autowired 모호성 해결 시 @Qualifier/이름 매칭이 @Priority보다 우선 &lt;span&gt;&lt;/span&gt;, 제네릭 타입 매칭 강화 &lt;span&gt;&lt;/span&gt;)&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;215:1-215:218&quot;&gt;&lt;b&gt;초기화 (Initialize):&lt;/b&gt; BeanPostProcessor (예: CommonAnnotationBeanPostProcessor)가 @PostConstruct와 같은 초기화 어노테이션을 처리하여 지정된 커스텀 초기화 메소드를 호출합니다. InitializingBean 인터페이스 구현 시 afterPropertiesSet 메소드도 이 단계에서 호출됩니다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;216:1-216:143&quot;&gt;&lt;b&gt;(선택적) 프록시 생성 (Proxy):&lt;/b&gt; AOP 설정이 적용된 경우, 다른 BeanPostProcessor (예: AbstractAutoProxyCreator)가 원본 빈 인스턴스를 감싸는 AOP 프록시 객체를 생성할 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &amp;nbsp; &lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;217:1-218:0&quot;&gt;&lt;b&gt;준비 완료 (Ready):&lt;/b&gt; 모든 초기화 및 후처리 과정이 완료된 빈(원본 또는 프록시)은 이제 애플리케이션에서 사용될 준비가 된 상태이며, 다른 빈에 주입되거나 컨테이너로부터 직접 조회될 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Spring_Framework&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Spring_Framework&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;figure id=&quot;og_1744862826559&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Framework - Wikipedia&quot; data-og-description=&quot;From Wikipedia, the free encyclopedia This article is about the Spring Framework. For the Spring Boot, see Spring Boot. Application framework for Java platform The Spring Framework is an application framework and inversion of control container for the Java&quot; data-og-host=&quot;en.wikipedia.org&quot; data-og-source-url=&quot;https://en.wikipedia.org/wiki/Spring_Framework&quot; data-og-url=&quot;https://en.wikipedia.org/wiki/Spring_Framework&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Spring_Framework&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://en.wikipedia.org/wiki/Spring_Framework&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Framework - Wikipedia&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;From Wikipedia, the free encyclopedia This article is about the Spring Framework. For the Spring Boot, see Spring Boot. Application framework for Java platform The Spring Framework is an application framework and inversion of control container for the Java&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;en.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744862833670&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Dependency Injection :: Spring Framework&quot; data-og-description=&quot;Constructor-based DI is accomplished by the container invoking a constructor with a number of arguments, each representing a dependency. Calling a static factory method with specific arguments to construct the bean is nearly equivalent, and this discussion&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Dependency Injection :: Spring Framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Constructor-based DI is accomplished by the container invoking a constructor with a number of arguments, each representing a dependency. Calling a static factory method with specific arguments to construct the bean is nearly equivalent, and this discussion&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744862843193&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ComponentScan (Spring Framework 6.2.5 API)&quot; data-og-description=&quot;Indicates whether automatic detection of classes annotated with @Component @Repository, @Service, or @Controller should be enabled.&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ComponentScan (Spring Framework 6.2.5 API)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Indicates whether automatic detection of classes annotated with @Component @Repository, @Service, or @Controller should be enabled.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/core/beans.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744862851338&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;The IoC Container :: Spring Framework&quot; data-og-description=&quot;Preview&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/reference/core/beans.html&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/reference/core/beans.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/reference/core/beans.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;The IoC Container :: Spring Framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Preview&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Annotation</category>
      <category>Bean lifecycle</category>
      <category>Spring</category>
      <category>spring bean</category>
      <category>빈 생명주기</category>
      <author>kahnco</author>
      <guid isPermaLink="true">https://kahnco.tistory.com/45</guid>
      <comments>https://kahnco.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 17 Apr 2025 13:08:29 +0900</pubDate>
    </item>
    <item>
      <title>[CS] UUID v4 vs v7</title>
      <link>https://kahnco.tistory.com/44</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보편적으로 고유한 식별자(Universally Unique Identifiers, UUIDs)는 컴퓨터 시스템 전반에 걸쳐 정보를 고유하게 식별하는 데 필수적인 128비트 숫자입니다.&lt;span&gt;&lt;/span&gt; 분산 시스템과 데이터베이스에서 정보의 무결성을 보장하는 데 중요한 역할을 하며, 현대 컴퓨팅 환경에서 그 중요성은 날로 증가하고 있습니다.&lt;span&gt;&lt;/span&gt; 다양한 UUID 버전 중에서 버전 4는 오랫동안 널리 사용되어 왔지만, 최근에는 시간 기반 정렬 기능을 갖춘 버전 7이 등장하여 주목받고 있습니다.&lt;span&gt;&lt;/span&gt; 애플리케이션의 특정 요구 사항에 따라 적절한 UUID 버전을 선택하는 것은 매우 중요하며, 이를 위해서는 각 버전의 특징과 장단점에 대한 깊이 있는 이해가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.cockroachlabs.com/blog/what-is-a-uuid/&quot;&gt;https://www.cockroachlabs.com/blog/what-is-a-uuid/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744855055222&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;What is a UUID, and what is it used for?&quot; data-og-description=&quot;You need to identify your database rows somehow. Calling them row 1, row 2, etc. comes with some hidden hazards. There's a better way: UUIDs.&quot; data-og-host=&quot;www.cockroachlabs.com&quot; data-og-source-url=&quot;https://www.cockroachlabs.com/blog/what-is-a-uuid/&quot; data-og-url=&quot;https://www.cockroachlabs.com/blog/what-is-a-uuid/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cVW192/hyYIhAMp0m/vEVE8KjkiSfwWvQT6hJgxk/img.jpg?width=2400&amp;amp;height=800&amp;amp;face=0_0_2400_800,https://scrap.kakaocdn.net/dn/butuqc/hyYH5G6FE9/FP10VVsUkcKwKJFqlK2LVK/img.jpg?width=2400&amp;amp;height=800&amp;amp;face=0_0_2400_800,https://scrap.kakaocdn.net/dn/cPZWkg/hyYIfCXwoy/mkzQOBFhQ9i9vPKfVti1Xk/img.png?width=1941&amp;amp;height=373&amp;amp;face=0_0_1941_373&quot;&gt;&lt;a href=&quot;https://www.cockroachlabs.com/blog/what-is-a-uuid/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.cockroachlabs.com/blog/what-is-a-uuid/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cVW192/hyYIhAMp0m/vEVE8KjkiSfwWvQT6hJgxk/img.jpg?width=2400&amp;amp;height=800&amp;amp;face=0_0_2400_800,https://scrap.kakaocdn.net/dn/butuqc/hyYH5G6FE9/FP10VVsUkcKwKJFqlK2LVK/img.jpg?width=2400&amp;amp;height=800&amp;amp;face=0_0_2400_800,https://scrap.kakaocdn.net/dn/cPZWkg/hyYIfCXwoy/mkzQOBFhQ9i9vPKfVti1Xk/img.png?width=1941&amp;amp;height=373&amp;amp;face=0_0_1941_373');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;What is a UUID, and what is it used for?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;You need to identify your database rows somehow. Calling them row 1, row 2, etc. comes with some hidden hazards. There's a better way: UUIDs.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.cockroachlabs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9562.html&quot;&gt;https://www.rfc-editor.org/rfc/rfc9562.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744855061596&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;RFC 9562: Universally Unique IDentifiers (UUIDs)&quot; data-og-description=&quot;The authors gratefully acknowledge the contributions of Rich Salz, Michael Mealling, Ben Campbell, Ben Ramsey, Fabio Lima, Gonzalo Salgueiro, Martin Thomson, Murray S. Kucherawy, Rick van Rein, Rob Wilton, Sean Leonard, Theodore Y. Ts'o, Robert Kieffer, Se&quot; data-og-host=&quot;www.rfc-editor.org&quot; data-og-source-url=&quot;https://www.rfc-editor.org/rfc/rfc9562.html&quot; data-og-url=&quot;https://www.rfc-editor.org/rfc/rfc9562.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9562.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.rfc-editor.org/rfc/rfc9562.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RFC 9562: Universally Unique IDentifiers (UUIDs)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The authors gratefully acknowledge the contributions of Rich Salz, Michael Mealling, Ben Campbell, Ben Ramsey, Fabio Lima, Gonzalo Salgueiro, Martin Thomson, Murray S. Kucherawy, Rick van Rein, Rob Wilton, Sean Leonard, Theodore Y. Ts'o, Robert Kieffer, Se&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.rfc-editor.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc4122&quot;&gt;https://datatracker.ietf.org/doc/html/rfc4122&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744855066232&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace&quot; data-og-description=&quot;This specification defines a Uniform Resource Name namespace for UUIDs (Universally Unique IDentifier), also known as GUIDs (Globally Unique IDentifier). A UUID is 128 bits long, and can guarantee uniqueness across space and time. UUIDs were originally use&quot; data-og-host=&quot;datatracker.ietf.org&quot; data-og-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc4122&quot; data-og-url=&quot;https://datatracker.ietf.org/doc/html/rfc4122&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qgpP4/hyYIa2IWLW/gvIiIRow55fHeRZDK038ck/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc4122&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc4122&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qgpP4/hyYIa2IWLW/gvIiIRow55fHeRZDK038ck/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This specification defines a Uniform Resource Name namespace for UUIDs (Universally Unique IDentifier), also known as GUIDs (Globally Unique IDentifier). A UUID is 128 bits long, and can guarantee uniqueness across space and time. UUIDs were originally use&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;datatracker.ietf.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;성능 비교&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 생성 속도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID v7은 UUID v4와 유사하거나 약간 느린 생성 속도를 보입니다. 일부 벤치마크에서는 UUID v7의 생성 속도가 UUID v4와 거의 동일하거나 약간 빠르다고 나타냅니다.&lt;span&gt;&lt;/span&gt; Rust 기반 구현에서는 UUID v7 생성이 UUID v4보다 약 40% 느릴 수 있지만, 여전히 매우 빠른 수준입니다.&lt;span&gt;&lt;/span&gt; Go 언어 기반 벤치마크에서는 UUID v7 생성이 UUID v4와 비슷한 성능을 보입니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://ardentperf.com/2024/02/03/uuid-benchmark-war/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ardentperf.com/2024/02/03/uuid-benchmark-war/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744854887276&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;UUID Benchmark War&quot; data-og-description=&quot;This month&amp;rsquo;s PGSQL Phriday #015 topic is about UUIDs, hosted by L&amp;aelig;titia Avrot. L&amp;aelig;titia has called for a debate. No, no, no. I say let&amp;rsquo;s have an all-out war. A benchmark war. I have deci&amp;hellip;&quot; data-og-host=&quot;ardentperf.com&quot; data-og-source-url=&quot;https://ardentperf.com/2024/02/03/uuid-benchmark-war/&quot; data-og-url=&quot;http://ardentperf.com/2024/02/03/uuid-benchmark-war/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/x7VFN/hyYHd6UUek/BJKWQQgkiH4VauPvi1xue0/img.png?width=3110&amp;amp;height=1732&amp;amp;face=0_0_3110_1732,https://scrap.kakaocdn.net/dn/kPGO9/hyYIg9ImWb/2moKJwXmM5kvGXXMvHMj3k/img.png?width=639&amp;amp;height=356&amp;amp;face=0_0_639_356,https://scrap.kakaocdn.net/dn/iUsbc/hyYIil9vck/iuJHe076IisHynyWdckvv0/img.png?width=1023&amp;amp;height=743&amp;amp;face=0_0_1023_743&quot;&gt;&lt;a href=&quot;https://ardentperf.com/2024/02/03/uuid-benchmark-war/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ardentperf.com/2024/02/03/uuid-benchmark-war/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/x7VFN/hyYHd6UUek/BJKWQQgkiH4VauPvi1xue0/img.png?width=3110&amp;amp;height=1732&amp;amp;face=0_0_3110_1732,https://scrap.kakaocdn.net/dn/kPGO9/hyYIg9ImWb/2moKJwXmM5kvGXXMvHMj3k/img.png?width=639&amp;amp;height=356&amp;amp;face=0_0_639_356,https://scrap.kakaocdn.net/dn/iUsbc/hyYIil9vck/iuJHe076IisHynyWdckvv0/img.png?width=1023&amp;amp;height=743&amp;amp;face=0_0_1023_743');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;UUID Benchmark War&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This month&amp;rsquo;s PGSQL Phriday #015 topic is about UUIDs, hosted by L&amp;aelig;titia Avrot. L&amp;aelig;titia has called for a debate. No, no, no. I say let&amp;rsquo;s have an all-out war. A benchmark war. I have deci&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ardentperf.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 정렬 가능성 및 효율성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID v7의 가장 큰 성능 이점은 시간 기반 정렬 기능입니다. UUID v7의 처음 48비트는 생성 시간을 나타내는 Unix 에포크 타임스탬프(밀리초 단위)로 구성되어 있어, 시간 순서대로 정렬이 가능합니다.&lt;span&gt; &lt;/span&gt;이는 데이터베이스 인덱스에서 특히 중요합니다. 시간 순서대로 정렬된 UUID는 데이터베이스 인덱스의 지역성을 향상시켜 쓰기 성능을 최적화하고 인덱스 단편화를 줄입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, UUID v4는 완전히 무작위로 생성되므로 순서가 없어 데이터베이스 인덱스 지역성이 떨어지고, 쓰기 작업이 많을 경우 성능 저하를 유발할 수 있습니다.&lt;span&gt; &lt;/span&gt;따라서 시간 기반 쿼리가 많거나 최근 데이터에 대한 접근 빈도가 높은 애플리케이션에서는 UUID v7이 UUID v4보다 훨씬 효율적인 성능을 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://uuid7.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://uuid7.com/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744855041322&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;UUIDv7 Benefits&quot; data-og-description=&quot;UUIDv7: The Time-Sortable Identifier for Modern Databases The world of unique identifiers is vast, and UUIDs (Universally Unique Identifiers) have long held a prominent position due to their ability to uniquely identify information across distributed syste&quot; data-og-host=&quot;uuid7.com&quot; data-og-source-url=&quot;https://uuid7.com/&quot; data-og-url=&quot;https://uuid7.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://uuid7.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://uuid7.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;UUIDv7 Benefits&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;UUIDv7: The Time-Sortable Identifier for Modern Databases The world of unique identifiers is vast, and UUIDs (Universally Unique Identifiers) have long held a prominent position due to their ability to uniquely identify information across distributed syste&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;uuid7.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기술적 구조&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. UUID&amp;nbsp; v4&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID 버전 4는 128비트 길이로, 32개의 16진수로 표현되며 8-4-4-4-12 패턴으로 하이픈으로 구분됩니다.&lt;span&gt;&lt;/span&gt; 이 중 6비트는 버전(4)과 변형(일반적으로 RFC 4122)을 나타내는 데 사용됩니다.&lt;span&gt;&lt;/span&gt; 나머지 122비트는 임의로 생성됩니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;21:1-27:48&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr data-sourcepos=&quot;21:1-21:20&quot;&gt;
&lt;td&gt;&lt;b&gt;필드&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;길이(비트)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;23:1-23:36&quot;&gt;
&lt;td data-sourcepos=&quot;23:1-23:11&quot;&gt;random_a&lt;/td&gt;
&lt;td data-sourcepos=&quot;23:13-23:16&quot;&gt;48&lt;/td&gt;
&lt;td data-sourcepos=&quot;23:18-23:34&quot;&gt;임의 데이터 (옥텟 0-5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;24:1-24:48&quot;&gt;
&lt;td data-sourcepos=&quot;24:1-24:5&quot;&gt;ver&lt;/td&gt;
&lt;td data-sourcepos=&quot;24:7-24:9&quot;&gt;4&lt;/td&gt;
&lt;td data-sourcepos=&quot;24:11-24:46&quot;&gt;버전 필드 (0b0100, 4) (옥텟 6, 비트 48-51)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;25:1-25:46&quot;&gt;
&lt;td data-sourcepos=&quot;25:1-25:11&quot;&gt;random_b&lt;/td&gt;
&lt;td data-sourcepos=&quot;25:13-25:16&quot;&gt;12&lt;/td&gt;
&lt;td data-sourcepos=&quot;25:18-25:44&quot;&gt;임의 데이터 (옥텟 6-7, 비트 52-63)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;26:1-26:43&quot;&gt;
&lt;td data-sourcepos=&quot;26:1-26:5&quot;&gt;var&lt;/td&gt;
&lt;td data-sourcepos=&quot;26:7-26:9&quot;&gt;2&lt;/td&gt;
&lt;td data-sourcepos=&quot;26:11-26:41&quot;&gt;변형 필드 (0b10) (옥텟 8, 비트 64-65)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;27:1-27:48&quot;&gt;
&lt;td data-sourcepos=&quot;27:1-27:11&quot;&gt;random_c&lt;/td&gt;
&lt;td data-sourcepos=&quot;27:13-27:16&quot;&gt;62&lt;/td&gt;
&lt;td data-sourcepos=&quot;27:18-27:46&quot;&gt;임의 데이터 (옥텟 8-15, 비트 66-127)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID v4의 세 번째 그룹의 첫 번째 문자는 항상 '4'이며, 네 번째 그룹의 첫 번째 문자는 '8', '9', 'a', 또는 'b' 중 하나입니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. UUID v7&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID 버전 7도 128비트 길이로, 표준 8-4-4-4-12 16진수 문자열 형식으로 표현됩니다.&lt;span&gt;&lt;/span&gt; 주요 구성 요소는 다음과 같습니다&lt;span&gt;&lt;/span&gt;:&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;35:1-41:67&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr data-sourcepos=&quot;35:1-35:20&quot;&gt;
&lt;td&gt;&lt;b&gt;필드&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;길이(비트)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;37:1-37:88&quot;&gt;
&lt;td data-sourcepos=&quot;37:1-37:14&quot;&gt;unix_ts_ms&lt;/td&gt;
&lt;td data-sourcepos=&quot;37:16-37:19&quot;&gt;48&lt;/td&gt;
&lt;td data-sourcepos=&quot;37:21-37:86&quot;&gt;빅 엔디안 Unix 에포크 타임스탬프 (1970년 1월 1일 00:00:00 UTC 이후 밀리초) (비트 0-47)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;38:1-38:42&quot;&gt;
&lt;td data-sourcepos=&quot;38:1-38:5&quot;&gt;ver&lt;/td&gt;
&lt;td data-sourcepos=&quot;38:7-38:9&quot;&gt;4&lt;/td&gt;
&lt;td data-sourcepos=&quot;38:11-38:40&quot;&gt;버전 필드 (0b0111, 7) (비트 48-51)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;39:1-39:80&quot;&gt;
&lt;td data-sourcepos=&quot;39:1-39:9&quot;&gt;rand_a&lt;/td&gt;
&lt;td data-sourcepos=&quot;39:11-39:14&quot;&gt;12&lt;/td&gt;
&lt;td data-sourcepos=&quot;39:16-39:78&quot;&gt;의사 난수 데이터 (고유성 제공). 서브 밀리초 정밀도 또는 단조 시퀀스 카운터 포함 가능 (비트 52-63)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;40:1-40:37&quot;&gt;
&lt;td data-sourcepos=&quot;40:1-40:5&quot;&gt;var&lt;/td&gt;
&lt;td data-sourcepos=&quot;40:7-40:9&quot;&gt;2&lt;/td&gt;
&lt;td data-sourcepos=&quot;40:11-40:35&quot;&gt;변형 필드 (0b10) (비트 64-65)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;41:1-41:67&quot;&gt;
&lt;td data-sourcepos=&quot;41:1-41:9&quot;&gt;rand_b&lt;/td&gt;
&lt;td data-sourcepos=&quot;41:11-41:14&quot;&gt;62&lt;/td&gt;
&lt;td data-sourcepos=&quot;41:16-41:65&quot;&gt;의사 난수 데이터 (고유성 보장). 단조 시퀀스 카운터 포함 가능 (비트 66-127)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID v7은 unix_ts_ms 필드를 통해 시간 순서대로 생성되므로, 데이터베이스 인덱스에서 효율적인 정렬 및 검색이 가능합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;보안성과 충돌 가능성&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. UUID v4&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID 버전 4는 122비트의 임의성을 가지므로 충돌 가능성은 극히 낮습니다.&lt;span&gt;&lt;/span&gt; 이론적으로 충돌이 발생할 수 있지만, 실제로는 매우 드뭅니다. 예를 들어, 매초 10억 개의 UUID를 생성한다고 해도 충돌이 발생할 확률은 매우 낮습니다.&lt;span&gt;&lt;/span&gt; 그러나 사용되는 난수 생성기의 품질이 중요하며, 암호학적으로 안전한 PRNG(CSPRNG)를 사용하는 것이 권장됩니다.&lt;span&gt;&lt;/span&gt; UUID v4는 무작위성으로 인해 생성 시간을 알 수 없어 세션 ID나 API 키와 같은 보안에 민감한 식별자에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://versprite.com/blog/universally-unique-identifiers/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://versprite.com/blog/universally-unique-identifiers/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1744855406417&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Universally Unique IDentifiers (UUIDs) Are Yours Secure?&quot; data-og-description=&quot;Secure your UUIDs (universally unique identifiers). What is UUID used for? Learn UUID versions and their significance in software development.&quot; data-og-host=&quot;versprite.com&quot; data-og-source-url=&quot;https://versprite.com/blog/universally-unique-identifiers/&quot; data-og-url=&quot;https://versprite.com/blog/universally-unique-identifiers/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vIydu/hyYH5tzZmI/Mfg1Y7mghrMBwvFAVxjKxK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/b4anag/hyYIhneS3G/FHWkKDIimxkD21yBRNwqg1/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://versprite.com/blog/universally-unique-identifiers/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://versprite.com/blog/universally-unique-identifiers/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vIydu/hyYH5tzZmI/Mfg1Y7mghrMBwvFAVxjKxK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/b4anag/hyYIhneS3G/FHWkKDIimxkD21yBRNwqg1/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Universally Unique IDentifiers (UUIDs) Are Yours Secure?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Secure your UUIDs (universally unique identifiers). What is UUID used for? Learn UUID versions and their significance in software development.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;versprite.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. UUID v7&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID 버전 7은 48비트 타임스탬프와 74비트의 임의 비트를 결합하여 고유성을 보장합니다.&lt;span&gt;&lt;/span&gt; UUID v4에 비해 임의 비트 수가 적지만, 충돌 가능성은 여전히 매우 낮습니다.&lt;span&gt;&lt;/span&gt; UUID v7의 타임스탬프는 생성 시간을 대략적으로 노출하므로, 보안상 민감한 정보가 ID에 포함될 경우 주의가 필요합니다.&lt;span&gt;&lt;/span&gt; 그러나 데이터베이스 기본 키와 같이 순서 기반의 성능이 중요한 경우에는 UUID v7이 더 나은 선택일 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://uuid.ramsey.dev/en/stable/rfc4122/version7.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://uuid.ramsey.dev/en/stable/rfc4122/version7.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;57:1-67:54&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr data-sourcepos=&quot;57:1-57:30&quot;&gt;
&lt;td&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;UUID 버전 4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;UUID 버전 7&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;59:1-59:55&quot;&gt;
&lt;td data-sourcepos=&quot;59:1-59:9&quot;&gt;기본 메커니즘&lt;/td&gt;
&lt;td data-sourcepos=&quot;59:11-59:26&quot;&gt;임의/의사 임의 숫자 생성&lt;/td&gt;
&lt;td data-sourcepos=&quot;59:28-59:53&quot;&gt;Unix Epoch 타임스탬프 + 임의 비트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;60:1-60:45&quot;&gt;
&lt;td data-sourcepos=&quot;60:1-60:10&quot;&gt;타임스탬프 포함&lt;/td&gt;
&lt;td data-sourcepos=&quot;60:12-60:16&quot;&gt;아니요&lt;/td&gt;
&lt;td data-sourcepos=&quot;60:18-60:43&quot;&gt;예 (48비트 Unix 타임스탬프(밀리초))&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;61:1-61:47&quot;&gt;
&lt;td data-sourcepos=&quot;61:1-61:8&quot;&gt;정렬 가능성&lt;/td&gt;
&lt;td data-sourcepos=&quot;61:10-61:22&quot;&gt;본질적으로 순서 없음&lt;/td&gt;
&lt;td data-sourcepos=&quot;61:24-61:45&quot;&gt;시간 순서 (생성 시간별 정렬 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;62:1-62:59&quot;&gt;
&lt;td data-sourcepos=&quot;62:1-62:11&quot;&gt;데이터베이스 성능&lt;/td&gt;
&lt;td data-sourcepos=&quot;62:13-62:28&quot;&gt;낮은 인덱스 지역성 가능성&lt;/td&gt;
&lt;td data-sourcepos=&quot;62:30-62:57&quot;&gt;일반적으로 더 나은 인덱스 지역성 및 쓰기 성능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;63:1-63:52&quot;&gt;
&lt;td data-sourcepos=&quot;63:1-63:13&quot;&gt;개인 정보 보호 영향&lt;/td&gt;
&lt;td data-sourcepos=&quot;63:15-63:29&quot;&gt;높음 (시간 정보 없음)&lt;/td&gt;
&lt;td data-sourcepos=&quot;63:31-63:50&quot;&gt;낮음 (대략적인 생성 시간 공개)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;64:1-64:76&quot;&gt;
&lt;td data-sourcepos=&quot;64:1-64:13&quot;&gt;보안 (추측 가능성)&lt;/td&gt;
&lt;td data-sourcepos=&quot;64:15-64:36&quot;&gt;높음 (강력한 CSPRNG 사용 시)&lt;/td&gt;
&lt;td data-sourcepos=&quot;64:38-64:74&quot;&gt;일반적으로 좋음, 하지만 타임스탬프가 작은 공격 표면 제공 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;65:1-65:50&quot;&gt;
&lt;td data-sourcepos=&quot;65:1-65:14&quot;&gt;엔트로피 (임의 비트)&lt;/td&gt;
&lt;td data-sourcepos=&quot;65:16-65:22&quot;&gt;122비트&lt;/td&gt;
&lt;td data-sourcepos=&quot;65:24-65:48&quot;&gt;74비트 (서브 밀리초 정밀도 포함 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;66:1-66:47&quot;&gt;
&lt;td data-sourcepos=&quot;66:1-66:8&quot;&gt;RFC 사양&lt;/td&gt;
&lt;td data-sourcepos=&quot;66:10-66:19&quot;&gt;RFC 4122&lt;/td&gt;
&lt;td data-sourcepos=&quot;66:21-66:45&quot;&gt;RFC 9562 (RFC 4122를 대체)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;67:1-67:54&quot;&gt;
&lt;td data-sourcepos=&quot;67:1-67:10&quot;&gt;주요 사용 사례&lt;/td&gt;
&lt;td data-sourcepos=&quot;67:12-67:27&quot;&gt;범용, 보안에 민감한 ID&lt;/td&gt;
&lt;td data-sourcepos=&quot;67:29-67:52&quot;&gt;데이터베이스 기본 키, 시간 순서 식별자&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID 버전 4는 강력한 개인 정보 보호 및 예측 불가능성이 필요한 보안에 민감한 애플리케이션에 적합합니다.&lt;span&gt;&lt;/span&gt; 세션 토큰, API 키 및 기타 비밀과 같이 예측 불가능성이 주요 관심사인 보안 식별자에 적합합니다.&lt;span&gt;&lt;/span&gt; 임의 인덱싱의 성능 영향이 미미하거나 다른 최적화 기술을 통해 관리할 수 있는 애플리케이션에서도 유용합니다.&lt;span&gt;&lt;/span&gt; 버전 4에 크게 의존하는 레거시 시스템 또는 기존 표준과의 호환성이 주요 요구 사항인 경우에도 적합합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 UUID 버전 7은 시간 기반 정렬 및 향상된 인덱스 지역성이 더 나은 성능을 위해 바람직한 새로운 시스템의 데이터베이스 기본 키에 선호되는 선택입니다.&lt;span&gt;&lt;/span&gt; 연대순 식별자 순서가 분석 및 쿼리에 유용한 로깅 시스템, 이벤트 추적 및 기타 애플리케이션에도 유용합니다.&lt;span&gt;&lt;/span&gt; 시간 순서 ID는 UUIDv1의 개인 정보 보호 문제 없이 데이터 관리를 단순화하고 성능을 향상시킬 수 있는 분산 시스템에도 적합합니다.&lt;span&gt;&lt;/span&gt; 향상된 특성으로 인해 이전 시간 기반 UUID(v1 및 v6)보다 일반적으로 권장되는 버전입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID 버전 4와 버전 7은 모두 고유 식별자를 제공하지만, 그 방식과 특징에는 뚜렷한 차이가 있습니다.&lt;span&gt;&lt;/span&gt; 버전 4는 무작위성에 기반하여 높은 수준의 개인 정보 보호 및 예측 불가능성을 제공하므로 보안에 민감한 애플리케이션에 적합합니다.&lt;span&gt;&lt;/span&gt; 반면 버전 7은 시간 기반 정렬 기능을 도입하여 데이터베이스 성능을 향상시키는 데 중점을 두어, 현대 데이터베이스 중심 애플리케이션에 매력적인 선택입니다.&lt;span&gt;&lt;/span&gt; 따라서 개발자는 UUID 버전을 선택할 때 성능, 개인 정보 보호 및 시간 기반 정렬 필요성과 같은 애플리케이션의 특정 요구 사항을 신중하게 고려해야 합니다.&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;</description>
      <category>CS</category>
      <category>rfc 4122</category>
      <category>rfc 9562</category>
      <category>uuid</category>
      <category>V4</category>
      <category>V7</category>
      <author>kahnco</author>
      <guid isPermaLink="true">https://kahnco.tistory.com/44</guid>
      <comments>https://kahnco.tistory.com/44#entry44comment</comments>
      <pubDate>Thu, 17 Apr 2025 11:06:50 +0900</pubDate>
    </item>
    <item>
      <title>[CS] 동시성과 병렬성의 개념</title>
      <link>https://kahnco.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 멀티스레드 프로그래밍의 핵심 개념인 &lt;b&gt;동시성(Concurrency)&lt;/b&gt; 과 &lt;b&gt;병렬성(Parallelism)&lt;/b&gt; 을 구체적으로 설명하고, &lt;b&gt;Java Spring 프레임워크&lt;/b&gt;에서 이를 어떻게 확인하고 활용할 수 있는지 예제 코드와 함께 살펴본다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1. 동시성(Concurrency)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성은 여러 작업이 &lt;b&gt;논리적으로 동시에 실행되는 것처럼 보이도록&lt;/b&gt; 처리하는 방식을 의미한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;▶︎ 동시성의 핵심 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;단일 CPU에서도 구현 가능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;여러 작업을 번갈아 실행하면서, 사용자는 작업이 동시에 이루어지고 있다고 느낀다.&lt;/li&gt;
&lt;li&gt;컨텍스트 스위칭(Context Switching)을 활용하여 작업 간 전환이 빠르게 이루어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;▶︎ 동시성 예시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹서버가 여러 클라이언트 요청을 동시에 처리하는 경우&lt;/li&gt;
&lt;li&gt;Java에서 여러 스레드(Thread)를 만들어 논리적인 병행 처리를 수행하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;▶︎ 동시성의 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자원 활용을 극대화하여 작업의 응답성을 높임&lt;/li&gt;
&lt;li&gt;대기 시간이 긴 I/O 작업을 수행할 때 효율적으로 활용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2. 병렬성(Parallelism)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬성은 여러 작업이 &lt;b&gt;물리적으로 동시에 수행&lt;/b&gt;되는 방식을 의미한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;▶︎ 병렬성의 핵심 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반드시 다중 CPU 또는 다중 코어 환경이 필요하다.&lt;/li&gt;
&lt;li&gt;각 작업이 서로 독립된 CPU 또는 코어에서 동시에 처리된다.&lt;/li&gt;
&lt;li&gt;여러 작업을 동시에 처리하여 총 작업 시간을 줄인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;▶︎ 병렬성 예시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티코어 환경에서의 데이터 처리 및 연산 작업&lt;/li&gt;
&lt;li&gt;Java의 병렬 스트림(Parallel Stream)을 이용하여 데이터를 병렬 처리하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;▶︎ 병렬성의 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업 속도가 빨라지고 처리량이 증가한다.&lt;/li&gt;
&lt;li&gt;CPU 집약적인 작업에서 성능 향상 효과가 크다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3. 동시성과 병렬성의 차이점 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 동시성 (Concurrency) 병렬성 (Parallelism)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;정의&lt;/td&gt;
&lt;td&gt;논리적으로 여러 작업이 동시에 실행&lt;/td&gt;
&lt;td&gt;물리적으로 여러 작업이 동시에 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU 조건&lt;/td&gt;
&lt;td&gt;단일 CPU에서도 가능&lt;/td&gt;
&lt;td&gt;멀티 코어 CPU 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;응답성 향상, 자원 활용 최적화&lt;/td&gt;
&lt;td&gt;처리 성능 향상, 처리 속도 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;웹서버의 멀티스레드 요청 처리&lt;/td&gt;
&lt;td&gt;데이터 분석, 병렬 연산 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  4. Java Spring에서 동시성과 병렬성의 구현 예제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서는 비동기(@Async) 기능을 통해 손쉽게 동시성과 병렬성을 구현할 수 있다.&lt;br /&gt;예제를 통해 실제로 이를 확인해보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ Spring 프로젝트에서 비동기 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Async를 사용하기 위한 설정을 먼저 수행한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean(name = &quot;threadPoolTaskExecutor&quot;)
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix(&quot;Async-Thread-&quot;);
        executor.initialize();
        return executor;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 동시성(Concurrency) 예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다수의 작업이 논리적으로 동시에 수행되는 모습을 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비동기 서비스 클래스 작성:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Service
public class ConcurrentService {

    @Async(&quot;threadPoolTaskExecutor&quot;)
    public void processTask(int taskNumber) throws InterruptedException {
        System.out.println(&quot;작업 시작: &quot; + taskNumber + &quot; - 스레드: &quot; + Thread.currentThread().getName());
        Thread.sleep(3000); // 3초 지연
        System.out.println(&quot;작업 완료: &quot; + taskNumber + &quot; - 스레드: &quot; + Thread.currentThread().getName());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨트롤러 작성 (호출부):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@RestController
public class ConcurrentController {
    
    @Autowired
    private ConcurrentService concurrentService;

    @GetMapping(&quot;/concurrent&quot;)
    public String executeConcurrentTasks() throws InterruptedException {
        for (int i = 1; i &amp;lt;= 5; i++) {
            concurrentService.processTask(i);
        }
        return &quot;Concurrent Tasks Started&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;br /&gt;스레드가 교차하면서 여러 작업이 논리적으로 동시에 처리되는 것을 로그를 통해 확인할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 병렬성(Parallelism) 예제 (병렬 스트림 사용)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 CPU 코어를 활용하여 병렬 처리가 이루어지는 모습을 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스 클래스 작성:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Service
public class ParallelService {

    public int sumUsingParallelStream() {
        return IntStream.rangeClosed(1, 1_000_000)
                        .parallel() // 병렬 스트림 사용
                        .sum();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨트롤러 작성 (호출부):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@RestController
public class ParallelController {

    @Autowired
    private ParallelService parallelService;

    @GetMapping(&quot;/parallel&quot;)
    public String executeParallelTasks() {
        long start = System.currentTimeMillis();

        int sum = parallelService.sumUsingParallelStream();

        long end = System.currentTimeMillis();

        return &quot;합계: &quot; + sum + &quot;, 소요 시간: &quot; + (end - start) + &quot;ms&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;br /&gt;멀티코어 CPU에서 작업이 동시에 진행되어, 단일 스트림 대비 빠르게 결과를 도출하는 것을 볼 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 사용 시 주의점 및 팁&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시성을 구현할 때 스레드의 개수가 너무 많으면 컨텍스트 스위칭 비용이 증가하여 오히려 성능이 떨어질 수 있으므로, 적절한 스레드 풀 크기를 설정해야 한다.&lt;/li&gt;
&lt;li&gt;병렬 스트림은 데이터의 크기가 작거나 단순한 작업에서는 오히려 성능 저하를 유발할 수 있으므로, 충분히 복잡하거나 양이 많은 작업에서 사용하는 것이 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  6. 마무리 및 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 다음의 내용을 다루었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동시성&lt;/b&gt;은 논리적으로 동시에 처리하는 방식으로, 응답성과 자원 활용을 높인다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;병렬성&lt;/b&gt;은 물리적으로 동시에 처리하는 방식으로, 처리 성능과 속도를 증가시킨다.&lt;/li&gt;
&lt;li&gt;Spring에서 @Async와 병렬 스트림을 통해 두 가지 개념을 간편하게 구현 가능하다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS</category>
      <category>async</category>
      <category>concurrency</category>
      <category>parallelism</category>
      <category>Spring</category>
      <category>동시성</category>
      <category>병렬성</category>
      <author>kahnco</author>
      <guid isPermaLink="true">https://kahnco.tistory.com/43</guid>
      <comments>https://kahnco.tistory.com/43#entry43comment</comments>
      <pubDate>Tue, 15 Apr 2025 11:55:36 +0900</pubDate>
    </item>
  </channel>
</rss>