<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>KoK_log</title>
        <link>https://velog.io/</link>
        <description>개발 이것저것</description>
        <lastBuildDate>Mon, 06 Oct 2025 07:21:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>KoK_log</title>
            <url>https://velog.velcdn.com/images/levi_/profile/9c001118-6155-43da-96b0-1a89092571de/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. KoK_log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/levi_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[SELECT - 조회]]></title>
            <link>https://velog.io/@levi_/SELECT-%EC%A1%B0%ED%9A%8C</link>
            <guid>https://velog.io/@levi_/SELECT-%EC%A1%B0%ED%9A%8C</guid>
            <pubDate>Mon, 06 Oct 2025 07:21:17 GMT</pubDate>
            <description><![CDATA[<h2 id="1-select">1. SELECT</h2>
<blockquote>
<p>데이터베이스에 데이터가 저장되어 있다면, 그 데이터를 바탕으로 다양한 인사이트를 얻을 수 있다. 그중에서도 데이터를 조회하는 데이터 조작어(DML) 인 <code>SELECT</code> 문에 대해 알아보자.</p>
</blockquote>
<ul>
<li><code>SELECT</code> : 무엇을 가져올것인가? (조회할 열, 컬럼을 지정)</li>
<li><code>FROM</code> : 어디에서 가져올것인가? (데이터가 들어있는 테이블을 지정)</li>
</ul>
<p>예를 들어 customers 테이블의 정보를 조회하고 싶다고 하면</p>
<pre><code>SELECT * FROM customers; -- &#39;*&#39;는 해당 테이블의 모든 데이터를 조회한다는 의미</code></pre><p><em><strong>실행결과</strong></em></p>
<table>
<thead>
<tr>
<th>customer_id</th>
<th>name</th>
<th>email</th>
<th>password</th>
<th>address</th>
<th>join_date</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>이순신</td>
<td><a href="mailto:yisunsin@example.com">yisunsin@example.com</a></td>
<td>password123</td>
<td>서울특별시 중구 세종대로</td>
<td>2023-05-01 00:00:00</td>
</tr>
<tr>
<td>2</td>
<td>세종대왕</td>
<td><a href="mailto:sejong@example.com">sejong@example.com</a></td>
<td>password456</td>
<td>서울특별시 종로구 사직로</td>
<td>2024-05-01 00:00:00</td>
</tr>
<tr>
<td>3</td>
<td>장영실</td>
<td><a href="mailto:youngsil@example.com">youngsil@example.com</a></td>
<td>password789</td>
<td>부산광역시 동래구 북천동</td>
<td>2025-05-01 00:00:00</td>
</tr>
</tbody></table>
<p>위 표처럼 모든 데이터를 조회하면 해당 테이블의 모든 데이터가 전부 조회된다.
하지만 때로는 비밀번호같은 민감한 정보는 필요없을 수 있다. 
지금은 테스트용이라 데이터가 별로 없지만 만약 <code>customers</code>테이블에 수백만명의 회원 데이터가 저장되어있다고 생각하면,</p>
<ol>
<li><code>성능저하</code> : 불필요한 데이터까지 모두 가져와서 DB시스템에 큰 부담을 줄 수 있다. 속도도 저하된다.</li>
<li><code>가독성 저하</code> : 내가 보고싶은 데이터는 &#39;이름&#39;과 &#39;메일&#39;뿐인데, 수십개의 열이 함께 포함되니 한눈에 파악하기 어렵다.</li>
<li><code>네트워크 트래픽 낭비</code> :  DB 서버에서 우리 컴퓨터로 데이터를 전송할 때, 필요 없는 데이터까지 함께 보내므로 네트워크 자원을 낭비할 수 있다.</li>
</ol>
<h3 id="1-1-특정-열만-선택">1-1. 특정 열만 선택</h3>
<p><code>SELECT *</code> 대신  <code>SELECT</code> 절에 우리가 보고싶은 열의 이름들을 <code>,</code> 로 구분하여 적어주면 된다.</p>
<pre><code>SELECT name, email FROM customers;</code></pre><p><strong><em>실행결과</em></strong></p>
<table>
<thead>
<tr>
<th>name</th>
<th>email</th>
</tr>
</thead>
<tbody><tr>
<td>이순신</td>
<td><a href="mailto:yisunsin@example.com">yisunsin@example.com</a></td>
</tr>
<tr>
<td>세종대왕</td>
<td><a href="mailto:sejong@example.com">sejong@example.com</a></td>
</tr>
<tr>
<td>장영실</td>
<td><a href="mailto:youngsil@example.com">youngsil@example.com</a></td>
</tr>
</tbody></table>
<p>실행 결과를 보면 customer_id, password같은 민감하거나 불필요한정보는 빼고 <code>name</code>과 <code>email</code>데이터만 조회됐다.
이렇게 <code>SELECT FROM</code> 절을 사용해서 성능저하나 가독성 저하 등을 방지하고 원하는 데이터만 골라 조회할 수 있다.</p>
<hr>
<h2 id="2-where---기본검색">2. WHERE - 기본검색</h2>
<pre><code>SELECT 열이름
FROM 테이블이름
WHERE 조건</code></pre><ul>
<li><code>WHERE</code> 절은 <code>FROM</code> 절 바로 뒤에 위치하고 우리가 원하는 행(row)만 걸러내는 필터 역할을 한다.</li>
</ul>
<table>
<thead>
<tr>
<th>연산자</th>
<th>의미</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>=</td>
<td>같다</td>
<td>WHERE name = &#39;이순신&#39;</td>
</tr>
<tr>
<td>!= 또는 &lt;&gt;</td>
<td>같지 않다</td>
<td>WHERE name != &#39;세종대왕&#39;</td>
</tr>
<tr>
<td>&gt;</td>
<td>크다</td>
<td>WHERE price &gt; 10000</td>
</tr>
<tr>
<td>&lt;</td>
<td>작다</td>
<td>WHERE stock_quantity &lt; 50</td>
</tr>
<tr>
<td>&gt;=</td>
<td>크거나 같다</td>
<td>WHERE price &gt;= 10000</td>
</tr>
<tr>
<td>&lt;=</td>
<td>작거나 같다</td>
<td>WHERE stock_quantity &lt;= 50</td>
</tr>
</tbody></table>
<pre><code>SELECT * FROM customers WHERE email &#39;yisunsin@example.com&#39;;</code></pre><p><strong><em>실행결과</em></strong></p>
<table>
<thead>
<tr>
<th>customer_id</th>
<th>name</th>
<th>email</th>
<th>password</th>
<th>address</th>
<th>join_date</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>이순신</td>
<td><a href="mailto:yisunsin@example.com">yisunsin@example.com</a></td>
<td>password123</td>
<td>서울특별시 중구 세종대로</td>
<td>2023-05-01 00:00:00</td>
</tr>
<tr>
<td>수십, 수백만건의 데이터가 있더라고 WHERE절을 사용해 정확한 조건을 걸어준다면 우리에게 필요한 단 하나의 데이터를 골라서 조회할 수 있다.</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p>더 복잡한 조건이 필요하다면 논리연산자를 사용하면 된다.</p>
<ul>
<li><code>AND</code> : 양쪽의 조건이 모두 참일 때 최종적으로 참이 된다.</li>
<li><code>OR</code> : 양쪽의 조건 중 하나라도 참이면 최종적으로 참이 된다.</li>
<li><code>NOT</code> : 주어진 조건을 부정한다. 
(이 후 알아볼 <code>IN</code>, <code>LIKE</code>, <code>BETWEEN</code>, <code>IS NULL</code>등과 함께 사용된다.)</li>
</ul>
<h3 id="2-1-and">2-1. AND</h3>
<pre><code>-- 가격이 5000원 이상이면서, 재고가 50개 이상인 상품 조회하기(AND)
SELECT * FROM products WHERE price &gt;= 5000 AND stock_quantity &gt;= 50;</code></pre><p><em><strong>실행결과</strong></em></p>
<table>
<thead>
<tr>
<th>product_id</th>
<th>name</th>
<th>description</th>
<th>price</th>
<th>stock_quantity</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>갤럭시</td>
<td>최신 AI 기능이 탑재된 고성능 스마트폰</td>
<td>10000</td>
<td>55</td>
</tr>
<tr>
<td>2</td>
<td>아이폰</td>
<td>직관적인 사용자 경험을 제공하는 스마트폰</td>
<td>5000</td>
<td>55</td>
</tr>
<tr>
<td>3</td>
<td>보급형 스마트폰</td>
<td>NULL</td>
<td>5000</td>
<td>100</td>
</tr>
</tbody></table>
<h3 id="2-2-or">2-2. OR</h3>
<pre><code>-- 가격이 20000원이거나, 재고가 100개 이상인 제품 조회하기(OR)
SELECT * FROM products WHERE price = 20000 OR stock_quantity &gt;= 100;</code></pre><p><em><strong>실행결과</strong></em></p>
<table>
<thead>
<tr>
<th>product_id</th>
<th>name</th>
<th>description</th>
<th>price</th>
<th>stock_quantity</th>
</tr>
</thead>
<tbody><tr>
<td>2</td>
<td>LG그램</td>
<td>초경량 디자인과 강력한 성능을 자랑하는 노트북</td>
<td>20000</td>
<td>35</td>
</tr>
<tr>
<td>3</td>
<td>에어팟</td>
<td>편리한 사용성의 무선 이어폰</td>
<td>3000</td>
<td>110</td>
</tr>
<tr>
<td>5</td>
<td>보급형 스마트폰</td>
<td>NULL</td>
<td>5000</td>
<td>100</td>
</tr>
</tbody></table>
<h3 id="2-3-not">2-3. NOT</h3>
<pre><code>-- 가격이 20000원이 아닌 제품을 조회하기(NOT)
SELECT * FROM products WHERE price != 20000;</code></pre><p><strong><em>실행결과</em></strong></p>
<table>
<thead>
<tr>
<th>product_id</th>
<th>name</th>
<th>description</th>
<th>price</th>
<th>stock_quantity</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>갤럭시</td>
<td>최신 AI기능이 탑재된 고성능 스마트폰</td>
<td>10000</td>
<td>55</td>
</tr>
<tr>
<td>3</td>
<td>아이폰</td>
<td>직관적인 사용자 경험을 제공하는 스마트폰</td>
<td>5000</td>
<td>55</td>
</tr>
<tr>
<td>4</td>
<td>에어팟</td>
<td>편리한 사용성의 무선 이어폰</td>
<td>3000</td>
<td>110</td>
</tr>
<tr>
<td>5</td>
<td>보급형 스마트폰</td>
<td>NULL</td>
<td>5000</td>
<td>100</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-where---편리한-조건검색">3. WHERE - 편리한 조건검색</h2>
<h3 id="3-1-between--특정-범위에-있는-값-찾기">3-1. BETWEEN : 특정 범위에 있는 값 찾기</h3>
<pre><code>SELECT * FROM products WHERE price BETWEEN 5000 AND 15000;</code></pre><ul>
<li>실행 결과는 같지만 이전에 &gt;= 연산자를 사용했을 때보다 훨씬 간결하게 작성할수 있다.</li>
<li>BETWEEN은 양 끝 값을 <code>포함</code> 한다.</li>
</ul>
<h3 id="3-1-1-not-between--특정-범위를-제외한-값-찾기">3-1-1. NOT BETWEEN : 특정 범위를 제외한 값 찾기</h3>
<ul>
<li>BETWEEN 앞에 NOT을 붙이면 정확히 그 반대의 의미가 된다.
<code>이 범위에 속하지 않는다.</code></li>
</ul>
<pre><code>-- 테이블에서 가격이 5000원 이상, 15000원 이하가 아닌 상품 조회
SELECT * FROM products WHERE price NOT BETWEEN 5000 AND 15000;</code></pre><p><em><strong>실행결과</strong></em></p>
<table>
<thead>
<tr>
<th>product_id</th>
<th>name</th>
<th>description</th>
<th>price</th>
<th>stock_quantity</th>
</tr>
</thead>
<tbody><tr>
<td>2</td>
<td>LG그램</td>
<td>초경량 디자인과 강력한 성능을 자랑하는 노트북</td>
<td>20000</td>
<td>35</td>
</tr>
<tr>
<td>4</td>
<td>에어팟</td>
<td>편리한 사용성의 무선 이어폰</td>
<td>3000</td>
<td>110</td>
</tr>
</tbody></table>
<h3 id="3-2-in--목록에-포함된-값-찾기">3-2. <code>IN</code> : 목록에 포함된 값 찾기</h3>
<pre><code>SELECT * FROM products WHERE name = &#39;갤럭시&#39; OR name = &#39;아이폰&#39; OR name = &#39;에어팟&#39;;    </code></pre><ul>
<li>위 쿼리처럼 작성하게 되면 조회해야하는 상품이 수십, 수백개 일때는 
<code>IN</code>을 사용해야한다.</li>
<li><code>IN(목록)</code> 구문은 괄호 안에 있는 목록 중 하나라도 일치하는 것이 있으면 선택한다. </li>
</ul>
<pre><code>SELECT * FROM products WHERE name IN (&#39;갤럭시&#39;, &#39;아이폰&#39;, &#39;에어팟&#39;);</code></pre><ul>
<li>실행 결과는 이전과 같다. 가동성은 훨씬 좋아졌다.</li>
</ul>
<h3 id="3-2-1-not-in--목록에-포함되지-않은-값-찾기">3-2-1. NOT IN : 목록에 포함되지 않은 값 찾기</h3>
<p>마찬가지로 IN 앞에 NOT을 붙이면 반대의 의미로 사용 가능하다.</p>
<pre><code>SELECT * FROM products WHERE NOT IN (&#39;갤럭시&#39;, &#39;아이폰&#39;, &#39;에어팟&#39;);</code></pre><ul>
<li>실행결과는 이전과 같다. 마찬가지로 가독성은 훨씬 향상되었다.</li>
</ul>
<h3 id="3-3-like--문자열의-일부로-검색하기패턴매칭">3-3. <code>LIKE</code> : 문자열의 일부로 검색하기(패턴매칭)</h3>
<ul>
<li><code>=</code> 연산자는 문자열 전체가 정확히 일치해야만 사용할 수 있다. 이처럼 문자열의 일부만으로 데이터를 검색하고 싶을 때 <code>LIKE</code> 연산자와 <code>와일드카드</code>를  함께 사용한다.</li>
</ul>
<p><em><strong>와일드카드 문자</strong></em></p>
<ul>
<li><p><code>%</code> : 0개 이상의 모든 문자를 의미한다.</p>
<ul>
<li><code>sejong%</code> : <code>sejong</code>으로 시작하는 모든 문자열 (<code>sejong@example.com</code>, <code>sejong123</code> 등)</li>
<li><code>%example.com</code> : <code>example.com</code>으로 끝나는 모든 문자열(<code>aaa@example.com</code>, <code>hello@example.com</code> 등)</li>
<li><code>%서울%</code> : <code>서울</code> 이라는 단어를 포함하는 모든 문자열(<code>수도서울</code>, <code>서울에 살자</code>,  <code>수도 서울에 살자</code> 등)</li>
</ul>
</li>
<li><p><code>_</code> : 정확히 한 개의 문자를 의미</p>
<ul>
<li><code>이_신</code> : &#39;이&#39;로 시작하고 &#39;신&#39; 으로 끝나는 세글자 이름(<code>이순신</code>, <code>이방신</code> 등, 예를 들어 <code>이나라신</code>은 정확히 한 개의 문자가 아니므로 탈락)</li>
</ul>
</li>
</ul>
<h3 id="3-3-1-not-like">3-3-1 NOT LIKE</h3>
<pre><code>SELECT * FROM customers WHERE address NOT LIKE &#39;서울특별시%&#39;;</code></pre><table>
<thead>
<tr>
<th>customers_id</th>
<th>name</th>
<th>email</th>
<th>password</th>
<th>address</th>
<th>join_date</th>
</tr>
</thead>
<tbody><tr>
<td>3</td>
<td>장영실</td>
<td><a href="mailto:youngsil@example.com">youngsil@example.com</a></td>
<td>password789</td>
<td>부산광역시 동래구 북천동</td>
<td>2025-05-01 00:00:00</td>
</tr>
</tbody></table>
<ul>
<li>주소가 서울특별시인 고객을 제외한 나머지 고객을 확인할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL, 제약조건]]></title>
            <link>https://velog.io/@levi_/SQL-%EC%A0%9C%EC%95%BD%EC%A1%B0%EA%B1%B4</link>
            <guid>https://velog.io/@levi_/SQL-%EC%A0%9C%EC%95%BD%EC%A1%B0%EA%B1%B4</guid>
            <pubDate>Wed, 01 Oct 2025 10:07:34 GMT</pubDate>
            <description><![CDATA[<h2 id="1-sql">1. SQL</h2>
<blockquote>
<p>앞서 <code>CREATE</code> , <code>INSERT</code> , <code>SELECT</code> 같은 몇 가지 영어 단어로 데이터베이스를 다뤄봤다. 이 명령어들의 집합이 바로 <strong>SQL(Structured Query Language)</strong>, &#39;구조화된 질의 언어&#39;다.</p>
</blockquote>
<ul>
<li>SQL은 국제 표준 기구(ISO/ANSI)에 의해 표준이 정해진
<code>관계형 데이터베이스의 표준 언어</code> 다. 대부분의 관계형 데이터베이스는 이 표준 SQL을 지원한다. 각 데이터베이스마다 자신들만의 추가기능이나 약간의 문법 차이는 있지만, <code>CREATE</code> , <code>SELECT</code> , <code>INSERT</code> 와 같은 핵심적인 문법은 거의 동일하다.</li>
</ul>
<blockquote>
<p><em><strong>데이터베이스 방언(Dialect)</strong></em>
SQL 표준을 넘어서 각 DBMS는 자신들만의 추가 기능이나 추가 문법을 제공하는데, 이것을 방언(사투리,
Dialect)라고 한다.</p>
</blockquote>
<ul>
<li>SQL의 또 다른 중요한 특징은 <code>선언적 언어</code> 라는 점이다.
우리는 데이터베이스에게 **
&#39;어떻게(How)&#39;** 데이터를 가져올지 지시하는 것이 아니라, **
&#39;무엇을(What)&#39;** 원하는지만 선언적으로 명시한다는 뜻이다.</li>
</ul>
<hr>
<h2 id="2-sql-명령어의-4가지-종류">2. SQL 명령어의 4가지 종류</h2>
<h3 id="2-1-ddl-data-definition-language-데이터-정의어">2-1. <strong>DDL (Data Definition Language, 데이터 정의어)</strong></h3>
<ul>
<li><strong>목적</strong>: 데이터의 &#39;구조&#39;를 정의하고 관리하는 언어다. 데이터 그 자체가 아니라, 데이터를 담을 그릇(테이블)이
나 창고(데이터베이스)의 설계도를 만들고, 수정하고, 제거하는 역할을 한다.</li>
<li><strong>주요 명령어</strong>:
<code>CREATE</code> : 데이터베이스, 테이블 등의 구조를 생성한다.
<code>ALTER</code> : 이미 만들어진 테이블의 구조를 변경한다. (이후에 학습한다.)
<code>DROP</code> : 데이터베이스, 테이블을 완전히 삭제한다.</li>
</ul>
<h3 id="2-2-dml-data-manipulation-language-데이터-조작어">2-2. <strong>DML (Data Manipulation Language, 데이터 조작어)</strong></h3>
<ul>
<li><strong>목적</strong>: 테이블 안에 들어있는 실제 &#39;데이터&#39;를 직접 조작(추가, 조회, 수정, 삭제)하는 언어다. SQL에서 가장
빈번하게 사용되는 명령어들이 여기에 속한다.</li>
<li><strong>주요 명령어</strong>:
<code>INSERT</code> : 테이블에 새로운 데이터를 추가한다.
<code>SELECT</code> : 테이블에서 데이터를 조회(검색)한다.
<code>UPDATE</code> : 기존 데이터를 수정한다.
<code>DELETE</code> : 기존 데이터를 삭제한다.</li>
</ul>
<h3 id="2-3-dcl-data-control-language-데이터-제어어">2-3. DCL (Data Control Language, 데이터 제어어)**</h3>
<ul>
<li><strong>목적</strong>: 데이터에 대한 접근 권한을 부여(<code>GRANT</code> )하거나 회수(<code>REVOKE</code> )하는 등, 데이터의 보안과 관련된 권
한을 제어한다.</li>
<li><strong>주요 명령어</strong>:
<code>GRANT</code> : 특정 사용자에게 특정 작업에 대한 수행 권한을 부여한다.
<code>REVOKE</code> : 특정 사용자에게서 이미 부여한 권한을 회수한다.</li>
</ul>
<h3 id="2-4-tcl-transaction-control-language-트랜잭션-제어어">2-4. TCL (Transaction Control Language, 트랜잭션 제어어)**</h3>
<ul>
<li><strong>목적</strong>: DML에 의해 수행된 데이터 변경 작업들을 하나의 &#39;거래(Transaction)&#39; 단위로 묶어서 관리하는 언어
다. 작업의 일관성을 보장하여 데이터가 잘못되는 것을 방지한다.</li>
<li><strong>주요 명령어</strong>:
<code>COMMIT</code> : 트랜잭션의 모든 작업을 최종적으로 데이터베이스에 확정, 저장한다.
<code>ROLLBACK</code> : 트랜잭션의 모든 작업을 취소하고 이전 상태로 되돌린다.</li>
</ul>
<blockquote>
<p>SQL은 데이터를 조회하는 언어를 넘어, 데이터의 구조를 정의하고(DDL), 실제 데이터를 조작하며 (DML), 접근 권한을 제어하고(DCL), 작업의 안정성을 보장하는(TCL) 매우 체계적이고 강력한 언어 체계다.</p>
</blockquote>
<hr>
<h2 id="3-제약조건">3. 제약조건</h2>
<blockquote>
<p>제약 조건은 테이블에 데이터를 저장할 때, 특정 규칙을 지키도록 강제하는 장치다. 이 규칙 덕분에 우리는 잘못되거나 일관성 없는 데이터가 입력되는 것을 원천적으로 차단할 수 있다. 데이터의 <code>무결성</code>이 제약 조건의 핵심 목표다.</p>
</blockquote>
<ul>
<li><p>** <code>NOT NULL</code> <strong>: **필수 입력 항목 지정</strong>
이 제약 조건이 걸린 열에는 <code>NULL</code> 값(값이 없음)을 허용하지 않는다. 즉, 데이터를 <code>INSERT</code> 할 때 이 열의 값은 반드시 입력되어야 한다. 고객의 이름, 아이디, 상품의 가격 등은 비어있으면 안 되는 핵심 정보이므로 <code>NOT
NULL</code> 을 설정해야 한다.</p>
</li>
<li><p>** <code>UNIQUE</code> ** : <strong>중복 불가 항목 지정</strong>
이 제약조건이 걸린 열의 값은 테이블 내에서 항상 고유해야 한다.(중복허용X) 고객ID, 이메일주소 등은 다른사람과 중복되면 안되기 때문에 <code>UNIQUE</code>제약조건을 사용한다.
<code>PRIMARY KEY</code> 와의 차이점 : <code>PRIMARY KEY</code> 는 테이블 당 하나만 존재할 수 있지만, <code>UNIQUE</code>는 여러 열에 설정가능하다. <code>PRIMARY KEY</code>는 <code>UNIQUE</code>와 <code>NOT NULL</code> 속성을 모두 포함하는 개념이다.</p>
</li>
<li><p>** <code>PRIMARY KEY</code> ** : <strong>테이블의 대표 식별자</strong>
테이블의 모든 행을 유일하게 식별하는 열. <code>NOT NULL</code> 과 <code>UNIQUE</code> 의 특징을 모두 가진다. 모든 테이블에는 반드시 <code>PRIMARY KEY</code> 가 있어야 한다.
<code>AUTO_INCREMENT</code> : MySQL에서 <code>PRIMARY KEY</code> 에 자주 사용하는 옵션이다. 정수 타입의 <code>PRIMARY KEY</code>
열에 이 옵션을 설정하면, 새로운 데이터가 추가될 때마다 1씩 자동으로 증가하는 번호를 할당해 준다. 직접 ID를 고민해서 넣을 필요가 없어 매우 편리하다.</p>
</li>
<li><p>** <code>FOREIGN KEY (FK)</code> <strong>: **테이블 간의 관계 설정</strong>
한 테이블을 다른 테이블과 연결하는 관계의 고리 참조하는 열의 값은 반드시 참조되는 테이블의 <code>PRIMARY KEY</code> 값 중 하나여야 한다는 <code>참조 무결성</code>을 강제한다.</p>
</li>
<li><p>** <code>DEFAULT</code> ** : <strong>기본값 설정</strong>
데이터를 <code>INSERT</code> 할 때 특정 열의 값을 명시하지 않으면, 자동으로 설정된 기본 값이 입력된다.</p>
</li>
<li><p><code>CHECK</code> : <strong>컬럼에 입력되는 값이 특정 조건을 만족하는지 검사</strong>
조건에 맞지 않는 데이터의 입력을 막는다.
10보다 작은 숫자가 입력되지 않도록 막는다.
18세 미만의 회원이 저장되지 않도록 막는다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 - CRUD]]></title>
            <link>https://velog.io/@levi_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-CRUD</link>
            <guid>https://velog.io/@levi_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-CRUD</guid>
            <pubDate>Wed, 01 Oct 2025 09:28:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>테이블을 생성한 뒤에는 그 안에 데이터를 넣고 <code>CREATE</code>, 읽고 <code>READ</code>, 수정 <code>UPDATE</code>, 삭제 <code>DELETE</code> 하는 방법을 알아보자, 이 네가지 작업을 묶어서 <code>CRUD</code> 라고 한다.</p>
</blockquote>
<hr>
<h2 id="1-insert-데이터-넣기---create">1. INSERT 데이터 넣기 - CREATE</h2>
<ul>
<li>INSERT는 테이블에 새로운 행을 추가하는 명령어이다.</li>
</ul>
<pre><code>INSERT INTO sample (
    product_id, 
    name, 
    price, 
    stock_quantity, 
    release_date
) VALUES (
    1, 
    &#39;청바지&#39;, 
    35000, 
    50, 
    &#39;2025-10-01&#39;
);</code></pre><blockquote>
<p>이 구문은 &quot;<code>sample</code> 테이블의 <code>product_id</code> , <code>name</code> , <code>price</code> , <code>stock_quantity</code> , <code>release_date</code> 열에 각각
<code>1</code> , <code>&#39;청바지&#39;</code> , <code>35000</code> , <code>50</code> , <code>&#39;2025-10-01&#39;</code> 값을 넣어서 새로운 행을 추가해라&quot; 라는 의미다.</p>
</blockquote>
<hr>
<h2 id="2-select-데이터-조회---read">2. SELECT 데이터 조회 - READ</h2>
<ul>
<li>SELECT는 데이터를 조회하는 명령어이다.
sample 테이블에 모든 데이터를 조회하고 싶다면 <code>*</code>를 사용하면 된다.<pre><code>SELECT * FROM sample;</code></pre></li>
</ul>
<blockquote>
<p><strong><em>실행결과</em></strong></p>
</blockquote>
<table>
<thead>
<tr>
<th>product_id</th>
<th>name</th>
<th>price</th>
<th>stock_quantity</th>
<th>release_date</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>청바지</td>
<td>35000</td>
<td>50</td>
<td>2025-10-01</td>
</tr>
</tbody></table>
<ul>
<li>이 명령어는 sample 테이블의 모든 열과 행을 조회한다. 만약 테이블의 특정 열의 데이터를 조회하고 싶다면 <code>*</code> 대신 해당 열의 이름을 넣어주면 된다. 이때 <code>,</code> 로 각각의 열(컬럼)을 구분한다.</li>
</ul>
<pre><code>SELECT name, price from sample;</code></pre><blockquote>
<p><strong><em>실행결과</em></strong></p>
</blockquote>
<table>
<thead>
<tr>
<th>name</th>
<th>price</th>
</tr>
</thead>
<tbody><tr>
<td>청바지</td>
<td>35000</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-update-데이터-수정---update">3. UPDATE 데이터 수정 - UPDATE</h2>
<ul>
<li>이미 저장된 데이터를 수정하는 명령어이다. 예를 들어 &#39;청바지&#39;의 가격을 &#39;25000&#39;으로 수정해보자<pre><code>UPDATE sample
SET price = 40000
WHERE product_id = 1;</code></pre><blockquote>
<p><em><strong>실행결과</strong></em>
<em><strong>1 row(s) affected Rows matched: 1 Changed: 1 Warnings: 0</strong></em></p>
</blockquote>
</li>
</ul>
<table>
<thead>
<tr>
<th>price</th>
</tr>
</thead>
<tbody><tr>
<td>25000</td>
</tr>
</tbody></table>
<ul>
<li>이 구문은 &quot;<code>sample</code> 테이블에서, <code>product_id</code> 가 <code>1</code> 인 행을 찾아서, 그 행의 <code>price</code> 값을 <code>25000</code>으로 변경해라&quot; 라는 의미다.</li>
</ul>
<blockquote>
<p><code>UPDATE</code> : 데이터를 변경할 테이블을 지정한다.
<code>SET</code> : 변경할 열(COLUMN)과 그 값을 지정한다.
<code>WHERE</code> : 변경할 행(ROW)을 선택한다.</p>
</blockquote>
<hr>
<h2 id="4-delete-데이터-지우기---delete">4. DELETE 데이터 지우기 - DELETE</h2>
<ul>
<li><code>DELETE</code> 는 테이블에서 특정 행을 삭제하는 명령어다. &#39;청바지&#39; 상품을 삭제해 보자.<pre><code>DELETE FROM sample
WHERE product_id = 1;</code></pre><blockquote>
<p>이 구문은 &quot;<code>sample</code> 테이블에서, <code>product_id</code> 가 <code>1</code> 인 행을 삭제해라&quot; 라는 의미다.
<code>DELETE FROM</code> : 삭제할 테이블을 지정한다.
<code>WHERE</code> : 삭제할 행(로우)을 선택한다.</p>
</blockquote>
</li>
</ul>
<blockquote>
<p><em><strong>실행 결과</strong></em>
<em><strong>1 row affected (0.00 sec)</strong></em></p>
</blockquote>
<table>
<thead>
<tr>
<th>product_id</th>
<th>name</th>
<th>price</th>
<th>stock_quantity</th>
<th>release_date</th>
</tr>
</thead>
<tbody><tr>
<td>Null</td>
<td>Null</td>
<td>Null</td>
<td>Null</td>
<td>Null</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 실습]]></title>
            <link>https://velog.io/@levi_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@levi_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Wed, 01 Oct 2025 08:50:13 GMT</pubDate>
            <description><![CDATA[<h2 id="1-데이터베이스-생성">1. 데이터베이스 생성</h2>
<h3 id="1-1-create-database-my_shop">1-1. CREATE DATABASE my_shop;</h3>
<ul>
<li>데이터베이스는 <code>;</code> 단위로 SQL을 인식한다.</li>
<li>해당 SQL을 실행하면 MYSQL에 <code>my_shop</code> 데이터베이스가 생성된다.</li>
<li>해당 DB 안에 테이블들을 설계하면 된다.</li>
</ul>
<h3 id="1-2-use-my_shop">1-2. USE my_shop;</h3>
<ul>
<li>작업할 데이터베이스 지정하기</li>
<li>항상 작업할 때 데이터베이스를 지정해줘야한다. 지정하지 않으면
데이터베이스가 선택되지 않았다고 오류가 발생한다.<blockquote>
<p>Error Code: 1046. No database selected Select the default DB to be used by double-clicking its name in the SCHEMAS list in the sidebar.</p>
</blockquote>
</li>
</ul>
<hr>
<h2 id="2-테이블-설계">2. 테이블 설계</h2>
<pre><code>CREATE TABLE sample (
    product_id INT PRIMARY KEY,
    name VARCHAR(100),
    price INT,
    stock_quantity INT,
    release_date DATE
);</code></pre><p><code>CREATE TABLE</code>은 테이블의 구조를 정의하고 생성하는 명령어다. 어떤 열(Column)들로 구성될지, 각 열에는 어떤 종류의 데이터가 들어갈지를 명시해야 한다.</p>
<hr>
<h2 id="3-기본-키primary-key">3. 기본 키(PRIMARY KEY)</h2>
<blockquote>
<p>데이터베이스 테이블에는 항상 기본키가 있어야 한다.
기본 키란, 테이블에 있는 모든 행들 중에서 특정 행 하나를 유일하게 식별할 수 있는 열 또는 열들의 조합이다.</p>
</blockquote>
<ul>
<li>위 테이블에는 product_id에 기본 키를 지정했으므로 해당 열에는 항상 다른 값이 들어가야한다. 다시 한번 강조하지만, 기본 키는 항상 유일해야한다.</li>
</ul>
<h3 id="3-1-데이터타입">3-1. 데이터타입</h3>
<ul>
<li><p>각 열에 들어갈 데이터의 종류를 알려주는 <code>데이터타입</code>의 종류</p>
<ul>
<li><code>INT</code> : 정수를 의미한다. 1,2 ... 100 등 정수를 저장할 때 사용한다.</li>
<li><code>VARCHAR(n)</code> : 문자열을 의미한다. n은 저장할 수 있는 최대 글자 수를 의미한다. <code>VARCHAR(100)</code> 은 최대 100글자 까지 저장할 수 있다.</li>
<li><code>DATE</code> : 날짜를 저장할 때 사용한다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="4-구조확인-삭제">4. 구조확인, 삭제</h2>
<blockquote>
<p>테이블을 설계할 때 오타가 나거나, 의도와 다르게 잘못 설계된 테이블이 설계될 수도있다. 삭제하는 방법을 알아보자</p>
</blockquote>
<h3 id="4-1-desc-sample">4-1. DESC sample;</h3>
<ul>
<li>테이블 구조 확인하기, 테이블이 어떤 열들로, 어떤 데이터타입으로 구성되어 있는지 구조를 보여준다.</li>
</ul>
<blockquote>
<p><em><strong>실행결과</strong></em></p>
</blockquote>
<table>
<thead>
<tr>
<th>Feild</th>
<th>Type</th>
<th>Null</th>
<th>Key</th>
<th>Default</th>
<th>Extra</th>
</tr>
</thead>
<tbody><tr>
<td>product_id</td>
<td>int</td>
<td>NO</td>
<td>PRI</td>
<td>NULL</td>
<td></td>
</tr>
<tr>
<td>name</td>
<td>varchar(100)</td>
<td>YES</td>
<td></td>
<td>NULL</td>
<td></td>
</tr>
<tr>
<td>price</td>
<td>int</td>
<td>YES</td>
<td></td>
<td>NULL</td>
<td></td>
</tr>
<tr>
<td>stock_quantity</td>
<td>int</td>
<td>YES</td>
<td></td>
<td>NULL</td>
<td></td>
</tr>
<tr>
<td>release_date</td>
<td>date</td>
<td>YES</td>
<td>NULL</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3 id="4-2-show-databases">4-2. SHOW DATABASES;</h3>
<ul>
<li>현재 서버에 있는 모든 데이터베이스 목록을 보여준다.</li>
</ul>
<blockquote>
<p><em><strong>실행결과</strong></em></p>
</blockquote>
<table>
<thead>
<tr>
<th>DATABASE</th>
</tr>
</thead>
<tbody><tr>
<td>information_schema</td>
</tr>
<tr>
<td>my_shop</td>
</tr>
<tr>
<td>mysql</td>
</tr>
<tr>
<td>performance_schema</td>
</tr>
<tr>
<td>sys</td>
</tr>
</tbody></table>
<h3 id="4-3-show-tables">4-3. SHOW TABLES;</h3>
<ul>
<li>해당 명령 실행 전에 반드시 <code>USE my_shop</code>을 실행한 뒤에 실행해야한다.</li>
<li>데이터베이스 안에 있는 테이블 목록을 보여준다.</li>
</ul>
<blockquote>
<p><strong><em>실행결과</em></strong></p>
</blockquote>
<table>
<thead>
<tr>
<th>TABLES_in_my_shop</th>
</tr>
</thead>
<tbody><tr>
<td>sample</td>
</tr>
</tbody></table>
<h3 id="4-4-drop-table--drop-database">4-4. DROP TABLE; , DROP DATABASE;</h3>
<ul>
<li>실수로 잘못된 데이터베이스, 테이블을 만들거나 더이상 필요없는 데이터베이스, 테이블을 삭제할 때 사용한다. <code>DROP</code> 명령어는 구조 자체를 완전히 삭제하는 명령어이기 때문에 신중하게 사용해야한다.</li>
</ul>
<blockquote>
<p><em>** SHOW TABLES; 실행결과**</em></p>
</blockquote>
<table>
<thead>
<tr>
<th>TABLES_in_my_shop</th>
</tr>
</thead>
<tbody><tr>
<td></td>
</tr>
</tbody></table>
<blockquote>
<p><em>** SHOW DATABASES; 실행결과**</em></p>
</blockquote>
<table>
<thead>
<tr>
<th>Database</th>
</tr>
</thead>
<tbody><tr>
<td>information_schema</td>
</tr>
<tr>
<td>mysql</td>
</tr>
<tr>
<td>performance_schema</td>
</tr>
<tr>
<td>sys</td>
</tr>
</tbody></table>
<blockquote>
<p><em><strong>주의할 점</strong></em> : 실제 운영 중인 서비스라면 <code>DROP</code> 명령어 특히 <code>DROP DATABASES</code>는 절대 함부로 사용하면 안된다. 모든 데이터가 통째로 사라져버릴 수 있기 때문에 삭제 전에는 항상 백업이 되어있는지, 정말 삭제대상이 맞는지 더블체크 하는것이 중요하다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[관계형 DB VS NoSQL]]></title>
            <link>https://velog.io/@levi_/%EA%B4%80%EA%B3%84%ED%98%95-DB-VS-NoSQL</link>
            <guid>https://velog.io/@levi_/%EA%B4%80%EA%B3%84%ED%98%95-DB-VS-NoSQL</guid>
            <pubDate>Thu, 25 Sep 2025 10:16:23 GMT</pubDate>
            <description><![CDATA[<h2 id="1-dbms">1. DBMS</h2>
<blockquote>
<p>처음에는 단순 파일 시스템으로 데이터를 저장했고, 파일 시스템의 문제를 해
결하기 위해 이어서 계층형과 네트워크형 DBMS가 생겼다. 이후 가장 많이 쓰이는 <strong>관계형 DBMS(RDBMS)</strong> 가 자리 잡았고, 최근에는 빅데이터 처리나 특수 요구 사항에 대응하기 위해 <strong>NoSQL DBMS</strong>가 빠르게 발전하고 있다.</p>
</blockquote>
<p><strong>&quot;데이터를 어떻게 하면 더 효과적이고 더 안전하게 관리할 수 있을까?&quot;</strong></p>
<h3 id="1-1-rdbms-nosql">1-1. RDBMS, NoSQL</h3>
<h3 id="rdbms"><em><strong>RDBMS</strong></em></h3>
<ul>
<li>현재 가장 많이 사용되는 데이터베이스 시스템이다.</li>
<li>테이블 간의 관계를 기반으로 데이터를 구조화 한 시스템이다.</li>
</ul>
<p>데이터를 <strong>테이블(Table)</strong> 이라는 정형화된 구조에 저장한다.
테이블은 <strong>행(Row, 레코드)</strong> 과 <strong>열(Column, 필드, 속성)</strong> 로 구성되며,<br>이 테이블들 간의 관계를 통해 데이터를 연결하고 관리한다.</p>
<h4 id="rdbms가-기본인-이유">RDBMS가 기본인 이유</h4>
<ul>
<li><strong><em>정형화된 데이터 관리</em></strong> : 데이터 구조의 명확성, 데이터타입, 제약조건을 통해  무결성을 보장한다. 구조가 명확하고 일관성이 중요한 데이터에 적합</li>
<li><em><strong>ACID 트랜잭션 보장</strong></em> : 트랜잭션의 ACID(원자성, 일관성, 고립성, 지속성) 특성을 잘 지원, 데이터 처리 신뢰성이 높다. 금융거래, 주문/결제    에 필수</li>
<li><strong><em>SQL (Structured Query Language)</em></strong> : 강력하고 표준화된 언어를 사용하여 복잡한 데이터 조회나 조작을 쉽게 할 수 있다. DDL, DCL, DML등이 있다.</li>
</ul>
<h3 id="nosql"><strong>NoSQL</strong></h3>
<ul>
<li>빅데이터와 특정 요구사항에 맞춰 생긴 시스템</li>
<li>관계형데이터베이스의 한계를 보완했다.</li>
</ul>
<p>관계형 모델을 사용하지 않거나, SQL을 주요 데이터 접근 언어로 사용하지 않
는 DBMS들을 의미한다. RDBMS가 모든 상황에 최적인 것은 아니기 때문에, 특정 요구사항(대량 비정형 데이터 처리, 매우 빠른 읽기/쓰기 속도, 유연한 데이터 모델 등)을 만족시키기 위해 등장했다.</p>
<table>
<thead>
<tr>
<th>분류</th>
<th>데이터 단위</th>
<th>핵심 장점</th>
<th>대표 사례</th>
<th>대표 DBMS</th>
</tr>
</thead>
<tbody><tr>
<td>키-값 저장소</td>
<td>키 -&gt; 값 한 쌍</td>
<td>초고속 단건 읽기/쓰기</td>
<td>세션, 캐시</td>
<td>Redis, Memcached</td>
</tr>
<tr>
<td>문서 DB</td>
<td>JSON/BSON 문서</td>
<td>유연한 스키마</td>
<td>상품 카탈로그</td>
<td>MongoDB, Couchbase</td>
</tr>
<tr>
<td>컬럼 패밀리 저장소</td>
<td>열(Column)그룹</td>
<td>대용량 분산/분석</td>
<td>로그/시계열</td>
<td>Cassandra,HBase</td>
</tr>
<tr>
<td>그래프DB</td>
<td>노드, 엣지</td>
<td>복잡한 관계 탐색</td>
<td>추천/이상 탐지</td>
<td>Neo4j</td>
</tr>
</tbody></table>
<blockquote>
<p>NoSQL은 RDBMS를 대체한다기보다는 <strong>상호 보완적인 관계</strong>
로 이해하는 것이 좋다. 핵심 데이터는 RDBMS에 저장하되, 특정 기능
(예: 실시간 인기 검색어, 사용자 세션 관리, 상품 추천 등)에는 NoSQL을 함께 사용할 수도 있다.</p>
</blockquote>
<h3 id="2-rdbms의-호환성과-ansi-sql-표준">2. RDBMS의 호환성과 ANSI SQL 표준</h3>
<p><strong>ANSI/ISO</strong>에서 지정한 SQL 표준 덕분에 서로 다른 DBMS를 사용한다고 하더라도 새롭게 배울 필요가 없다. 
여기서 RDBMS들이 공통적으로 사용할 수 있는 표준 SQL문법을 정의했다.
Oracle, MySQL, MS SQL, PostgreSQL 등 대부분의 RDBMS는 이 표준을 준수하려고 노력한다. 물론 각각의 DBMS마다 고유한 추가 기능(함수나 문법)을 가지고 있다.</p>
<h3 id="3-rdbms의-핵심-개념">3. RDBMS의 핵심 개념</h3>
<blockquote>
<p><em><strong>관계형 모델의 핵심 개념, 모든 데이터는 &#39;표&#39;에서 시작한다.</strong></em></p>
</blockquote>
<ul>
<li><p><strong>테이블 (Table)</strong>: 관계형 데이터베이스에서 데이터를 저장하는 가장 기본적인 구조다. 엑셀의 &#39;시트(Sheet)&#39;와 거의 동일한 개념이다. 테이블은 특정 주제와 관련된 데이터들의 집합이다.</p>
</li>
<li><p><strong>행 (Row)</strong>: 테이블의 각 가로줄을 의미한다. 하나의 행은 개별적인 데이터 항목 하나를 나타낸다. 예를 들어, <code>고객테이블</code> 에서 하나의 행은 고객 한 명의 정보를 의미한다. 1 고객의 정보, 2 고객의 정보 등이 각각 하나의 행이 된다.</p>
</li>
<li><p><em>실무 용어*</em>: 행은 &#39;레코드(Record)&#39; 또는 &#39;튜플(Tuple)&#39;이라고도 불린다.</p>
</li>
<li><p><strong>열 (Column)</strong>: 테이블의 각 세로줄을 의미한다. 열은 테이블에 어떤 종류의 데이터가 저장될지를 정의한다. <code>고객테이블</code> 이라면 <code>고객번호</code> , <code>이름</code> , <code>연락처</code> , <code>주소</code> 등이 각각의 열이 된다.</p>
</li>
<li><p><em>실무 용어*</em>: 열은 &#39;속성(Attribute)&#39; 또는 &#39;필드(Field)&#39;라고도 한다.</p>
</li>
</ul>
<h4 id="3-1-기본-키primary-key---데이터-중-단-하나를-식별하는-방법">3-1. 기본 키(Primary Key) - 데이터 중 &#39;단 하나&#39;를 식별하는 방법</h4>
<blockquote>
<p><strong>기본 키란, 테이블에 있는 모든 행(Row)들 중에서 특정 행 하나를 유일하게 식별할 수 있는 열(Column) 또는 열들의 조합</strong>을 말한다.</p>
</blockquote>
<h4 id="기본-키의-두가지-규칙">기본 키의 두가지 규칙</h4>
<ol>
<li><code>고유성</code> : 기본 키로 지정된 열의 값은 같은 테이블 내에서 절대로 중복될 수 없다. 모든 행이 서로 다른 값을 가져야 한다.</li>
<li><code>NOT NULL</code> : 기본 키로 지정된 열에는 반드시 값이 있어야 한다. 비어있거나 (NULL) 인 상태는 허용되지 않는다.</li>
</ol>
<ul>
<li><em><strong>수많은 데이터 속에서 특정 데이터 하나를 빠르고 정확하게 찾아내고, 수정하고, 삭제하기 위해서다. 기본 키가 없다면 우리는 수많은 데이터에서 원하는 정보를 특정할 수 없다. 따라서 모든 테이블에는 기본 키를 설정하는 것이 원칙이다.</strong></em></li>
</ul>
<h4 id="3-2-외래-키foreign-key---따로-떨어진-표들을-관계로-묶는-방법">3-2. 외래 키(Foreign Key) - 따로 떨어진 표들을 &quot;관계&quot;로 묶는 방법</h4>
<blockquote>
<p><strong>외래 키란, 한 테이블(A)의 열(Column)이 다른 테이블(B)의 기본 키(Primary Key) 값을 참조하는 것</strong>을 말한다.</p>
</blockquote>
<p><strong>부모와 자식의 관계</strong></p>
<ul>
<li>두 테이블이 FK 값을 통해 관계가 있을 때 한쪽을 <code>부모</code>, 한쪽을 <code>자식</code>이라 한다.
자식 테이블은 FK 값을 통해 부모 테이블을 참조한다. FK 값을 가진 곳이 자식 테이블이다.
여기서는 <code>orders</code> 테이블이 FK 값인 <code>customer_id</code> 를 통해 <code>customers</code> 테이블을 참조한다.
따라서 둘의 관계에서 <code>orders</code> 가 자식 테이블이고, <code>customers</code> 가 부모 테이블이 된다.</li>
</ul>
<p><strong>외래 키의 중요한 규칙</strong></p>
<ul>
<li><code>참조 무결성</code> : 외래 키 열에 있는 값은, 반드시 부모 테이블(참조 당하는 쪽의 기본 키 값 중 하나이거나, 혹은 NULL이어야 한다. 
참조 무결성을 지키지 않으면, 데이터베이스가 오류를 발생시켜 막아준다. 이 덕분에 데이터의 정합성이 보장되는 것이다.</li>
</ul>
<p><strong>외래키를 사용하는 이유</strong></p>
<ul>
<li>데이터의 중복을 막고, 데이터의 일관성을 유지하며, 논리적으로 분리된 데이터들 사이에 &#39;관계&#39;를 맺어주기 위해서다. 이를 통해 여러 개의 테이블로 전체 시스템을 구조화할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스]]></title>
            <link>https://velog.io/@levi_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@levi_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Thu, 25 Sep 2025 08:37:44 GMT</pubDate>
            <description><![CDATA[<h2 id="1-데이터베이스란">1. 데이터베이스란?</h2>
<blockquote>
<p>데이터 는 단순한 사실의 집합이고, 정보 는 그 데이터를 가공하고 해석해 의미를 전달하는 결과물이다. 데이터베이스는 이런 데이터 를 체계적으로 저장하고 관리해, 필요할 때 빠르게 조회하고 분석할 수 있도록 한다. 잘 정리된 데이터베이스를 활용하면, 단순한 기록도 유용한 정보 로 전환될 수 있다.</p>
</blockquote>
<h3 id="1-1-데이터">1-1. 데이터</h3>
<p><strong>의미가 없는 단순한 정보</strong>
아직 가공되지 않은, 단순한 기록, 사실 자체를 의미한다.</p>
<ul>
<li>user1</li>
<li>셔츠</li>
<li>20,000</li>
<li>2025-09-24</li>
</ul>
<p>이 데이터들만 봐서는 user1이 누구인지, 20,000이 어떠한 숫자를 의미하는지, 알 수 없다. </p>
<h3 id="1-2-데이터의-구조화">1-2. 데이터의 구조화</h3>
<table>
<thead>
<tr>
<th>주문번호</th>
<th>고객ID</th>
<th>상품</th>
<th>수량</th>
<th>주문금액</th>
<th>주문날짜</th>
</tr>
</thead>
<tbody><tr>
<td>0001</td>
<td>user1</td>
<td>장갑</td>
<td>3</td>
<td>12,000</td>
<td>2025.09.12</td>
</tr>
<tr>
<td>0002</td>
<td>user2</td>
<td>바지</td>
<td>5</td>
<td>20,000</td>
<td>2025.09.15</td>
</tr>
<tr>
<td>0003</td>
<td>user1</td>
<td>셔츠</td>
<td>2</td>
<td>20,000</td>
<td>2025.09.15</td>
</tr>
<tr>
<td>0004</td>
<td>user1</td>
<td>장갑</td>
<td>1</td>
<td>24,000</td>
<td>2025.09.20</td>
</tr>
<tr>
<td>0005</td>
<td>user3</td>
<td>바지</td>
<td>4</td>
<td>20,000</td>
<td>2025.09.20</td>
</tr>
<tr>
<td>0006</td>
<td>user3</td>
<td>셔츠</td>
<td>0</td>
<td>40,000</td>
<td>2025.09.21</td>
</tr>
</tbody></table>
<p>1-1의 단순한 데이터와 비교해봤을때 각 데이터들이 어떤 의미를 가지고 있는지 확실하게 알 수 있다. 이렇게 데이터를 구조화시켰을 때 우리는 이 구조화된 데이터들을 통해 어떠한 _<strong>&quot;의사결정&quot;</strong>_을 할 수 있게된다.</p>
<h3 id="1-3-정보">1-3. 정보</h3>
<p><strong>구조화된 데이터를 통해 어떠한 목적을 가지고 분석, 가공을 통해 얻은 의미있는 결과물</strong></p>
<h4 id="1-3-1-필터링">1-3-1. 필터링</h4>
<p>위의 <em><strong>&quot;구조화된 데이터&quot;</strong></em> 를 통해 어떤 유저가 어떤상품을 몇개 주문했는지 알 수 있다.</p>
<ul>
<li><em><strong>고객ID가 &quot;user1&quot;인 사용자를 기준으로 데이터를 분류한다.</strong></em></li>
</ul>
<h4 id="1-3-2-그룹화-집계">1-3-2. 그룹화, 집계</h4>
<p>분류한 데이터를 &quot;상품&quot;에 따라 다시 분류하고 해당 &quot;상품&quot; 들의 주문 개수를 각각 합산한다.</p>
<ul>
<li><em><strong>&quot;user1&quot;이 구매한 &quot;장갑&quot; &quot;3개&quot;, &quot;셔츠&quot; &quot;2개&quot;</strong></em></li>
</ul>
<p>위처럼 &quot;구조화된 데이터&quot; 를 토대로, 특정 유저가 어떤 상품을 필요로 하는지, 쇼핑몰을 자주 이용하는 유저인지, 한 달에 사용한 금액이 얼마인지 등 다양한 유의미한 정보를 얻을 수 있다. 이를 기반으로 쇼핑몰 운영에 필요한 좋은 의사결정을 내릴 수 있다. </p>
<blockquote>
<p>앞으로 학습하게 될 <em><strong>&quot;데이터베이스&quot;</strong></em> 란 위에서 정리한 <em><strong>&quot;데이터&quot;</strong></em> 를 체계적으로 저장하고, 원하는 조건으로 분류, 가공하여 유의미한 <em><strong>&quot;정보&quot;</strong></em> 를 얻을 수 있게 해주는 강력한 핵심 툴이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[영속성전이(CASCADE) 와 고아객체]]></title>
            <link>https://velog.io/@levi_/%EC%98%81%EC%86%8D%EC%84%B1%EC%A0%84%EC%9D%B4CASCADE-%EC%99%80-%EA%B3%A0%EC%95%84%EA%B0%9D%EC%B2%B4</link>
            <guid>https://velog.io/@levi_/%EC%98%81%EC%86%8D%EC%84%B1%EC%A0%84%EC%9D%B4CASCADE-%EC%99%80-%EA%B3%A0%EC%95%84%EA%B0%9D%EC%B2%B4</guid>
            <pubDate>Mon, 14 Jul 2025 06:56:12 GMT</pubDate>
            <description><![CDATA[<h2 id="1-영속성전이란">1. 영속성전이란?</h2>
<blockquote>
<p>영속성 전이는 특정 엔티티를 영속화(persist)할 때, 연관된 다른 엔티티도 함께 영속화되도록 도와주는 기능이다.
예를 들어 <code>Team</code>과 <code>Member</code>가 1:N 관계일 때, 팀을 저장할 때 연관된 멤버도 함께 저장하고 싶다면 어떻게 해야 할까?</p>
</blockquote>
<p>아래는 <em><strong>Parent</strong></em> 와 <em><strong>Child</strong></em> 가 <em><strong>1:N</strong></em> 관계로 매핑된 엔티티 예시이다.
부모 엔티티(Parent)가 자식 엔티티(Child)를 리스트로 관리하고 있다.</p>
<pre><code>@Entity
@Getter
@Setter
public class Parent {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = &quot;parent&quot;)
    private List&lt;Child&gt; childList = new ArrayList&lt;&gt;();

    public void addChild(Child child) {
        childList.add(child);
        child.setParent(this);
    }
}</code></pre><pre><code>@Entity
@Getter
@Setter
public class Child {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = &quot;parent_id&quot;)
    private  Parent parent;
}</code></pre><h3 id="기존-방식-직접-영속화">기존 방식: 직접 영속화</h3>
<pre><code>Child child1 = new Child();
Child child2 = new Child();

Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);
em.persist(child1);
em.persist(child2);

tx.commit();</code></pre><p>위 코드에서는 <em><strong>parent, child1, child2</strong></em> 를 각각 영속성 컨텍스트에 등록해야 한다.
하지만 이 관계에 <em><strong>cascade = CascadeType.ALL</strong></em> 을 설정하면, 부모 객체만 영속화 해도 자식 객체가 함께 저장된다.</p>
<h3 id="cascade-적용-방식">Cascade 적용 방식</h3>
<pre><code>@OneToMany(mappedBy = &quot;parent&quot;, cascade = CascadeType.ALL)
private List&lt;Child&gt; childList = new ArrayList&lt;&gt;();</code></pre><p>이제 <em><strong>em.persist(parent)</strong></em> 만 호출해도 <em><strong>child1, child2</strong></em> 가 함께 저장된다.</p>
<blockquote>
<p>이처럼 부모 엔티티의 상태 변경이 자식 엔티티에게 전이되는 것을
<strong>&quot;영속성 전이(Cascade)&quot;</strong> 라고 하며,
영속성 전이는 <em><strong>&quot;연관관계 매핑&quot;</strong></em> 과는 별개의 기능이며, 엔티티의 생명주기를 함께 관리하고자 할 때 주로 사용된다.
연관관계를 <em><strong>&quot;어떻게 매핑하느냐&quot;</strong></em> 와는 무관하며,
저장·삭제 시 편의성을 높이기 위한 별도의 기능이다.
따라서 연관관계 매핑만으로는 전이가 발생하지 않으며,
<em><strong>Cascade</strong></em> 옵션을 명시적으로 설정해야 한다.</p>
</blockquote>
<h3 id="cascade의-종류">CASCADE의 종류</h3>
<ul>
<li><em><strong>ALL</strong></em> : 모두 적용</li>
<li><em><strong>PERSIST</strong></em> : 영속</li>
<li><em><strong>REMOVE</strong></em> : 삭제</li>
</ul>
<hr>
<h2 id="2-고아객체">2. 고아객체</h2>
<blockquote>
<p>부모 엔티티와의 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능이다.
즉, 부모 엔티티에서 자식을 컬렉션에서 제거하거나, 자식 엔티티의 부모 필드를 null로 만들면,
해당 자식은 <strong>&quot;고아 객체&quot;</strong> 가 되고, DB에서도 삭제된다.</p>
</blockquote>
<pre><code>@OneToMany(mappedBy = &quot;parent&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
private List&lt;Child&gt; childList = new ArrayList&lt;&gt;();</code></pre><pre><code>Child child1 = new Child();
Child child2 = new Child();

Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);

em.flush();
em.clear();

Parent findParent = em.find(Parent.class,parent.getId());
findParent.getChildList().remove(0);

tx.commit();</code></pre><p>위와 같이 <strong><em>orphanRemoval = true</em></strong> 를 설정해주고 <em><strong>Parent</strong></em> 에서<em><strong>ChildList</strong></em> 의 첫번째 컬렉션을 <em><strong>remove</strong></em> 해주면 <em><strong>delete</strong></em> 쿼리가 발생한다.
<img src="https://velog.velcdn.com/images/levi_/post/3f5e4943-dafc-48f0-8bc6-41392587074d/image.png" alt="">
실제 DB에서도 삭제된걸 확인할 수 있다.
<img src="https://velog.velcdn.com/images/levi_/post/99c1f23c-ef54-4fb3-87bb-370cd48a3906/image.png" alt=""></p>
<h3 id="2-1-고아객체-주의점">2-1. 고아객체 주의점</h3>
<p><strong><em>1. 참조하는 곳이 하나일 때만 사용해야한다</em></strong></p>
<p>연관관계가 끊기면 삭제되기 떄문인데, 고아 객체 제거는 부모 엔티티와 자식 엔티티의 연관관계가 끊기는 순간 DELETE 쿼리가 발생한다.</p>
<pre><code>parent1.getChildren().remove(child); 
→ child와의 연관관계 끊김 → child는 orphan → 삭제</code></pre><p><em><strong>만약 여러 객체가 Child를 참조하고 있다면?</strong></em></p>
<pre><code>Parent1 → Child  
Parent2 → Child </code></pre><p><em><strong>Parent1</strong></em> 이 <em><strong>removeChild(child)</strong></em> 하면 <em><strong>child</strong></em> 가 삭제된다.
하지만 <em><strong>Parent2</strong></em> 입장에선 원하지 않아도 참조하던 엔티티가 삭제되버린다.
그래서 고아객체 제거는 
<em><strong>&quot;자식 엔티티가 오직 하나의 부모에게만 소속되어 있는 경우&quot;</strong></em> 만 안전하게 사용 가능하다.</p>
<p><em><strong>2. 왜 @OneToOne, @OneToMany만 지원할까?</strong></em></p>
<p>위에서 설명한 내용과 이어지는 내용인데, 
<em><strong>@ManyToOne, @ManyToMany</strong></em> 에서는 다대다/다대일 관계이기 때문에 고아 객체를 누군가 다른 곳에서도 참조 중일 수 있다.
예를 들어서</p>
<pre><code>@ManyToMany
List&lt;Student&gt; students;</code></pre><p>학생 한명이 여러 수업에 소속되어 있을 수 있다. 그런데 한 수업에서만 빠졌다고 해서 해당 학생을 DB에서 삭제 해버리면 문제가 발생한다.</p>
<p>참고: 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고
아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께
제거된다. 이것은 <em><strong>CascadeType.REMOVE</strong></em> 처럼 동작한다.</p>
<blockquote>
<p><em><strong>orphanRemoval = true</strong></em> 는 부모엔티티가 자식엔티티를 완전히 소유할때만 사용해야한다. 자식엔티티가 여러 곳에서 참조될 수 있는 구조에서는 연관관계가 끊긴다고 해서 바로 삭제하면 다른 참조 관계에도 영향을 주는 심각한 문제가 발생할 수 있다.
그래서 JPA는 이 기능을 <em><strong>@OneToOne, @OneToMany</strong></em> 에서만 지원하며,
자식의 생명주기를 부모가 전적으로 관리하는 경우에만 사용하는 것이 안전하다.</p>
</blockquote>
<h2 id="3-영속성전이--고아객체-생명주기">3. 영속성전이 + 고아객체, 생명주기</h2>
<p><em><strong>CascadeType.ALL + orphanRemoval=true</strong></em></p>
<p>엔티티의 관계에서 <em><strong>cascade = CascadeType.ALL</strong></em> 과 <em><strong>orphanRemoval = true</strong></em> 를 함께 사용하면
부모 엔티티 하나를 통해 자식 엔티티의 생성부터 삭제까지 생명주기를 일관성 있게 관리할 수 있다.</p>
<ul>
<li><p><em><strong>CascadeType.ALL</strong></em>
부모를 <em><strong>persist()</strong></em> 하면 자식도 함께 저장되고
부모를 <em><strong>remove()</strong></em> 하면 자식도 함께 삭제됨
→ 자식의 영속 상태 전이</p>
</li>
<li><p><em><strong>orphanRemoval = true</strong></em>
부모와의 연관관계가 끊기면 자식은 자동으로 삭제됨
→ 자식의 자동 제거</p>
<pre><code>@OneToMany(mappedBy = &quot;parent&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
private List&lt;Child&gt; children = new ArrayList&lt;&gt;();</code></pre><p>이렇게 설정해두면 다음과 같은 일들이 가능하다:</p>
</li>
<li><p><em><strong>em.persist(parent)</strong></em> 만으로 자식까지 모두 저장</p>
</li>
<li><p><em><strong>em.remove(parent)</strong></em> 만으로 자식까지 모두 삭제</p>
</li>
<li><p><em><strong>parent.getChildren().remove(child)</strong></em> → DB에서 child 자동 삭제</p>
</li>
</ul>
<p>즉, 자식은 부모의 생명주기에 완전히 의존하게 된다.</p>
<blockquote>
<p><em><strong>CascadeType.ALL</strong></em> 과 <em><strong>orphanRemoval = true</strong></em> 를 함께 설정하면
부모 엔티티를 통해 자식 엔티티의 생명주기를 일관성 있게 관리할 수 있다.
이는 <em><strong>도메인 주도 설계(DDD)</strong></em> 에서 <em><strong>Aggregate Root</strong></em> 가 내부 구성요소의 생명주기를 책임지는 구조와 잘 맞아떨어지며,
실무에서도 자식이 부모에게 전적으로 종속되는 경우에 매우 유용하게 사용될 수 있을것 같다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA - 즉시로딩과 지연로딩, N+1문제]]></title>
            <link>https://velog.io/@levi_/JPA-%EC%A6%89%EC%8B%9C%EB%A1%9C%EB%94%A9%EA%B3%BC-%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9-N1%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@levi_/JPA-%EC%A6%89%EC%8B%9C%EB%A1%9C%EB%94%A9%EA%B3%BC-%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9-N1%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Sun, 13 Jul 2025 10:53:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Member와 Team이라는 두 엔티티가 있다고 가정해보자. 이 둘 사이에 JPA 연관관계가 설정되어 있다면, Member를 조회할 때 자동으로 Team도 <em><strong>Join</strong></em> 을 통해 함께 조회되도록 설정할 수 있다. 
하지만 항상 Member를 조회할 때마다 Team까지 함께 조회해야 할까? 실제로는 Team 정보가 필요 없는 경우도 있을 수 있다. 
이처럼 연관된 엔티티를 꼭 즉시 불러오지 않고, 실제로 필요한 시점에 조회할 수 있도록 지원하는 방식이 바로 <em><strong>지연로딩(Lazy Loading)</strong></em> 이다. 이번 글에서는 JPA에서 제공하는 
<em><strong>즉시로딩(Eager Loading)</strong></em> 과 <em><strong>지연로딩(Lazy Loading)</strong></em> 전략에 대해 알아보자.</p>
</blockquote>
<h2 id="1-지연로딩lazy">1. 지연로딩(LAZY)</h2>
<p>지연로딩이 적용된 연관관계 필드는 처음 조회할 때 실제 엔티티 대신 <strong>프록시 객체(Proxy)</strong> 를 반환한다. 이 프록시는 가짜 객체처럼 보이지만, 실제로는 내부에 아무 데이터도 없고, 단지 영속성 컨텍스트와 식별자(ID)만을 가지고 있다. 실제로 필드나 메서드에 접근하는 순간, 그때 DB를 조회하고 진짜 엔티티 데이터를 채워 넣는다.</p>
<p>즉, 지연로딩은 <strong>&quot;연관된 엔티티를 꼭 사용할 때만 불러오자&quot;</strong> 는 전략으로, 성능 최적화에 매우 유용하다. 대용량 데이터를 다루거나, 연관관계가 많은 복잡한 모델에서는 불필요한 조인을 줄여 성능을 높일 수 있다.</p>
<pre><code>@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = &quot;TEAM_ID&quot;)
private Team team;</code></pre><p>위와 같이 <em><strong>fetch = FetchType.LAZY</strong></em> 를 통해 지연로딩 설정을 할 수 있다.</p>
<pre><code>Team team = new Team();
team.setName(&quot;teamA&quot;);
em.persist(team);

Member member1 = new Member();
member1.setUsername(&quot;member1&quot;);
member1.setTeam(team);
em.persist(member1);

em.flush();</code></pre><pre><code>Member m = em.find(Member.class, member1.getId());

System.out.println(&quot;m1 = &quot; + m.getTeam().getClass());</code></pre><p>먼저 실습을 위해 Team을 만들고, Member를 만들어 Team에 세팅해줬다.</p>
<p>em.find로 member1의 id를 찾아와서 해당 id로 team의 class를 찾아보면
<img src="https://velog.velcdn.com/images/levi_/post/f62b1f78-e88d-4792-a771-a79d5d3cbc30/image.png" alt="">
위와 같은 프록시 객체로 출력되는걸 볼 수 있다. 추가로 em.find로 Member클래스를 조회 시 Team에 대한 조회쿼리는 발생되지 않는걸 알 수 있다.</p>
<pre><code>Member m = em.find(Member.class, member1.getId());

System.out.println(&quot;m1 = &quot; + m.getTeam().getClass());

System.out.println(&quot;==================&quot;);
m.getTeam().getName();
System.out.println(&quot;==================&quot;);

tx.commit();</code></pre><p><img src="https://velog.velcdn.com/images/levi_/post/adeafc16-142a-4b3c-b219-2f4c81a08ab3/image.png" alt=""></p>
<p>위와 같이 직접 Team 엔티티의 데이터에 접근하려고 하는 시점에 Team에대한 조회쿼리가 발생한다.</p>
<hr>
<h2 id="2-지연로딩과-프록시객체">2. 지연로딩과 프록시객체</h2>
<blockquote>
<p>여기서 궁금한 점이 하나 생긴다.
지연로딩이 설정된 연관관계는 해당 엔티티에 직접 접근하기 전까지는 실제 객체 대신 프록시 객체가 반환된다.
그리고 연관된 엔티티에 실제로 접근하는 시점 team.getName()과 같이 데이터를 조회하려고 하면,
JPA는 영속성 컨텍스트를 통해 프록시 내부를 초기화하여 실제 엔티티의 데이터를 조회하고 연결해 준다.
이 과정을 통해 이후에는 프록시를 통해 실제 엔티티에 접근할 수 있게 된다.
그럼, 프록시가 초기화된 이후에는 더 이상 프록시가 아닌 실제 엔티티가 되는 걸까?
아니면 프록시 상태는 유지되면서 내부만 초기화되는 걸까?</p>
</blockquote>
<p><em><strong>결론부터 말하자면, 프록시객체는 계속 프록시객체이다.
다만, 그 프록시 내부에 실제 엔티티의 데이터가 로딩되어 있을 뿐이다.</strong></em></p>
<pre><code>Member m = em.find(Member.class, member1.getId());
Team team = member.getTeam(); // 아직은 프록시 객체
System.out.println(&quot;m1 = &quot; + m.getTeam().getClass());</code></pre><p><img src="https://velog.velcdn.com/images/levi_/post/e6b5bed2-a781-4c8e-bf50-362af7739f9a/image.png" alt=""></p>
<ul>
<li><p>이 시점의 team객체는 Hibernate가 생성한 프록시객체 이다.</p>
</li>
<li><p>실제 DB에서 데이터를 조회하지 않았고, team.getName() 같은 메서드를 호출하면 그 순간 초기화된다.</p>
<br/>
```
System.out.println(team.getName()); // 이 순간 DB에서 조회 발생
```
</li>
<li><p>내부적으로 프록시객체가 영속성컨텍스트에 &quot;진짜 Team엔티티를 달라&quot; 고 요청을 해서 프록시객체 초기화가 이루어진다. 
(내부에 데이터를 채운다.)</p>
</li>
<li><p>하지만 프록시 객체 자체가 다른 객체로 바뀌는 건 아니다.
즉, _<strong>team.getClass()</strong>_는 여전히 _<strong>Team$HibernateProxy</strong>_이다.</p>
</li>
</ul>
<hr>
<h2 id="3-즉시로딩eager">3. 즉시로딩(EAGER)</h2>
<blockquote>
<p>만약 Member와 Team을 자주 함께 써야하는 상황이라면?
무작정 지연로딩을 사용하면 오히려 성능상 손해를 볼 수도 있다.
지연로딩과 반대되는 개념이 바로 <strong>즉시로딩(Eager Loading)</strong> 이다. <em><strong>즉시로딩</strong></em> 은 엔티티를 조회하는 시점에 연관된 엔티티도 함께 즉시 조회하는 전략이다. 
예를 들어 <em><strong>Member</strong></em> 엔티티에서 <em><strong>Team</strong></em> 을 즉시로딩으로 설정하면, <em><strong>Member</strong></em> 를 조회할 때 <em><strong>Team</strong></em> 도 즉시 조인(Join) 쿼리를 통해 함께 불러온다.</p>
</blockquote>
<pre><code>@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = &quot;TEAM_ID&quot;)
private Team team;</code></pre><ul>
<li>즉시로딩은 위와 같이 <em><strong>fetch = FetchType.EAGER</strong></em> 로 설정할 수 있다.</li>
</ul>
<pre><code>Member m = em.find(Member.class, member1.getId());</code></pre><p><img src="https://velog.velcdn.com/images/levi_/post/69f40722-f553-49b0-9ae8-8a7d3d3fd70b/image.png" alt=""></p>
<ul>
<li>즉시로딩으로 설정 후 실행해보면 위와 같이 JOIN을 통해 Member와 Team을 함께 조회한다.
한번에 조회해 오기 때문에 프록시 객체가 필요없어진다.<pre><code>System.out.println(&quot;m1 = &quot; + m.getTeam().getClass());</code></pre><img src="https://velog.velcdn.com/images/levi_/post/80fa96aa-0e5f-4aae-a2a1-4209c40dfecc/image.png" alt="">
프록시객체가 아닌 실제 엔티티가 출력되는걸 확인할 수 있다.</li>
</ul>
<hr>
<h2 id="4-프록시와-즉시로딩-주의점">4. 프록시와 즉시로딩 주의점</h2>
<blockquote>
<p>JPA의 로딩 전략은 잘 사용하면 성능 최적화에 큰 도움이 되지만, 잘못 사용하면 N+1 문제, 불필요한 쿼리, 예외 등의 문제가 발생할 수 있다. 아래는 지연로딩(프록시)과 즉시로딩을 사용할 때 각각 주의해야 할 점들이다.</p>
</blockquote>
<ul>
<li>가급적 지연 로딩만 사용</li>
<li>즉시 로딩을 적용하면 예상하지 못한 SQL이 발생할 수 있다.</li>
<li>즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.</li>
<li><em><strong>@ManyToOne, @OneToOne</strong></em> 은 기본이 즉시 로딩 -&gt; <em><strong>LAZY</strong></em> 로 설정</li>
<li><em><strong>@OneToMany, @ManyToMany</strong></em> 는 기본이 지연 로딩</li>
</ul>
<hr>
<h3 id="4-1-n1문제">4-1. N+1문제</h3>
<blockquote>
<p>N+1 문제는 JPA에서 지연로딩(LAZY) 설정과 연관관계 매핑이 함께 사용될 때 자주 발생하는 성능 이슈다.</p>
</blockquote>
<p>처음 1번의 쿼리(JPQL 또는 Criteria 등)를 통해 N개의 엔티티를 조회한 후,
그 각각의 엔티티에 연관된 데이터를 조회하기 위해 추가적으로 N개의 쿼리가 발생하는 현상.
<em>*<em>즉, 총 1 + N번의 쿼리가 나가는 구조이기 때문에 N+1 문제라고 부른다.
*</em></em></p>
<p>예를들어 Member Team 사이에 Team에 EAGER로 설정되어있는 상황이다.</p>
<pre><code>List&lt;Member&gt; members = em.createQuery(&quot;select m from Member m&quot;, Member.class)
                    .getResultList();</code></pre><p>멤버와 팀을 각각 설정해주고 실행해보면 처음에 Member 조회쿼리가 한 번 발생하고 그 이후에 Member와 연관된 Team쿼리가 멤버 수만큼 발생한다.
<img src="https://velog.velcdn.com/images/levi_/post/7ddc41d5-cb98-4282-9449-77837b450708/image.png" alt=""></p>
<p>그럼, LAZY로 설정하면 N+1 문제를 완벽하게 방지할 수 있을까?</p>
<p>결론부터 말하자면 No 위처럼 단순하게 조회하는 쿼리는 추가 쿼리가 발생하지 않는다 하지만</p>
<p><em><strong>N+1 문제의 원인은 LAZY + 반복 접근 패턴</strong></em></p>
<pre><code>List&lt;Member&gt; members = em.createQuery(&quot;select m from Member m&quot;, Member.class)
                        .getResultList();

for (Member m : members) {
    System.out.println(m.getTeam().getName());
}</code></pre><ul>
<li>이때는 member 수만큼 getTeam()이 호출됨 → 프록시가 초기화됨 → N개의 SELECT 쿼리가 발생</li>
</ul>
<p><em><strong>즉, 접근 시점에서 LAZY가 개별 쿼리를 유발하면서 N+1 문제가 발생한다. LAZY라고 해도 N+1문제가 생기지 않는건 아니다.</strong></em></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>EAGER</td>
<td>무조건 조인해서 가져오므로 과도한 쿼리 발생 위험</td>
</tr>
<tr>
<td>LAZY</td>
<td>쿼리를 늦추긴 하지만, 반복 접근 시 N+1 문제 유발 가능</td>
</tr>
<tr>
<td>해결법</td>
<td>Fetch Join, EntityGraph, BatchSize 등의 전략 활용</td>
</tr>
</tbody></table>
<hr>
<h3 id="4-2-fetch-join으로-n1-문제-해결하기">4-2. Fetch Join으로 N+1 문제 해결하기</h3>
<blockquote>
<p>N+1 문제를 해결하는 가장 대표적인 방법은 Fetch Join을 사용하는 것이다.
Fetch Join은 JPA의 JPQL에서 제공하는 기능으로, 연관된 엔티티를 함께 한 번에 조회할 수 있도록 해준다.</p>
</blockquote>
<pre><code>List&lt;Member&gt; members = em.createQuery(&quot;select m from Member m join fetch m.team&quot;, Member.class)
                    .getResultList();

// 추가 쿼리 발생 안 함
for (Member m : members) {
    System.out.println(&quot;username = &quot; + m.getUsername());
    System.out.println(&quot;team = &quot; + m.getTeam().getName()); 
}</code></pre><ul>
<li>join fetch m.team을 통해 연관된 Team도 함께 즉시 조인하여 조회한다.</li>
<li>member와 team을 동시에 가져오기 때문에, getTeam() 호출 시에도 추가 쿼리가 발생하지 않는다. → 쿼리 1번만 실행</li>
</ul>
<p><img src="https://velog.velcdn.com/images/levi_/post/1e1306be-76e5-4755-a6ff-48b7e00e31ea/image.png" alt="">
N+1 문제는 연관 엔티티가 즉시로딩이거나, 지연로딩 설정과 반복적인 접근이 결합될 때 발생한다.
이를 해결하기 위해서는 Fetch Join을 사용하여 연관된 엔티티를 함께 조인 조회하면 된다.</p>
<p><em><strong>주의할 점은 Fetch Join은 성능 최적화의 중요한 도구이지만, 남용하면 불필요한 조인이 늘어날 수 있으므로 꼭 필요한 상황에만 사용하는 것이 좋다.</strong></em></p>
<hr>
<h3 id="4-3-entitygraph로-n1문제-해결하기">4-3. @EntityGraph로 N+1문제 해결하기</h3>
<blockquote>
<p>EntityGraph는 JPQL 없이도 연관된 엔티티를 함께 조회할 수 있게 해주는 JPA의 기능이다.
특히 Spring Data JPA와 함께 쓰면 매우 간편하게 사용 가능하다.</p>
</blockquote>
<pre><code>@NamedEntityGraph(
        name = &quot;Member.withTeam&quot;,
        attributeNodes = @NamedAttributeNode(&quot;team&quot;)
)
public class Member extends BaseEntity {</code></pre><ul>
<li>Member 엔티티에서 team 필드를 EntityGraph로 지정</li>
</ul>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {

    // N+1 문제 없이 team까지 같이 조회됨
    @EntityGraph(value = &quot;Member.withTeam&quot;, type = EntityGraph.EntityGraphType.LOAD)
    @Query(&quot;select m from Member m&quot;)
    List&lt;Member&gt; findAllWithTeam(); 
}</code></pre><ul>
<li>Repository에서 EntityGraph 사용 (Spring Data JPA 기준)</li>
</ul>
<pre><code>List&lt;Member&gt; members = memberRepository.findAllWithTeam();

// 추가 쿼리 없이 동작
for (Member member : members) {
    System.out.println(&quot;member: &quot; + member.getUsername());
    System.out.println(&quot;team: &quot; + member.getTeam().getName()); 
}</code></pre><br>

<table>
<thead>
<tr>
<th>방식</th>
<th>설명</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><code>join fetch</code></td>
<td>JPQL에 직접 fetch join 명시</td>
<td>직관적이고 강력</td>
<td>쿼리가 길어짐, 재사용 어려움</td>
</tr>
<tr>
<td><code>@EntityGraph</code></td>
<td>선언적으로 연관 로딩 지정</td>
<td>재사용성 높음, 깔끔한 코드</td>
<td>복잡한 조인에는 한계</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA - 지연로딩과 프록시 객체 이해하기]]></title>
            <link>https://velog.io/@levi_/%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@levi_/%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Sat, 12 Jul 2025 06:46:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>JPA의 주요 장점 중 하나는 객체 그래프 탐색을 통한 연관관계 조회가 가능하다는 점이다. 하지만 모든 연관 엔티티를 한 번에 조회하면 비효율적일 수 있기 때문에, JPA는 <strong>필요한 시점에 데이터를 조회하는 지연 로딩(Lazy Loading)</strong>을 지원한다.</p>
</blockquote>
<p><em><strong>이 지연 로딩 기능은 대부분의 구현체(Hibernate 등)에서 프록시 객체를 통해 구현된다.</strong></em></p>
<h2 id="1-프록시란">1. 프록시란?</h2>
<p><em><strong>프록시(proxy)</strong></em> 란 어떤 동작을 대신 수행해주는 객체를 의미한다.
JPA에서는 실제 엔티티 대신 프록시 객체를 먼저 반환하고, 해당 엔티티의 데이터에 접근하는 시점에 DB 조회를 수행한다.</p>
<p>이 기술은 Hibernate뿐만 아니라 Spring의 트랜잭션 처리, AOP 등 다양한 곳에서 사용된다.</p>
<hr>
<h2 id="2-find-vs-getreference">2. find() vs getReference()</h2>
<p>JPA에서는 엔티티를 조회할 때 두 가지 메서드를 사용할 수 있다.</p>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>em.find()</td>
<td>즉시 실제 객체를 조회하며 바로 SELECT 쿼리 실행</td>
</tr>
<tr>
<td>em.getReference()</td>
<td>프록시 객체 반환, 실제 필드 접근 시점에 SELECT 쿼리 실행</td>
</tr>
</tbody></table>
<p><img src="https://velog.velcdn.com/images/levi_/post/a6416513-b4a3-481c-afcb-fadd454f304c/image.png" alt=""> <a href="https://www.inflearn.com/courses/lecture?courseId=324109&amp;type=LECTURE&amp;unitId=21708&amp;tab=curriculum&amp;subtitleLanguage=ko">출처: 자바 ORM표준 JPA 프로그래밍 - 기본편</a></p>
<pre><code>package hellojpa;

import jakarta.persistence.*;

import java.time.LocalDateTime;
import java.util.List;

public class JpaMain {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;hello&quot;);
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            Member member = new Member();
            member.setUsername(&quot;hello&quot;);

            em.persist(member);

            em.flush();
            em.clear();

            // 실제 SELECT 안 나감
            Member findMember = em.getReference(Member.class, member.getId()); 
            System.out.println(&quot;findMember = &quot; + findMember.getClass());
            System.out.println(&quot;findMember.id = &quot; + findMember.getId());
            System.out.println(&quot;findMember.username = &quot; + findMember.getUsername());

            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}</code></pre><p><img src="https://velog.velcdn.com/images/levi_/post/a2f5fff4-ab49-499e-b6ed-58c35ee49854/image.png" alt=""> </p>
<blockquote>
<ul>
<li>em.find()는 바로 SELECT쿼리가 나간다.</li>
</ul>
</blockquote>
<ul>
<li>em.getReference()는 프록시를 반환한다.</li>
</ul>
<hr>
<h2 id="3-프록시객체-초기화">3. 프록시객체 초기화</h2>
<p>프록시는 처음에는 단순한 껍데기 객체일 뿐이다.
실제 데이터 접근이 일어나면 영속성컨텍스트가 개입하여 프록시 내부를 초기화하고, 그 이후 실제 엔티티처럼 동작한다.</p>
<pre><code>Member member1 = new Member();
member1.setUsername(&quot;member1&quot;);
em.persist(member1);

// INSERT 쿼리 실행
em.flush();    
// 영속성 컨텍스트 초기화
em.clear();       

Member refMember = em.getReference(Member.class, member1.getId());
System.out.println(&quot;refMember 클래스 = &quot; + refMember.getClass());


refMember.getUsername(); 

// true
System.out.println(&quot;isLoaded = &quot; + emf.getPersistenceUnitUtil().isLoaded(refMember)); </code></pre><p><img src="https://velog.velcdn.com/images/levi_/post/3ffa95e2-9ff6-49b6-94b0-b476401575fd/image.png" alt=""> <a href="https://www.inflearn.com/courses/lecture?courseId=324109&amp;type=LECTURE&amp;unitId=21708&amp;tab=curriculum&amp;subtitleLanguage=ko">출처: 자바 ORM표준 JPA 프로그래밍 - 기본편</a></p>
<p><em><strong>초기화 순서는 다음과 같다.</strong></em> </p>
<ul>
<li><p><strong>getReference() 호출</strong>
→ 이 시점에는 DB 조회 없이 프록시 객체만 생성된다.</p>
</li>
<li><p><strong>프록시 객체 접근 (getName() 등 호출)</strong>
→ 내부적으로 JPA가 영속성 컨텍스트에 실제 엔티티 조회 요청</p>
</li>
<li><p><strong>영속성 컨텍스트 처리</strong>
→ 먼저 1차 캐시에서 엔티티를 찾고, 없으면 DB에서 조회</p>
</li>
<li><p><strong>실제 엔티티 로딩 및 저장</strong>
→ DB에서 조회한 데이터를 바탕으로 엔티티를 만들고, 영속성 컨텍스트에 저장</p>
</li>
<li><p><strong>프록시 → 실제 엔티티 위임</strong>
→ 프록시는 내부적으로 target.getName() 같은 방식으로 실 데이터에 접근</p>
</li>
</ul>
<hr>
<h2 id="4-프록시-객체의-특징">4. 프록시 객체의 특징</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>가짜 객체</td>
<td>프록시는 실제 엔티티가 아닌, Hibernate가 만든 클래스</td>
</tr>
<tr>
<td>초기화 시점</td>
<td>실제 필드에 접근할 때(DB 쿼리 실행)</td>
</tr>
<tr>
<td>타입</td>
<td>클래스는 다르지만 원본 엔티티를 상속하므로 instanceof로 체크 가능</td>
</tr>
<tr>
<td>1차 캐시 확인</td>
<td>이미 엔티티가 로딩되어 있으면 프록시 대신 실제 객체 반환</td>
</tr>
<tr>
<td>초기화 후에도 프록시 유지</td>
<td>프록시 객체가 실제 엔티티로 바뀌지 않음, 단지 위임만 함</td>
</tr>
<tr>
<td>주의사항</td>
<td>초기화 전에 em.close()되면 <em><strong>LazyInitializationException</strong></em> 발생</td>
</tr>
</tbody></table>
<blockquote>
<p>프록시 객체가 초기화되지 않은 상태에서 영속성 컨텍스트가 종료되면, 실제 데이터를 로딩할 수 없어 <em><strong>LazyInitializationException</strong></em> 이 발생한다.</p>
</blockquote>
<pre><code>Member proxy = em.getReference(Member.class, id);
em.close();
proxy.getUsername(); // 예외 발생!</code></pre><p><em><strong>지연 로딩을 사용하는 경우, 반드시 영속성 컨텍스트 범위 안에서 프록시가 초기화되어야 한다.</strong></em></p>
<hr>
<h2 id="5-프록시-객체-초기화-여부-확인">5. 프록시 객체 초기화 여부 확인</h2>
<pre><code>System.out.println(emf.getPersistenceUnitUtil().isLoaded(refMember));</code></pre><blockquote>
<p>프록시 객체는 실제 데이터에 접근하는 시점에 영속성 컨텍스트를 통해 초기화된다.
예를 들어, 아래 코드처럼 <em><strong>refMember.getUsername()</strong></em> 처럼 프록시 객체의 실제 데이터를 조회하려고 하면, 내부적으로 초기화가 발생하고 <em><strong>PersistenceUnitUtil.isLoaded()</strong></em> 는 true를 반환한다.</p>
</blockquote>
<ul>
<li>반면, 실제 데이터에 접근하지 않은 상태에서는 프록시 객체는 초기화되지 않았기 때문에 <em><strong>isLoaded()</strong></em> 결과는 <em><strong>false</strong></em> 로 출력된다.</li>
<li>이처럼 프록시는 <strong>지연 로딩(Lazy Loading)</strong> 을 통해 성능을 최적화하지만, 예상치 못한 시점에 초기화될 수 있으므로 주의가 필요하다.</li>
</ul>
<hr>
<h2 id="6-마무리">6. 마무리</h2>
<ul>
<li>프록시는 지연 로딩을 가능하게 해주는 핵심 기술이다.</li>
<li>Hibernate는 이를 통해 성능을 최적화하지만, 초기화 시점, 영속성 컨텍스트 종료 등에 주의해야 한다.</li>
<li>초기화 시점을 놓치면 LazyInitializationException이 발생할 수 있으니 주의가 필요하다.</li>
</ul>
<blockquote>
<p>지금까지 JPA에서 프록시 객체가 어떻게 동작하는지 알아보았다.
프록시는 <em><strong>JPA의 지연 로딩(Lazy Loading)</strong></em> 을 가능하게 하는 핵심 요소이다.
다음 글에서는 프록시를 기반으로 동작하는 지연 로딩의 실제 활용 방식, 즉시 로딩과의 비교 등을 함께 살펴보겠습니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA - 상속관계매핑]]></title>
            <link>https://velog.io/@levi_/JPA-%EC%83%81%EC%86%8D%EA%B4%80%EA%B3%84%EB%A7%A4</link>
            <guid>https://velog.io/@levi_/JPA-%EC%83%81%EC%86%8D%EA%B4%80%EA%B3%84%EB%A7%A4</guid>
            <pubDate>Fri, 27 Jun 2025 10:07:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>JPA로 도메인 모델을 설계하다보면 공통된 속성을 가진 여러 엔티티들을 만들어야 하는 상황이 생길 수 있다. 
예를 들어 <em><strong>Item</strong></em> 이라는 상위개념 아래에 <em><strong>Book, Movie, Album</strong></em> 같은 하위개념이 있을 수 있다. 
이럴 때 각 엔티티마다 같은 필드를 반복해서 정의하는 대신, 
자바의 <em><strong>&quot;상속&quot;</strong></em> 개념을 활용해 <em><strong>상위 → 하위</strong></em> 개념으로 구조화할 수 있다.</p>
</blockquote>
<p><em><strong>이번 글에서는 JPA에서의 &quot;상속&quot; 을 어떻게 테이블에 매핑하는지, 세가지 상속매핑 전략, 특징에 대해서 정리해보려고 한다.</strong></em></p>
<hr>
<h2 id="1-상속매핑이란">1. 상속매핑이란?</h2>
<ul>
<li>객체지향 상속의 개념을 RDB테이블에 매핑하는 방법</li>
<li>객체의 상속구조와 DB의 슈퍼타입 서브타입 관계를 매핑
<img src="https://velog.velcdn.com/images/levi_/post/4747941c-7caa-4854-b914-a4d8df0cb794/image.png" alt=""> <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">출처 : 인프런 - 자바 ORM표준 JPA프로그래밍 - 기본편(김영한)</a></li>
</ul>
<h2 id="2-상속매핑-전략">2. 상속매핑 전략</h2>
<pre><code>@Inheritance(strategy=InheritanceType.XXX) 
// JOINED: 조인 전략
// SINGLE_TABLE: 단일 테이블 전략
// TABLE_PER_CLASS: 구현 클래스마다 테이블 전략</code></pre><hr>
<h3 id="2-1-single_table">2-1. <em><strong>SINGLE_TABLE</strong></em></h3>
<pre><code>@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;
}</code></pre><table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/levi_/post/a77dbbd7-7a27-47cf-9239-22aa3fe001e6/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/levi_/post/de4aec6d-b07e-4b7a-8b33-ebb3892c5cc7/image.png" alt=""></td>
</tr>
<tr>
<td><img src="https://velog.velcdn.com/images/levi_/post/4638942c-5ba1-4c7b-9439-b51425eb1698/image.png" alt=""></td>
<td></td>
</tr>
<tr>
<td><a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">출처 : 인프런 - 자바 ORM표준 JPA프로그래밍 - 기본편(김영한)</a></td>
<td></td>
</tr>
</tbody></table>
<p><em><strong>하나의 테이블에 모든 데이터를 다 넣음</strong></em></p>
<table>
<thead>
<tr>
<th><em><strong>장점</strong></em></th>
<th><em><strong>단점</strong></em></th>
</tr>
</thead>
<tbody><tr>
<td>• 조인이 필요 없으므로 일반적으로 조회 성능이 빠름<br>• 조회 쿼리가 단순함</td>
<td>• 자식 엔티티가 매핑한 컬럼은 모두 null 허용<br>• 단일 테이블에 모든 것을 저장하므로<br> 테이블이 커질 수 있다. <br>• <em><strong>상황에 따라서</strong></em> 조회 성능이 오히려 느려질 수 있다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="2-2-joined">2-2. <em><strong>JOINED</strong></em></h3>
<pre><code>@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;
} </code></pre><table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/levi_/post/011e9f03-1169-43d4-b1ae-e775d0744a9b/image.png" alt=""><img src="https://velog.velcdn.com/images/levi_/post/8d620f70-90ed-4ab4-8bee-35b730871363/image.png" alt=""></td>
<td></td>
</tr>
<tr>
<td><br></td>
<td></td>
</tr>
</tbody></table>
<p><a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">출처 : 인프런 - 자바 ORM표준 JPA프로그래밍 - 기본편(김영한)</a></p>
<div style="display:flex; justify-content:center;">

<table>
<thead>
<tr>
<th><em><strong>장점</strong></em></th>
<th><em><strong>단점</strong></em></th>
</tr>
</thead>
<tbody><tr>
<td>• 테이블 정규화<br>• 외래 키 참조 무결성 제약조건 활용가능<br>• 저장공간 효율화</td>
<td>• 조회시 조인을 많이 사용, 성능 저하<br>• 조회 쿼리가 복잡함<br>• 데이터 저장시 INSERT SQL 2번 호출</td>
</tr>
</tbody></table>
</div>

<hr>
<h3 id="2-3-table_per_class">2-3. <strong>TABLE_PER_CLASS</strong></h3>
<pre><code>@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
} 

@Entity
public class Book extends Item {
    private String author;
    private String isbn;
}

@Entity
public class Movie extends Item {
    private String director;
    private String actor;
}</code></pre><p><em><strong>생성되는 테이블 예시</strong></em>
Book</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>AUTHOR</th>
<th>ISBN</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>해리포터</td>
<td>J.K롤링</td>
<td>12345</td>
</tr>
</tbody></table>
<p>Movie</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>DIRECTOR</th>
<th>ACTOR</th>
</tr>
</thead>
<tbody><tr>
<td>2</td>
<td>인셉션</td>
<td>크리스토퍼 놀란</td>
<td>레오나르도 디카프리오</td>
</tr>
</tbody></table>
<blockquote>
<p><em><strong>SELECT * FROM Item 불가능. 계층 전체 조회 시 union 필요.</strong></em></p>
</blockquote>
<table>
<thead>
<tr>
<th><strong><em>장점</em></strong></th>
<th><strong><em>단점</em></strong></th>
</tr>
</thead>
<tbody><tr>
<td>• 테이블 독립적<br> → 자식만 단독으로 조회할 때 효율적<br> • null 컬럼 없음</td>
<td>• 부모 타입으로 전체 조회 불가능 (UNION 필요)<br> • ID 중복 관리 어려움<br> • 실무에서 거의 안 씀</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-dtype">3. DTYPE</h2>
<h3 id="3-1-dtype이란">3-1. DTYPE이란?</h3>
<blockquote>
<p>DTYPE은 JPA가 상속 매핑 시 자동으로 생성하는 &quot;구분자(Discriminator)&quot; 컬럼이다.
어떤 자식 클래스의 인스턴스인지를 식별하기 위해 사용된다.</p>
</blockquote>
<ul>
<li><p><em><strong>@Inheritance</strong></em> 전략 중 <em><strong>SINGLE_TABLE</strong></em> 또는 <em><strong>JOINED</strong></em> 전략을 사용할 때 생성됨</p>
</li>
<li><p><em><strong>@DiscriminatorColumn(name = &quot;DTYPE&quot;)</strong>_을 명시하지 않아도 기본값(</em><strong>DTYPE</strong><em>)으로 자동 생성됨 _<strong>@Inheritance</strong></em> 전략 중 <em><strong>SINGLE_TABLE</strong></em> 또는 <em><strong>JOINED</strong></em> 전략을 사용할 때 생성됨</p>
</li>
</ul>
<pre><code>@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = &quot;DTYPE&quot;)
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
}

@Entity
@DiscriminatorValue(&quot;BOOK&quot;)
public class Book extends Item {
    private String author;
}</code></pre><p><em><strong>생성된 테이블</strong></em></p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>AUTHOR</th>
<th>DTYPE</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>해리포터</td>
<td>J.K롤링</td>
<td>BOOK</td>
</tr>
<tr>
<td>2</td>
<td>인터스텔라</td>
<td>NULL</td>
<td>MOVIE</td>
</tr>
</tbody></table>
<h3 id="3-2-사용하는-이유">3-2. 사용하는 이유</h3>
<ul>
<li>부모 테이블에서 데이터를 조회할 때 어떤 자식 타입인지 구분하기 위함</li>
<li><em><strong>@DiscriminatorValue</strong></em> 를 지정하지 않으면 기본적으로 클래스 이름이 들어감</li>
</ul>
<h2 id="4-mappedsuperclass">4. @MappedSuperclass</h2>
<p><img src="https://velog.velcdn.com/images/levi_/post/9793d322-de89-45e3-8100-efcafa0c62b4/image.png" alt="">
<a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">출처 : 인프런 - 자바 ORM표준 JPA프로그래밍 - 기본편(김영한)</a></p>
<ul>
<li><strong><em>id, name, createdDate</em></strong> 같은 공통 필드가 여러 엔티티에 반복될 때</li>
<li>공통 속성만 객체로 상속받고, 테이블은 독립적으로 존재하게 만들고 싶을 때</li>
</ul>
<p><em><strong>사용예시</strong></em></p>
<pre><code>@Getter
@Setter
@MappedSuperclass
public class BaseEntity {

    private String createdBy;
    private LocalDateTime createdDate;
    private String lastModifiedBy;
    private LocalDateTime lastModifiedDate;
}</code></pre><pre><code>@Entity
@Getter
@Setter
public class Team extends BaseEntity {

    @Id @GeneratedValue
    @Column(name = &quot;TEAM_ID&quot;)
    private Long id;
    private String name;

    @OneToMany
    @JoinColumn(name = &quot;TEAM_ID&quot;)
    private List&lt;Member&gt; members = new ArrayList&lt;&gt;();
}</code></pre><pre><code>@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member extends BaseEntity {

    @Id @GeneratedValue
    @Column(name = &quot;MEMBER_ID&quot;)
    private Long id;

    @Column(name = &quot;USERNAME&quot;)
    private String username;

    @ManyToOne
    @JoinColumn(name = &quot;TEAM_ID&quot;, insertable = false, updatable = false)
    private Team team;

    @OneToOne
    @JoinColumn(name = &quot;LOCKER_ID&quot;)
    private Locker locker;

    @OneToMany(mappedBy = &quot;member&quot;)
    private List&lt;MemberProduct&gt; memberProducts = new ArrayList&lt;&gt;();
}</code></pre><p><em><strong>@MappedSuperclass는 공통 필드를 상속만 받고, 엔티티와 테이블은 따로 유지하고 싶을 때 사용하는 구조이다.</strong></em></p>
<h2 id="마무리">마무리</h2>
<blockquote>
<p>JPA에서 상속 매핑은 단순히 객체지향적인 설계를 넘어서, 
어떻게 데이터베이스 테이블을 나눌지에 대한 전략이기도 하다. 
각각의 전략은 성능, 쿼리 복잡도, 유지보수성 측면에서 명확한 차이를 가지므로, 상황에 맞게 신중하게 선택하는 것이 중요할것 같다.</p>
</blockquote>
<p><em><strong>“객체지향스럽게 짜면 되겠지?” 하고 상속만 썼다가, 테이블 구조가 엉망이 되거나 쿼리 성능이 급격히 떨어질 수 있다.
도메인 구조, 데이터 양, 성능 요건을 고려하여 
전략을 신중히 선택해야 할 것 같다.</strong></em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA - 연관관계 매핑]]></title>
            <link>https://velog.io/@levi_/JPA-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91</link>
            <guid>https://velog.io/@levi_/JPA-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91</guid>
            <pubDate>Sun, 22 Jun 2025 09:53:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>전통적인 DB 설계 방식에서는 테이블간 관계를 <strong><em>외래키(FK)</em></strong> 로 연결한다. 
예를 들어 Order테이블에 member_id컬럼이 존재한다면, 이 컬럼을 통해 Member테이블과 Order테이블 간의 연관관계가 있다는걸 알 수 있다. 
하지만 JPA에서는 이렇게 외래 키만을 필드로 두는 것보다 실제 Java 객체 간의 참조를 기반으로 연관관계를 매핑하는 것이 핵심이다.</p>
</blockquote>
<p><strong><em>즉, order.memberId처럼 단순히 외래 키 값을 보관하는 것이 아니라, order.getMember()처럼 객체 자체를 직접 참조하는 방식이 권장된다. 이것이 바로 객체지향적인 설계와 데이터베이스 설계의 차이이며, JPA가 추구하는 방향이다.</em></strong></p>
<h2 id="1-테이블에-객체를-맞춰-설계한다면">1. 테이블에 객체를 맞춰 설계한다면?</h2>
<pre><code>@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {

    @Id @GeneratedValue
    @Column(name = &quot;MEMBER_ID&quot;)
    private String id;

    @Column(name = &quot;USERNAME&quot;)
    private String username;

    @Column(name = &quot;TEAM_ID&quot;)
    private Long teamId;
}</code></pre><pre><code>@Entity
@Getter
@Setter
public class Team {

    @Id @GeneratedValue
    @Column(name = &quot;TEAM_ID&quot;)
    private Long id;

    private String name;

}</code></pre><pre><code>Member member = new Member();
member.setUsername(&quot;member1&quot;);
member.setTeamId(team.getId());
em.persist(member);</code></pre><ul>
<li>위 코드는 그냥 보면 문제가 없는 코드이지만, Team을 참조하는 필드가 아닌 Team의 외래키인 Long teamId를 직접 필드로 두고 있다. 
이건 마치 DB테이블의 외래키컬럼에 값을 직접 넣는 것과 유사하다. 
하지만 이 방식은 객체지향보다는 DB에 의존적인 절차지향적인 코드라고 볼 수 있다.<h3 id="1-1-테이블중심-설계의-문제점">1-1. 테이블중심 설계의 문제점</h3>
연관된 객체를 직접 다룰 수 없다.<pre><code>Team team = findMember.getTeam(); 
// Member가 Team을 참조하는 필드를 가지고있는게 아니기 때문에 불가능</code></pre>매번 아래와 같은 방식으로 접근해야 한다.<pre><code>Long teamId = findMember.getTeamId();
Team team = em.find(Team.class, teamId);</code></pre></li>
</ul>
<h3 id="1-2-양방향-연관관계-설정-불가능">1-2. 양방향 연관관계 설정 불가능</h3>
<blockquote>
<p>예를 들어, Team 입장에서 그 팀에 소속된 멤버들을 조회하고 싶다면
List&lt; Member &gt; 같은 필드는 사용할 수 없으며, 직접 쿼리를 날려야 한다.</p>
</blockquote>
<h3 id="1-3-jpa의-연관관계-관리-기능을-활용할-수-없다">1-3. JPA의 연관관계 관리 기능을 활용할 수 없다.</h3>
<blockquote>
<p><em><strong>Cascade, orphanRemoval, FetchType.LAZY</strong></em> 같은 강력한 기능들은
객체 간 관계를 통해서만 동작한다. FK만 있으면 아무 의미 없다.</p>
</blockquote>
<h2 id="2-객체-간-연관관계-매핑-시-이점">2. 객체 간 연관관계 매핑 시 이점</h2>
<h3 id="2-1-객체-그대로-연결할-수-있다">2-1. 객체 그대로 연결할 수 있다.</h3>
<pre><code>member.setTeam(team);</code></pre><ul>
<li>이렇게 해주면 Member객체는 Team객체를 참조할 수 있고, Team객체도 List&lt; Member &gt; 로 구성된 멤버목록을 가질 수 있다.</li>
</ul>
<h3 id="2-2-코드가-객체지향적이고-간결해진다">2-2. 코드가 객체지향적이고 간결해진다.</h3>
<pre><code>System.out.println(member.getTeam().getName());</code></pre><ul>
<li>객체끼리 연관관계 매핑을 통해 서로 참조하게 되면 위 코드들처럼 복잡하게 여러번 타고 들어가는게 아닌, 간결하고 객체지향적으로 데이터에 접근이 가능하다.    </li>
</ul>
<h3 id="2-3-jpa가-연관된-객체를-자동으로-관리한다">2-3. JPA가 연관된 객체를 자동으로 관리한다</h3>
<ul>
<li>연관된 엔티티를 함께 저장 (Cascade)</li>
<li>연관된 엔티티 삭제 (orphanRemoval)</li>
<li>지연 로딩 (LAZY)으로 성능 최적화</li>
<li>더 직관적인 코드 작성 가능</li>
</ul>
<h2 id="3-테이블-중심-설계의-문제점-요약">3. 테이블 중심 설계의 문제점 요약</h2>
<blockquote>
<ul>
<li>직접 FK 설정    </li>
<li><blockquote>
<p>객체를 직접 연결하지 않으므로 객체 간 탐색이 불가능</p>
</blockquote>
</li>
</ul>
</blockquote>
<ul>
<li>데이터 중심 사고    </li>
<li><blockquote>
<p>설계가 SQL 중심이 되며, 객체지향 언어의 이점을 활용하지 못함</p>
</blockquote>
</li>
<li>코드 중복    </li>
<li><blockquote>
<p>반복적으로 em.find() 등을 사용해야 함</p>
</blockquote>
</li>
<li>기능 제한    </li>
<li><blockquote>
<p><em><strong>Cascade, FetchType, orphanRemoval</strong></em> 등이 적용되지 않음</p>
</blockquote>
</li>
<li>유지보수 어려움    </li>
<li><blockquote>
<p>관계 파악이 어려워지고, 실수도 잦아짐</p>
</blockquote>
</li>
</ul>
<h2 id="4-객체는-객체답게-관계는-연관관계로">4. 객체는 객체답게, 관계는 연관관계로</h2>
<blockquote>
<p>JPA는 단순히 SQL 대신 자동으로 쿼리를 만들어주는 도구가 아니다.
객체지향적인 코드로 설계한 엔티티들을 DB와 매핑해주는 기술이다.
그래서 우리는 <em><strong>&quot;테이블 설계를 그대로 따라&quot;</strong></em> JPA를 사용하면 안 된다.
객체는 객체답게 설계하고, 관계는 
_<strong>@ManyToOne, @OneToMany</strong>_로 명확히 매핑해줘야
JPA의 모든 기능과 성능을 제대로 활용할 수 있다.</p>
</blockquote>
<h2 id="5-11-1n-n1-nm">5. 1:1, 1:N, N:1, N:M</h2>
<blockquote>
<p>JPA에서는 객체 간의 관계를 DB 테이블의 외래키를 이용해 매핑한다.
하지만 객체 지향적으로 설계하기 위해선 단순히 외래키만 사용할 게 아니라, 정확한 방향성과 연관관계 매핑 어노테이션을 잘 이해하고 써야 한다.</p>
</blockquote>
<h3 id="5-1-11-onetoone">5-1. 1:1 (OneToOne)</h3>
<pre><code>- 한 엔티티가 다른 엔티티와 1:1로만 연결될 때</code></pre><p>예: <em><strong>User ↔ UserProfile</strong></em></p>
<pre><code>@Entity
public class User {
    @Id @GeneratedValue
    private Long id;

    @OneToOne
    @JoinColumn(name = &quot;profile_id) // FK
    private UserProfile profile;
}</code></pre><pre><code>@Entity
public class UserProfile {
    @Id @GeneratedValue
    private Long id;

    @OneToOne(mappedBy = &quot;profile&quot;)
    private User user;
}</code></pre><h3 id="5-2-1n-onetomany">5-2. 1:N (OneToMany)</h3>
<ul>
<li><p>한 엔티티가 여러 개의 엔티티를 가질 때
예: <strong><em>Team → 여러 Member</em></strong></p>
<pre><code>@Entity
public class Team {
  @Id @GeneratedValue
  private Long id;

  @OneToMany
  @JoinColumn(name = &quot;team_id&quot;) // FK는 member 테이블에 생김
  private List&lt;Member&gt; members = new ArrayList&lt;&gt;();
}</code></pre></li>
<li><p>하지만 단방향 1:N은 실제로 잘 사용하지 않음.
→ 이유: JPA가 중간 테이블을 안 쓰고 외래키를 업데이트해야 하기 때문.
해결책: N:1 쪽에서 매핑하고, 양방향으로 쓰자</p>
<pre><code>@Entity
public class Member {
  @Id @GeneratedValue
  private Long id;

  @ManyToOne
  @JoinColumn(name = &quot;team_id&quot;)
  private Team team;
}</code></pre><pre><code>@Entity
public class Team {
  @Id @GeneratedValue
  private Long id;

  @OneToMany(mappedBy = &quot;team&quot;) // 읽기 전용
  private List&lt;Member&gt; members = new ArrayList&lt;&gt;();
}</code></pre><h3 id="5-3-n1-manytoone">5-3. N:1 (ManyToOne)</h3>
</li>
<li><p>여러 개의 엔티티가 하나의 엔티티와 연결될 때
예: 여러 <strong><em>Member → 하나의 Team</em></strong> (가장 자주 사용됨)</p>
<pre><code>@Entity
public class Member {
  @Id @GeneratedValue
  private Long id;

  @ManyToOne
  @JoinColumn(name = &quot;team_id&quot;) // FK
  private Team team;
}</code></pre></li>
</ul>
<h3 id="5-4-nm-manytomany">5-4. N:M (ManyToMany)</h3>
<ul>
<li><p>서로 다대다 관계일 때 
→ 예: <em><strong>Student ↔ Course</strong></em></p>
<pre><code>@Entity
public class Student {
  @Id @GeneratedValue
  private Long id;

  @ManyToMany
  @JoinTable(name = &quot;student_course&quot;,
             joinColumns = @JoinColumn(name = &quot;student_id&quot;),
             inverseJoinColumns = @JoinColumn(name = &quot;course_id&quot;))
  private List&lt;Course&gt; courses = new ArrayList&lt;&gt;();
}</code></pre><pre><code>@Entity
public class Course {
  @Id @GeneratedValue
  private Long id;

  @ManyToMany(mappedBy = &quot;courses&quot;)
  private List&lt;Student&gt; students = new ArrayList&lt;&gt;();
}</code></pre></li>
<li><p><em><strong>@ManyToMany</strong>_는 중간 테이블에 추가 컬럼(등록일 등)을 못 넣기 때문에, 실무에선 중간 엔티티</em><strong>(예: Enrollment)</strong><em>를 만들어 **_1:N + N:1</em>**로 풀어냄</p>
</li>
</ul>
<p><em><strong>JPA에서는 항상 연관관계의 방향과 주인을 정확히 설계하고,
필요시 mappedBy, JoinColumn, JoinTable을 올바르게 지정해줘야 한다.</strong></em></p>
<h2 id="6-연관관계의-주인과-mappedby">6. 연관관계의 주인과 mappedBy</h2>
<h3 id="6-1-연관관계의-주인이란">6-1. 연관관계의 주인이란?</h3>
<blockquote>
<p>_<strong>연관관계의 주인(Owner)</strong>_은 DB의 외래키(FK)를 관리하는 엔티티이다.
즉, 어떤 쪽이 외래키 값을 insert/update할 책임이 있는지를 나타낸다.</p>
</blockquote>
<p><strong><em>JPA는 &#39;주인&#39;만 외래키를 관리할 수 있다.</em></strong></p>
<h3 id="6-2-mappedby란">6-2. mappedBy란?</h3>
<blockquote>
<p>mappedBy는 반대편에서 연관관계의 주인을 지정하는 속성이다.
mappedBy = &quot;team&quot; → 이 말은 &quot;이 연관관계의 관리는 Member.team이 함&quot;이라는 의미이다.</p>
</blockquote>
<p><strong><em>즉, mappedBy가 붙은 쪽은 읽기 전용, insert/update에 관여하지 않는다.</em></strong></p>
<p><strong>Member N:1 Team (ManyToOne — 외래키 주인)</strong></p>
<pre><code>@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = &quot;team_id&quot;) // FK 관리 → 이게 &#39;연관관계 주인&#39;
    private Team team;
}</code></pre><p><strong>Team 1:N Member (OneToMany — 주인이 아님)</strong></p>
<pre><code>@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = &quot;team&quot;) // &#39;team&#39;은 Member 엔티티 안에 있는 필드명
    private List&lt;Member&gt; members = new ArrayList&lt;&gt;();
}</code></pre><h3 id="6-3-왜-이렇게-나눠야할까">6-3. 왜 이렇게 나눠야할까?</h3>
<ul>
<li>양방향 관계는 실제로는 단방향 두 개가 아닌, 하나의 외래키만 존재</li>
<li>DB에서는 외래키가 member.team_id 하나인데, 양쪽에서 insert, update를 하면 충돌 위험</li>
<li>그래서 한쪽만 외래키를 관리해야 하며, 그게 바로 연관관계의 주인</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA - Entity매핑, PK매핑]]></title>
            <link>https://velog.io/@levi_/JPA-Entity%EB%A7%A4%ED%95%91-PK%EB%A7%A4%ED%95%91</link>
            <guid>https://velog.io/@levi_/JPA-Entity%EB%A7%A4%ED%95%91-PK%EB%A7%A4%ED%95%91</guid>
            <pubDate>Sat, 21 Jun 2025 07:17:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>JPA에서 테이블과 객체를 매핑하기 위해 @Entity 어노테이션을 사용해서 객체 &lt;-&gt; 테이블 간의 매핑을 해준다.
하지만 아무 클래스에나 @Entity를 붙인다고 해서 다 동작하는 것은 아니다.</p>
</blockquote>
<h2 id="1-entity의-필수-조건">1. @Entity의 필수 조건</h2>
<blockquote>
<ul>
<li>기본 생성자 필수</li>
</ul>
</blockquote>
<ul>
<li>finalClass 사용 불가</li>
<li>InnerClass 사용 불가 (static InnerClass는 허용)</li>
<li>Interface, Enum 사용 불가</li>
<li>필드 중복 없이 명확한 primary key (@Id) 선언 필요</li>
</ul>
<h3 id="1-1-사용-불가능한-이유">1-1. 사용 불가능한 이유?</h3>
<p><em><strong>JPA는 객체를 생성할 때 리플렉션 + 기본생성자를 사용한다.
또한, 지연로딩(Lazy Loading) 시 프록시 객체를 생성하기 위해 엔티티를 상속받는다.</strong></em></p>
<ul>
<li><strong><em>finalClass</em></strong> </li>
<li><blockquote>
<p>프록시 객체 생성을 위한 상속이 필요한데, final은 상속 불가</p>
</blockquote>
</li>
<li><strong>_Enum _</strong></li>
<li><blockquote>
<p>인스턴스 생성 불가능 (new 키워드 사용 불가), 필드로는 사용 가능하다.</p>
</blockquote>
</li>
<li><strong>_Interface _</strong></li>
<li><blockquote>
<p>인스턴스 생성 불가능, 필드 / 상태를 가질 수 없음</p>
</blockquote>
</li>
<li><strong><em>non-static InnerClass</em></strong> </li>
<li><blockquote>
<p>바깥 클래스의 인스턴스가 필요해서 JPA 리플렉션 생성 실패</p>
</blockquote>
</li>
</ul>
<blockquote>
<p><strong><em>@Entity</em></strong>는 평범한 클래스여야 하며,
기본생성자 + non-final + 독립적인 클래스로 만들어야 한다.
내부적으로 프록시객체를 쓰거나 리플렉션으로 객체를 생성하기 때문에 위 조건들이 필요하다.</p>
</blockquote>
<h2 id="2-pk매핑">2. PK매핑</h2>
<h3 id="2-1-id---entity-기본-키-지정">2-1. @Id - Entity 기본 키 지정</h3>
<ul>
<li>Entity클래스에서 해당 필드를 기본키(PK)로 사용하겠다는 뜻</li>
<li>기본키가 반드시 존재해야 JPA가 Entity를 구분하고, 영속성컨텍스트에서 관리할 수 있다.<h3 id="2-2-generatedvalue---기본-키-자동-생성-전략">2-2. @GeneratedValue - 기본 키 자동 생성 전략</h3>
<em><strong>PK를 직접 할당하지 않고, JPA가 할당하도록 할 수 있다.</strong></em></li>
</ul>
<blockquote>
<ul>
<li><em><strong>AUTO</strong></em></li>
<li><blockquote>
<p>DB 방언에 따라 자동 선택 (보통 SEQUENCE or IDENTITY)    기본값</p>
</blockquote>
</li>
</ul>
</blockquote>
<ul>
<li><em><strong>IDENTITY</strong></em>    </li>
<li><blockquote>
<p>DB의 auto_increment 컬럼 사용 (MySQL 등)    insert 후 PK 값 결정됨</p>
</blockquote>
</li>
<li><em><strong>SEQUENCE</strong></em>    </li>
<li><blockquote>
<p>DB 시퀀스 객체 사용 (Oracle, PostgreSQL 등)    성능 좋음, 미리 값 예약 가능</p>
</blockquote>
</li>
<li><em><strong>TABLE</strong></em>    </li>
<li><blockquote>
<p>별도 키 생성 테이블에서 PK 관리    유연하지만 성능 느림</p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA - 영속성컨텍스트]]></title>
            <link>https://velog.io/@levi_/JPA-%EC%98%81%EC%86%8D%EC%84%B1%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@levi_/JPA-%EC%98%81%EC%86%8D%EC%84%B1%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Thu, 19 Jun 2025 08:59:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>JPA를 이해하려면 영속성 컨텍스트를 이해해야한다. JPA를 이해하는데 있어서 가장 중요한 두가지가 있다.</p>
</blockquote>
<ul>
<li>객체와 관계형 데이터베이스 매핑 <ul>
<li>DB를 어떻게 설계하고 객체를 어떻게 설계해서 중간에서 JPA를         어떻게 매핑해서사용할 것인지     </li>
</ul>
</li>
<li>영속성 컨텍스트<ul>
<li>JPA가 실제로 내부에서 어떻게 동작하는지
이번엔 JPA에서 가장 중요한 개념인 영속성 컨텍스트에 대해서 정리해보겠다.</li>
</ul>
</li>
</ul>
<h2 id="1-entitymanagerfactory-entitymanager">1. EntityManagerFactory, EntityManager</h2>
<p>JPA를 쓰게되면, EntityManagerFactory와 EntityManager에 대해서 이해해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/levi_/post/a3a448a8-8438-4461-b83f-5400a566c7c1/image.png" alt=""> <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍(기본편, 김영한)</a></p>
<ul>
<li><p>먼저 웹 어플리케이션을 개발한다고 가정했을 때 고객의 요청이 올 때마다 <em><strong>EntityManagerFactory를</strong></em> 통해서 _<strong>EntityManager</strong>_를 생성하게 된다.</p>
</li>
<li><p><em><strong>EntityManagerFactory를</strong></em> 쉽게 생각하면 <em><strong>EntityManager</strong></em> 를 생성하는 공장 역할이고, <strong><em>DB연결 / 캐시 / 메타데이터 관리</em></strong> 역할을 한다.</p>
<ul>
<li>어플리케이션 로딩시점에 딱 1번만 생성되어서 공유한다.</li>
</ul>
</li>
<li><p><em><strong>EntityManager는</strong></em> 내부적으로 데이터베이스커넥션을 사용해서 DB를 사용하게 된다.</p>
<ul>
<li>JPA에서 엔티티(객체)와 데이터베이스 사이의 연결 다리 역할을 하는 객체이고 조회, 저장, 수정, 삭제 등 모든 DB 작업을 담당하는 JPA의 실행 담당자이다.</li>
<li>스레드간 공유가 절대 안된다. 요청마다 새로 생성해서 사용한다.</li>
</ul>
</li>
</ul>
<p><em><strong>EntityManagerFactory와 EntityManager에 대해서 간단하게 알아봤는데, 그렇다면 영속성 컨텍스트는 도데체 뭘까?</strong></em></p>
<h2 id="2-영속성-컨텍스트">2. 영속성 컨텍스트</h2>
<blockquote>
<p>영속성컨텍스트란 <em><strong>&quot;엔티티를 영구 저장하는 환경&quot;</strong></em> 이라는 뜻이다. 
DB에 저장한다 라는 개념보다는 영속성 컨텍스트를 통해서 <em><strong>&quot;엔티티를 영속화 한다.&quot;</strong></em> 라고 생각하면 된다.</p>
</blockquote>
<ul>
<li>엔티티를 <em><strong>&quot;영속성컨텍스트&quot;</strong></em> 에 저장한다. -&gt; 영속화</li>
<li>이 후 커밋 시점에 flush가 동작하면서 SQL을 발송하고 commit되면서 DB에 반영된다.</li>
</ul>
<p>영속성 컨텍스트는 논리적인 개념으로, 눈에 보이지 않는다. 보통 EntityManager를 통해서 영속성컨텍스트에 접근한다.</p>
<pre><code>EntityManager.persist(entity);</code></pre><p><img src="https://velog.velcdn.com/images/levi_/post/8fbb6776-c845-4701-a6bd-a5177433f658/image.png" alt=""> <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍(기본편, 김영한)</a></p>
<p><em><strong>EntityManager를 생성하게되면  1:1로 영속성컨텍스트가 생성된다.</strong></em>
-&gt; EntityManager 안에 영속성컨텍스트라는 눈에 보이지않는 공간이 생긴다.</p>
<h3 id="2-1-entity의-생명주기영속상태">2-1. Entity의 생명주기(영속상태)</h3>
<p><img src="https://velog.velcdn.com/images/levi_/post/9a2c716b-b51a-49b0-a286-c0e4ac1d0be9/image.png" alt=""> <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍(기본편, 김영한)</a></p>
<blockquote>
<ul>
<li><em><strong>비영속(new / transient)</strong></em><ul>
<li>아직 영속성컨텍스트와 전혀 관련이 없는 <em><strong>새로운</strong></em> 상태</li>
</ul>
</li>
</ul>
</blockquote>
<pre><code>Member member = new Member(); // 비영속 상태, 객체만 생성
member.setName(&quot;hello&quot;);
// EntityManager가 관리하지 않음, DB와도 아무 관련 없음</code></pre><p><img src="https://velog.velcdn.com/images/levi_/post/2432bd26-15d3-4c7d-ac4a-1fcad33ddd1c/image.png" alt=""> <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍(기본편, 김영한)</a></p>
<blockquote>
<ul>
<li><strong><em>영속(managed)</em></strong><ul>
<li>영속화되어 영속성컨텍스트에 <em><strong>관리</strong></em> 되는 상태</li>
</ul>
</li>
</ul>
</blockquote>
<pre><code>// JPA가 관리 중, 변경 감지 가능, 쓰기 지연 저장소에 저장됨
// flush() or commit() 시점에 insert 쿼리 실행됨
// 1차 캐시에 저장됨
em.persist(member); // 영속 상태 진입</code></pre><p><img src="https://velog.velcdn.com/images/levi_/post/1353a681-041b-47b3-8f18-1522959fe7f2/image.png" alt=""> <a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍(기본편, 김영한)</a></p>
<blockquote>
<ul>
<li><em><strong>준영속(detached)</strong></em><ul>
<li>영속성컨텍스트에 저장되었다가(영속화) 다시 <em><strong>분리</strong></em> 된 상태</li>
</ul>
</li>
</ul>
</blockquote>
<pre><code>em.detach(member);         // 또는
em.close();                // EntityManager 종료
// 엔티티 객체는 살아있지만, 변경 감지 안 됨
// 다시 영속으로 만들려면 em.merge() 필요</code></pre><blockquote>
<ul>
<li><em><strong>삭제(removed)</strong></em><ul>
<li><em><strong>삭제</strong></em> 된 상태</li>
</ul>
</li>
</ul>
</blockquote>
<pre><code>// 영속성 컨텍스트에는 있지만, flush or commit 시점에 delete 쿼리나감
// 실제 DB 반영은 commit 이후
em.remove(member);</code></pre><h3 id="2-2-영속성컨텍스트의-이점">2-2. 영속성컨텍스트의 이점</h3>
<ol>
<li>1차캐시</li>
<li>동일성 보장</li>
<li>변경 감지 (Dirty Checking)</li>
<li>트랜잭션을 지원하는 쓰기지연</li>
<li>지연로딩 (Lazy Loading)</li>
</ol>
<p><strong><em>1. 1차캐시</em></strong>
<img src="https://velog.velcdn.com/images/levi_/post/4c1271e6-1350-4a4d-8dde-14fb6d00b531/image.png" alt="">
1차캐시에서 조회
<img src="https://velog.velcdn.com/images/levi_/post/0fd759e3-8e64-4d16-883c-ad7701268686/image.png" alt=""> 
1차캐시에 없으면 DB에서 조회
<a href="https://www.inflearn.com/course/ORM-JPA-Basic/dashboard">출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍(기본편, 김영한)</a></p>
<ul>
<li>영속성컨텍스트 내부에는 1차 캐시가 있는데 @Id와 Entity로 구성되어있다.</li>
<li>개발자가 DB pk로 매핑한 필드가 key가 되고, Entity객체 자체가 값이 된다.<pre><code>// 엔티티 생성한 상태(비영속)
Member member = new Member();
member.setId(&quot;member1&quot;);
member.setUserName(&quot;회원1&quot;);
</code></pre></li>
</ul>
<p>// 엔티티를 영속, 1차캐시에 저장됨
em.persist(member);</p>
<p>// 1차캐시에서 조회
Member findMember = em.find(Member.class, &quot;member1&quot;);
// 1차캐시에 없으면 DB조회 -&gt; 1차캐시 저장 -&gt; Entity반환
Member findMember2 = em.find(Member.class, &quot;member2&quot;);</p>
<pre><code>&gt; 1차 캐시는 EntityManager가 생성될 때 함께 만들어지는 영속성 컨텍스트 내부의 메모리 캐시로, 트랜잭션 범위 내에서만 유효하다.
따라서 같은 EntityManager 내에서는 반복 조회 시 DB가 아닌 캐시에서 데이터를 가져와 성능 이점을 줄 수 있지만, 애플리케이션 전체에서 공유되지는 않으므로 한계도 있다.
애플리케이션 레벨에서 공유 가능한 캐시는 JPA나 Hibernate에서 제공하는 2차 캐시(Second-Level Cache)를 사용해야 한다.

**_2. 영속엔티티의 동일성 보장_**</code></pre><p>Member findMember = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);</p>
<p>// 동일성 비교 true
System.out.println(findMember == findMember2);</p>
<pre><code>&gt;JPA에서는 같은 트랜잭션 내에서 1차 캐시에 저장된 엔티티를 조회할 경우, 항상 동일한 객체 인스턴스를 반환하므로 객체의 동일성이 보장된다.
즉, 동일한 식별자(PK)로 여러 번 조회하더라도 같은 엔티티 인스턴스를 공유하게 되어, 객체 간 비교 시에도 == 연산이 성립한다.

_**3. 트랜잭션을 지원하는 쓰기지연**_</code></pre><p>EntityManager em = emf.createEntityManager();
EntityManager transaction = em.getTransaction();
transaction.begin(); // 트랜잭션 시작</p>
<p>em.persist(memberA);
em.persist(memberB);
// 여기까지 Insert쿼리 안나감, 영속성컨텍스트에 쌓이는 중</p>
<p>// commit하는 시점에 flush가 동작 -&gt; 쓰기지연저장소에서 쿼리보냄
// -&gt; 이후 commit되면서 db에 진짜 반영
transaction.commit();</p>
<pre><code>![](https://velog.velcdn.com/images/levi_/post/b6aae3de-79b3-4c81-8264-8060a98a5cef/image.png) 
![](https://velog.velcdn.com/images/levi_/post/a941e8a2-8b2e-444a-a239-3a6698cd9e90/image.png) [출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍(기본편, 김영한)](https://www.inflearn.com/course/ORM-JPA-Basic/dashboard)
&gt;- 영속화 되는 시점에 쿼리가 바로 나가지 않고 쓰기지연저장소와 1차캐시에 저장되었다가 commit되는 시점에 db에 반영되는 방식을 통해서 버퍼링같은 기능을 사용할 수 있다.
- Hibernate에는 Hibernate.jdbc.batch_size 라는 옵션이 있는데 이 옵션을 사용하면 지정한 사이즈만큼 쿼리를 모아서 db로 한번에 보내고 commit시킨다.

**_4. 변경감지_**</code></pre><p>EntityManager em = emf.createEntityManager();
EntityManager transaction = em.getTransaction();
transaction.begin(); // 트랜잭션 시작</p>
<p>// 영속 엔티티 조회
member memberA = em.find(Member.class, &quot;memberA&quot;);</p>
<p>// 영속 엔티티 수정
// JPA에서는 영속 상태의 엔티티에 필드 값 변경이 발생하면, 
// 트랜잭션 커밋 시점에 변경된 내용을 자동으로 감지하여 UPDATE 쿼리를 생성하고 실행한다.
memberA.setUserName(&quot;hi&quot;);
memberA.setAge(10);</p>
<p>// 이런 코드 작성하지 않아도 됨
em.update(member);</p>
<p>transaction.commit();</p>
<pre><code>![](https://velog.velcdn.com/images/levi_/post/0d81f882-7084-4379-b1c8-5d98e01b50f8/image.png) [출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍(기본편, 김영한)](https://www.inflearn.com/course/ORM-JPA-Basic/dashboard)
&gt;- JPA는 commit을 하게되면 내부적으로 flush가 호출된다.
- flush가 호출되면 1차캐시에서 Entity와 스냅샷을 비교한다.
- 스냅샷은 값을 읽어온 최초 시점의 상태를 저장 해놓은 공간이다.
- 스냅샷을 비교했을 때 Entity와 스냅샷의 값이 다르다면 UPDATE SQL이 생성된다.
- 이후 commit되면서 DB에 반영된다.

**_5. 엔티티 삭제_**</code></pre><p>Member memberA = em.find(Member.class, &quot;memberA&quot;);</p>
<p>em.remove(memberA); // 엔티티 삭제</p>
<pre><code>**_JPA에서 SELECT 쿼리는 쓰기 지연 저장소를 거치지 않고, find() 호출 시점에 즉시 데이터베이스로 쿼리가 나간다.
반면, INSERT, DELETE, UPDATE 쿼리는 쓰기 지연 저장소에 우선 저장되었다가 flush 시점에 한 번에 데이터베이스로 전송된다.
단, UPDATE는 예외적으로 flush 시점에 변경 감지(dirty checking) 가 수행되어 이때 변경된 필드가 감지되면 비로소 쓰기 지연 저장소에 UPDATE SQL이 생성되고, 이후 DB로 반영된다는 점에서 차이가 있다._**


## 3. Flush란?
&gt; 영속성컨텍스트의 변경내용을 DB에 반영하는 JPA 핵심 메소드

- 쓰기지연저장소(Insert / Update / Delete / 대기 큐)에 쌓인 SQL을 DB에 실제로 반영
- 트랜잭션을 commit하기 전에 자동으로 호출되지만, 직접 호출해서 사용할 수 있음
- flush는 영속성컨텍스트를 비우는게 아닌 영속성컨텍스트의 변경내용을 DB에 동기화 하는 작업
- _**&quot;트랜잭션&quot;**_ 이라는 작업단위가 중요하다. 
-&gt; commit직전에만 동기화 하면 된다.

### 3-1. flush 호출방법
- em.flush() - 직접 호출
- 트랜잭션 커밋 - 자동호출
- JPQL쿼리실행 - 자동호출</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JPA - JPA와 Hibernate]]></title>
            <link>https://velog.io/@levi_/JPA%EC%99%80-Hibernate</link>
            <guid>https://velog.io/@levi_/JPA%EC%99%80-Hibernate</guid>
            <pubDate>Wed, 18 Jun 2025 10:09:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>JPA는 자바의 ORM기술이다. 
ORM이란 <em><strong>Object-Relational Mapping</strong></em> 의 약자로 객체와 관계형 데이터베이스의 테이블을 매핑해주는 기술이다.</p>
</blockquote>
<p>JPA에 대해서 알아보기 전에 먼저 ORM에 대해서 정리해보자</p>
<h2 id="orm이란">ORM이란?</h2>
<p>보통 JAVA를 사용해서 프로그래밍을 할 때 객체를 사용해서 프로그래밍을 하는데, 이 객체를 관계형 데이터베이스 테이블에 1:1로 자동으로 매핑해준다. 
즉, 객체를 RDB 테이블에 자동으로 영속화 해주는 기술이다.
영속화에 대해서는 이 글(수정하기)에서 자세히 다뤄보겠습니다.</p>
<blockquote>
<p><em><strong>장점</strong></em></p>
</blockquote>
<ul>
<li><em><strong>생산성 향상</strong></em>
SQL 대신 객체 중심으로 개발할 수 있어서 코드 작성이 간결해지고 빠르다. 반복적인 CRUD 코드를 자동 생성해 주어 개발 속도가 빨라진다.</li>
<li><em><strong>DB 독립성</strong></em>
특정 DB에 종속되지 않고 여러 종류의 데이터베이스로 쉽게 전환할 수 있다. DB 방언(Dialect) 기능으로 각 DB의 SQL 차이를 자동으로 처리한다.</li>
<li><em><strong>복잡한 관계 매핑 지원</strong></em>
1:1, 1:N, N:M 등 복잡한 객체 간 연관관계를 직관적으로 표현하고 관리할 수 있다.</li>
<li><em><strong>캐시 및 성능 최적화 기능 제공</strong></em>
1차 캐시와 2차 캐시를 활용해 데이터베이스 접근 횟수를 최소화함으로써 성능을 향상시킨다.</li>
</ul>
<blockquote>
<p><em><strong>단점</strong></em></p>
</blockquote>
<ul>
<li><em><strong>성능 오버헤드</strong></em>
자동 매핑과 캐시, 지연 로딩 등의 기능 때문에 직접 SQL을 작성하는 것보다 성능이 떨어질 수 있다.</li>
<li><em><strong>복잡한 쿼리 작성 어려움</strong></em>
복잡한 조인이나 통계 쿼리 같은 경우는 직접 SQL을 작성하는 것이 더 효율적일 수 있다. ORM 쿼리 언어는 한계가 있다.</li>
<li><em><strong>디버깅 어려움</strong></em>
내부에서 자동으로 SQL이 생성되므로 문제가 생겼을 때 원인을 파악하기 어렵다.</li>
<li><em><strong>N+1 문제 발생 가능성</strong></em>
연관된 엔티티를 조회할 때 쿼리가 과도하게 많이 발생하는 N+1 문제가 자주 발생한다. 이 문제는 성능 저하를 심화시키고, ORM을 제대로 이해하고 최적화하지 않으면 심각한 병목이 될 수 있다.</li>
</ul>
<h2 id="jpa란">JPA란?</h2>
<p>JPA란, 자바에서 객체와 관계형 데이터베이스 간의 매핑을 도와주는 ORM 표준 인터페이스이다.
자바를 사용해 프로그래밍할 때, 객체를 데이터베이스 테이블과 자동으로 매핑해주는 중간 다리 역할을 하며, 개발자는 직접 SQL을 작성하는 수고를 줄이고 객체 중심으로 DB 작업을 수행할 수 있게 해준다.</p>
<p><em><strong>JPA는 인터페이스이기 때문에 JPA를 구현체를 함께 사용해야 한다. 대표적으로는 Hibernate, EclipseLink, OpenJPA 가 있다.</strong></em> 
<img src="https://velog.velcdn.com/images/levi_/post/e1c935da-9eed-4992-8ea7-05d545b1eb98/image.png" alt=""></p>
<h2 id="hibernate">Hibernate</h2>
<blockquote>
<p>Hibernate는 Java언어를 위한 대표적인 ORM 프레임워크이고, JPA 인터페이스를 구현한 구현체이다.</p>
</blockquote>
<p><em><strong>JPA의 특징, 장점을 그대로 가지고 개발자가 직접 사용할 수 있게 한다.</strong></em></p>
<blockquote>
<ul>
<li><em><strong>JPA 구현체</strong></em>
JPA가 정의한 표준 명세를 실제로 구현한 라이브러리로, Spring Data JPA에서 기본 구현체로 사용된다.</li>
</ul>
</blockquote>
<ul>
<li><em><strong>자동 매핑</strong></em>
자바 객체와 테이블 간의 매핑을 어노테이션이나 XML 설정을 통해 자동으로 처리함</li>
<li><em><strong>변경 감지(Dirty Checking)</strong></em>
트랜잭션 내에서 엔티티의 값이 변경되면 자동으로 변경 사항을 감지하고 DB에 반영함</li>
<li><em><strong>지연 로딩(Lazy Loading)</strong></em>
연관된 엔티티를 실제로 사용할 때까지 로딩을 미룸으로써 성능 최적화</li>
<li><em><strong>1차 / 2차 캐시 지원</strong></em>
동일 트랜잭션 내에서의 중복 DB 접근을 줄여주는 캐싱기능 제공</li>
<li><em><strong>DB 방언 제공</strong></em>
다양한 DBMS에 맞는 SQL문법을 자동으로 적용해주어 DB 독립성을 높여줌 -&gt; JPA는 특정 DB에 종속되지 않는다.</li>
<li><strong><em>JPQL과 HQL 제공</em></strong>
객체 중심의 쿼리언어(JPQL, Hibernate Query Language)를 통해 SQL보다 객체 지향적인 쿼리 작성 가능</li>
</ul>
<h2 id="jpa-사용-예제">JPA 사용 예제</h2>
<p><img src="https://velog.velcdn.com/images/levi_/post/107f4654-03e8-4719-b2cb-84cddf5333eb/image.png" alt=""></p>
<pre><code>package hellojpa;

import jakarta.persistence.*;
import jakarta.transaction.Transactional;

import java.util.List;

@Transactional
public class JpaMain {

    public static void main(String[] args) {
    // 엔티티매니저팩토리 생성 -&gt; unitname넘겨줌으로써 
    // 엔티티매니저팩토리가 설정파일 읽음
        EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;hello&quot;);
        // 엔티티매니저 생성
        EntityManager em = emf.createEntityManager();
        // 엔티티매니저에서 트랜잭션 가져옴
        EntityTransaction tx = em.getTransaction();
        // 트랜잭션 시작
        tx.begin();

        // 로직 시작
        Member member = new Member();
        member.setId(1L);
        member.setName(&quot;HelloA&quot;);

        // member객체 영속성 컨텍스트 등록 -&gt; 1차캐시저장, 쓰기지연저장소 저장
        em.persist(member);

        // 커밋시점 
        // (flush 실행 -&gt; DB로 SQL 발송 -&gt; commit -&gt; 실제 DB에 반영)
        tx.commit();

        // 리소스 정리
        em.close();
        emf.close();
    }
}</code></pre><p><em><strong>JPA 구동방식은 다음과 같다.</strong></em></p>
<ol>
<li>persistence.xml (설정 파일 또는 Spring에서는 application.yml / application.properties)</li>
<li>EntityManagerFactory 생성
→ DB 연결, 설정 읽기, Entity 메타데이터 초기화</li>
<li>EntityManager 생성
→ 실제로 DB 작업을 수행하는 객체 (트랜잭션 단위로 사용)</li>
<li>EntityManager를 통해 엔티티 저장, 조회, 수정 등 수행</li>
</ol>
<blockquote>
<p>_<strong>EntityManagerFactory</strong>_는 JPA 설정 정보, 메타데이터, DB 커넥션 설정 등을 갖고 있는 무거운 객체이므로 애플리케이션 로딩 시 단 한 번만 생성되어야 하며, 이후에는 이 팩토리를 통해 트랜잭션 단위로 가벼운 _<strong>EntityManager</strong>_를 생성해 사용한다.
추가로 JPA는 데이터 변경 시에 반드시 트랜잭션 안에서 실행되어야한다.</p>
</blockquote>
<h3 id="jpa를-통한-수정작업">JPA를 통한 수정작업</h3>
<ul>
<li>JPA는 변경 사항을 자동으로 감지<em><strong>(DIRTY CHECKING)</strong></em> 해서 <em><strong>flush()</strong></em> 시점에 SQL을 보내고 <em><strong>commit()</strong></em> 시점에 DB에 반영한다. </li>
<li>해당 기능을 통해서 JPA를 이용해 수정작업을 하게 되면 별도의 em.persist() 처럼 영속성컨텍스트 등록을 통해 저장하는 과정을 거치지 않아도 트랜잭션 커밋 시점에 JPA가 자동으로 엔티티 변경을 감지해서 UPDATE쿼리를 날리게된다.</li>
<li>이 자동 반영은 영속성 컨텍스트가 활성화되어 있어야 하고,
그 영속성 컨텍스트는 보통 트랜잭션이 열려 있어야 유지된다.</li>
<li>트랜잭션이 없으면 변경을 감지해도 &quot;언제 반영할지&quot;, &quot;롤백 가능한지&quot;를 판단할 수 없기 때문에, 변경 사항은 무조건 트랜잭션 안에서 실행해야 안전하고 일관되게 작동한다.<pre><code>package hellojpa;
</code></pre></li>
</ul>
<p>import jakarta.persistence.*;</p>
<p>public class JpaMain {</p>
<pre><code>public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;hello&quot;);
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();
    tx.begin();

    try {
        // 엔티티매니저로 Member class의 아이디가 1L인 컬럼 찾기
        Member findMember = em.find(Member.class, 1L);
        // 찾아온 member의 이름을 HelloJPA로 변경
        findMember.setName(&quot;HelloJPA&quot;);

        // 이후 em.persist()로 영속성컨텍스트에 등록하지않아도
        // 트랜잭션 커밋시점에 변경감지를 통해 UPDATE쿼리 발송
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
    } finally {
        em.close();
    }
    emf.close();
}</code></pre><p>}</p>
<p>```
<img src="https://velog.velcdn.com/images/levi_/post/1c0e6af3-5052-4f9b-8b78-863ff57b550f/image.png" alt="">
변경 전
<img src="https://velog.velcdn.com/images/levi_/post/49ffa62c-8748-4795-a326-8f9a9dce5e17/image.png" alt="">
변경 후
<img src="https://velog.velcdn.com/images/levi_/post/5a06582c-9758-4f5e-83e0-ad53a83d7456/image.png" alt=""></p>
<p>지금까지 정리한 내용은 JPA의 내부 동작방식과 기본 개념에 대해서 정리해보았다.
다음엔 JPA 영속성 컨텍스트, 엔티티매핑, 연관관계 등 더 자세하게 공부하고 다뤄봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java - Object(java.lang)]]></title>
            <link>https://velog.io/@levi_/Java-Objectjava.lang</link>
            <guid>https://velog.io/@levi_/Java-Objectjava.lang</guid>
            <pubDate>Wed, 12 Mar 2025 09:48:10 GMT</pubDate>
            <description><![CDATA[<h3 id="자바에서-object클래스는-모든-클래스의-부모클래스이다"><em>자바에서 Object클래스는 모든 클래스의 부모클래스이다.</em></h3>
<p>자바는 상속을 통해 부모의 기능을 자식에게 물려줘서 사용할 수 있게 하는데,
상속을 받은 자식은 부모의 모든 기능을 사용할 수 있습니다.</p>
<blockquote>
</blockquote>
<pre><code>public class Car {
    public void move() {
        System.out.println(&quot;차를 이동합니다.&quot;);
    }
    public void openDoor() {
        System.out.println(&quot;문을 엽니다.&quot;);
    }
}</code></pre><blockquote>
</blockquote>
<pre><code>public class ElectricCar extends Car {
    public void charge() {
        System.out.println(&quot;충전합니다.&quot;);
    }
}</code></pre><blockquote>
</blockquote>
<pre><code>public class GasCar extends Car {
    public void fillup() {
        System.out.println(&quot;기름을 주유합니다.&quot;);
    }
}</code></pre><p>코드를 보면 ElectricCar와 GasCar는 모두 Car를 상속받는다. 그럼 ElectricCar와 GasCar는 Car클래스의 move()메소드와 openDoor()메소드를 사용할 수 있다.</p>
<p>그런데 여기서, Car클래스도 상속을 받고있다 그런데 왜 다른 클래스들 처럼 extends키워드를 명시하지 않았을까? </p>
<p><em><strong>자바에서 모든 클래스의 부모클래스는 &quot;Object클래스&quot;이다.</strong></em> 클래스에 따로 상속을 명시하지 않으면 묵시적으로 Object클래스를 상속받는다.</p>
<h3 id="object클래스-다형성의-한계"><strong>Object클래스 다형성의 한계</strong></h3>
<p>Object클래스는 모든 클래스들의 부모이므로 모든 객체를 다 담을 수 있다.
하지만 Object를 통해 전달받은 메소드를 호출할 일이 있다면 그에 맞게 다운캐스팅을 해줘야한다.</p>
<p>다형성은 다형적 참조와 메서드 오버라이딩이 함께 이루어져야 한다. 그런데 Object클래스는 더 이상 상위 클래스가 없기 때문에 메서드 오버라이딩에 한계가 생긴다. Object클래스가 세상의 모든 메서드들을 다 알고있는건 아니기 때문이다.</p>
<h3 id="javalang-패키지"><strong>java.lang 패키지</strong></h3>
<p>자바가 기본으로 제공하는 라이브러리중 가장 기본이 되는 패키지이다. Language의 줄임말이다.</p>
<blockquote>
<p><strong><em>대표적인 클래스들</em></strong>
<strong>Object</strong> : 모든 자바 객체의 부모 클래스
<strong>String</strong> : 문자열
<strong>Integer, Long, Double</strong> : 래퍼타입, 기본형 데이터 타입을 객체로 만든 것
<strong>Class</strong> : 클래스 메타 정보
<strong>System</strong> : 시스템과 관련된 기본 기능들을 제공</p>
</blockquote>
<blockquote>
<p>이 외에도 java.lang패키지의 Object클래스 에서 제공하는 메서드들도 있는데 간략하게 알아보자
toString() : 객체의 정보를 제공
equals() : 객체의 같음을 비교
getClass() : 객체의 클래스정보를 제공</p>
</blockquote>
<h3 id="정적-의존관계와-동적-의존관계"><strong>정적 의존관계와 동적 의존관계</strong></h3>
<p>정적 의존관계 : 컴파일 시간에 결정되며 주로 클래스 간의 관계를 의미, 프로그램을 실행하지 않고, 클래스 내에서 사용하는 타입들만 보면 쉽게 의존관계를 파악할 수 있다.</p>
<p>동적 의존관계 : 런타임 시간에 확인 가능한 의존관계, 런타임에 어떤 인스턴스를 사용하는지를 나타내는 것</p>
<p>참고로 단순히 의존관계 또는 어디에 의존한다고 하면 보통은 정적 의존관계를 뜻한다.</p>
<h3 id="정리">정리</h3>
<p>Object클래스는 자바의 모든 클래스들의 부모 클래스이고 Object클래스가 제공하는 메소드들도 개발자들이 사용하는 공통기능을 제공해준다.</p>
<p>하지만 Object클래스는 더 이상 상위의 상속을 받는 클래스가 없기 때문에 다형성을 활용하는데 있어서 한계가 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SPRING - MVC]]></title>
            <link>https://velog.io/@levi_/SPRING-MVC</link>
            <guid>https://velog.io/@levi_/SPRING-MVC</guid>
            <pubDate>Fri, 28 Feb 2025 01:11:05 GMT</pubDate>
            <description><![CDATA[<p>김영한 강사님의 <strong>&quot;스프링부트 입문편&quot;</strong> 강의를 보면서 자료조사한 내용을 토대로 MVC가 동작하는 방식을 정리해보겠습니다.</p>
<blockquote>
<p>MVC란?
<strong>웹 계층에 서블릿API를 기반으로 클라이언트의 요청을 처리하는 모듈이 있는데 이를 스프링 웹 MVC라고 한다.</strong>
Model, View, Controller 로 구성되어 있고 각 역할에 맞게 클라이언트의 요청을 처리한다.</p>
</blockquote>
<h2 id="mvc-동작방식"><strong>MVC 동작방식</strong></h2>
<hr>
<h3 id="model"><strong>Model</strong></h3>
<p> Model에는 View에 필요한 데이터가 포장된다고 생각하면 된다. Controller가 처리한 데이터를 담아서 View로 보낼 때 Model로 감싸서 보낸다.</p>
<h3 id="view"><strong>View</strong></h3>
<p>Model에 담겨온 데이터와 반환받은 View이름을 통해 화면을 구성한다. 화면구성에 집중하는 역할이다.</p>
<h3 id="controller"><strong>Controller</strong></h3>
<p>실제 비즈니스 로직을 처리한다. 좀 더 자세히 말하자면, Controller가 Service를 호출해서 Service에서 비즈니스 로직을 처리 후 Repository를 통해 DB에 데이터 저장 후 반환받아서 다시 Controller로 반환하면 Model 객체에 데이터가 담겨서 View로 반환되는 것이다.</p>
<h3 id="dispatcherservlet"><strong>DispatcherServlet</strong></h3>
<p>이게 뭔가 하고 찾아보니 Fornt Controller라고도 불리는데, 이게 등장하기 전에는 모든 컨트롤러가 클라이어느의 요청을 받는데, 공통되는 코드가 발생 할 수 밖에 없었다. FrontController가 등장하면서 모든 요청을 FrontController 하나로 받고 요청에 맞는 컨트롤러를 찾아 처리하면 되기 때문에 공통된 요소를 FrontController하나가 처리할 수 있게 된다.
<img src="https://velog.velcdn.com/images/levi_/post/a8dfecb4-b8a8-474d-b2f5-74baf2635119/image.webp" alt=""></p>
<h3 id="정리"><strong>정리</strong></h3>
<blockquote>
<ul>
<li>클라이언트가 요청을 보냄</li>
</ul>
</blockquote>
<ul>
<li>DispatcherServlet이 요청을 가로채고 HandlerMapping을 통해 적절한 Controller를 조회</li>
<li>Handler를 처리할 수 있는 HandlerAdapter를 조회</li>
<li>HandlerAdapter를 통해 Controller를 실행</li>
<li>Controller가 Service Repository를 통해 실제 비즈니스로직 처리 후 값을 반환 받음 (이 부분은 추후에 더 자세히 다룰 예정)</li>
<li>DispatcherServlet이 Model과, View를 반환받는데, 좀 더 찾아보니 Controller가 String으로 반환하든, ModelaAndView로 반환하든, 파라미터의 Model객체에 정보를 담든 <strong>어댑터가 알아서 변환해준다.</strong> 고 함</li>
<li>그 후, ViewResolver를 호출해서 데이터를 전달하고 ViewResolver가 전달받은 View이름을 통해 파일을 찾는다. 이 때 템플릿엔진을 이용해서 HTML을 생성</li>
<li>다시 DispatcherServlet이 View를 반환받고, View 위치를 찾았기 때문에 반환받은 데이터들을 토대로 View가 실행되어서 클라이언트에게 요청을 반환</li>
</ul>
<blockquote>
<p>결국 정리해보자면 Controller, View, Service, Repository만 개발자가 작성을 잘 하면 나머지는 스프링이 알아서 처리해주는 것 같다. 그래도 동작흐름을 알아두면 좋지 않을까 해서 머리속에 있는 내용을 최대한 정리해 봤다.</p>
</blockquote>
<p>이 다음엔 웹 애플리케이션 계층구조 (Service, Repository, Controller, domain)에 대해서 알아봐야겠다.</p>
<p>끗!</p>
]]></description>
        </item>
    </channel>
</rss>