<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>KEYMU_velog</title>
        <link>https://velog.io/</link>
        <description>Junior Backend Developer</description>
        <lastBuildDate>Sun, 07 Dec 2025 13:23:06 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>KEYMU_velog</title>
            <url>https://velog.velcdn.com/images/dhlee47-l/profile/8a7e0cbf-c043-4e05-abf8-c007698cd003/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. KEYMU_velog. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dhlee47-l" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[JVM] 10. 프런트엔드 컴파일과 최적화]]></title>
            <link>https://velog.io/@dhlee47-l/JVM-10.-%ED%94%84%EB%9F%B0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EA%B3%BC-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@dhlee47-l/JVM-10.-%ED%94%84%EB%9F%B0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EA%B3%BC-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Sun, 07 Dec 2025 13:23:06 GMT</pubDate>
            <description><![CDATA[<p>컴파일타임? 아래와 같이 3가지로 설명될 수 있음:</p>
<ol>
<li>프런트엔드 컴파일러: JDK의 javac(.java -&gt; .class로 변경), 이클립스 JDT의 증분 컴파일러(변경된 파일만 다시 컴파일하도록. 일반 컴파일은 500개 파일 중 하나만 고쳐도 의존성 따라 싹 다 다시 컴파일해야 하는데 그러지 않도록하는 것이 증분 컴파일임)</li>
<li>JIT 컴파일러: 핫스팟 가상 머신의 C1, C2 컴파일러</li>
<li>AOT 컴파일러</li>
</ol>
<p>이번 장은 그 중 1번.
이전 6, 7, 8, 9장에서 설명되었던 런타임단계의 클래스 로딩, 링킹, 초기화, 스프링 컨테이너 빈 생성, 의존관계 주입 등 class파일을 JVM 메모리에 올리고 객체를 Container 등록하는 과정 전에 일어나는 빌드 타임 작업에 대한 설명을 할 거란 말. 톰캣 올리기 전 끝나있는 class 파일 만드는 &quot;컴파일&quot;에 대해 설명하는 장임.</p>
<p>최적화 또한 다음과 같이 정의한다:
javac와 같은 프런트엔드 컴파일러는 코드 실행 효율 측면의 최적화는 거의 하지 않으며, JDK 1.3 이후로는 성능 최적화를 런타임 컴파일러에 집중시켰다. 
(그루비 클래스 파일 등도 최적화 효과를 공평하게 누리기 위해)</p>
<p>개발단계까지 최적화를 넓혀 부른다면, 
런타임: 실행 효율을 높이는 최적화를 JIT 컴파일러에서 지속해서 수행
컴파일타임: 개발자 코딩 효율 높이는 최적화를 프런트엔드 컴파일러가 수행</p>
<p>10.2 javac 컴파일러</p>
<ul>
<li>순수 자바로 작성</li>
<li>JDK 6부터 javac 표준 자바 SE API에 포함. 원래는 구현 코드도 다 tools.jar에 별도로 담겨있었음. tools.jar 라이브러리가 클래스패스에 존재해야 javac 쓸 수 있었음 -&gt; 표준 자바 클래스 라이브러리로 지금은 옮겨짐</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/723656a8-50bc-45a8-838f-b76854657e91/image.png" alt=""></p>
<p>Java소스파일을 클래스 파일로 컴파일 하는 방법은 아주 느슨하게 설명되어있어 자유도가 높음
장점: 자바 프런트엔드 컴파일러의 자유도 vs 단점:컴파일 과정이 특정 JDK 또는 컴파일러 구현에 따라 달라지는 경우도 생김</p>
<p>javac 코드의 전체 구조 확인을 통한 컴파일 처리 단계: 1단계 준비 + 3단계 처리
0: 플러그인 annotation 처리기 초기화
1: 구문 분석 및 심벌 테이블 채우기(소스 코드 토큰화하여 추상 구문 트리 구성/심벌 주소와 심벌 정보 생성)
2: 플러그인 annotation 처리기들로 annotation 처리
3: 의미분석 및 바이트코드 생성(특성검사를 통한 문법의 정적 정보 확인/데이터 흐름 및 제어 흐름 분석을 통한 프로그램의 동적 실행 과정 확인/편의문법 제거하여 원래 형식으로 복원/바이트 코드 생성)</p>
<p>compile 도중에 플러그인 annotation 실행 시, 새로운 심벌 생성될 수 있음.
그럼 이 새 심벌 다시 처리하려고 심벌 테이블 채우던 때로 돌아가야 함
그 상호작용순서는 아래와 같음:
<img src="https://velog.velcdn.com/images/dhlee47-l/post/1eca4219-e3cc-4c90-a733-e76da23103f0/image.png" alt=""></p>
<p>javac의 컴파일 과정 맡은 클래스: com.sun.tools.javac.main.JavaCompiler
앞선 3단계 처리는 compile() method에 집중되어있음</p>
<p>javac 소스코드 기준으로 3단계 처리 과정 항목별 설명:
<img src="https://velog.velcdn.com/images/dhlee47-l/post/05761c45-c11d-42da-aa09-7940e7409244/image.png" alt=""></p>
<p>1: 구문분석 및 심벌 테이블 채우기
구문분석: </p>
<ul>
<li>parseFiles() method. </li>
<li>소스코드, 문자스트림 -&gt; 토큰 집합으로(int a = b -&gt; int, a, =, b)</li>
<li>com.sun.tools.javac.parser.Scanner 클래스가 해당 나눔을 담당 -&gt; 해당 나눔은 곧 &quot;추상 구문 트리&quot;를 만드는 것과 같음</li>
</ul>
<p>심벌 테이블 채우기: </p>
<ul>
<li>enterTrees() method. </li>
<li>심벌테이블 = 심벌 주소 + 심벌 정보</li>
<li>의미 분석 과정 중 의미 확인(이름을 원래 선언과 일치하게 사용하는지 확인), 중간 코드 생성 시 참조, 목적 코드 생성 시에도 주소 할당에 심벌 테이블 활용</li>
<li>com. sun. tools.javac.comp.Enter 클래스가 담당 -&gt; 이 단계의 결과로 컴파일 단위 각각에 대한 추상 구문 트리의 최상위 노드와 package-info.java의 최상위 노드 목록 만들어짐(전체 프로젝트의 구조 지도를 만든다는 뜻 -&gt; 다음 단계에서 그래야 타입 검사, 흐름 분석, 바이트 코드를 생성할 수 있으므로)</li>
</ul>
<p>2: 애너테이션 처리</p>
<ul>
<li>애너테이션 처리 중에 구문 트리를 수정하면 컴파일러는 &#39;구문 분석 및 심벌 테이블 채우기&#39; 단계로 돌아가야 함 -&gt; 이걸 모든 플러그인 애너테이션 처리기가 구문 트리 수정 안할 때까지 반복함</li>
<li>컴파일러의 애너테이션 처리 API 사용: 개발자 코드가 컴파일러 동작에 영향 줄 수 있음. 예시로, Lombok의 Getter/Setter 생성, null 확인, 검사 예외 테이블 생성, equals(), hashCode() method 생성 등을 자동으로 작성하게 할 수 있음</li>
<li>해당 자동화 애너테이션 처리기는 0단계인 initProcessAnnotations()에서 초기화하여 실행은 processAnnotation()이 담당.</li>
<li>processAnnotation()은 com.sun.tools.javac.processing.JavacProcessingEnvironment 클래스의 doProcessing()을 실행 할 새 애너테이션 처리기가 있을 시 호출 -&gt; 해당 메서드가 새 JavaCompiler 객체 생성하여 컴파일 이후 단계 처리.</li>
</ul>
<p>3: 의미 분석과 바이트코드 생성</p>
<ul>
<li>의미분석: 구조적으로 올바른 소스가 &#39;맥락상으로도 올바른지&#39; 확인(예: 타입 검사, 제어 흐름 검사, 데이터 흐름 검사)
그 중 </li>
</ul>
<p>1) 특성 검사: 변수를 사용하기 앞서 선언이 되어 있는지, 변수와 할당될 데이터의 타입이 일치하는지 등을 확인 /  접기(constant folding)라는 최적화도 수행
2) 데이터와 제어 흐름 분석: 프로그램이 맥락상 논리적으로 올바른지 확인. 기본적으로 클래스 로딩 시 수행하는 데이터 및 제어 흐름 분석의 목적과 같음 BUT 검증 범위가 다르기 때문에 항목에 따라 컴파일 타임에만 수행되거나 런타임에만 수행되기도 함
(ex. 지역변수는 상수 풀에 대한 심벌 참조가 없어 접근 플래그 정보 저장 못되므로, final이 붙어도 런타임에는 아무 영향이 없음. so, 오직 컴파일타임에 javac 컴파일러에서만 보장, 검사되는 대표적 예시)</p>
<ul>
<li>편의 문법 제거: desugar() method 촉발. 람다 등 편의 문법을 원래 기본 구문으로 복원</li>
<li>바이트코드 생성: com.sun.tools.javac.jvm.Gen 클래스 담당 / 심벌 테이블 단계에서 이름만 등록되고 실제 코드 구현은 바이트코드 생성 시 완성되는 자동생성작업도 여기서 진행(ex. &lt;init())</li>
</ul>
<p>JAVA 편의문법:
Generic, 오토박싱/언박싱, 개선된 for문, 가변 길이 매개 변수, 조건부 컴파일 등(C#과 비교하여 코드 트렌드를 따르는 다양한 편의 문법의 예시와 역사, 사용법)</p>
<p>실전: 플러그인 애너테이션 처리기 제작</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Oracle과 MSSQL의 테이블 소유권 차이]]></title>
            <link>https://velog.io/@dhlee47-l/Oracle%EA%B3%BC-MSSQL%EC%9D%98-%ED%85%8C%EC%9D%B4%EB%B8%94-%EC%86%8C%EC%9C%A0%EA%B6%8C-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@dhlee47-l/Oracle%EA%B3%BC-MSSQL%EC%9D%98-%ED%85%8C%EC%9D%B4%EB%B8%94-%EC%86%8C%EC%9C%A0%EA%B6%8C-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Mon, 17 Mar 2025 07:43:18 GMT</pubDate>
            <description><![CDATA[<h3 id="1-기본-구조-차이">1. 기본 구조 차이</h3>
<p><strong>Oracle:</strong></p>
<ul>
<li>사용자(User)와 스키마(Schema)가 1:1로 매핑됨</li>
<li>사용자를 생성하면 동일한 이름의 스키마가 자동으로 생성됨</li>
<li>테이블은 직접 사용자(=스키마)에 속함</li>
</ul>
<p><strong>MSSQL:</strong></p>
<ul>
<li>사용자와 스키마가 분리되어 있음</li>
<li>한 사용자가 여러 스키마를 소유할 수 있고, 여러 사용자가 하나의 스키마에 객체를 생성할 수 있음</li>
<li>테이블은 스키마에 속하고, 스키마는 사용자에게 속함</li>
<li>한 계정으로 여러 DB에 붙을 수 있음</li>
</ul>
<h3 id="2-테이블-참조-방식">2. 테이블 참조 방식</h3>
<p><strong>Oracle:</strong></p>
<pre><code class="language-oracle">SELECT * FROM SCOTT.EMP;  -- SCOTT은 사용자이자 스키마</code></pre>
<p><strong>MSSQL:</strong></p>
<pre><code class="language-mssql">SELECT * FROM Sales.Orders;  -- Sales는 스키마, 소유자는 별도로 존재</code></pre>
<h3 id="3-권한을-누가-주는가">3. 권한을 누가 주는가?</h3>
<p><strong>Oracle:</strong> sys</p>
<p><strong>MSSQL:</strong> sa</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Oracle_기초2] 권한과 Role]]></title>
            <link>https://velog.io/@dhlee47-l/Oracle%EA%B8%B0%EC%B4%882-%EA%B6%8C%ED%95%9C%EA%B3%BC-Role</link>
            <guid>https://velog.io/@dhlee47-l/Oracle%EA%B8%B0%EC%B4%882-%EA%B6%8C%ED%95%9C%EA%B3%BC-Role</guid>
            <pubDate>Sat, 22 Feb 2025 12:45:18 GMT</pubDate>
            <description><![CDATA[<h3 id="시스템권한system-privileges">시스템권한(System Privileges)</h3>
<ul>
<li>시스템권한은 사용자가 데이터베이스에서 특정 작업을 수행 할 수 있도록 한다</li>
<li>권한의 ANY 키워드는 사용자가 모든 스키마에서 권한을 가짐을 의미 한다.</li>
<li>GRANT 명령은 사용자 또는 ROLE에 대해서 권한을 부여 할 수 있다.</li>
<li>REVOKE 명령은 권한을 회수 한다.</li>
</ul>
<h4 id="대표적인-시스템권한">대표적인 시스템권한</h4>
<ul>
<li>CREATE SESSION : 데이터 베이스를 연결할 수 있는 권한</li>
<li>CREATE ROLE : 오라클 데이터베이스 역할을 생성할 수 있는 권한</li>
<li>CREATE VIEW : 뷰의 생성 권한</li>
<li>ALTER USER : 생성한 사용자의 정의를 변경할 수 있는 권한</li>
<li>DROP USER : 생성한 사용자를 삭제시키는 권한</li>
</ul>
<h3 id="시스템권한-부여-문법">시스템권한 부여 문법</h3>
<ul>
<li>system_privilege : 부여할 시스템권한의 이름</li>
<li>role : 부여할 데이터베이스 역할의 이름</li>
<li>user, role : 부여할 사용자 이름과 다른 데이터 베이스 역할 이름</li>
<li>PUBLIC : 시스템권한, 또는 데이터베이스 역할을 모든 사용자에게 부여할 수 있다.</li>
<li>WITH ADMIN OPTION : 권한을 부여 받은 사용자도 부여 받은 권한을 다른 사용자 또는 역할로 부여할 수 있게된다.</li>
</ul>
<h4 id="시스템권한-회수">시스템권한 회수</h4>
<pre><code class="language-sql">-- scott 사용자에게 부여한 생성, 수정, 삭제 권한을 회수 한다.
SQL&gt;REVOKE CREATE USER, ALTER USER, DROP USER
    FROM scott;
 권한이 회수되었습니다.</code></pre>
<p>시스템 권한을 회수해도, 회수 당한 사용자가 권한을 부여한 당사자는 여전히 받은 권한을 유지한다.</p>
<h3 id="객체권한object-privileges">객체권한(Object Privileges)</h3>
<ul>
<li>테이블이나 뷰, 시퀀스, 프로시저, 함수, 또는 패키지 중 지정된 한 객체에 특별한 작업을 수행 할 수 있게 한다.</li>
<li>객체 소유자는 다른 사용자에게 특정 객체권한을 부여 할 수 있다.</li>
<li>PUBLIC으로 권한을 부여하면 회수할 때도 PUBLIC으로 해야 한다.</li>
<li>기본적으로 소유한 객체에 대해서는 모든 권한이 자동적으로 획득된다.</li>
<li>WITH GRANT OPTION 옵션은 ROLE에 권한을 부여할 때는 사용할 수 없다.</li>
</ul>
<h4 id="객체권한-부여-예제">객체권한 부여 예제</h4>
<pre><code class="language-sql">-- scott USER에게 emp테이블을 SELECT, INSERT할 수 있는 권한을 부여했다.
-- scott USER도 다른 USER에게 그 권한을 부여 할 수 있다.
SQL&gt;GRANT SELECT, INSERT
    ON emp
    TO scott
    WITH  GRANT  OPTION;
 권한이 부여되었습니다.</code></pre>
<h4 id="객체권한의-회수">객체권한의 회수</h4>
<ul>
<li>객체 권한의 철회는 그 권한을 부여한 부여자만이 수행할수 있다.</li>
<li>CASCADE CONSTRAINTS : 이 명령어의 사용으로 참조 객체 권한에서 사용 된 참조 무결성 제한을 같이 삭제 할 수 있다.</li>
<li>WITH GRANT OPTION으로 객체 권한을 부여한 사용자의 객체 권한을 철회하면, 권한을 부여받은 사용자가 부여한 객체 권한 또한 같이 철회되는 종속철회가 발생한다.</li>
</ul>
<blockquote>
<p>시스템 권한: &quot;무언가를 만들거나 없앨 수 있는&quot; 권한
객체 권한: &quot;이미 있는 것을 사용할 수 있는&quot; 권한</p>
</blockquote>
<h3 id="롤role">롤(ROLE)</h3>
<ul>
<li>ROLE을 이용하면 권한 부여와 회수를 쉽게 할 수 있다.</li>
<li>ROLE은 CREATE ROLE권한을 가진 USER에 의해서 생성 된다.</li>
<li>한 사용자가 여러개의 ROLL을 ACCESS할 수 있고, 여러 사용자에게 같은 ROLE을 부여할 수 있다.</li>
<li>시스템 권한을 부여하고, 취소할 때와 동일한 명령을 사용하여 사용자에게 부여하고, 취소 한다.</li>
<li>사용자는 ROLE에 ROLE을 부여할 수 있다.</li>
<li>오라클 데이터베이스를 설치하면 기본적으로 CONNECT, RESOURCE, DBA ROLE이 제공 된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/b5d16fd6-9211-4fae-a494-995e00c7542f/image.png" alt=""></p>
<h4 id="role의-부여-순서">ROLE의 부여 순서</h4>
<p>① ROLE의 생성 : CREATE ROLE manager
② ROLE에 권한 부여 : GRANT create session, create table TO manager
③ ROLE을 사용자 또는 ROLE에게 부여 : GRANT manager TO scott, test;</p>
<pre><code class="language-sql">-- ROLE을 생성 합니다.
SQL&gt;CREATE ROLE manager;

-- ROLE에 권한을 부여 합니다.
SQL&gt;GRANT create session, create table TO manager;

-- 권한이 부여된ROLE을 USER나 ROLE에 부여 합니다.
SQL&gt;GRANT manager TO scott, test;</code></pre>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/bd1e9b3b-5f99-42eb-8db9-46fa27b62961/image.png" alt=""></p>
<h4 id="resource-role">RESOURCE ROLE</h4>
<ul>
<li>Store Procedure나 Trigger와 같은 PL/SQL을 사용할 수 있는 권한 들로 이루어져 있다.</li>
<li>PL/SQL을 사용하려면 RESOURCE ROLE을 부여해야 한다.</li>
<li>유저를 생성하면 일반적으로 CONNECT, RESOURCE롤을 부여 한다.</li>
</ul>
<h4 id="dba-role">DBA ROLE</h4>
<ul>
<li>모든 시스템 권한이 부여된 ROLE 이다.</li>
<li>DBA ROLE은 데이터베이스 관리자에게만 부여해야 한다.</li>
</ul>
<p>출처: <a href="http://www.gurubee.net/lecture/1345">http://www.gurubee.net/lecture/1345</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Oracle_기초1] User 생성과 권한 설정]]></title>
            <link>https://velog.io/@dhlee47-l/Oracle%EA%B8%B0%EC%B4%881</link>
            <guid>https://velog.io/@dhlee47-l/Oracle%EA%B8%B0%EC%B4%881</guid>
            <pubDate>Sat, 22 Feb 2025 08:59:30 GMT</pubDate>
            <description><![CDATA[<h3 id="user-생성">USER 생성</h3>
<pre><code class="language-sql">-- SQL PLUS를 실행시키고 SCOTT/TIGER로 접속을 한다.
SQL&gt;CREATE USER TEST IDENTIFIED BY TEST;

1행에 오류:
ORA-01031: 권한이 불충분합니다

-- SCOTT USER는 사용자 생성 권한이 없어서 사용자를 생성할 수 없다.
-- DBA Role이 있는 유저로 접속 
-- sqlplus / as sysdba 로 접속하셔도 됩니다.
SQL&gt;CONN sys/manager AS SYSDBA

 -- USER를 다시 생성.
SQL&gt;CREATE USER TEST IDENTIFIED BY TEST;    
 사용자가 생성되었습니다.    </code></pre>
<h4 id="새로-생성한-user-접근권한-부여">새로 생성한 USER 접근권한 부여</h4>
<pre><code>SQL&gt; CONN TEST/TEST

ERROR:
ORA-01045: 사용자 TEST는 CREATE SESSION 권한을 가지고있지 않음;

-- 새로 생성한 TEST USER는 권한이 없어서 접근할 수가 없다.
-- 모든 USER는 권한이 있고 권한에 해당하는 역할만 할 수 있다.
-- TEST라는 USER를 사용하기 위해서도 권한을 부여해 주어야 한다.
SQL&gt; CONN sys/manager AS SYSDBA
연결되었습니다.

SQL&gt; GRANT connect, resource TO TEST ;
권한이 부여되었습니다.

SQL&gt; CONN TEST/TEST
연결되었습니다.</code></pre><p>User 정보를 변경해보자.</p>
<h4 id="alter-user문으로-변경-가능한-옵션">ALTER USER문으로 변경 가능한 옵션</h4>
<ul>
<li>비밀번호</li>
<li>운영체제 인증</li>
<li>디폴트 테이블 스페이스</li>
<li>임시 테이블 스페이스</li>
<li>테이블 스페이스 분배 할당</li>
<li>프로파일 및 디폴트 역할</li>
</ul>
<h3 id="user-수정">USER 수정</h3>
<pre><code class="language-sql">-- SYS 권한으로 접속한다.    
C:\&gt; SQLPLUS /NOLOG
SQL&gt; CONN / AS SYSDBA       

-- scott USER의 비밀번호를 수정한다.
SQL&gt; ALTER USER scott IDENTIFIED BY lion;    
 사용자가 변경되었습니다.

-- scott USER의 비밀번호가 변경된 것을 확인할 수 있다.
SQL&gt; CONN scott/lion    
접속되었습니다.


SQL&gt; CONN / AS SYSDBA
접속되었습니다.

-- scott USER의 비밀번호를 처음처럼 수정한다.
SQL&gt; ALTER USER scott IDENTIFIED BY tiger;    
사용자가 변경되었습니다.
</code></pre>
<h3 id="user-삭제">USER 삭제</h3>
<pre><code class="language-sql">DROP USER user_name [CASCADE}</code></pre>
<h3 id="user-정보-확인">USER 정보 확인</h3>
<pre><code class="language-sql">-- 데이터베이스에 등록된 사용자를 조회하기 위해서는 DBA_USERS라는 
    데이터사전을 조회하면 된다.
-- SQL*Plus를 실행시켜  SYS계정으로 접속을 한다.
SQL&gt; CONN / AS SYSDBA 

SQL&gt; SELECT username, default_tablespace, temporary_tablespace
     FROM DBA_USERS;

USERNAME         DEFAULT_TABLESPACE      TEMPORARY_TABLES
---------------- -------------------     ----------------
SYS               SYSTEM                  TEMP
SYSTEM            TOOLS                   TEMP
OUTLN             SYSTEM                  SYSTEM
DBSNMP            SYSTEM                  SYSTEM
ORDSYS            SYSTEM                  SYSTEM
ORDPLUGINS        SYSTEM                  SYSTEM
MDSYS             SYSTEM                  SYSTEM
CTXSYS            DRSYS                   DRSYS
SCOTT             SYSTEM                  SYSTEM
TEST              TEST                    SYSTEM
STORM             STORM                   SYSTEM
KJS               SYSTEM                  SYSTEM

 위와 같이 유저와 테이블 스페이스에 대한 정보가 화면에 나온다.      </code></pre>
<p>출처: <a href="http://www.gurubee.net/lecture/1006">http://www.gurubee.net/lecture/1006</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Oracle] GROUP BY, ROLLUP, GROUPING_ID]]></title>
            <link>https://velog.io/@dhlee47-l/Oracle-GROUP-BY-ROLLUP-GROUPINGID</link>
            <guid>https://velog.io/@dhlee47-l/Oracle-GROUP-BY-ROLLUP-GROUPINGID</guid>
            <pubDate>Fri, 21 Feb 2025 07:33:57 GMT</pubDate>
            <description><![CDATA[<h3 id="rollup">ROLLUP</h3>
<pre><code class="language-sql">SELECT 
    AUTHOR_ID,
    AUTHOR_NAME,
    CATEGORY,
    TOTAL_SALES
FROM (
    SELECT 
        NVL(TO_CHAR(A.AUTHOR_ID), &#39;전체&#39;) AS AUTHOR_ID,
        NVL(A.AUTHOR_NAME, &#39;전체&#39;) AS AUTHOR_NAME,
        NVL(B.CATEGORY, &#39;전체&#39;) AS CATEGORY,
        TO_CHAR(SUM(B.PRICE * S.SALES), &#39;999,999,999&#39;) AS TOTAL_SALES
    FROM BOOK B
    JOIN AUTHOR A ON B.AUTHOR_ID = A.AUTHOR_ID
    JOIN BOOK_SALES S ON B.BOOK_ID = S.BOOK_ID
    GROUP BY ROLLUP(A.AUTHOR_ID, A.AUTHOR_NAME, B.CATEGORY)
)
ORDER BY AUTHOR_ID</code></pre>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/da197b6c-2723-49ba-a49a-7ec3b502e34f/image.png" alt=""></p>
<p>GROUP BY 시 ROLLUP으로 소계, 총합을 표현하는 코드를 작성했고, 이렇게 작성 시 1 전체 전체 5,751,000 같은 필요없는 값이 나오게 되었다. 물론 MAX 함수를 통해 AUTHOR_NAME을 뽑았다면 해결 가능하지만, 오늘 팀장님께 QUERY 교육 중 GROUPING_ID의 존재에 대해 알게 되어 정리한다.</p>
<h3 id="grouping_id">GROUPING_ID</h3>
<pre><code class="language-sql">SELECT
    AUTHOR_ID,
    AUTHOR_NAME,
    CATEGORY,
    TOTAL_SALES
FROM (
    SELECT
        NVL(TO_CHAR(A.AUTHOR_ID), &#39;합계&#39;) AS AUTHOR_ID,
        NVL(A.AUTHOR_NAME, &#39;합계&#39;) AS AUTHOR_NAME,
        NVL(B.CATEGORY, &#39;소계&#39;) AS CATEGORY,
        TO_CHAR(SUM(B.PRICE * S.SALES), &#39;999,999,999&#39;) AS TOTAL_SALES,
         GROUPING_ID(A.AUTHOR_ID,  A.AUTHOR_NAME, B.CATEGORY) gid
    FROM BOOK B
    JOIN AUTHOR A ON B.AUTHOR_ID = A.AUTHOR_ID
    JOIN BOOK_SALES S ON B.BOOK_ID = S.BOOK_ID
    GROUP BY ROLLUP(A.AUTHOR_ID,  A.AUTHOR_NAME, B.CATEGORY)
    ORDER BY AUTHOR_ID
)
WHERE gid != 3
ORDER BY AUTHOR_ID</code></pre>
<p>이렇게 처리하게 되면, </p>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/203b3f8d-226e-4066-92b3-16eaa61affe0/image.png" alt=""></p>
<p>0, 0, 1, 3, 0, 1, 3, 7 이렇게 2의 3승개의 데이터가 나오게 되어 3, 3을 제거하면 우리가 필요로 하지 않았던 데이터가 사라지게 된다.</p>
<p>필요없는 데이터를 처리하기 위해 GROUPING_ID를 활용한 경험을 정리해보았다.</p>
<p><a href="http://www.gurubee.net/lecture/2678">구루비_ROLLUP</a>
<a href="http://www.gurubee.net/lecture/2679">구루비_GROUPING_ID</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring + React] IaaS 서비스 활용 배포]]></title>
            <link>https://velog.io/@dhlee47-l/Spring-React-IaaS-%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%99%9C%EC%9A%A9-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@dhlee47-l/Spring-React-IaaS-%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%99%9C%EC%9A%A9-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Fri, 14 Feb 2025 03:15:39 GMT</pubDate>
            <description><![CDATA[<h2 id="다양한-클라우드-서비스-유형과-docker">다양한 클라우드 서비스 유형과 Docker</h2>
<h3 id="iaas-infrastructure-as-a-service">IaaS (Infrastructure as a Service)</h3>
<ul>
<li>가상 서버, 스토리지, 네트워크 등 기본적인 컴퓨팅 인프라를 제공하는 서비스</li>
<li>Amazon EC2가 대표적인 예시</li>
</ul>
<h4 id="iaas의-특징">IaaS의 특징</h4>
<ul>
<li>사용자가 직접 OS, 미들웨어, 런타임, 애플리케이션을 설치하고 관리</li>
<li>더 많은 통제권과 유연성 제공</li>
<li>관리 책임이 큼 (OS 업데이트, 보안 패치, 런타임 환경 설정 등)</li>
</ul>
<h4 id="iaas-배포-프로세스-예시">IaaS 배포 프로세스 예시</h4>
<ol>
<li>EC2, RDS 서버 연결</li>
<li>JPA로 테이블 생성</li>
<li>React 서버 실행</li>
<li>연결 확인 후 tar 압축파일 생성</li>
</ol>
<h3 id="paas-platform-as-a-service">PaaS (Platform as a Service)</h3>
<ul>
<li>애플리케이션 실행에 필요한 플랫폼을 제공하는 서비스</li>
<li>AWS Elastic Beanstalk이 대표적인 예시</li>
</ul>
<h4 id="paas의-특징">PaaS의 특징</h4>
<ul>
<li>OS, 미들웨어, 런타임 환경을 서비스 제공자가 관리</li>
<li>개발과 배포에 더 집중 가능</li>
<li>코드 업로드만으로 배포 가능</li>
<li>자동 스케일링과 로드 밸런싱 제공</li>
<li>환경 관리 자동화</li>
</ul>
<h3 id="docker-컨테이너화-플랫폼">Docker: 컨테이너화 플랫폼</h3>
<ul>
<li>애플리케이션을 &quot;컨테이너&quot;라는 표준화된 단위로 패키징하는 플랫폼</li>
</ul>
<h4 id="docker-컨테이너란">Docker 컨테이너란?</h4>
<ul>
<li>컨테이너는 애플리케이션과 그것을 실행하는데 필요한 모든 의존성(라이브러리, 설정 등)을 하나의 패키지로 묶은 것</li>
<li>마치 선박 컨테이너처럼 표준화된 방식으로 어디서든 실행이 가능</li>
</ul>
<h4 id="docker의-장점">Docker의 장점</h4>
<ul>
<li>환경 일관성: &quot;내 컴퓨터에서는 작동했는데...&quot; 문제 해결</li>
<li>격리성: 각 애플리케이션이 독립된 환경에서 실행</li>
<li>이식성: 개발/테스트/운영 환경 간 쉬운 이동</li>
<li>자원 효율성: VM보다 가벼운 구조</li>
</ul>
<h4 id="docker-사용-예시">Docker 사용 예시</h4>
<p><strong>Spring Boot 애플리케이션을 위한 Dockerfile 예시:</strong></p>
<pre><code class="language-dockerfile">FROM openjdk:17
COPY target/FITPLE-1.0.0.jar app.jar
EXPOSE 8081
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]</code></pre>
<h2 id="docker와-클라우드-서비스의-관계">Docker와 클라우드 서비스의 관계</h2>
<ul>
<li>Docker 자체는 IaaS나 PaaS로 직접 분류되지 않으며, 사용 방식에 따라 다르게 활용될 수 있음</li>
</ul>
<h3 id="iaas-방식">IaaS 방식</h3>
<ul>
<li>EC2에 Docker를 직접 설치하고 관리</li>
<li>더 많은 통제권과 책임</li>
<li>직접적인 Docker 설치, 관리, 운영 필요</li>
</ul>
<h3 id="paas-방식">PaaS 방식</h3>
<ul>
<li>AWS ECS, EKS와 같은 관리형 컨테이너 서비스 사용</li>
<li>AWS가 Docker 실행 환경을 관리</li>
<li>컨테이너 이미지만 제공하면 됨</li>
</ul>
<blockquote>
<p>IaaS: 빈 아파트를 받아서 직접 가구를 놓고 꾸미는 것
PaaS: 풀옵션 원룸처럼 다 갖춰진 환경을 받는 것
Docker: 가구나 살림살이를 표준화된 상자에 담아서 쉽게 옮길 수 있게 해주는 포장 기술</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring AOP]]></title>
            <link>https://velog.io/@dhlee47-l/Spring-AOP</link>
            <guid>https://velog.io/@dhlee47-l/Spring-AOP</guid>
            <pubDate>Tue, 11 Feb 2025 04:51:43 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/b66415e2-b315-41ca-afac-3ae26ae205fc/image.png" alt=""></p>
<p>서비스에 필요한 기능: 비즈니스 로직
시간을 재거나, 권한을 체크하거나, transaction을 거는 기능(=부가기능): 인프라 로직</p>
<h3 id="인프라-로직">인프라 로직</h3>
<ul>
<li>애플리케이션 전 영역에서 나타남</li>
<li>중복코드 -&gt; 유지보수 힘듬</li>
<li>비즈니스 로직과 섞여 있어 비즈니스 로직을 이해하기 힘들게 만듬</li>
<li>로깅, 트랜잭션, 성능 측정 등의 부가기능</li>
<li>Cross-cutting concern</li>
</ul>
<h3 id="aspect-oriented-programming">Aspect-Oriented Programming</h3>
<ul>
<li>OOP와 같은 하나의 패러다임으로, 각 언어마다 AOP 구현체를 필요로 함</li>
<li>Java: AspectJ</li>
</ul>
<h4 id="target">Target</h4>
<ul>
<li>어떤 대상에 부가 기능을 부여할 것인가<h4 id="advice">Advice</h4>
</li>
<li>어떠한 기능을 부가할 것인가<h4 id="join-point">Join point</h4>
</li>
<li>어디에 적용할 것인가: 메서드가 호출, 실행, 필드에 접근할 때, 필드에 접근할 때 등에 적용 / But <strong>Spring</strong>에서는 <strong>메서드가 실행될 때</strong> 만으로 한정<h4 id="point-cut">Point cut</h4>
</li>
<li>포인트컷(Pointcut)은 관심 조인 포인트를 결정하므로 어드바이스가 실행되는 시기를 제어할 수 있음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[250123 면접 질문 기록]]></title>
            <link>https://velog.io/@dhlee47-l/250123-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@dhlee47-l/250123-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Thu, 23 Jan 2025 01:20:49 GMT</pubDate>
            <description><![CDATA[<h3 id="질문-리스트">질문 리스트</h3>
<ol>
<li><p>자기소개</p>
</li>
<li><p>Spring을 쓰는 이유가 뭐냐</p>
</li>
<li><p>JPA가 나오게 된 이유가 뭐라고 생각하나?</p>
</li>
<li><p>MyBatis로 매핑할때랑 JPA 쓸 때랑 어떤 차이가 있었는지?</p>
</li>
<li><p>IoC, DI에 대한 심도 있는 질문. DI를 쓰는 이유를 말해봐라. Singleton 패턴에 대해서 아는지?</p>
</li>
<li><p>AOP, Filter 기능을 프로젝트에서 활용해본 적 있는지?</p>
</li>
<li><p>List, Map 등 Java Collection을 필요에 의해 사용해 본 경험이 있는가?</p>
</li>
<li><p>ArrayList, HashMap 말고 다른 Java Collection 사용해본 적은 없는지?</p>
</li>
<li><p>JVM 1.7, 1.8에서 22까지 나왔는데 이들이 바뀌면서 어떤 차이를 보여줬는지 알고 있나?</p>
</li>
<li><p>JVM의 메모리 영역 등 내부 작동 방식에 대해 알고 있나? 설명해봐라.</p>
</li>
<li><p>JPA에서 비영속적 객체를 영속적으로 바꾸는 건 언제 했고 왜 했는지?</p>
</li>
<li><p>국비 프로젝트를 진행하면 비전공자들과는 어떤 식으로 함께 했는지? 주로 가르쳐주는 방식이었는지?</p>
</li>
<li><p>프로젝트를 진행하다가 갈등이 생기면 어떻게 했는지?</p>
</li>
<li><p>회사에 궁금한 점은?</p>
</li>
<li><p>언제부터 출근할 수 있는지?</p>
</li>
<li><p>회사에 마지막으로 어필하고 싶은 점은?</p>
</li>
</ol>
<hr>
<p>전반적으로 면접이 아니라 수업을 듣고 온 것 같은 느낌. 뭔가 괜히 감사했음.
무례한 질문 하나도 없었음.
개발자 2분이서 질문하심. 
오전 9시 면접인데 오전 8시 반에 가서 대기할 줄 알았는데, 바로 시험 보고 면접 봄.
서초라 위치적으로 좋은 곳 같음.
사무실이 매우 cozy 하고 따뜻했음.</p>
<p>2차도 보고싶다</p>
<hr>
<p>느낀점: </p>
<ul>
<li>국비수업 때 이론 정리한 거 조금 더 읽고 가자</li>
<li>자소서 워딩을 조심하자</li>
<li>경험을 토대로 얘기하자</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JWT] Json Web Token]]></title>
            <link>https://velog.io/@dhlee47-l/JWT-Json-Web-Token</link>
            <guid>https://velog.io/@dhlee47-l/JWT-Json-Web-Token</guid>
            <pubDate>Tue, 14 Jan 2025 08:14:43 GMT</pubDate>
            <description><![CDATA[<p>Spring에서 Session 기반의 인증하는 법을 공부했을 때, session library가 cookie에 저장되어 전달된다. 그러나, 모바일 앱일 경우에는 그런 인증 방식 못 쓴다. 이럴 때 쓰는 다른 웹 authentication 방식으로 JWT가 있다.</p>
<h3 id="jwt">JWT</h3>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/018c78ac-a40c-4cb2-8f44-9128eb05b702/image.png" alt=""></p>
<ul>
<li>RFC 7519 표준으로 정의된 방식</li>
<li>JSON format을 Base 64로 encode해서 만든다</li>
<li>payload에 담겨있는 것 = Claim (사용자에 대한 property)</li>
<li>signature : 검증을 위해 필요 (header + payload 인코딩)</li>
</ul>
<h4 id="signature">signature:</h4>
<pre><code class="language-js">HMACSHA256 (
      base64UrlEncode(header) + &quot;.&quot; +
      base64UrlEncode(payload),
    your-256-bit-secret
) </code></pre>
<ul>
<li>secret: 서버만 가지고 있는 인증을 위한 비밀키</li>
</ul>
<h3 id="jwt-인증-절차">JWT 인증 절차</h3>
<ol>
<li>username, password <strong>인증시도</strong></li>
<li><strong>인증 확인</strong> - DB 연동</li>
<li>JWT <strong>토큰 (서버가) 발급</strong></li>
<li>JWT를 <strong>response header에 담아</strong> 보냄</li>
<li><strong>Client</strong>는 JWT를 <strong>저장</strong> (localStorage, cookie에)</li>
<li><strong>다음 요청</strong>부터 저장된 JWT를 <strong>request header</strong>의 Authorization 값에 보냄</li>
<li>요청에 담긴 <strong>JWT (서버가) 검증</strong></li>
<li>검증 성공 시, <strong>인증 처리</strong>(session 기반 x, but 여기서 <strong>session이 생겨 인증정보 저장</strong>됨), 권한 부여</li>
<li>인증 후, <strong>요청처리</strong> (buisness logic), <strong>response</strong> (response 후 session 삭제)</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] React는 왜 Hook를 쓰는가?]]></title>
            <link>https://velog.io/@dhlee47-l/React-React%EB%8A%94-%EC%99%9C-Hook%EB%A5%BC-%EC%93%B0%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@dhlee47-l/React-React%EB%8A%94-%EC%99%9C-Hook%EB%A5%BC-%EC%93%B0%EB%8A%94%EA%B0%80</guid>
            <pubDate>Thu, 09 Jan 2025 07:15:08 GMT</pubDate>
            <description><![CDATA[<p>React에서 일반 변수 대신 Hook(useState)을 사용하는 이유가 뭘지, 처음 들었을 땐 당황스러웠다. 쓰다보니 당연했던 점을 잊을까 적어보려한다.</p>
<h3 id="리렌더링과-상태-유지">리렌더링과 상태 유지</h3>
<ul>
<li>일반 변수를 사용하면, 컴포넌트가 리렌더링될 때마다 변수가 초기화 됨</li>
</ul>
<p>예를 들어, 위 코드에서 let number = 1로 했다면:</p>
<pre><code class="language-javascript">const App061 = () =&gt; {
  let number = 1;  // 컴포넌트가 리렌더링될 때마다 다시 1로 초기화됨

  const add = () =&gt; {
    number++;  // 값은 증가하지만 리렌더링되면 다시 1로 돌아감
    console.log(number);  // 콘솔에는 증가된 값이 보이지만
    // UI는 업데이트되지 않음
  };

  return &lt;h1&gt;숫자: {number}&lt;/h1&gt;;  // 항상 1만 표시됨
};</code></pre>
<h3 id="리렌더링-트리거">리렌더링 트리거</h3>
<ul>
<li>useState의 setState 함수(여기서는 setNumber)는 React에게 &quot;상태가 변경되었으니 화면을 다시 그려야 해&quot;라고 알려줌</li>
<li>일반 변수는 값이 변경되어도 React는 이를 모르기 때문에 화면이 업데이트되지 않음</li>
</ul>
<h3 id="비동기적-업데이트-처리">비동기적 업데이트 처리</h3>
<ul>
<li>React는 성능 최적화를 위해 여러 상태 업데이트를 배치(batch)로 처리</li>
<li>useState는 이러한 비동기적 업데이트를 안전하게 처리할 수 있음</li>
</ul>
<pre><code class="language-javascript">
// 이전 상태를 기반으로 안전하게 업데이트
setNumber(prev =&gt; prev + 1);</code></pre>
<p>그 차이점을 예시로 보자면,</p>
<pre><code class="language-javascript">
// 일반 변수를 사용한 경우
let normalVar = 0;
const handleClick = () =&gt; {
  normalVar += 1;  // 값은 변경되지만
  console.log(normalVar);  // 콘솔에만 보이고
  // 화면은 업데이트되지 않음
};

// useState를 사용한 경우
const [stateVar, setStateVar] = useState(0);
const handleClick = () =&gt; {
  setStateVar(prev =&gt; prev + 1);  // 값이 변경되고
  // React가 컴포넌트를 다시 렌더링하여
  // 화면도 업데이트됨
};</code></pre>
<p>따라서 Hook(useState)을 사용하는 것은:</p>
<pre><code>컴포넌트의 상태를 안정적으로 유지하고 UI를 자동으로 업데이트하며, React의 렌더링 사이클과 조화롭게 동작하기 위해서!</code></pre><p>그러니까 react가 처리를 하면 화면 업데이트가 안되니까 변수말고 hook을 쓴다는거다. React의 핵심 특징 중 하나는 &quot;상태가 변경되면 화면을 자동으로 다시 그린다&quot;는 점인데, 근데 React가 일반 변수의 변경은 감지할 수 없으므로 화면 업데이트가 필요한 값들은 항상 useState같은 Hook으로 관리하는 것이 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] React의 value와 HTML의 value의 차이]]></title>
            <link>https://velog.io/@dhlee47-l/React-React%EC%9D%98-value%EC%99%80-HTML%EC%9D%98-value%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@dhlee47-l/React-React%EC%9D%98-value%EC%99%80-HTML%EC%9D%98-value%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Thu, 09 Jan 2025 07:03:24 GMT</pubDate>
            <description><![CDATA[<p>프런트 공부를 처음 시작하면 배우는 checkbox, radio 버튼, select를 react와 배우던 와중, 굉장히 이질적인 문장이 귀에 들어왔다. 바로</p>
<blockquote>
<p><strong>React</strong>의 <strong>value</strong>는 <strong>HTML</strong> 에서 <strong>value</strong> 와 <em><strong>다르다</strong></em></p>
</blockquote>
<p>무슨 뜻일까?</p>
<p>코드로 먼저 확인하자.</p>
<pre><code>import React, { useState } from &#39;react&#39;;


const App016 = () =&gt; {

  const [fruit, setFruit] = useState(&#39;grape&#39;);

  const [key, setKey] = useState(1);

  const resetSelect = () =&gt; setKey(prev =&gt; prev + 1);

  const items = [
    { id: 1, name: &#39;Apple&#39; },
    { id: 2, name: &#39;Banana&#39; },
    { id: 3, name: &#39;Cherry&#39; },
  ];

    return (
      &lt;&gt;
        &lt;h2&gt;Controlled Component (value=)&lt;/h2&gt;
        &lt;select value={fruit} onChange={(e) =&gt; setFruit(e.target.value)}&gt;
          &lt;option value=&quot;apple&quot;&gt;사과&lt;/option&gt;
          &lt;option value=&quot;banana&quot;&gt;바나나&lt;/option&gt;
          &lt;option value=&quot;orange&quot;&gt;오렌지&lt;/option&gt;
          &lt;option value=&quot;grape&quot;&gt;포도&lt;/option&gt;
        &lt;/select&gt;
        &lt;p&gt;Selected: {fruit}&lt;/p&gt;
      &lt;/&gt;
    );
};

export default App016;</code></pre><p>e.target.value를 보자. </p>
<h2 id="html-value-vs-react-value">HTML Value vs. React Value</h2>
<h3 id="동작-방식의-차이">동작 방식의 차이</h3>
<ul>
<li>HTML value: 초기값만 설정하고 이후 사용자의 입력에 따라 자유롭게 변경</li>
<li>React value: 컴포넌트의 state와 연결되어 있으며, state가 변경되지 않으면 값이 변경되지 않음</li>
</ul>
<h3 id="제어-방식의-차이">제어 방식의 차이</h3>
<pre><code class="language-jsx">// HTML
&lt;select value=&quot;apple&quot;&gt;  // 초기값만 설정하고 이후 자유롭게 변경 가능

// React
&lt;select value={fruit} onChange={(e) =&gt; setFruit(e.target.value)}&gt;  
// value와 onChange가 쌍으로 동작</code></pre>
<ul>
<li>React에서는 value와 onChange 핸들러가 세트로 동작하여 &quot;제어 컴포넌트(Controlled Component)&quot;를 형성한다.</li>
</ul>
<h3 id="상태-관리">상태 관리</h3>
<ul>
<li>HTML: DOM 요소 자체가 상태를 관리</li>
<li>React: 컴포넌트의 state가 상태를 관리하고, 이 state가 변경될 때마다 컴포넌트가 다시 렌더링 됨</li>
</ul>
<pre><code class="language-jsx">const [fruit, setFruit] = useState(&#39;grape&#39;);  // state 관리
...
&lt;select value={fruit} onChange={(e) =&gt; setFruit(e.target.value)}&gt;</code></pre>
<p>여기서 <strong>value는 단순한 속성이 아니라 React의 단방향 데이터 흐름을 구현하는 중요한 요소</strong></p>
<p>사용자가 select를 변경하면:</p>
<pre><code>- onChange 이벤트가 발생
- setFruit로 state 업데이트
- 컴포넌트 리렌더링
- 새로운 value 값이 select에 반영</code></pre><p>React는 한 페이지 내에서 component가 변화될 때 </p>
<h3 id="state의-기본-개념">State의 기본 개념</h3>
<pre><code class="language-jsx">const [count, setCount] = useState(0);</code></pre>
<pre><code>count: 현재 상태값
setCount: 상태를 업데이트하는 함수
0: 초기값</code></pre><h3 id="state의-특징">State의 특징</h3>
<ul>
<li>컴포넌트에 종속적: 각 컴포넌트는 자신만의 독립적인 state를 가짐</li>
<li>비동기적 업데이트: setState는 비동기적으로 처리됨</li>
<li>불변성 유지: 직접 수정하지 않고 항상 setState 함수를 통해 업데이트해야 함</li>
</ul>
<h4 id="잘못된-state-사용-예시">잘못된 State 사용 예시</h4>
<pre><code class="language-jsx">// ❌ 잘못된 방법
count = count + 1;  // state 직접 수정

// ✅ 올바른 방법
setCount(count + 1);  // setState 함수 사용</code></pre>
<h4 id="이전-상태를-기반으로-업데이트">이전 상태를 기반으로 업데이트</h4>
<pre><code class="language-jsx">// 이전 상태값을 사용할 때의 올바른 방법
setCount(prevCount =&gt; prevCount + 1);</code></pre>
<h4 id="객체-형태의-state-업데이트">객체 형태의 State 업데이트</h4>
<pre><code class="language-jsx">const [user, setUser] = useState({
  name: &#39;John&#39;,
  age: 25
});

// 객체 업데이트 시 spread 연산자 사용
setUser({
  ...user,
  age: 26
});</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[헬스 MBTI API 만들기]]></title>
            <link>https://velog.io/@dhlee47-l/MBTI-API-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dhlee47-l/MBTI-API-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 07 Jan 2025 03:33:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ol>
<li>앤드포인트 설계</li>
<li>비즈니스 로직 구현</li>
<li>응답구조 결정</li>
<li>데이터베이스 연동</li>
<li>만들어야하는 테이블</li>
</ol>
</blockquote>
<h2 id="1-앤드포인트-설계">1. 앤드포인트 설계</h2>
<h3 id="트레이너-회원-mbti-매칭-시스템의-api-엔드포인트를-설계">트레이너-회원 MBTI 매칭 시스템의 API 엔드포인트를 설계</h3>
<p>기본 URL: <strong>/api/v1</strong></p>
<h4 id="mbti-관련-엔드포인트">MBTI 관련 엔드포인트</h4>
<pre><code>GET /mbti-traits             - 모든 MBTI 유형의 특성 조회
GET /mbti-traits/{type}      - 특정 MBTI 유형의 특성 조회 (예: /mbti-traits/ENFJ)</code></pre><h4 id="트레이너-관련-엔드포인트">트레이너 관련 엔드포인트</h4>
<pre><code>GET    /trainers             - 트레이너 목록 조회
GET    /trainers/{id}        - 특정 트레이너 정보 조회
POST   /trainers             - 새로운 트레이너 등록
PUT    /trainers/{id}        - 트레이너 정보 수정
DELETE /trainers/{id}        - 트레이너 정보 삭제</code></pre><h4 id="매칭-관련-엔드포인트">매칭 관련 엔드포인트</h4>
<pre><code>POST   /matching             - MBTI 기반 트레이너 매칭 요청
GET    /matching/history     - 매칭 히스토리 조회</code></pre><h3 id="각-엔드포인트의-쿼리-파라미터와-요청응답-예시를-설계">각 엔드포인트의 쿼리 파라미터와 요청/응답 예시를 설계</h3>
<h4 id="mbti-특성-조회">MBTI 특성 조회</h4>
<pre><code>GET /api/v1/mbti-traits
- 쿼리 파라미터:
  - category (선택): training, communication, motivation
  예시: /mbti-traits?category=training

GET /api/v1/mbti-traits/{type}
- 경로 변수: type (MBTI 유형)
- 쿼리 파라미터 없음</code></pre><h4 id="트레이너-목록-조회">트레이너 목록 조회</h4>
<pre><code>GET /api/v1/trainers
- 쿼리 파라미터:
  - mbti: MBTI 유형으로 필터링
  - specialty: 전문분야 (weight, diet, rehab 등)
  - page: 페이지 번호 (기본값 0)
  - size: 페이지 크기 (기본값 10)
  - sort: 정렬 기준 (experience, rating)
  예시: /trainers?mbti=ENFJ&amp;specialty=weight&amp;page=0&amp;size=10</code></pre><h4 id="매칭-요청">매칭 요청</h4>
<pre><code>POST /api/v1/matching
- 요청 바디 파라미터:
  - userMbti: 사용자 MBTI
  - preferredStyle: 선호하는 트레이닝 스타일
  - goals: 운동 목표
  - location: 선호 지역
  - priceRange: 가격대</code></pre><h3 id="각-엔드포인트의-요청응답-구조-자세한-설계">각 엔드포인트의 요청/응답 구조 자세한 설계</h3>
<h4 id="mbti-특성-전체-조회">MBTI 특성 전체 조회</h4>
<p>GET <strong>/api/v1/mbti-traits</strong></p>
<pre><code class="language-js">응답 예시:
{
    &quot;status&quot;: &quot;success&quot;,
    &quot;data&quot;: [
        {
            &quot;type&quot;: &quot;ENFJ&quot;,
            &quot;training&quot;: {
                &quot;preferredStyle&quot;: [&quot;group&quot;, &quot;motivation&quot;],
                &quot;learningMethod&quot;: &quot;demonstration&quot;,
                &quot;feedbackStyle&quot;: &quot;positive-reinforcement&quot;
            },
            &quot;communication&quot;: {
                &quot;style&quot;: &quot;encouraging&quot;,
                &quot;preferredFormat&quot;: &quot;detailed-explanation&quot;
            }
        },
        {
            &quot;type&quot;: &quot;INTJ&quot;,
            ...
        }
    ]
}</code></pre>
<h4 id="특정-mbti-특성-조회">특정 MBTI 특성 조회</h4>
<p>GET <strong>/api/v1/mbti-traits/ENFJ</strong></p>
<pre><code class="language-js">응답 예시:
{
    &quot;status&quot;: &quot;success&quot;,
    &quot;data&quot;: {
        &quot;type&quot;: &quot;ENFJ&quot;,
        &quot;training&quot;: { ... },
        &quot;communication&quot;: { ... }
    }
}</code></pre>
<h4 id="트레이너-목록-조회-1">트레이너 목록 조회</h4>
<p>GET <strong>/api/v1/trainers?mbti=ENFJ&amp;specialty=weight&amp;page=0&amp;size=10</strong></p>
<pre><code class="language-js">응답 예시:
{
    &quot;status&quot;: &quot;success&quot;,
    &quot;data&quot;: {
        &quot;trainers&quot;: [
            {
                &quot;id&quot;: 1,
                &quot;name&quot;: &quot;김트레이너&quot;,
                &quot;mbti&quot;: &quot;ENFJ&quot;,
                &quot;specialties&quot;: [&quot;weight&quot;, &quot;diet&quot;],
                &quot;experience&quot;: 5,
                &quot;rating&quot;: 4.8,
                &quot;priceRange&quot;: {
                    &quot;min&quot;: 50000,
                    &quot;max&quot;: 100000
                },
                &quot;location&quot;: &quot;강남구&quot;
            }
        ],
        &quot;pagination&quot;: {
            &quot;currentPage&quot;: 0,
            &quot;totalPages&quot;: 5,
            &quot;totalElements&quot;: 48
        }
    }
}</code></pre>
<h3 id="매칭-요청-1">매칭 요청</h3>
<p>POST <strong>/api/v1/matching</strong></p>
<pre><code class="language-js">요청 바디:
{
    &quot;userMbti&quot;: &quot;INTJ&quot;,
    &quot;preferences&quot;: {
        &quot;trainingStyle&quot;: [&quot;individual&quot;, &quot;data-driven&quot;],
        &quot;goals&quot;: [&quot;muscle-gain&quot;, &quot;strength&quot;],
        &quot;location&quot;: &quot;강남구&quot;,
        &quot;priceRange&quot;: {
            &quot;min&quot;: 50000,
            &quot;max&quot;: 150000
        }
    }
}</code></pre>
<pre><code class="language-js">응답 예시:
{
    &quot;status&quot;: &quot;success&quot;,
    &quot;data&quot;: {
        &quot;matches&quot;: [
            {
                &quot;trainerId&quot;: 1,
                &quot;matchScore&quot;: 95,
                &quot;matchReason&quot;: [
                    &quot;MBTI 궁합도: 높음&quot;,
                    &quot;선호 트레이닝 스타일 일치&quot;,
                    &quot;위치 적합&quot;
                ],
                &quot;trainerInfo&quot;: {
                    &quot;name&quot;: &quot;김트레이너&quot;,
                    &quot;specialties&quot;: [&quot;weight&quot;, &quot;diet&quot;],
                    ...
                }
            }
        ]
    }
}</code></pre>
<pre><code class="language-js">에러 응답 구조

{
    &quot;status&quot;: &quot;error&quot;,
    &quot;error&quot;: {
        &quot;code&quot;: &quot;INVALID_MBTI&quot;,
        &quot;message&quot;: &quot;Invalid MBTI type provided&quot;,
        &quot;details&quot;: &quot;MBTI type must be one of: ENFJ, INFJ...&quot;
    }
}</code></pre>
<h2 id="2-비즈니스-로직-구현">2. 비즈니스 로직 구현</h2>
<h3 id="mbti-매칭-점수-계산-로직">MBTI 매칭 점수 계산 로직</h3>
<pre><code class="language-java">@Service
public class MatchingService {
    public int calculateMatchScore(String userMbti, Trainer trainer) {
        int score = 0;

        // 1. MBTI 성향 매칭 (40점)
        score += calculateMbtiCompatibility(userMbti, trainer.getMbti());

        // 2. 트레이닝 스타일 매칭 (30점)
        score += calculateTrainingStyleMatch(userMbti, trainer.getTrainingStyle());

        // 3. 경험 점수 (20점)
        score += Math.min(trainer.getExperience() * 4, 20);

        // 4. 리뷰 평점 반영 (10점)
        score += (trainer.getRating() * 2);

        return score;
    }
}</code></pre>
<h2 id="3-응답구조-결정">3. 응답구조 결정</h2>
<p><strong>응답구조는 크게 성공과 실패 두 가지 경우로 나누어 설계</strong></p>
<h3 id="성공-응답-구조">성공 응답 구조</h3>
<pre><code class="language-java">@Getter
class MatchingResponse {
    private String status = &quot;success&quot;;
    private MatchingData data;
}

@Getter
class MatchingData {
    private List&lt;MatchResult&gt; matches;  // 매칭 결과 리스트
    private int totalScore;             // 전체 매칭 점수
}

@Getter
class MatchResult {
    private Long trainerId;             // 트레이너 ID
    private String trainerName;         // 트레이너 이름
    private String mbti;                // 트레이너 MBTI
    private int matchScore;             // 매칭 점수
    private Map&lt;String, Integer&gt; scoreDetails; // 점수 상세 내역
    private TrainerInfo trainerInfo;    // 트레이너 상세 정보
}</code></pre>
<h3 id="실패-응답-구조">실패 응답 구조</h3>
<pre><code class="language-java">@Getter
class ErrorResponse {
    private String status = &quot;error&quot;;
    private ErrorData error;
}

@Getter
class ErrorData {
    private String code;        // 에러 코드
    private String message;     // 에러 메시지
    private String details;     // 상세 설명
}</code></pre>
<p>이렇게 응답구조를 일관되게 가져가면 프론트엔드에서 데이터를 처리하기가 훨씬 수월하다.</p>
<p><strong>Lombok을 사용하면?</strong></p>
<pre><code class="language-java">
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
class MatchingResponse {
    private String status = &quot;success&quot;;
    private MatchingData data;
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
class MatchingData {
    private List&lt;MatchResult&gt; matches;
    private int totalScore;
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
class MatchResult {
    private Long trainerId;
    private String trainerName;
    private String mbti;
    private int matchScore;
    private Map&lt;String, Integer&gt; scoreDetails;
    private TrainerInfo trainerInfo;
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
class ErrorResponse {
    private String status = &quot;error&quot;;
    private ErrorData error;
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
class ErrorData {
    private String code;
    private String message;
    private String details;
}
</code></pre>
<h2 id="4-데이터베이스-연동을-위한-spring-boot--mysql-설정">4. 데이터베이스 연동을 위한 Spring Boot + MySQL 설정</h2>
<h3 id="applicationyml-설정">application.yml 설정</h3>
<pre><code class="language-yaml">spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mbti_matching?useSSL=false&amp;serverTimezone=UTC
    username: root
    password: yourpassword
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true</code></pre>
<h3 id="entity-클래스-생성">Entity 클래스 생성</h3>
<pre><code class="language-java">@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = &quot;trainers&quot;)
public class Trainer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String mbti;

    @ElementCollection
    private List&lt;String&gt; specialties;

    private int experience;
    private double rating;

    @Embedded
    private Address address;
}

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = &quot;matching_results&quot;)
public class MatchingResult {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Trainer trainer;

    private String userMbti;
    private int matchScore;
    private LocalDateTime matchedAt;
}</code></pre>
<h3 id="repository-인터페이스">Repository 인터페이스</h3>
<pre><code class="language-java">@Repository
public interface TrainerRepository extends JpaRepository&lt;Trainer, Long&gt; {
    List&lt;Trainer&gt; findByMbti(String mbti);
    List&lt;Trainer&gt; findBySpecialtiesContaining(String specialty);
}

@Repository
public interface MatchingResultRepository extends JpaRepository&lt;MatchingResult, Long&gt; {
    List&lt;MatchingResult&gt; findByUserMbti(String userMbti);
}</code></pre>
<h2 id="5-데이터베이스-테이블-설계">5. 데이터베이스 테이블 설계</h2>
<h3 id="trainers-트레이너-정보">trainers (트레이너 정보)</h3>
<pre><code class="language-sql">
CREATE TABLE trainers (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    mbti VARCHAR(4) NOT NULL,
    experience INT NOT NULL,
    rating DECIMAL(2,1),
    introduction TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);</code></pre>
<h3 id="trainer_specialties-트레이너-전문분야">trainer_specialties (트레이너 전문분야)</h3>
<pre><code class="language-sql">CREATE TABLE trainer_specialties (
    trainer_id BIGINT,
    specialty VARCHAR(50),
    PRIMARY KEY (trainer_id, specialty),
    FOREIGN KEY (trainer_id) REFERENCES trainers(id)
);</code></pre>
<h3 id="matching_results-매칭-결과">matching_results (매칭 결과)</h3>
<pre><code class="language-sql">CREATE TABLE matching_results (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    trainer_id BIGINT,
    user_mbti VARCHAR(4) NOT NULL,
    match_score INT NOT NULL,
    matched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (trainer_id) REFERENCES trainers(id)
);</code></pre>
<h3 id="mbti_traits-mbti-특성">mbti_traits (MBTI 특성)</h3>
<pre><code class="language-sql">CREATE TABLE mbti_traits (
    mbti_type VARCHAR(4) PRIMARY KEY,
    preferred_training_style JSON,
    communication_style VARCHAR(100),
    learning_preference VARCHAR(100)
);
trainer_reviews (트레이너 리뷰)</code></pre>
<pre><code>sql
CREATE TABLE trainer_reviews (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    trainer_id BIGINT,
    rating INT NOT NULL,
    comment TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (trainer_id) REFERENCES trainers(id)
);</code></pre><hr>
<p>정리하자면,</p>
<h2 id="spring-boot로-api를-만들-때-전체적인-구조">Spring Boot로 API를 만들 때 전체적인 구조</h2>
<blockquote>
<p><strong>Controller:</strong> API 엔드포인트를 정의하고 요청을 받는 곳
<strong>DTO:</strong> 요청/응답의 데이터 구조를 정의하는 클래스
<strong>Service:</strong> 비즈니스 로직이 실제로 구현되는 곳
<strong>Entity:</strong> 데이터베이스 테이블과 매핑되는 클래스
<strong>Repository:</strong> 데이터베이스 작업을 처리하는 인터페이스</p>
</blockquote>
<pre><code class="language-java">// 1. Controller + DTO
@RestController
@RequestMapping(&quot;/api/v1&quot;)
public class MatchingController {
    @PostMapping(&quot;/matching&quot;)
    public ResponseEntity&lt;MatchingResponseDTO&gt; matchTrainer(@RequestBody MatchingRequestDTO request) {
        return ResponseEntity.ok(matchingService.findMatch(request));
    }
}

// 2. Service (비즈니스 로직)
@Service
public class MatchingService {
    public int calculateMatchScore(String userMbti, Trainer trainer) {
        int score = 0;
        score += calculateMbtiCompatibility(userMbti, trainer.getMbti());
        ...
    }
}

// 3. Entity
@Entity
@Table(name = &quot;trainers&quot;)
public class Trainer {
    @Id
    private Long id;
    private String mbti;
    ...
}

// 4. Repository
@Repository
public interface TrainerRepository extends JpaRepository&lt;Trainer, Long&gt; {
    List&lt;Trainer&gt; findByMbti(String mbti);
}</code></pre>
<p>우리가 만드는 API:</p>
<pre><code class="language-java">@RestController
public class TrainerController {
    @Autowired
    private TrainerRepository trainerRepository; // 데이터베이스 연결

    @GetMapping(&quot;/api/v1/trainers&quot;)
    public List&lt;Trainer&gt; getTrainers() {
        // MySQL 데이터베이스에서 트레이너 정보를 가져옴
        return trainerRepository.findAll();
    }
}</code></pre>
<ul>
<li>외부 API를 사용할 때는 그 회사의 데이터베이스에서 데이터를 가져오는 것이고
우리가 API를 만들 때는 우리가 직접 MySQL같은 데이터베이스를 만들고, 거기에 데이터를 넣어서 사용하는 것.</li>
<li>그래서 아까 테이블 설계에서 만든 데이터베이스 테이블에 실제 트레이너 정보, MBTI 정보 등을 넣어야 API가 제대로 동작한다.</li>
</ul>
<h2 id="sql-insert-문을-사용해-데이터-넣기">SQL INSERT 문을 사용해 데이터 넣기</h2>
<h3 id="mbti-특성-데이터">MBTI 특성 데이터</h3>
<pre><code class="language-sql">
-- 기존 데이터 삭제 및 AUTO_INCREMENT 초기화
DELETE FROM mbti_traits;
ALTER TABLE mbti_traits AUTO_INCREMENT = 1;

-- mbti_traits 데이터 삽입
INSERT INTO mbti_traits (mbti_type, preferred_training_style, communication_style, learning_preference)
VALUES 
(&#39;ISTJ&#39;, &#39;{&quot;styles&quot;: [&quot;structured&quot;, &quot;consistent&quot;, &quot;traditional&quot;], &quot;focus&quot;: [&quot;detail-oriented&quot;, &quot;routine-based&quot;]}&#39;, &#39;명확하고 구체적인 지시&#39;, &#39;단계별 체계적 학습&#39;),
(&#39;ISFJ&#39;, &#39;{&quot;styles&quot;: [&quot;supportive&quot;, &quot;organized&quot;, &quot;patient&quot;], &quot;focus&quot;: [&quot;personal-care&quot;, &quot;steady-progress&quot;]}&#39;, &#39;따뜻하고 개인적인 관심&#39;, &#39;실제 경험 기반 학습&#39;),
(&#39;INFJ&#39;, &#39;{&quot;styles&quot;: [&quot;holistic&quot;, &quot;personalized&quot;, &quot;growth-focused&quot;], &quot;focus&quot;: [&quot;long-term-vision&quot;, &quot;meaningful-goals&quot;]}&#39;, &#39;통찰력 있는 조언&#39;, &#39;개념적 이해와 실천&#39;),
(&#39;INTJ&#39;, &#39;{&quot;styles&quot;: [&quot;analytical&quot;, &quot;efficient&quot;, &quot;goal-oriented&quot;], &quot;focus&quot;: [&quot;strategy&quot;, &quot;optimization&quot;]}&#39;, &#39;논리적이고 직접적인 소통&#39;, &#39;이론과 시스템 이해&#39;),
(&#39;ISTP&#39;, &#39;{&quot;styles&quot;: [&quot;practical&quot;, &quot;adaptable&quot;, &quot;hands-on&quot;], &quot;focus&quot;: [&quot;technique&quot;, &quot;immediate-results&quot;]}&#39;, &#39;간단명료한 설명&#39;, &#39;실전 중심 학습&#39;),
(&#39;ISFP&#39;, &#39;{&quot;styles&quot;: [&quot;gentle&quot;, &quot;flexible&quot;, &quot;individual&quot;], &quot;focus&quot;: [&quot;comfort&quot;, &quot;enjoyment&quot;]}&#39;, &#39;부드럽고 개인적인 방식&#39;, &#39;감각적 체험 학습&#39;),
(&#39;INFP&#39;, &#39;{&quot;styles&quot;: [&quot;authentic&quot;, &quot;value-driven&quot;, &quot;creative&quot;], &quot;focus&quot;: [&quot;personal-meaning&quot;, &quot;inner-motivation&quot;]}&#39;, &#39;공감적이고 영감을 주는 소통&#39;, &#39;자기주도적 학습&#39;),
(&#39;INTP&#39;, &#39;{&quot;styles&quot;: [&quot;logical&quot;, &quot;innovative&quot;, &quot;knowledge-based&quot;], &quot;focus&quot;: [&quot;understanding&quot;, &quot;mastery&quot;]}&#39;, &#39;개념적이고 논리적인 설명&#39;, &#39;원리 탐구 중심&#39;),
(&#39;ESTP&#39;, &#39;{&quot;styles&quot;: [&quot;dynamic&quot;, &quot;energetic&quot;, &quot;action-oriented&quot;], &quot;focus&quot;: [&quot;challenge&quot;, &quot;variety&quot;]}&#39;, &#39;즉각적이고 활동적인 소통&#39;, &#39;행동 중심 학습&#39;),
(&#39;ESFP&#39;, &#39;{&quot;styles&quot;: [&quot;fun&quot;, &quot;social&quot;, &quot;encouraging&quot;], &quot;focus&quot;: [&quot;enjoyment&quot;, &quot;social-interaction&quot;]}&#39;, &#39;열정적이고 친근한 소통&#39;, &#39;그룹 활동 학습&#39;),
(&#39;ENFP&#39;, &#39;{&quot;styles&quot;: [&quot;enthusiastic&quot;, &quot;creative&quot;, &quot;motivating&quot;], &quot;focus&quot;: [&quot;inspiration&quot;, &quot;possibilities&quot;]}&#39;, &#39;열정적이고 영감을 주는 소통&#39;, &#39;창의적 접근 학습&#39;),
(&#39;ENTP&#39;, &#39;{&quot;styles&quot;: [&quot;challenging&quot;, &quot;innovative&quot;, &quot;strategic&quot;], &quot;focus&quot;: [&quot;improvement&quot;, &quot;experimentation&quot;]}&#39;, &#39;도전적이고 지적인 토론&#39;, &#39;실험적 학습&#39;),
(&#39;ESTJ&#39;, &#39;{&quot;styles&quot;: [&quot;organized&quot;, &quot;result-driven&quot;, &quot;directive&quot;], &quot;focus&quot;: [&quot;efficiency&quot;, &quot;achievement&quot;]}&#39;, &#39;명확한 지시와 피드백&#39;, &#39;구조화된 목표 중심&#39;),
(&#39;ESFJ&#39;, &#39;{&quot;styles&quot;: [&quot;supportive&quot;, &quot;collaborative&quot;, &quot;structured&quot;], &quot;focus&quot;: [&quot;harmony&quot;, &quot;encouragement&quot;]}&#39;, &#39;친근하고 상세한 안내&#39;, &#39;협력적 학습&#39;),
(&#39;ENFJ&#39;, &#39;{&quot;styles&quot;: [&quot;inspiring&quot;, &quot;people-focused&quot;, &quot;growth-oriented&quot;], &quot;focus&quot;: [&quot;development&quot;, &quot;potential&quot;]}&#39;, &#39;동기부여와 격려&#39;, &#39;관계 중심 학습&#39;),
(&#39;ENTJ&#39;, &#39;{&quot;styles&quot;: [&quot;strategic&quot;, &quot;challenging&quot;, &quot;leadership&quot;], &quot;focus&quot;: [&quot;achievement&quot;, &quot;excellence&quot;]}&#39;, &#39;전략적이고 결과 중심적 소통&#39;, &#39;목표 지향적 학습&#39;);</code></pre>
<h3 id="트레이너-데이터-30명">트레이너 데이터 (30명)</h3>
<pre><code class="language-sql">-- 기존 데이터 삭제 및 AUTO_INCREMENT 초기화
DELETE FROM trainers;
ALTER TABLE trainers AUTO_INCREMENT = 1;

-- trainers 데이터 삽입
INSERT INTO trainers (name, mbti, experience, rating, introduction, created_at) 
VALUES 
(&#39;김지훈&#39;, &#39;ENFJ&#39;, 5, 4.8, &#39;따뜻한 동기부여로 목표 달성을 돕는 트레이너입니다.&#39;, NOW()),
(&#39;이서연&#39;, &#39;INTJ&#39;, 7, 4.9, &#39;과학적 접근으로 최적의 결과를 만듭니다.&#39;, NOW()),
(&#39;박민준&#39;, &#39;ISTP&#39;, 3, 4.7, &#39;효율적인 동작과 자세 교정에 강점이 있습니다.&#39;, NOW()),
(&#39;정다은&#39;, &#39;ENFP&#39;, 4, 4.6, &#39;즐거운 운동으로 건강한 습관을 만들어요!&#39;, NOW()),
(&#39;최준호&#39;, &#39;ISTJ&#39;, 8, 4.9, &#39;체계적인 프로그램으로 확실한 변화를 약속합니다.&#39;, NOW()),
(&#39;한미래&#39;, &#39;ESFJ&#39;, 2, 4.5, &#39;초보자도 편하게 시작할 수 있는 트레이닝을 제공합니다.&#39;, NOW()),
(&#39;송태현&#39;, &#39;ENTJ&#39;, 6, 4.8, &#39;목표 달성을 위한 명확한 방향을 제시합니다.&#39;, NOW()),
(&#39;임수진&#39;, &#39;ISFP&#39;, 4, 4.7, &#39;개인의 특성을 고려한 맞춤형 트레이닝을 진행합니다.&#39;, NOW()),
(&#39;강동원&#39;, &#39;ESTP&#39;, 5, 4.6, &#39;다이나믹한 트레이닝으로 재미있게 운동해요!&#39;, NOW()),
(&#39;윤서아&#39;, &#39;INFJ&#39;, 3, 4.8, &#39;건강한 마인드와 함께하는 전인적 트레이닝&#39;, NOW()),
(&#39;조현우&#39;, &#39;ESTJ&#39;, 9, 4.9, &#39;목표 달성을 위한 체계적인 프로그램을 제공합니다.&#39;, NOW()),
(&#39;백지영&#39;, &#39;INTP&#39;, 4, 4.7, &#39;과학적 원리 기반의 효과적인 트레이닝&#39;, NOW()),
(&#39;신준영&#39;, &#39;ESFP&#39;, 3, 4.5, &#39;즐거운 분위기로 운동을 재미있게!&#39;, NOW()),
(&#39;오민지&#39;, &#39;ISFJ&#39;, 6, 4.8, &#39;꾸준한 관리와 케어로 함께 성장해요&#39;, NOW()),
(&#39;권현석&#39;, &#39;ENTP&#39;, 5, 4.7, &#39;창의적인 운동 방법으로 지루하지 않게!&#39;, NOW()),
(&#39;유하은&#39;, &#39;INFP&#39;, 2, 4.6, &#39;개인의 페이스를 존중하는 맞춤 트레이닝&#39;, NOW()),
(&#39;장민재&#39;, &#39;ENFJ&#39;, 7, 4.9, &#39;전문적인 지도와 따뜻한 응원을 함께 제공합니다.&#39;, NOW()),
(&#39;황세아&#39;, &#39;ISTJ&#39;, 4, 4.7, &#39;기초부터 차근차근 탄탄하게 지도합니다.&#39;, NOW()),
(&#39;문지원&#39;, &#39;ESTP&#39;, 5, 4.8, &#39;활기찬 트레이닝으로 운동이 즐거워집니다.&#39;, NOW()),
(&#39;양현직&#39;, &#39;INTJ&#39;, 8, 4.9, &#39;데이터 기반의 과학적인 트레이닝을 제공합니다.&#39;, NOW()),
(&#39;구자민&#39;, &#39;ESFJ&#39;, 3, 4.6, &#39;친근한 방식으로 편안한 트레이닝을 진행합니다.&#39;, NOW()),
(&#39;배수현&#39;, &#39;ENFP&#39;, 4, 4.7, &#39;긍정적인 에너지로 즐거운 운동을 만듭니다.&#39;, NOW()),
(&#39;홍준표&#39;, &#39;ISTP&#39;, 6, 4.8, &#39;정확한 자세와 효율적인 동작을 중점으로 지도합니다.&#39;, NOW()),
(&#39;서지안&#39;, &#39;ENTJ&#39;, 5, 4.7, &#39;목표 달성을 위한 전략적인 트레이닝을 제공합니다.&#39;, NOW()),
(&#39;남궁원&#39;, &#39;ISFP&#39;, 3, 4.6, &#39;개인의 특성을 고려한 맞춤형 프로그램을 진행합니다.&#39;, NOW()),
(&#39;도하윤&#39;, &#39;INFJ&#39;, 4, 4.8, &#39;심신의 균형을 고려한 전인적 트레이닝을 제공합니다.&#39;, NOW()),
(&#39;설윤아&#39;, &#39;ESTJ&#39;, 7, 4.9, &#39;체계적이고 효율적인 트레이닝으로 목표를 달성합니다.&#39;, NOW()),
(&#39;진현서&#39;, &#39;INTP&#39;, 5, 4.7, &#39;과학적 접근으로 최적의 트레이닝을 설계합니다.&#39;, NOW()),
(&#39;추민혁&#39;, &#39;ESFP&#39;, 2, 4.5, &#39;즐거운 분위기에서 효과적인 운동을 진행합니다.&#39;, NOW()),
(&#39;노지현&#39;, &#39;ISFJ&#39;, 6, 4.8, &#39;세심한 관리와 꾸준한 피드백을 제공합니다.&#39;, NOW());</code></pre>
<pre><code class="language-sql">-- trainer_specialties 데이터 삽입
INSERT INTO trainer_specialties (trainer_id, specialty)
VALUES 
(1, &#39;weight-training&#39;), (1, &#39;diet-management&#39;),
(2, &#39;bodybuilding&#39;), (2, &#39;posture-correction&#39;),
(3, &#39;crossfit&#39;), (3, &#39;functional-training&#39;),
(4, &#39;diet-management&#39;), (4, &#39;yoga&#39;),
(5, &#39;weight-training&#39;), (5, &#39;powerlifting&#39;),
(6, &#39;pilates&#39;), (6, &#39;rehabilitation&#39;),
(7, &#39;bodybuilding&#39;), (7, &#39;weight-training&#39;),
(8, &#39;yoga&#39;), (8, &#39;stretching&#39;),
(9, &#39;crossfit&#39;), (9, &#39;hiit&#39;),
(10, &#39;meditation&#39;), (10, &#39;holistic-training&#39;),
(11, &#39;weight-training&#39;), (11, &#39;sports-conditioning&#39;),
(12, &#39;functional-training&#39;), (12, &#39;mobility&#39;),
(13, &#39;dance-fitness&#39;), (13, &#39;cardio&#39;),
(14, &#39;rehabilitation&#39;), (14, &#39;senior-fitness&#39;),
(15, &#39;calisthenics&#39;), (15, &#39;parkour&#39;),
(16, &#39;yoga&#39;), (16, &#39;mindfulness&#39;),
(17, &#39;weight-training&#39;), (17, &#39;diet-management&#39;),
(18, &#39;posture-correction&#39;), (18, &#39;core-training&#39;),
(19, &#39;crossfit&#39;), (19, &#39;olympic-lifting&#39;),
(20, &#39;bodybuilding&#39;), (20, &#39;contest-prep&#39;),
(21, &#39;pilates&#39;), (21, &#39;pre-natal&#39;),
(22, &#39;dance-fitness&#39;), (22, &#39;zumba&#39;),
(23, &#39;martial-arts&#39;), (23, &#39;kickboxing&#39;),
(24, &#39;sports-conditioning&#39;), (24, &#39;athletic-training&#39;),
(25, &#39;yoga&#39;), (25, &#39;meditation&#39;),
(26, &#39;holistic-training&#39;), (26, &#39;wellness-coaching&#39;),
(27, &#39;weight-training&#39;), (27, &#39;powerlifting&#39;),
(28, &#39;functional-training&#39;), (28, &#39;mobility&#39;),
(29, &#39;cardio&#39;), (29, &#39;hiit&#39;),
(30, &#39;rehabilitation&#39;), (30, &#39;senior-fitness&#39;);</code></pre>
<h3 id="사용자회원-데이터-50명">사용자(회원) 데이터 (50명)</h3>
<pre><code class="language-sql">-- 기존 데이터 삭제 및 AUTO_INCREMENT 초기화
DELETE FROM users;
ALTER TABLE users AUTO_INCREMENT = 1;

-- users 테이블 데이터 삽입
INSERT INTO users (username, mbti, age, gender, fitness_goal, preferred_training_style, experience_level, created_at) 
VALUES 
(&#39;user1&#39;, &#39;ENFJ&#39;, 25, &#39;F&#39;, &#39;weight-loss&#39;, &#39;{&quot;preferred&quot;: [&quot;supportive&quot;, &quot;structured&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user2&#39;, &#39;INTJ&#39;, 30, &#39;M&#39;, &#39;muscle-gain&#39;, &#39;{&quot;preferred&quot;: [&quot;analytical&quot;, &quot;independent&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user3&#39;, &#39;ISFP&#39;, 28, &#39;F&#39;, &#39;flexibility&#39;, &#39;{&quot;preferred&quot;: [&quot;gentle&quot;, &quot;creative&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user4&#39;, &#39;ENTP&#39;, 35, &#39;M&#39;, &#39;strength&#39;, &#39;{&quot;preferred&quot;: [&quot;challenging&quot;, &quot;varied&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user5&#39;, &#39;ISFJ&#39;, 27, &#39;F&#39;, &#39;wellness&#39;, &#39;{&quot;preferred&quot;: [&quot;consistent&quot;, &quot;supportive&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user6&#39;, &#39;ESTP&#39;, 32, &#39;M&#39;, &#39;athletic&#39;, &#39;{&quot;preferred&quot;: [&quot;dynamic&quot;, &quot;intense&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user7&#39;, &#39;INFP&#39;, 29, &#39;F&#39;, &#39;mindfulness&#39;, &#39;{&quot;preferred&quot;: [&quot;gentle&quot;, &quot;personal&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user8&#39;, &#39;ESTJ&#39;, 31, &#39;M&#39;, &#39;weight-loss&#39;, &#39;{&quot;preferred&quot;: [&quot;structured&quot;, &quot;efficient&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user9&#39;, &#39;ENFP&#39;, 26, &#39;F&#39;, &#39;toning&#39;, &#39;{&quot;preferred&quot;: [&quot;fun&quot;, &quot;creative&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user10&#39;, &#39;ISTP&#39;, 33, &#39;M&#39;, &#39;strength&#39;, &#39;{&quot;preferred&quot;: [&quot;practical&quot;, &quot;technical&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user11&#39;, &#39;ENTJ&#39;, 28, &#39;F&#39;, &#39;muscle-gain&#39;, &#39;{&quot;preferred&quot;: [&quot;challenging&quot;, &quot;efficient&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user12&#39;, &#39;INFJ&#39;, 34, &#39;M&#39;, &#39;wellness&#39;, &#39;{&quot;preferred&quot;: [&quot;holistic&quot;, &quot;meaningful&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user13&#39;, &#39;ESFP&#39;, 27, &#39;F&#39;, &#39;fitness&#39;, &#39;{&quot;preferred&quot;: [&quot;fun&quot;, &quot;social&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user14&#39;, &#39;ISTJ&#39;, 36, &#39;M&#39;, &#39;strength&#39;, &#39;{&quot;preferred&quot;: [&quot;structured&quot;, &quot;consistent&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user15&#39;, &#39;ESFJ&#39;, 29, &#39;F&#39;, &#39;weight-loss&#39;, &#39;{&quot;preferred&quot;: [&quot;supportive&quot;, &quot;social&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user16&#39;, &#39;INTP&#39;, 31, &#39;M&#39;, &#39;muscle-gain&#39;, &#39;{&quot;preferred&quot;: [&quot;analytical&quot;, &quot;independent&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user17&#39;, &#39;ENFJ&#39;, 28, &#39;F&#39;, &#39;flexibility&#39;, &#39;{&quot;preferred&quot;: [&quot;encouraging&quot;, &quot;structured&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user18&#39;, &#39;ISTP&#39;, 33, &#39;M&#39;, &#39;strength&#39;, &#39;{&quot;preferred&quot;: [&quot;practical&quot;, &quot;efficient&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user19&#39;, &#39;ENFP&#39;, 26, &#39;F&#39;, &#39;wellness&#39;, &#39;{&quot;preferred&quot;: [&quot;fun&quot;, &quot;creative&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user20&#39;, &#39;INTJ&#39;, 35, &#39;M&#39;, &#39;muscle-gain&#39;, &#39;{&quot;preferred&quot;: [&quot;analytical&quot;, &quot;systematic&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user21&#39;, &#39;ISFP&#39;, 27, &#39;F&#39;, &#39;toning&#39;, &#39;{&quot;preferred&quot;: [&quot;gentle&quot;, &quot;personal&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user22&#39;, &#39;ESTJ&#39;, 32, &#39;M&#39;, &#39;strength&#39;, &#39;{&quot;preferred&quot;: [&quot;structured&quot;, &quot;efficient&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user23&#39;, &#39;INFP&#39;, 29, &#39;F&#39;, &#39;mindfulness&#39;, &#39;{&quot;preferred&quot;: [&quot;gentle&quot;, &quot;creative&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user24&#39;, &#39;ENTP&#39;, 34, &#39;M&#39;, &#39;athletic&#39;, &#39;{&quot;preferred&quot;: [&quot;challenging&quot;, &quot;varied&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user25&#39;, &#39;ISFJ&#39;, 28, &#39;F&#39;, &#39;wellness&#39;, &#39;{&quot;preferred&quot;: [&quot;supportive&quot;, &quot;consistent&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user26&#39;, &#39;ESTP&#39;, 31, &#39;M&#39;, &#39;strength&#39;, &#39;{&quot;preferred&quot;: [&quot;dynamic&quot;, &quot;challenging&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user27&#39;, &#39;INFJ&#39;, 27, &#39;F&#39;, &#39;flexibility&#39;, &#39;{&quot;preferred&quot;: [&quot;holistic&quot;, &quot;meaningful&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user28&#39;, &#39;ESFP&#39;, 33, &#39;M&#39;, &#39;fitness&#39;, &#39;{&quot;preferred&quot;: [&quot;fun&quot;, &quot;energetic&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user29&#39;, &#39;ISTJ&#39;, 30, &#39;F&#39;, &#39;weight-loss&#39;, &#39;{&quot;preferred&quot;: [&quot;structured&quot;, &quot;consistent&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user30&#39;, &#39;ENTJ&#39;, 35, &#39;M&#39;, &#39;muscle-gain&#39;, &#39;{&quot;preferred&quot;: [&quot;challenging&quot;, &quot;efficient&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user31&#39;, &#39;ESFJ&#39;, 28, &#39;F&#39;, &#39;wellness&#39;, &#39;{&quot;preferred&quot;: [&quot;supportive&quot;, &quot;social&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user32&#39;, &#39;INTP&#39;, 32, &#39;M&#39;, &#39;strength&#39;, &#39;{&quot;preferred&quot;: [&quot;analytical&quot;, &quot;technical&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user33&#39;, &#39;ENFJ&#39;, 29, &#39;F&#39;, &#39;toning&#39;, &#39;{&quot;preferred&quot;: [&quot;encouraging&quot;, &quot;structured&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user34&#39;, &#39;ISTP&#39;, 34, &#39;M&#39;, &#39;athletic&#39;, &#39;{&quot;preferred&quot;: [&quot;practical&quot;, &quot;technical&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user35&#39;, &#39;ENFP&#39;, 27, &#39;F&#39;, &#39;flexibility&#39;, &#39;{&quot;preferred&quot;: [&quot;fun&quot;, &quot;creative&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user36&#39;, &#39;INTJ&#39;, 33, &#39;M&#39;, &#39;muscle-gain&#39;, &#39;{&quot;preferred&quot;: [&quot;analytical&quot;, &quot;systematic&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user37&#39;, &#39;ISFP&#39;, 28, &#39;F&#39;, &#39;mindfulness&#39;, &#39;{&quot;preferred&quot;: [&quot;gentle&quot;, &quot;personal&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user38&#39;, &#39;ESTJ&#39;, 31, &#39;M&#39;, &#39;strength&#39;, &#39;{&quot;preferred&quot;: [&quot;structured&quot;, &quot;efficient&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user39&#39;, &#39;INFP&#39;, 26, &#39;F&#39;, &#39;wellness&#39;, &#39;{&quot;preferred&quot;: [&quot;gentle&quot;, &quot;creative&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user40&#39;, &#39;ENTP&#39;, 35, &#39;M&#39;, &#39;athletic&#39;, &#39;{&quot;preferred&quot;: [&quot;challenging&quot;, &quot;varied&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user41&#39;, &#39;ISFJ&#39;, 29, &#39;F&#39;, &#39;weight-loss&#39;, &#39;{&quot;preferred&quot;: [&quot;supportive&quot;, &quot;consistent&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user42&#39;, &#39;ESTP&#39;, 32, &#39;M&#39;, &#39;strength&#39;, &#39;{&quot;preferred&quot;: [&quot;dynamic&quot;, &quot;challenging&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user43&#39;, &#39;INFJ&#39;, 28, &#39;F&#39;, &#39;flexibility&#39;, &#39;{&quot;preferred&quot;: [&quot;holistic&quot;, &quot;meaningful&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user44&#39;, &#39;ESFP&#39;, 34, &#39;M&#39;, &#39;fitness&#39;, &#39;{&quot;preferred&quot;: [&quot;fun&quot;, &quot;energetic&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user45&#39;, &#39;ISTJ&#39;, 27, &#39;F&#39;, &#39;toning&#39;, &#39;{&quot;preferred&quot;: [&quot;structured&quot;, &quot;consistent&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user46&#39;, &#39;ENTJ&#39;, 33, &#39;M&#39;, &#39;muscle-gain&#39;, &#39;{&quot;preferred&quot;: [&quot;challenging&quot;, &quot;efficient&quot;]}&#39;, &#39;advanced&#39;, NOW()),
(&#39;user47&#39;, &#39;ESFJ&#39;, 30, &#39;F&#39;, &#39;wellness&#39;, &#39;{&quot;preferred&quot;: [&quot;supportive&quot;, &quot;social&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user48&#39;, &#39;INTP&#39;, 35, &#39;M&#39;, &#39;strength&#39;, &#39;{&quot;preferred&quot;: [&quot;analytical&quot;, &quot;technical&quot;]}&#39;, &#39;intermediate&#39;, NOW()),
(&#39;user49&#39;, &#39;ENFJ&#39;, 28, &#39;F&#39;, &#39;mindfulness&#39;, &#39;{&quot;preferred&quot;: [&quot;encouraging&quot;, &quot;structured&quot;]}&#39;, &#39;beginner&#39;, NOW()),
(&#39;user50&#39;, &#39;ISTP&#39;, 32, &#39;M&#39;, &#39;athletic&#39;, &#39;{&quot;preferred&quot;: [&quot;practical&quot;, &quot;technical&quot;]}&#39;, &#39;advanced&#39;, NOW());</code></pre>
<h3 id="매칭-결과와-리뷰-데이터">매칭 결과와 리뷰 데이터</h3>
<pre><code class="language-sql">-- 기존 데이터 삭제 및 AUTO_INCREMENT 초기화
DELETE FROM matching_results;
ALTER TABLE matching_results AUTO_INCREMENT = 1;

DELETE FROM trainer_reviews;
ALTER TABLE trainer_reviews AUTO_INCREMENT = 1;

-- matching_results 데이터 삽입
INSERT INTO matching_results (trainer_id, user_mbti, match_score, matched_at)
VALUES 
-- ENFJ 유저들의 매칭
(2, &#39;ENFJ&#39;, 85, &#39;2024-01-15&#39;),
(7, &#39;ENFJ&#39;, 92, &#39;2024-01-16&#39;),
(15, &#39;ENFJ&#39;, 88, &#39;2024-01-17&#39;),
(20, &#39;ENFJ&#39;, 90, &#39;2024-01-18&#39;),

-- INTJ 유저들의 매칭
(5, &#39;INTJ&#39;, 95, &#39;2024-01-19&#39;),
(12, &#39;INTJ&#39;, 87, &#39;2024-01-20&#39;),
(18, &#39;INTJ&#39;, 89, &#39;2024-01-21&#39;),

-- ISFP 유저들의 매칭
(8, &#39;ISFP&#39;, 91, &#39;2024-01-22&#39;),
(16, &#39;ISFP&#39;, 86, &#39;2024-01-23&#39;),
(25, &#39;ISFP&#39;, 88, &#39;2024-01-24&#39;),

-- ENTP 유저들의 매칭
(3, &#39;ENTP&#39;, 93, &#39;2024-02-01&#39;),
(9, &#39;ENTP&#39;, 87, &#39;2024-02-02&#39;),
(22, &#39;ENTP&#39;, 85, &#39;2024-02-03&#39;),

-- 다른 MBTI 유형들의 매칭
(1, &#39;ISFJ&#39;, 89, &#39;2024-02-04&#39;),
(4, &#39;ESTP&#39;, 92, &#39;2024-02-05&#39;),
(6, &#39;INFP&#39;, 84, &#39;2024-02-06&#39;),
(10, &#39;ESTJ&#39;, 88, &#39;2024-02-07&#39;),
(11, &#39;ENFP&#39;, 90, &#39;2024-02-08&#39;),
(13, &#39;ISTP&#39;, 86, &#39;2024-02-09&#39;),
(14, &#39;ENTJ&#39;, 91, &#39;2024-02-10&#39;);</code></pre>
<pre><code class="language-sql">-- trainer_reviews 데이터 삽입
INSERT INTO trainer_reviews (trainer_id, rating, comment, created_at)
VALUES 
(1, 5, &#39;트레이너님의 꼼꼼한 지도 덕분에 운동이 많이 늘었어요!&#39;, &#39;2024-01-20&#39;),
(1, 4, &#39;체계적인 프로그램으로 목표 달성에 도움이 되었습니다.&#39;, &#39;2024-01-21&#39;),
(2, 5, &#39;과학적인 접근방식이 매우 인상적이었습니다.&#39;, &#39;2024-01-22&#39;),
(2, 5, &#39;전문성이 돋보이는 트레이너님입니다.&#39;, &#39;2024-01-23&#39;),
(3, 4, &#39;효율적인 운동 방법을 잘 알려주셨어요.&#39;, &#39;2024-01-24&#39;),
(4, 5, &#39;즐겁게 운동할 수 있도록 도와주셔서 감사합니다.&#39;, &#39;2024-01-25&#39;),
(5, 5, &#39;정확한 자세 교정으로 통증이 많이 개선되었어요.&#39;, &#39;2024-01-26&#39;),
(6, 4, &#39;초보자도 쉽게 따라할 수 있게 설명해주셔서 좋았습니다.&#39;, &#39;2024-01-27&#39;),
(7, 5, &#39;목표 달성을 위한 동기부여가 큰 도움이 되었어요.&#39;, &#39;2024-01-28&#39;),
(8, 4, &#39;맞춤형 프로그램으로 꾸준한 관리가 가능했습니다.&#39;, &#39;2024-01-29&#39;),
(9, 5, &#39;활기찬 에너지로 운동이 즐거웠어요!&#39;, &#39;2024-01-30&#39;),
(10, 5, &#39;전인적인 관리로 건강이 많이 개선되었습니다.&#39;, &#39;2024-02-01&#39;),
(11, 4, &#39;체계적인 프로그램이 마음에 들었어요.&#39;, &#39;2024-02-02&#39;),
(12, 5, &#39;전문적인 지식을 바탕으로 한 트레이닝이 좋았습니다.&#39;, &#39;2024-02-03&#39;),
(13, 4, &#39;즐거운 분위기에서 운동할 수 있었어요.&#39;, &#39;2024-02-04&#39;),
(14, 5, &#39;꾸준한 관리와 피드백이 도움이 되었습니다.&#39;, &#39;2024-02-05&#39;),
(15, 4, &#39;창의적인 운동 방법으로 지루하지 않았어요.&#39;, &#39;2024-02-06&#39;);</code></pre>
<h3 id="사용자-선호도와-세션-기록-데이터">사용자 선호도와 세션 기록 데이터</h3>
<pre><code class="language-sql">-- 기존 데이터 삭제 및 AUTO_INCREMENT 초기화
DELETE FROM user_preferences;
ALTER TABLE user_preferences AUTO_INCREMENT = 1;

DELETE FROM training_sessions;
ALTER TABLE training_sessions AUTO_INCREMENT = 1;

-- user_preferences 데이터 삽입
INSERT INTO user_preferences (user_id, preferred_time, preferred_intensity, health_conditions, goals, created_at)
VALUES 
(1, &#39;morning&#39;, &#39;moderate&#39;, &#39;없음&#39;, &#39;{&quot;primary&quot;: &quot;weight-loss&quot;, &quot;secondary&quot;: &quot;muscle-tone&quot;}&#39;, NOW()),
(2, &#39;evening&#39;, &#39;high&#39;, &#39;허리 통증&#39;, &#39;{&quot;primary&quot;: &quot;strength&quot;, &quot;secondary&quot;: &quot;posture&quot;}&#39;, NOW()),
(3, &#39;afternoon&#39;, &#39;low&#39;, &#39;없음&#39;, &#39;{&quot;primary&quot;: &quot;flexibility&quot;, &quot;secondary&quot;: &quot;stress-relief&quot;}&#39;, NOW()),
(4, &#39;morning&#39;, &#39;high&#39;, &#39;없음&#39;, &#39;{&quot;primary&quot;: &quot;muscle-gain&quot;, &quot;secondary&quot;: &quot;strength&quot;}&#39;, NOW()),
(5, &#39;evening&#39;, &#39;moderate&#39;, &#39;어깨 통증&#39;, &#39;{&quot;primary&quot;: &quot;rehabilitation&quot;, &quot;secondary&quot;: &quot;flexibility&quot;}&#39;, NOW());</code></pre>
<pre><code class="language-sql">-- training_sessions 데이터 삽입
INSERT INTO training_sessions (user_id, trainer_id, session_date, duration, session_type, notes, status)
VALUES 
-- 사용자 1의 세션
(1, 3, &#39;2024-01-15&#39;, 60, &#39;personal&#39;, &#39;체중 감량 프로그램 시작&#39;, &#39;completed&#39;),
(1, 3, &#39;2024-01-17&#39;, 60, &#39;personal&#39;, &#39;유산소 운동 강화&#39;, &#39;completed&#39;),
(1, 3, &#39;2024-01-19&#39;, 60, &#39;personal&#39;, &#39;근력 운동 도입&#39;, &#39;completed&#39;),

-- 사용자 2의 세션
(2, 5, &#39;2024-01-16&#39;, 90, &#39;personal&#39;, &#39;웨이트 트레이닝 기초&#39;, &#39;completed&#39;),
(2, 5, &#39;2024-01-18&#39;, 90, &#39;personal&#39;, &#39;자세 교정 중점&#39;, &#39;completed&#39;),
(2, 5, &#39;2024-01-20&#39;, 90, &#39;personal&#39;, &#39;강도 상승&#39;, &#39;cancelled&#39;),

-- 사용자 3의 세션
(3, 8, &#39;2024-01-15&#39;, 60, &#39;personal&#39;, &#39;스트레칭 위주 프로그램&#39;, &#39;completed&#39;),
(3, 8, &#39;2024-01-18&#39;, 60, &#39;personal&#39;, &#39;코어 강화 운동 추가&#39;, &#39;completed&#39;),
(3, 8, &#39;2024-01-21&#39;, 60, &#39;personal&#39;, &#39;유연성 향상 확인&#39;, &#39;scheduled&#39;),

-- 사용자 4의 세션
(4, 2, &#39;2024-01-17&#39;, 90, &#39;personal&#39;, &#39;근비대 프로그램 시작&#39;, &#39;completed&#39;),
(4, 2, &#39;2024-01-19&#39;, 90, &#39;personal&#39;, &#39;식단 조절 병행&#39;, &#39;completed&#39;),
(4, 2, &#39;2024-01-21&#39;, 90, &#39;personal&#39;, &#39;1RM 측정&#39;, &#39;scheduled&#39;),

-- 사용자 5의 세션
(5, 6, &#39;2024-01-16&#39;, 60, &#39;rehabilitation&#39;, &#39;어깨 재활 운동&#39;, &#39;completed&#39;),
(5, 6, &#39;2024-01-18&#39;, 60, &#39;rehabilitation&#39;, &#39;가동범위 확인&#39;, &#39;completed&#39;),
(5, 6, &#39;2024-01-20&#39;, 60, &#39;rehabilitation&#39;, &#39;단계별 부하 증가&#39;, &#39;scheduled&#39;);
</code></pre>
<pre><code class="language-sql">-- matching_statistics 데이터 삽입
INSERT INTO matching_statistics (trainer_id, total_matches, successful_matches, avg_rating, updated_at)
VALUES 
(1, 45, 40, 4.8, NOW()),
(2, 38, 35, 4.9, NOW()),
(3, 42, 38, 4.7, NOW()),
(4, 30, 27, 4.6, NOW()),
(5, 50, 45, 4.9, NOW()),
(6, 25, 22, 4.5, NOW()),
(7, 35, 32, 4.8, NOW()),
(8, 28, 25, 4.7, NOW()),
(9, 33, 30, 4.6, NOW()),
(10, 40, 36, 4.8, NOW());</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS ES6] JavaScript Prototype]]></title>
            <link>https://velog.io/@dhlee47-l/JS</link>
            <guid>https://velog.io/@dhlee47-l/JS</guid>
            <pubDate>Mon, 06 Jan 2025 05:38:25 GMT</pubDate>
            <description><![CDATA[<p>JS는 객체지향 언어가 아니다. 그래서 상속도 없다!
Prototype을 통해 객체지향기술과 비슷한 것을 구현할 수 없다. </p>
<h4 id="함수가-정의될-때-발생하는-2가지-동작">함수가 정의될 때 발생하는 2가지 동작</h4>
<ol>
<li>해당 함수에 <strong>Constructor 자격</strong>이 부여되고, 이 자격이 부여된 것만 <strong>new</strong>를 통해 객체 생성 가능
<img src="https://velog.velcdn.com/images/dhlee47-l/post/12342f85-c423-470e-8541-6fe75f220b71/image.png" alt=""></li>
</ol>
<ol start="2">
<li>해당 함수의 <strong>Prototype Object</strong> 생성하고 연결
<img src="https://velog.velcdn.com/images/dhlee47-l/post/f45ee88b-8b25-4561-9250-5403a7ce249c/image.png" alt=""></li>
</ol>
<h4 id="prototype-link-prototype-object">Prototype Link, Prototype Object</h4>
<ul>
<li>이 두가지를 통틀어 <strong>Prototype</strong> 이라고 부른다.
<img src="https://velog.velcdn.com/images/dhlee47-l/post/cbe828e2-a597-43b7-8a53-94037da1f59e/image.png" alt=""></li>
</ul>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/88f25399-d94a-4947-8229-6d9a728bb22c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/6cb15b30-7236-467a-9bde-1f8746a9d04c/image.png" alt=""></p>
<p>점점 최상위로 타고타고 올라가면 결국 js도 <strong>Object가 최상위</strong>
타고 타고 올라가는 것을 <strong>Prototype Chain</strong>이라고 부른다.</p>
<hr>
<h3 id="1-객체-생성과-생성자-함수">1. 객체 생성과 생성자 함수</h3>
<p>JavaScript에서 객체를 생성하는 가장 기본적인 방법은 생성자 함수를 사용하는 것이다. 생성자 함수는 객체를 만드는 틀이 되며, 이를 통해 생성된 객체를 인스턴스라고 한다.</p>
<h4 id="기본적인-생성자-함수">기본적인 생성자 함수</h4>
<pre><code class="language-javascript">function Person() {}
const person = new Person();  // Person 객체 생성
console.log(person, typeof person);  // Person {} &#39;object&#39;</code></pre>
<h4 id="속성이-있는-생성자-함수">속성이 있는 생성자 함수</h4>
<pre><code class="language-javascript">function Person(name, age) {
    this.name = name;
    this.age = age;

    this.hello = function() {
        console.log(`Hello, I&#39;m ${this.name}`);
    };
}

const person = new Person(&#39;Mark&#39;, 37);
person.hello();  // &quot;Hello, I&#39;m Mark&quot;</code></pre>
<h3 id="2-프로토타입과-상속">2. 프로토타입과 상속</h3>
<p>JavaScript는 프로토타입을 통해 객체 간의 상속을 구현한다. 모든 객체는 프로토타입 체인을 통해 다른 객체의 속성과 메서드를 상속받을 수 있다.</p>
<h4 id="프로토타입을-이용한-메서드-정의">프로토타입을 이용한 메서드 정의</h4>
<pre><code class="language-javascript">function Animal(type, name, sound) {
    this.type = type;
    this.name = name;
    this.sound = sound;
}

Animal.prototype.say = function() {
    console.log(this.sound);
};

function Dog(name, sound) {
    Animal.call(this, &#39;개&#39;, name, sound);
}
Dog.prototype = Animal.prototype;

const dog = new Dog(&#39;멍멍이&#39;, &#39;왈왈&#39;);
dog.say();  // &quot;왈왈&quot;</code></pre>
<h3 id="3-클래스-문법">3. 클래스 문법</h3>
<p>ES6부터는 class 키워드를 사용하여 더 명확하고 직관적인 객체 지향 프로그래밍이 가능해졌다.</p>
<h4 id="기본-클래스-정의">기본 클래스 정의</h4>
<pre><code class="language-javascript">class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    hello() {
        console.log(`Hello, I&#39;m ${this.name}`);
    }
}

const person = new Person(&#39;Mark&#39;, 37);
person.hello();</code></pre>
<h4 id="getter와-setter">getter와 setter</h4>
<pre><code class="language-javascript">class User {
    _name = &#39;no name&#39;;

    get name() {
        return this._name + &quot;!&quot;;
    }

    set name(value) {
        this._name = value;
    }
}</code></pre>
<h4 id="정적-메서드와-속성">정적 메서드와 속성</h4>
<pre><code class="language-javascript">class Calculator {
    static PI = 3.14159;

    static sum(a, b) {
        return a + b;
    }
}

console.log(Calculator.PI);      // 3.14159
console.log(Calculator.sum(1, 2)); // 3</code></pre>
<h4 id="클래스-상속">클래스 상속</h4>
<pre><code class="language-javascript">class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a sound.`);
    }
}

class Dog extends Animal {
    constructor(name) {
        super(name);
    }

    speak() {
        console.log(`${this.name} barks.`);
    }
}

const dog = new Dog(&#39;Rex&#39;);
dog.speak();  // &quot;Rex barks.&quot;</code></pre>
<h4 id="주요-특징-정리">주요 특징 정리</h4>
<ol>
<li>클래스는 호이스팅되지 않으므로 선언 전에 사용할 수 없다.</li>
<li>클래스 내의 모든 메서드는 엄격 모드(strict mode)에서 실행된다.</li>
<li>상속을 사용할 때는 반드시 <code>super()</code>를 호출해야 한다.</li>
<li>정적 메서드는 인스턴스가 아닌 클래스 자체에서 호출한다.</li>
</ol>
<h3 id="결론">결론</h3>
<p>JavaScript의 객체 지향 프로그래밍은 프로토타입 기반으로 동작하며, ES6의 클래스 문법은 이를 더 편리하게 사용할 수 있게 해준다. 클래스는 실제로 프로토타입 상속의 문법적 설탕(Syntactic Sugar)이지만, 코드의 가독성과 유지보수성을 크게 향상시킨다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS ES6] Features]]></title>
            <link>https://velog.io/@dhlee47-l/JS-ES6-Features</link>
            <guid>https://velog.io/@dhlee47-l/JS-ES6-Features</guid>
            <pubDate>Fri, 03 Jan 2025 06:56:33 GMT</pubDate>
            <description><![CDATA[<h2 id="scope란">Scope란?</h2>
<ul>
<li>JavaScript에서 Scope는 <strong>변수가 유효한 범위</strong>를 의미</li>
<li>코드의 어느 부분에서 변수에 접근하고 사용할 수 있는지를 결정하는 규칙</li>
</ul>
<h3 id="scope의-종류">Scope의 종류</h3>
<ul>
<li>JavaScript에는 크게 3가지 종류의 Scope가 있습니다:</li>
</ul>
<h4 id="1-global-scope-전역-범위">1. Global Scope (전역 범위)</h4>
<ul>
<li>전역 범위에 선언된 변수는 코드 어디에서나 접근이 가능</li>
</ul>
<pre><code class="language-javascript">let globalVar = &quot;나는 전역 변수&quot;;
const globalConst = &quot;나도 전역 변수&quot;;

function someFunction() {
    console.log(globalVar);     // &quot;나는 전역 변수&quot; 출력
    console.log(globalConst);   // &quot;나도 전역 변수&quot; 출력
}

someFunction();
console.log(globalVar);         // &quot;나는 전역 변수&quot; 출력</code></pre>
<h4 id="2-function-scope-함수-범위">2. Function Scope (함수 범위)</h4>
<ul>
<li>함수 내부에서 선언된 변수는 해당 함수 내에서만 접근이 가능</li>
<li>특히 <code>var</code> 키워드로 선언된 변수는 함수 스코프를 가짐</li>
</ul>
<pre><code class="language-javascript">function myFunction() {
    var functionVar = &quot;함수 안에서만 살아요&quot;;
    console.log(functionVar);   // &quot;함수 안에서만 살아요&quot; 출력
}

myFunction();
// console.log(functionVar);    // 에러! functionVar는 함수 밖에서 접근 불가</code></pre>
<h4 id="3-block-scope-블록-범위">3. Block Scope (블록 범위)</h4>
<ul>
<li>중괄호 <code>{}</code> 내부에서 <code>let</code>이나 <code>const</code>로 선언된 변수는 해당 블록 내에서만 접근이 가능</li>
</ul>
<pre><code class="language-javascript">if (true) {
    let blockVar = &quot;블록 안에서만 살아요&quot;;
    const blockConst = &quot;나도 블록 안에서만&quot;;
    console.log(blockVar);      // &quot;블록 안에서만 살아요&quot; 출력
    console.log(blockConst);    // &quot;나도 블록 안에서만&quot; 출력
}

// console.log(blockVar);       // 에러! 블록 밖이라 접근 불가
// console.log(blockConst);     // 에러! 블록 밖이라 접근 불가</code></pre>
<h3 id="변수-선언-방식에-따른-scope-차이">변수 선언 방식에 따른 Scope 차이</h3>
<h4 id="var-vs-letconst">var vs let/const</h4>
<pre><code class="language-javascript">function scopeTest() {
    var functionScoped = &quot;var는 함수 스코프&quot;;
    let blockScoped = &quot;let은 블록 스코프&quot;;

    if (true) {
        var functionScoped2 = &quot;여전히 함수 스코프&quot;;
        let blockScoped2 = &quot;블록 스코프&quot;;
        console.log(functionScoped);   // 접근 가능
        console.log(blockScoped);      // 접근 가능
    }

    console.log(functionScoped2);      // 접근 가능
    // console.log(blockScoped2);      // 에러! 블록을 벗어남
}</code></pre>
<h4 id="키워드-없는-선언-주의">키워드 없는 선언 (주의!)</h4>
<pre><code class="language-javascript">function dangerousScope() {
    noKeyword = &quot;위험해요!&quot;;  // 자동으로 전역 변수가 됨
}

dangerousScope();
console.log(noKeyword);  // &quot;위험해요!&quot; 출력 - 의도치 않은 전역 변수 생성</code></pre>
<h3 id="실제-사용-예시와-주의사항">실제 사용 예시와 주의사항</h3>
<pre><code class="language-javascript">// 블록 외부에서 선언
const outer1 = 100;
let outer2 = 200;
var outer3 = 300;

{
    // 블록 내부에서 외부 변수 접근
    console.log(outer1);  // 100
    console.log(outer2);  // 200
    console.log(outer3);  // 300

    // 블록 내부에서 새로운 변수 선언
    const inner1 = &quot;블록 안&quot;;
    let inner2 = &quot;블록 안&quot;;
    var inner3 = &quot;함수가 아닌 블록에서 var는 전역이 됨&quot;;
}

// console.log(inner1);  // 에러! block scope
// console.log(inner2);  // 에러! block scope
console.log(inner3);     // &quot;함수가 아닌 블록에서 var는 전역이 됨&quot; - var의 특이한 동작</code></pre>
<hr>
<h2 id="호이스팅이란">호이스팅이란?</h2>
<ul>
<li>JavaScript에서 변수나 함수의 <strong>선언</strong>이 코드의 최상단으로 끌어올려지는 것처럼 동작하는 현상</li>
</ul>
<h3 id="함수-호이스팅">함수 호이스팅</h3>
<ul>
<li>함수 선언문은 전체가 호이스팅</li>
</ul>
<pre><code class="language-javascript">// 함수를 호출하고
hello();  // &quot;안녕하세요!&quot; 출력

// 나중에 함수를 선언해도 정상 작동합니다
function hello() {
    console.log(&quot;안녕하세요!&quot;);
}</code></pre>
<ul>
<li>이것이 가능한 이유는 JavaScript 엔진이 코드를 실행하기 전에 함수 선언을 최상단으로 끌어올리기 때문</li>
</ul>
<h3 id="변수-호이스팅">변수 호이스팅</h3>
<ul>
<li>var로 선언된 변수는 호이스팅되지만, 초기화는 호이스팅되지 않음</li>
</ul>
<pre><code class="language-javascript">console.log(name);  // undefined
var name = &quot;John&quot;;  

// 위 코드는 실제로 다음과 같이 동작
var name;          // 선언부가 위로 호이스팅됨
console.log(name); // undefined
name = &quot;John&quot;;     // 할당은 원래 위치에서 실행</code></pre>
<h4 id="var-vs-letconst-1">var vs let/const</h4>
<pre><code class="language-javascript">// var의 경우
console.log(varVariable);  // undefined
var varVariable = 10;

// let의 경우
console.log(letVariable);  // ReferenceError!
let letVariable = 10;

// const의 경우
console.log(constVariable);  // ReferenceError!
const constVariable = 10;</code></pre>
<h3 id="호이스팅의-실제-예제">호이스팅의 실제 예제</h3>
<h4 id="1-함수-호이스팅">1. 함수 호이스팅</h4>
<pre><code class="language-javascript">// 함수 호출이 선언보다 먼저 와도 동작
hello2();  // &quot;hello2() 호출 실행되나요?&quot; 출력

function hello2() {
    console.log(&quot;hello2() 호출 실행되나요?&quot;);
}</code></pre>
<h4 id="2-var-호이스팅의-특이한-동작">2. var 호이스팅의 특이한 동작</h4>
<pre><code class="language-javascript">console.log(val_2);  // undefined
var val_2;          // 선언만 호이스팅됨

console.log(val_3);  // undefined
var val_3 = 10;     // 선언은 호이스팅되지만 할당은 그대로

val_4++;            // NaN - undefined에 증가 연산을 시도
console.log(val_4); // NaN
var val_4 = 10;</code></pre>
<h3 id="호이스팅이-문제가-되는-경우">호이스팅이 문제가 되는 경우</h3>
<h4 id="1-의도치-않은-undefined">1. 의도치 않은 undefined</h4>
<pre><code class="language-javascript">function getName() {
    console.log(name);  // undefined
    var name = &quot;John&quot;;  // 의도와 다르게 undefined 출력
    return name;
}</code></pre>
<h4 id="2-변수-재선언으로-인한-혼란">2. 변수 재선언으로 인한 혼란</h4>
<pre><code class="language-javascript">var x = 1;
{
    console.log(x);  // undefined
    var x = 2;       // 호이스팅으로 인해 위의 x가 undefined
}</code></pre>
<h4 id="정리">정리</h4>
<ol>
<li><p><strong>let과 const 사용하기</strong></p>
<ul>
<li>호이스팅으로 인한 혼란을 방지</li>
<li>더 예측 가능한 스코프 규칙</li>
</ul>
</li>
<li><p><strong>함수 선언을 코드 최상단에 배치</strong></p>
<ul>
<li>호이스팅에 의존하지 않고 코드를 명확하게 작성</li>
</ul>
</li>
<li><p><strong>변수 선언과 초기화를 함께하기</strong></p>
<ul>
<li>변수를 사용하기 전에 확실히 초기화</li>
</ul>
</li>
</ol>
<ul>
<li>호이스팅은 JavaScript의 독특한 특성 중 하나</li>
<li>ES6에서 도입된 <code>let</code>과 <code>const</code>를 사용하면 호이스팅으로 인한 혼란을 크게 줄일 수 있음</li>
<li>호이스팅을 이해하는 것은 JavaScript 코드의 동작을 예측하고 디버깅하는 데 매우 중요</li>
</ul>
<p><em>++ 나중에 ESLint 라는 도구를 사용 시, hoisting 이 발생하는 곳에 경고 발생함</em></p>
<hr>
<h2 id="this-바인딩과-호출-방식-이해하기">this 바인딩과 호출 방식 이해하기</h2>
<ul>
<li>메서드 호출 방식에 따른 this 바인딩의 차이점</li>
</ul>
<h4 id="1-일반적인-메서드-정의-방식들">1. 일반적인 메서드 정의 방식들</h4>
<pre><code class="language-javascript">const dog = {
    sound: &quot;멍멍!&quot;,

    // 1. 이름 있는 함수
    say1: function aaa() {
        console.log(`say1: ${this.sound}`);
    },

    // 2. 익명 함수
    say2: function() {
        console.log(`say2: ${this.sound}`);
    },

    // 3. ES6 메서드 단축 구문
    say3() {
        console.log(`say3: ${this.sound}`);
    },

    // 4. 화살표 함수 (this 바인딩 주의!)
    say4: () =&gt; {
        console.log(`say4: ${this.sound}`); // undefined
    }
}</code></pre>
<h4 id="2-this-바인딩의-차이점">2. this 바인딩의 차이점</h4>
<ul>
<li>메서드를 다른 변수에 할당할 때 this 바인딩이 어떻게 달라지는지 확인</li>
</ul>
<pre><code class="language-javascript">const cat = {
    sound: &quot;야옹~&quot;
};

// 1. 메서드로 호출 - 정상 동작
cat.say1 = dog.say1;
cat.say1();  // &quot;야옹~&quot; 출력

// 2. 일반 변수에 할당 - this 바인딩 끊김
const 개냥이 = cat.say1;
개냥이();  // undefined</code></pre>
<h4 id="3-해결-방법">3. 해결 방법</h4>
<ul>
<li>this 바인딩이 끊기는 문제를 해결하는 여러 방법</li>
</ul>
<pre><code class="language-javascript">// 1. bind() 사용
const 개냥이1 = cat.say1.bind(cat);

// 2. 화살표 함수로 감싸서 메서드 호출 형태 유지
const 개냥이2 = () =&gt; cat.say1();

// 3. call() 사용
const 개냥이3 = cat.say1;
개냥이3.call(cat);</code></pre>
<h4 id="정리-1">정리</h4>
<ul>
<li>메서드 호출 형태(객체.메서드())가 this 바인딩에 중요한 영향을 미친다.</li>
<li>화살표 함수를 메서드로 직접 정의하면 this 바인딩이 일어나지 않는다.</li>
<li>화살표 함수로 메서드를 감쌀 때는 내부에서 메서드 호출 형태를 유지하면 정상 동작한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] Native Query]]></title>
            <link>https://velog.io/@dhlee47-l/JPA-Native-Query</link>
            <guid>https://velog.io/@dhlee47-l/JPA-Native-Query</guid>
            <pubDate>Thu, 02 Jan 2025 04:28:36 GMT</pubDate>
            <description><![CDATA[<h2 id="native-query">Native Query</h2>
<ul>
<li><p>특정 DB에서 동작</p>
</li>
<li><p>@Query에 nativeQuery = true 속성 추가</p>
<pre><code class="language-java">// Native Query 사용
  @Query(value = &quot;select * from book&quot;, nativeQuery = true)
  List&lt;Book&gt; findAllCustom1();</code></pre>
</li>
<li><p>특정 DB에 의존 시, JPA의 장점에서 벗어남</p>
</li>
<li><p>DB 벤더를 바꾸거나, 이종 DB로 마이그레이션 하는 게 현업에서 흔한 일 아님</p>
</li>
</ul>
<h3 id="native-query-사용-이유">Native Query 사용 이유</h3>
<h4 id="1-성능-이슈-해결">1. 성능 이슈 해결</h4>
<pre><code class="language-java">// Native Query로 Update
    @Transactional  // UPDATE, INSERT, DELETE 수행하는 @Query 수행 시
    @Modifying  //  UPDATE, INSERT,DELETE 수행하는 @Query 임을 알림
    @Query(value = &quot;update book set category = &#39;IT 전문서&#39;&quot;, nativeQuery = true)
    int updateCategories();
    // DML 의 경우 리턴타입이 void, int, long 일수 있다.
    // int 나 long 리턴하게 되면 affected row 를 받게 된다.</code></pre>
<h4 id="2-jpa-기본-제공하는-기능이-아닐-시">2. JPA 기본 제공하는 기능이 아닐 시</h4>
<pre><code class="language-java">// JPA 에선 지원하지 않는 쿼리
    @Query(value=&quot;show tables&quot;, nativeQuery = true)
    List&lt;String&gt; showTables();</code></pre>
<hr>
<h2 id="converter">Converter</h2>
<ul>
<li>JPA에서 Entity의 필드와 데이터베이스 칼럼 사이의 변환을 담당</li>
<li>예시: <ul>
<li>enum: Converter 사용 / 정의한 순서대로 들어가는 Ordinal, 문자열 둘 중 하나를 쓰는데 가급적 문자열을 쓰도록 권장됨(유지보수를 위해)</li>
<li>enum Ordinal일 경우: OrdinalEnumValueConverter 동작</li>
</ul>
</li>
</ul>
<h3 id="기본-사용법">기본 사용법</h3>
<ul>
<li><p>기본적으로 JPA는 다음과 같은 단순 타입 변환을 자동으로 해준다</p>
<pre><code class="language-java">@Entity
public class Book {
  private String name;        // VARCHAR로 변환
  private LocalDateTime createdAt;  // TIMESTAMP로 변환
  private Boolean isActive;   // BOOLEAN/BIT로 변환
}</code></pre>
</li>
<li><p>하지만 복잡한 객체를 DB에 저장할 때는 @Converter가 필요하다</p>
<pre><code class="language-java">@Entity
public class Book {
  @Convert(converter = BookStatusConverter.class)
  private BookStatus status;  // 단순 Integer가 아닌 커스텀 객체로 변환
}</code></pre>
<h3 id="converter-구현">Converter 구현</h3>
<pre><code class="language-java">@Converter
public class BookStatusConverter implements AttributeConverter&lt;BookStatus, Integer&gt; {
  // BookStatus -&gt; DB 칼럼값으로 변환
  @Override
  public Integer convertToDatabaseColumn(BookStatus bookStatus) {
      return bookStatus.getCode();
  }

  // DB column 값 -&gt; BookStatus로 변환
  @Override
  public BookStatus convertToEntityAttribute(Integer s) {
      return s != null ? new BookStatus(s) : null;
  }
}</code></pre>
<h4 id="bookstatus-클래스">BookStatus 클래스</h4>
<pre><code class="language-java">@Data
public class BookStatus {
  private int code;
  private String description;

  public BookStatus(int code) {
      this.code = code;
      this.description = parseDescription(code);
  }

  private String parseDescription(int code) {
      return switch (code) {
          case 100 -&gt; &quot;판매종료&quot;;
          case 200 -&gt; &quot;판매중&quot;;
          case 300 -&gt; &quot;판매보류&quot;;
          default -&gt; &quot;미지원&quot;;
      };
  }

  public boolean isDisplayed(){
      return code == 200;
  }
}</code></pre>
<h4 id="주의할-점">주의할 점</h4>
</li>
<li><p>@Transactional과 함께 사용할 때 주의가 필요하다. Converter에서 잘못된 값을 반환하면 영속성 컨텍스트의 변경 감지로 인해 데이터가 유실될 수 있다.</p>
</li>
<li><p>Converter는 DB에 매우 가깝게 동작하므로 디버깅이 어렵다. 따라서 아래와 같은 점을 고려해야 한다:</p>
</li>
</ul>
<blockquote>
<ul>
<li>null 처리를 확실히 한다</li>
</ul>
</blockquote>
<ul>
<li>예외 발생 가능성을 최소화한다</li>
<li>변환 로직을 단순하게 유지한다</li>
</ul>
<h4 id="converter의-장점">Converter의 장점</h4>
<ul>
<li>도메인 로직 캡슐화: 단순 정수가 아닌 의미있는 객체로 상태를 표현할 수 있다</li>
<li>데이터 일관성: 상태값 변환 로직을 한 곳에서 관리할 수 있다</li>
<li>객체지향적 설계: DB는 단순하게 유지하면서 애플리케이션에서는 풍부한 객체를 사용할 수 있다</li>
</ul>
<hr>
<h2 id="embedded">Embedded</h2>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/8ff0338c-fcf3-4812-a8aa-90e8fdcf5582/image.png" alt=""></p>
<ul>
<li>코드 반복 -&gt; Embed 쓰기</li>
<li>가독성을 높여줌</li>
</ul>
<h3 id="임베디드-타입-정의-및-사용">임베디드 타입 정의 및 사용</h3>
<pre><code class="language-java">@Data
@AllArgsConstructor
@NoArgsConstructor
@Embeddable     // Embed 할 수 있는 클래스임을 선언
public class Address {
    private String city;        // 도시
    private String district;    // 구/군
    @Column(name = &quot;address_detail&quot;)
    private String detail;      // 상세주소
    private String zipCode;     // 우편번호
}</code></pre>
<pre><code class="language-java">@Embedded
@AttributeOverrides({
    @AttributeOverride(name = &quot;city&quot;, column = @Column(name = &quot;home_city&quot;)),
    @AttributeOverride(name = &quot;district&quot;, column = @Column(name = &quot;home_distirct&quot;)),
    @AttributeOverride(name = &quot;detail&quot;, column = @Column(name = &quot;home_address_detail&quot;)),
    @AttributeOverride(name = &quot;zipCode&quot;, column = @Column(name = &quot;home_zip_code&quot;)),
})
private Address homeAddress;  // 집주소

@Embedded
@AttributeOverrides({
    @AttributeOverride(name = &quot;city&quot;, column = @Column(name = &quot;company_city&quot;)),
    @AttributeOverride(name = &quot;district&quot;, column = @Column(name = &quot;company_distirct&quot;)),
    @AttributeOverride(name = &quot;detail&quot;, column = @Column(name = &quot;company_address_detail&quot;)),
    @AttributeOverride(name = &quot;zipCode&quot;, column = @Column(name = &quot;company_zip_code&quot;)),
})
private Address companyAddress;  // 회사 주소</code></pre>
<h3 id="임베디드-타입-활용">임베디드 타입 활용</h3>
<pre><code class="language-java">@Test
void embeddedTest1() {
    // 기본 저장 테스트
    User user = new User();
    user.setName(&quot;절미&quot;);
    user.setHomeAddress(new Address(&quot;서울시&quot;, &quot;강남구&quot;, &quot;강남대로 888&quot;, &quot;08865&quot;));
    user.setCompanyAddress(new Address(&quot;문선시&quot;, &quot;문선구&quot;, &quot;문선로 777&quot;, &quot;12345&quot;));
    userRepository.save(user);

    // 저장된 데이터 확인
    userRepository.findAll().forEach(System.out::println);
    userHistoryRepository.findAll().forEach(System.out::println);
}

@Test
void embeddedTest2() {
    // 1. 정상적인 주소 데이터
    User user1 = new User();
    user1.setName(&quot;steve&quot;);
    user1.setHomeAddress(new Address(&quot;서울시&quot;, &quot;강남구&quot;, &quot;강남대로 777 골드타워&quot;, &quot;08765&quot;));
    user1.setCompanyAddress(new Address(&quot;서울시&quot;, &quot;성동구&quot;, &quot;성수1로 333 우리빌딩&quot;, &quot;04455&quot;));
    userRepository.save(user1);

    // 2. null 주소 데이터
    User user2 = new User();
    user2.setName(&quot;joshua&quot;);
    user2.setHomeAddress(null);
    user2.setCompanyAddress(null);
    userRepository.save(user2);

    // 3. empty 주소 데이터
    User user3 = new User();
    user3.setName(&quot;jordan&quot;);
    user3.setHomeAddress(new Address());
    user3.setCompanyAddress(new Address());
    userRepository.save(user3);

    // 저장된 데이터 확인
    userRepository.findAll().forEach(System.out::println);
    userHistoryRepository.findAll().forEach(System.out::println);

    // DB에 실제 저장된 로우 데이터 확인
    System.out.println(&quot;🤪&quot;.repeat(20));
    userRepository.findAllRowRecord().forEach(a -&gt; System.out.println(a.entrySet()));
}</code></pre>
<pre><code class="language-java">        // Embedded된 Address 추가
        userHistory.setHomeAddress(user.getHomeAddress());
        userHistory.setCompanyAddress(user.getCompanyAddress());

        userHistoryRepository.save(userHistory);
    }
}</code></pre>
<ul>
<li>@Embeddable과 @Embedded를 사용하여 값 타입을 정의하고 사용</li>
<li>@AttributeOverrides로 같은 임베디드 타입을 여러번 사용할 때 컬럼명 충돌 방지</li>
<li>Address가 null이거나 empty인 경우 모든 필드가 null로 저장됨</li>
</ul>
<p><strong>BUT</strong></p>
<ul>
<li>annotation이 지저분해짐</li>
<li>@AttributeOverrieds 대신 또 다른 객체를 선언하는게 나을지, 각자의 판단이 필요</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] Custom Query]]></title>
            <link>https://velog.io/@dhlee47-l/JPA-Custom-Query</link>
            <guid>https://velog.io/@dhlee47-l/JPA-Custom-Query</guid>
            <pubDate>Thu, 02 Jan 2025 01:01:24 GMT</pubDate>
            <description><![CDATA[<h3 id="jpa-annotation-">JPA annotation +</h3>
<h4 id="dynamicinsert">@DynamicInsert</h4>
<ul>
<li>INSERT SQL을 생성할 때 null이 아닌 필드들만 포함</li>
<li>null 값을 가진 필드는 SQL문에서 제외되어, DB에 설정된 기본값(default value)이 적용</li>
<li>생성일자(createdAt) 필드가 null이면 해당 필드는 INSERT 문에서 제외되고, DB에 설정된 기본값(예: CURRENT_TIMESTAMP)이 사용됨</li>
</ul>
<h4 id="dynamicupdate">@DynamicUpdate</h4>
<ul>
<li>UPDATE SQL을 생성할 때 변경된 필드들만 포함</li>
<li>변경되지 않은 필드들은 UPDATE 문에서 제외</li>
<li>불필요한 컬럼 업데이트를 방지하고 SQL 성능을 최적화</li>
</ul>
<hr>
<h3 id="custom-query를-사용하는-이유">Custom Query를 사용하는 이유</h3>
<h4 id="1-query-method의-가독성너무-길어짐">1. Query Method의 가독성(너무 길어짐)</h4>
<ul>
<li>Query 구문은 SQL이 아닌 JPQL임.</li>
</ul>
<h3 id="jpql">JPQL</h3>
<ul>
<li>Entity 기반의 쿼리를 생성하기 위한 구문</li>
<li>(실제 생성된 물리적 테이블 이름: created_at) but, <strong>property명</strong>을 사용(createdAt) </li>
<li>dialect에 따라 쿼리 자동 생성</li>
<li>native query와 차이 있음</li>
</ul>
<h4 id="jpql-positional-parameter">JPQL Positional Parameter</h4>
<ul>
<li>?1, ?2...</li>
<li>1-based index</li>
</ul>
<h4 id="jpql-named-parameter">JPQL Named Parameter</h4>
<ul>
<li>@Param ,: 접두어 사용</li>
<li>Parameter 순서에 영향 x</li>
</ul>
<h4 id="2-entity-연결하지-않은-query-사용-가능">2. Entity 연결하지 않은 Query 사용 가능</h4>
<pre><code class="language-java">// interface
public interface BookNameAndCategory1 {
   String getName();
   String getCategory();
}</code></pre>
<pre><code class="language-java">// DTO
@Data
@AllArgsConstructor
@NoArgsConstructor
// 이는 엔티티가 아니다.
public class BookNameAndCategory2 {
   private String name;
   private String category;
}</code></pre>
<pre><code class="language-java">@Test
void queryTest(){
   bookRepository.findBookNameAndCategory3().forEach(b -&gt; {
       System.out.println(b.getName() + &quot; : &quot; + b.getCategory());
   });
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] 연관관계]]></title>
            <link>https://velog.io/@dhlee47-l/JPA-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@dhlee47-l/JPA-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84</guid>
            <pubDate>Thu, 02 Jan 2025 00:54:52 GMT</pubDate>
            <description><![CDATA[<p>JPA는 편리하지만 원치 않는 쿼리가 작동할 수 있고, 이는 잠재적 성능 이슈를 야기한다. </p>
<h3 id="onetoone">@OneToOne</h3>
<pre><code class="language-java">@Data
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Entity
public class BookReviewInfo extends BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // BookReviewInfo: Book
    // 1:1 연결
//    private Long bookId;   // FK 역할.

    @OneToOne(optional = false)   // Book 과 1:1 로 relation
    // optional = false  &lt;= Book(부모) 는 null 허용하지 않는다 (즉, 반드시 Book 을 참조)
    private Book book;   // 부모 Entity 참조
    // 기본적으로 Entity 에선 직접적으로 다른 Entity 를 직접 참조 못한다
    // @OneToOne 과 같은 relation 어노테이션을 지정해주어야 한다.

    // NULL 을 허용하면 wrapper 객체 사용
    // NULL 을 허용하지 않을거면 primitive 객체 사용 -&gt; DDL 에 NOT NULL 부여됨
    // 이번예제에서 아래 두개값은 기본값 0 을 사용하기 위해 primitive 를 사용합니다.
    //   --&gt; 굳이 null check 안해도 된다.
    private float averageReviewScore;
    private int reviewCount;

}</code></pre>
<pre><code class="language-java">@OneToOne(mappedBy = &quot;book&quot;)    // 해당 Entity 의 테이블에선 연관키를 가지지 않는다.
    @ToString.Exclude   // Lombok 의 ToString 에서 배제 (양방향에서의 순환참조 때문에)
    private BookReviewInfo bookReviewInfo;</code></pre>
<pre><code class="language-java">@Test
    void crudTest3() {
        givenBookReviewInfo();

        // Book(부모)에서 BookReviewInfo(자식) 조회
        System.out.println(&quot;🎅&quot;.repeat(20));

        // @ OneToOne 양방향 참조
        BookReviewInfo result2 = bookRepository
                .findById(1L)   // @OneToOne 연결된 entity 와의 join 문 발생
                .orElseThrow(RuntimeException::new)
                .getBookReviewInfo()
                ;

        System.out.println(&quot;&gt;&gt;&gt; &quot; + result2);
    }</code></pre>
<p>Test 결과:</p>
<pre><code class="language-sql">Hibernate: 
    create table book_review_info (
        average_review_score float(24) not null,
        review_count integer not null,
        book_id bigint not null unique,
        created_at timestamp(6),
        id bigint generated by default as identity,
        updated_at timestamp(6),
        primary key (id)
    )
Hibernate: 
    create table t_user (
        created_at timestamp(6),
        id bigint generated by default as identity,
        updated_at timestamp(6),
        email varchar(255) unique,
        name varchar(255),
        gender enum (&#39;FEMALE&#39;,&#39;MALE&#39;),
        primary key (id)
    )
Hibernate: 
    create table user_history (
        created_at timestamp(6),
        id bigint generated by default as identity,
        updated_at timestamp(6),
        user_id bigint,
        email varchar(255),
        name varchar(255),
        primary key (id)
    )
Hibernate: 
    create index IDXg8gqk4e142wekcb1t6d3v2mwx 
       on t_user (name)
Hibernate: 
    alter table if exists book_review_info 
       add constraint FKp5fhkokpbtoxmc3mxo8ay9e5l 
       foreign key (book_id) 
       references book

----------------------------------------
[ crudTest3() ] 호출

Hibernate: 
    select
        bri1_0.id,
        bri1_0.average_review_score,
        bri1_0.book_id,
        b1_0.id,
        b1_0.author_id,
        b1_0.category,
        b1_0.created_at,
        b1_0.name,
        b1_0.publisher_id,
        b1_0.updated_at,
        bri1_0.created_at,
        bri1_0.review_count,
        bri1_0.updated_at 
    from
        book_review_info bri1_0 
    join
        book b1_0 
            on b1_0.id=bri1_0.book_id 
    where
        bri1_0.id=?
&gt;&gt;&gt; Book(super=BaseEntity(createdAt=2024-12-30T09:28:23.398898, updatedAt=2024-12-30T09:28:23.398898), id=1, name=JPA 완전정복, category=null, authorId=1, publisherId=1)
🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅🎅
Hibernate: 
    select
        b1_0.id,
        b1_0.author_id,
        bri1_0.id,
        bri1_0.average_review_score,
        bri1_0.book_id,
        bri1_0.created_at,
        bri1_0.review_count,
        bri1_0.updated_at,
        b1_0.category,
        b1_0.created_at,
        b1_0.name,
        b1_0.publisher_id,
        b1_0.updated_at 
    from
        book b1_0 
    left join
        book_review_info bri1_0 
            on b1_0.id=bri1_0.book_id 
    where
        b1_0.id=?
&gt;&gt;&gt; BookReviewInfo(super=BaseEntity(createdAt=2024-12-30T09:28:23.422437, updatedAt=2024-12-30T09:28:23.422437), id=1, book=Book(super=BaseEntity(createdAt=2024-12-30T09:28:23.398898, updatedAt=2024-12-30T09:28:23.398898), id=1, name=JPA 완전정복, category=null, authorId=1, publisherId=1), averageReviewScore=4.5, reviewCount=2)
----------------------------------------
</code></pre>
<h3 id="onetomany">@OneToMany</h3>
<h4 id="1-기본-매핑-방식">1. 기본 매핑 방식</h4>
<p>*<em>중간 테이블 생성 방식: *</em></p>
<p>@OneToMany만 사용하면 자동으로 중간 테이블이 생성됩니다.</p>
<pre><code class="language-java">@OneToMany
private List&lt;UserHistory&gt; userHistories = new ArrayList&lt;&gt;();    // NPE 방지</code></pre>
<p><strong>생성되는 테이블 구조:</strong></p>
<pre><code class="language-sql"> create table t_user_user_histories (
    user_histories_id bigint not null unique,
    user_id bigint not null
)</code></pre>
<p>*<em>직접 외래키 방식: *</em></p>
<ul>
<li>중간 테이블 없이 직접 외래키를 사용하려면 @JoinColumn을 추가합니다.<pre><code class="language-java">@OneToMany
@JoinColumn(name = &quot;user_id&quot;)  // user_id 컬럼을 외래키로 사용
private List&lt;UserHistory&gt; userHistories = new ArrayList&lt;&gt;();</code></pre>
</li>
</ul>
<h4 id="2-fetch-전략-추가-공부">2. Fetch 전략 (+추가 공부)</h4>
<p><strong>@OneToMany의 EAGER 사용시 주의사항:</strong></p>
<pre><code class="language-java">@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = &quot;user_id&quot;)
private List&lt;UserHistory&gt; userHistories = new ArrayList&lt;&gt;();
</code></pre>
<p>*<em>N+1 문제 발생 위험: *</em></p>
<ul>
<li>성능 저하 가능성</li>
<li>대안: LAZY + 필요시 fetch join 사용</li>
</ul>
<pre><code class="language-java">@Query(&quot;SELECT u FROM User u JOIN FETCH u.userHistories&quot;)
List&lt;User&gt; findAllWithHistories();</code></pre>
<h4 id="3-양방향-매핑시-읽기-전용-설정">3. 양방향 매핑시 읽기 전용 설정</h4>
<pre><code class="language-java">// User 엔티티
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = &quot;user_id&quot;,    // Entity가 어떤 컬럼으로 join할지 지정
        insertable = false, updatable = false // read only 설정
)
private List&lt;UserHistory&gt; userHistories = new ArrayList&lt;&gt;();

// UserHistory 엔티티
@Column(name=&quot;user_id&quot;, insertable = false, updatable = false)
private Long userId;</code></pre>
<h3 id="manytoone">@ManyToOne</h3>
<h4 id="1-양방향-관계에서의-순환참조-문제">1. 양방향 관계에서의 순환참조 문제</h4>
<pre><code class="language-java">// Book 엔티티
class Book {
    @OneToOne(mappedBy = &quot;book&quot;)
    private BookReviewInfo bookReviewInfo;
}

// BookReviewInfo 엔티티
class BookReviewInfo {
    @OneToOne
    private Book book;
}</code></pre>
<p><strong>순환참조 해결:</strong></p>
<pre><code class="language-java">@ToString.Exclude   // Lombok의 ToString에서 배제
private BookReviewInfo bookReviewInfo;</code></pre>
<p><strong>@OneToMany, @ManyToOne</strong> 어느 Entity에서 연관 Entity가 필요한가에 따라 둘 중 어느것을 쓸 지가 달라진다.</p>
<hr>
<h4 id="각-연관관계-fetch-type의-default-속성">각 연관관계 fetch type의 default 속성</h4>
<blockquote>
<p>@OneToMany, @ManyToMany: <strong>LAZY</strong>
@ManyToOne, @OneToOne: <strong>EAGER</strong></p>
</blockquote>
<pre><code class="language-java">@Test
    @Transactional
    void bookRelationTest() {

        // 테스트용 데이터 입력(Publisher, Book, Review)
        givenBookAndReview();

        // 특정 User
        User user = userRepository.findByEmail(&quot;martin@redknight.com&quot;);

        System.out.println(&quot;👇👇👇👇👇👇여기가 무시무시한 query 생성지 👇👇👇👇👇👇&quot;);
        // 특정 User 가 남긴 Review 정보들 가져오기
        System.out.println(&quot;Review: &quot; + user.getReviews());     // getxxx() 시점에서 outer join들을 사용하여 읽어들임

        System.out.println(&quot;😁&quot;.repeat(20));
        // 특정 User 가 남긴 Review 중 첫번째 Review의 Book 정보 가져오기
        System.out.println(&quot;Book: &quot; + user.getReviews().get(0).getBook());

        System.out.println(&quot;😇&quot;.repeat(20));
        // 특정 User 가 남긴 Review 중 첫번째 Review의 Book의 Publisher 정보 가져오기
        System.out.println(&quot;Publisher: &quot; + user.getReviews().get(0).getBook().getPublisher());
    }</code></pre>
<pre><code class="language-sql">👇👇👇👇👇👇여기가 무시무시한 query 생성지 👇👇👇👇👇👇
Hibernate: 
    select
        r1_0.user_id,
        r1_0.id,
        b1_0.id,
        b1_0.author_id,
        bri1_0.id,
        bri1_0.average_review_score,
        bri1_0.book_id,
        bri1_0.created_at,
        bri1_0.review_count,
        bri1_0.updated_at,
        b1_0.category,
        b1_0.created_at,
        b1_0.name,
        p1_0.id,
        p1_0.created_at,
        p1_0.name,
        p1_0.updated_at,
        b1_0.updated_at,
        r1_0.content,
        r1_0.created_at,
        r1_0.score,
        r1_0.title,
        r1_0.updated_at 
    from
        review r1_0 
    left join
        book b1_0 
            on b1_0.id=r1_0.book_id 
    left join
        book_review_info bri1_0 
            on b1_0.id=bri1_0.book_id 
    left join
        publisher p1_0 
            on p1_0.id=b1_0.publisher_id 
    where
        r1_0.user_id=?
Review: [Review(super=BaseEntity(createdAt=2024-12-30T12:18:33.006304, updatedAt=2024-12-30T12:18:33.006304), id=1, title=내 인생을 바꾼 책, content=너무너무 재미있고 즐거운 책이었어요, score=5.0, user=User(super=BaseEntity(createdAt=2024-12-30T12:18:32.669926, updatedAt=2024-12-30T12:18:32.669926), id=1, name=martin, email=martin@redknight.com, gender=null), book=Book(super=BaseEntity(createdAt=2024-12-30T12:18:33.003352, updatedAt=2024-12-30T12:18:33.003352), id=1, name=JPA 완전정복, category=null, authorId=null))]
😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁
Book: Book(super=BaseEntity(createdAt=2024-12-30T12:18:33.003352, updatedAt=2024-12-30T12:18:33.003352), id=1, name=JPA 완전정복, category=null, authorId=null)
😇😇😇😇😇😇😇😇😇😇😇😇😇😇😇😇😇😇😇😇
Publisher: Publisher(super=BaseEntity(createdAt=2024-12-30T12:18:32.990396, updatedAt=2024-12-30T12:18:32.990396), id=1, name=K-출판사, books=[])</code></pre>
<p>JPA의 장점: 
getter만 썼을 뿐인데, 알아서 query가 실행된다는 점</p>
<hr>
<h3 id="manytomany">@ManyToMany</h3>
<p>보통 잘 안 쓰고, 1:N, M:1로 별도의 테이블을 빼서 만든다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] Entity Listener와 JPA CRUD]]></title>
            <link>https://velog.io/@dhlee47-l/JPA</link>
            <guid>https://velog.io/@dhlee47-l/JPA</guid>
            <pubDate>Fri, 27 Dec 2024 08:45:49 GMT</pubDate>
            <description><![CDATA[<h2 id="jpa-entity">JPA Entity</h2>
<h3 id="1-둘의-차이가-뭘까">1. 둘의 차이가 뭘까?</h3>
<p><strong>uniqueConstraints</strong></p>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/36f6fd9a-1c10-4719-a208-f52b2160a50c/image.png" alt=""></p>
<p><strong>email에만 unique 설정</strong></p>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/53a9c3db-53ad-4d58-a994-968fcae17750/image.png" alt=""></p>
<p><em><strong>정답: uniqueConstraints는 복합키로도 unique 설정 가능</strong></em></p>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/5ddb0cdc-a8a7-4518-a9fb-6b70439485c6/image.png" alt=""></p>
<hr>
<h3 id="2-save는-언제-insertupdate일까">2. save는 언제 INSERT/UPDATE일까?</h3>
<pre><code class="language-java">@Test
void insertAndUpdateTest() {
    System.out.println(&quot;\n-- TEST#insertAndUpdateTest() ---------------------------------------------&quot;);
    User user = new User();
    user.setName(&quot;kname&quot;);
    user.setEmail(&quot;kname@mail.com&quot;);

    userRepository.save(user);  // INSERT

    user2=userRepository.findById(1L).orElseThrow(RuntimeException::new);
    user2.setName(&quot;pname&quot;);

    userRepository.save(user2); // UPDATE

    System.out.println(&quot;\n------------------------------------------------------------\n&quot;);
}</code></pre>
<ul>
<li>id를 확인하고 있으면 update, 없으면 insert</li>
</ul>
<hr>
<h3 id="3-transient">3. @Transient</h3>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/727cbf32-83ca-485a-bc2f-f88ee32c492b/image.png" alt=""></p>
<ul>
<li>영속성 처리에서 배제되고, 굳이 db에 반영하고 싶지 않음</li>
<li>단기적으로 잠시 머무르는 변수</li>
</ul>
<hr>
<h3 id="4-enum">4. ENUM</h3>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/061a5a61-3470-4712-a2d0-a5cb050dcf92/image.png" alt=""></p>
<ul>
<li>JPA는 기본적으로 enum을 <strong>정수 타입</strong>으로 저장(0, 1 등)</li>
<li><strong>문자 타입</strong>으로 저장할 수 있도록 설정해주어야 함</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/2432b0ef-1394-4199-a993-63928bdc4a7d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/79f0a6d5-9e03-46f8-85cf-f654e6f509d8/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/65cd1008-44af-4ffc-82cf-df270c418f20/image.png" alt=""></p>
<hr>
<h2 id="listener-사용법">Listener 사용법</h2>
<h4 id="entity-listener">Entity Listener</h4>
<ul>
<li>특정 <strong>이벤트</strong>에서 Entity 관련으로 동작</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/3b852f58-3817-4c19-9340-27804972fdb2/image.png" alt=""></p>
<ul>
<li>auditing: PrePersist, PreUpdate 많이 쓰임</li>
</ul>
<h3 id="1-entity-객체-내부에서-선언">1. Entity 객체 내부에서 선언</h3>
<pre><code class="language-java">@PrePersist
    public void prePersist() {
        System.out.println(&quot;&gt;&gt;&gt; prePersist&quot;);
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }

    @PreUpdate
    public void preUpdate() {
        System.out.println(&quot;&gt;&gt;&gt; preUpdate&quot;);
        this.updatedAt = LocalDateTime.now();
    }

    @PreRemove
    public void preRemove() {
        System.out.println(&quot;&gt;&gt;&gt; preRemove&quot;);
    }

    @PostPersist
    public void postPersist() {
        System.out.println(&quot;&gt;&gt;&gt; postPersist&quot;);
    }

    @PostUpdate
    public void postUpdate() {
        System.out.println(&quot;&gt;&gt;&gt; postUpdate&quot;);
    }

    @PostRemove
    public void postRemove() {
        System.out.println(&quot;&gt;&gt;&gt; postRemove&quot;);
    }

    @PostLoad
    public void postLoad() {
        System.out.println(&quot;&gt;&gt;&gt; postLoad&quot;);
    }
</code></pre>
<ul>
<li>객체 내부에 선언 후 사용</li>
<li>현재시간 입력을 위해 매번 반복되는 코드 / 데이터 정확도 문제</li>
<li>여러 Entity에 동일하게 사용되는 Listener를 담은 클래스 사용하자</li>
</ul>
<h3 id="2-별도의-listener-class">2. 별도의 Listener Class</h3>
<pre><code class="language-java">public interface Auditable {
    LocalDateTime getCreatedAt();
    LocalDateTime getUpdatedAt();

    void setCreatedAt(LocalDateTime createdAt);
    void setUpdatedAt(LocalDateTime updatedAt);
}</code></pre>
<pre><code class="language-java">@EntityListeners(value = {MyEntityListener.class})</code></pre>
<pre><code class="language-java">public class User implements Auditable {</code></pre>
<ul>
<li>Listener class 사용 시, 공통적 부분에 대한 listener를 하나만 구현하여 @EntityListeners를 사용해 참조하여 사용가능하다. 반복적 코딩을 줄일 수 있다.</li>
</ul>
<h4 id="-log-data-히스토리-데이터에-곧-수정될-내용을-history-담기">+ Log Data (히스토리 데이터)에 &quot;곧 수정될 내용&quot;을 history 담기</h4>
<ul>
<li>event listener에서 특정값을 추가하는 경우 말고, 특정 데이터가 수정되면 해당값의 복사본을 다른 테이블에 저장해두는 event일 경우</li>
<li>User Entity에 User data라는 중요 데이터가 있으므로, 수정된 내역의 히스토리를 필요해 보임.</li>
</ul>
<pre><code class="language-java">import com.lec.spring.domain.UserHistory;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserHistoryRepository extends 
    JpaRepository&lt;UserHistory, Long&gt; {</code></pre>
<pre><code class="language-java">@Component
public class UserEntityListener {

    @Autowired
    private UserHistoryRepository userHistoryRepository;

    @PreUpdate  // User 가 UPDATE 수행하기 전
    @PrePersist     // User 가 INSERT 수행하기 전
    public void addHistory(Object o) {
        System.out.println(&quot;&gt;&gt;UserEntityListener addHistory() 호출&quot;);

        User user = (User) o;
        // UserHistory 에 UPDATE 될 User 정보 담아서 저장 (INSERT)
        UserHistory userHistory = new UserHistory();
        userHistory.setUserId(user.getId());
        userHistory.setName(user.getName());
        userHistory.setEmail(user.getEmail());

        userHistoryRepository.save(userHistory);    // INSERT
    }</code></pre>
<pre><code class="language-java">@EntityListeners(value = {MyEntityListener.class, UserEntityListener.class})</code></pre>
<ul>
<li>but,Entity Listener는 Spring Bean 주입받지 못해 NPE 발생</li>
</ul>
<pre><code class="language-java">// Listener 안에서 스프링 빈 객체 수동으로 주입받기
        UserHistoryRepository userHistoryRepository = BeanUtils.getBean(
        UserHistoryRepository.class);</code></pre>
<ul>
<li>UserHistory에도 @EntityListeners 설정 : INSERT, UPDATE될 때, createdAt, updatedAt이 이제 자동으로 세팅된다. 코드의 반복이 줄어들었음. </li>
</ul>
<pre><code class="language-java">@EntityListeners(value = MyEntityListener.class)</code></pre>
<ul>
<li>생성일, 수정일은 워낙 많은 Entity에서도 사용하여, Spring에서도 기본 Listener를 제공하고 있다.</li>
</ul>
<h3 id="3-spring에서-제공하는-auditingeventlistener-사용">3. Spring에서 제공하는 AuditingEventListener 사용</h3>
<p><strong>@EnableJpaAuditing</strong></p>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/861da743-4b69-4fb5-9e05-6c3cef89a728/image.png" alt=""></p>
<pre><code class="language-java">@EntityListeners(value = AuditingEntityListener.class)</code></pre>
<pre><code class="language-java">@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;</code></pre>
<ul>
<li>이렇게 되면, createdAt, updatedAt를 엄청 반복하니, 리팩토링해서 AuditingEntityListener 사용해보자.</li>
</ul>
<p><strong>BaseEntity</strong></p>
<pre><code class="language-java">@Data
@MappedSuperclass   // (JPA)가 이 클래스의 속성을, 상속받는 Entity 에 포함시켜줌
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity{

    @CreatedDate
    private LocalDateTime createdAt;
    @LastModifiedDate
    private LocalDateTime updatedAt;

}</code></pre>
<pre><code class="language-java">@EntityListeners(value = {UserEntityListener.class})
public class User extends BaseEntity implements Auditable {</code></pre>
<p><strong>Lombok의 toString 문제 해결</strong></p>
<pre><code class="language-java">// 부모쪽도 toString으로 호출
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)</code></pre>
<ul>
<li>매 Entity마다 Auditable을 implement 할 필요없이, BaseEntity만 Auditable을 implement하게 하면 된다.</li>
</ul>
<pre><code class="language-java">public class User extends BaseEntity {</code></pre>
<pre><code class="language-java">@Data
@MappedSuperclass   // (JPA)가 이 클래스의 속성을, 상속받는 Entity 에 포함시켜줌
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity implements Auditable {

    @CreatedDate
    private LocalDateTime createdAt;
    @LastModifiedDate
    private LocalDateTime updatedAt;

}</code></pre>
<hr>
<h2 id="jpa-crud">JPA CRUD</h2>
<pre><code class="language-java">@Repository
public interface PostRepository extends JpaRepository&lt;Post, Long&gt; {
}</code></pre>
<pre><code class="language-java">@Service
public class BoardServiceImpl implements BoardService {

    private final PostRepository postRepository;

    public BoardServiceImpl(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @Override
    public int write(Post post) {
        postRepository.save(post);
        return 1;
    }

    @Override
    public Post detail(Long id) {
        Optional&lt;Post&gt; postOptional = postRepository.findById(id);
        if (postOptional.isPresent()) {
            Post post = postOptional.get();

            if (post.getViewCnt() == null) {
                post.setViewCnt(1L);
            } else {
                post.setViewCnt(post.getViewCnt() + 1);
            }

            postRepository.save(post);
            return post;
        }
        return null;
    }


    @Override
    public List&lt;Post&gt; list() {
        return postRepository.findAll(Sort.by(Sort.Order.desc(&quot;id&quot;)));
    }

    @Override
    public Post selectById(Long id) {
        return postRepository.findById(id).orElse(null);
    }

    @Override
    public int update(Post post) {
        // 주의! 위 post 매개변수에는 viewcnt 값이 없기 때문에
        // 위 post 값으로 save 하면 viewcnt 값은 0 으로 초기화 된다.

        Optional&lt;Post&gt; existingPost = postRepository.findById(post.getId());
        if (existingPost.isPresent()) {
            Post updatedPost = existingPost.get();
            updatedPost.setSubject(post.getSubject());
            updatedPost.setContent(post.getContent());
            postRepository.save(updatedPost);
            return 1;
        }
        return 0;
    }

    @Override
    public int deleteById(Long id) {
        if (postRepository.existsById(id)) {
            postRepository.deleteById(id);
            return 1;
        }
        return 0;
    }
}</code></pre>
<p>이런 식으로 사용된다. 
refactoring 하면서 허무한 마음이 들었다. 내가 쓰던 sql query문들은 어디로..</p>
<p>물론 relation에 대해 배우면 좀 더 복잡해지겠지만, 일단 이걸로 돌아간다니!
ORM을 쓰는 이유가 너무나 뼈저리게 느껴졌다.</p>
<hr>
<h2 id="jpa-entity-relation">JPA Entity Relation</h2>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/68046a14-a9b5-4b01-810d-c2403190bc56/image.png" alt=""></p>
<ul>
<li>Entity는 양방향 참조하는 경우가 많다.</li>
</ul>
<p><strong>1:1</strong></p>
<ul>
<li>지속적으로 서비스 무중단 제공해야 하고, 속성을 추가해야하는 방식의 컬럼이라면, 1:1 연관관계를 맺는 테이블을 추가하는 것도 방법.</li>
</ul>
<hr>
<p><em><strong>3편에서 이어서</strong></em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] JPA와 Query Method]]></title>
            <link>https://velog.io/@dhlee47-l/JPA-JPA%EC%99%80-Query-Method</link>
            <guid>https://velog.io/@dhlee47-l/JPA-JPA%EC%99%80-Query-Method</guid>
            <pubDate>Thu, 26 Dec 2024 08:26:24 GMT</pubDate>
            <description><![CDATA[<h2 id="1-jpa">1. JPA</h2>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/dd814839-5705-495a-b637-22f99368676f/image.png" alt=""></p>
<p><strong>Persistence</strong>:</p>
<ul>
<li>영속성</li>
<li><strong>데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성</strong></li>
<li>영구적으로 파일, 데이터베이스 등 활용하여 저장</li>
</ul>
<p><strong>ORM</strong>:</p>
<ul>
<li>object relational mapping</li>
<li><strong>비영속성 객체를 영속성 객체에 연결해주는 기술</strong></li>
<li>직접 매핑하기 때문에 쿼리문을 거의 다루지 않음</li>
<li>자바 객체 다루듯 작업</li>
</ul>
<p><strong>JPA</strong>:</p>
<ul>
<li>Java Persistence API</li>
<li><strong>Java 진영에서 ORM 표준으로 채택된 API</strong></li>
<li>데이터베이스 코드 관련 유연성</li>
<li>어설프게 다루면 성능 이슈 만들 수 있음</li>
<li>데이터베이스에 대해 독립적이라, 특정 데이터베이스의 강력한 기능 활영하기 어려움</li>
</ul>
<p><strong>Hibernate</strong>:</p>
<ul>
<li>JPA의 실제 구현체</li>
</ul>
<p><strong>Spring Data JPA</strong>:</p>
<ul>
<li>Hibernate를 더 간편하게 사용하기 위해 만든 기술</li>
</ul>
<p>이제까지 사용한 <strong>MyBatis -&gt; Persistence Context</strong></p>
<p><strong>Entity</strong>:
<img src="https://velog.velcdn.com/images/dhlee47-l/post/1149febf-6f30-4677-b23f-0064780f1f7b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/f0616d10-408e-4ff1-8b4b-61f146ea6c18/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dhlee47-l/post/5d40c8c3-1e5d-4d78-9d0b-e5b70924c37b/image.png" alt=""></p>
<p><strong>Entity Manager</strong>:</p>
<ul>
<li>persistenece context에 넣어두고 객체들의 생사 관리</li>
<li>엔티티에 대한 정보를 수정한다</li>
</ul>
<p>엔티티 Manager 메소드의 상태</p>
<ul>
<li>비영속 상태</li>
<li>영속된</li>
<li>Detached(준영속)</li>
<li>지워져야 함</li>
</ul>
<p><strong>Optional</strong>:</p>
<ul>
<li>null일 수도 있는 객체를 감싸는 래퍼 클래스</li>
<li>NullPointerException을 방지하는데 도움을 줌</li>
<li>명시적으로 값이 없을 수 있다는 것을 표현</li>
</ul>
<p>Optional 사용 시 주의사항:</p>
<ul>
<li>Optional을 필드로 사용하지 않기</li>
<li>Optional을 생성자나 메서드의 매개변수로 사용하지 않기</li>
<li>Collections, Arrays 등의 컨테이너를 Optional로 감싸지 않기</li>
<li>Optional.get() 직접 호출은 피하고 대신 orElse, orElseGet, orElseThrow</li>
</ul>
<pre><code class="language-java">// Optional 생성
Optional&lt;String&gt; opt1 = Optional.empty();  // 빈 Optional
Optional&lt;String&gt; opt2 = Optional.of(&quot;값&quot;);  // null이 아닌 값으로 Optional 생성
Optional&lt;String&gt; opt3 = Optional.ofNullable(nullable);  // null일 수도 있는 값으로 생성

// 값 처리
opt.isPresent();  // 값이 존재하는지 확인
opt.isEmpty();    // 값이 없는지 확인
opt.get();        // 값 가져오기 (값이 없으면 예외 발생)
opt.orElse(&quot;기본값&quot;);  // 값이 없을 때 기본값 반환
opt.orElseGet(() -&gt; &quot;기본값 생성&quot;);  // 값이 없을 때 기본값 생성</code></pre>
<pre><code class="language-java">@Repository
public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
    Optional&lt;User&gt; findByEmail(String email);
}

@Service
public class UserService {
    public User getUser(String email) {
        return userRepository.findByEmail(email)
            .orElseThrow(() -&gt; new UserNotFoundException(&quot;사용자를 찾을 수 없습니다&quot;));
    }
}</code></pre>
<p><strong>Flush</strong>:</p>
<ul>
<li>SQL쿼리의 변화를 주는게 아니라 &#39;DB 반영 시점&#39;을 조정한다.</li>
<li>로그 출력으로는 변화를 확인하기 힘들다</li>
</ul>
<p><strong>QueryByExample(QBE)</strong></p>
<ul>
<li>복잡한 조건에 대한 쿼리문을 작성할 때 사용</li>
<li>비교적 최근에 만들어짐</li>
<li>크게 효과적이진 않음</li>
</ul>
<hr>
<h2 id="2-query-method">2. Query Method</h2>
<ul>
<li>where 조건절을 메소드 이름 선언만으로 만들어주도록 한다</li>
</ul>
<pre><code class="language-java">public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
    List&lt;User&gt; findByName(String name);
    User findByEmail(String email);
}</code></pre>
<ul>
<li>Repository 인터페이스를 생성하고 JpaRepository를 상속받는다.</li>
<li>제네릭 타입으로 엔티티 클래스와 ID 타입을 지정한다.</li>
<li>메소드 이름 규칙에 따라 쿼리 메소드를 정의.</li>
</ul>
<pre><code class="language-java">User findByEmail(String email);
User getByEmail(String email);
User readByEmail(String email);
User queryByEmail(String email);
User searchByEmail(String email);
User streamByEmail(String email);</code></pre>
<h3 id="조건절-키워드">조건절 키워드</h3>
<h4 id="비교-연산자">비교 연산자</h4>
<pre><code class="language-java">List&lt;User&gt; findByAgeGreaterThan(int age);              // &gt; 
List&lt;User&gt; findByAgeGreaterThanEqual(int age);         // &gt;=
List&lt;User&gt; findByAgeLessThan(int age);                 // 
List&lt;User&gt; findByAgeLessThanEqual(int age);            // &lt;=</code></pre>
<h4 id="논리-연산자">논리 연산자</h4>
<pre><code class="language-java">List&lt;User&gt; findByEmailAndName(String email, String name);   // AND
List&lt;User&gt; findByEmailOrName(String email, String name);    // OR </code></pre>
<h4 id="between">Between</h4>
<pre><code class="language-java">List&lt;User&gt; findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);
List&lt;User&gt; findByAgeBetween(int start, int end);</code></pre>
<h4 id="like-검색">Like 검색</h4>
<pre><code class="language-java">List&lt;User&gt; findByNameStartingWith(String prefix);      // LIKE &#39;prefix%&#39;
List&lt;User&gt; findByNameEndingWith(String suffix);        // LIKE &#39;%suffix&#39;
List&lt;User&gt; findByNameContains(String word);           // LIKE &#39;%word%&#39;
List&lt;User&gt; findByNameLike(String likePattern);        // </code></pre>
<h4 id="like-직접-지정-null-체크-in-절">LIKE 직접 지정, NULL 체크, IN 절</h4>
<pre><code class="language-java">List&lt;User&gt; findByIdIsNotNull();
List&lt;User&gt; findByIdIsNull();
List&lt;User&gt; findByNameIn(List&lt;String&gt; names);</code></pre>
<h4 id="정렬과-페이징">정렬과 페이징</h4>
<pre><code class="language-java">// 메소드 이름으로 정렬
List&lt;User&gt; findByNameOrderByIdDesc(String name);

// Sort 파라미터로 정렬
List&lt;User&gt; findByName(String name, Sort sort);</code></pre>
<h4 id="페이징">페이징</h4>
<pre><code class="language-java">// Pageable 파라미터로 페이징 처리
Page&lt;User&gt; findByName(String name, Pageable pageable);

// 사용 예시
PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, &quot;id&quot;));
Page&lt;User&gt; userPage = userRepository.findByName(&quot;John&quot;, pageRequest);</code></pre>
<h4 id="예시">예시</h4>
<pre><code class="language-java">// 최근 24시간 내 생성된 활성 사용자 찾기
List&lt;User&gt; findByCreatedAtAfterAndActiveTrue(LocalDateTime yesterday);

// 특정 이메일 도메인을 사용하는 사용자 찾기
List&lt;User&gt; findByEmailEndingWith(String domain);

// 이름으로 검색하고 ID로 정렬하여 상위 5명 가져오기
List&lt;User&gt; findTop5ByNameContainingOrderByIdDesc(String name);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA Spring #6]]></title>
            <link>https://velog.io/@dhlee47-l/JAVA-Spring-6</link>
            <guid>https://velog.io/@dhlee47-l/JAVA-Spring-6</guid>
            <pubDate>Thu, 14 Nov 2024 08:40:37 GMT</pubDate>
            <description><![CDATA[<h2 id="cookie--session">Cookie &amp; Session</h2>
<blockquote>
<p>동일한 user의 computer라 하더라도 session, cookie는 브라우저가 다르면 다르게 관리한다. <strong>cookie, session은 브라우저가 관리한다.</strong></p>
</blockquote>
<h3 id="cookie">Cookie</h3>
<ul>
<li>Server: <strong>Cookie 생성</strong> -&gt; Response에 담아 보냄</li>
<li>Browser: <strong>Cookie 저장</strong></li>
<li>Browser: 다음 요청 보낼 때 Cookie 담아보냄</li>
<li>Server: 내가 보냈던 거란 걸 알고 클라이언트 상세 정보를 저장(name value 쌍)</li>
</ul>
<p><strong>네트워크 연결성</strong></p>
<ul>
<li>connection도 system 자원이므로, 웹은 request, response가 완료되면 접속이 끊어짐 (= 통신의 연속성이 없음)</li>
<li>쿠키의 등장</li>
</ul>
<p><strong>Spring 환경에서 Cookie 다루는 법</strong></p>
<ol>
<li>Severlet의 HttpServletResponse 객체 사용</li>
<li>CookieValue(&quot;key&quot;) 사용</li>
</ol>
<pre><code class="language-java">package com.lec.spring.controller5;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Controller
@RequestMapping(&quot;/cookie&quot;)
public class CookieController {

    // 클라이언트로부터 온 request 안의 cookie 정보들 조회
    @RequestMapping(&quot;/list&quot;)
    public void list(HttpServletRequest request, Model model){
        // 클라이언트 안의 쿠키 정보는 request 시에 서버로 전달된다.
        // request.getCookies() 로 쿠키 받아올수 있다.
        Cookie[] cookies = request.getCookies();

        StringBuffer buff = new StringBuffer();

        if(cookies != null){  // 쿠키가 하나도 없다면 null 을 리턴한다
            for (int i = 0; i &lt; cookies.length; i++) {
                // Cookie 는 name-value 쌍으로 이루어진 데이터 (name, value 는 모두 String)
                String name = cookies[i].getName();
                String value = cookies[i].getValue();
                buff.append((i + 1) + &quot;] &quot; + name + &quot; : &quot; + value + &quot;&lt;br&gt;&quot;);
            }

        } else {
            buff.append(&quot;쿠키가 없습니다&lt;br&gt;&quot;);
        }

        model.addAttribute(&quot;result&quot;, buff.toString());

    }

    // 쿠키 생성 절차
    //1. 쿠키(Cookie) 클래스로 생성
    //2. 쿠키속성 설정(setter)
    //3. 쿠키의 전송 (response 객체에 탑재:addCookie())
    @RequestMapping(&quot;/create&quot;)
    public String create(HttpServletResponse response) throws IOException {
        String cookieName1 = &quot;num1&quot;;
        String cookieValue1 = &quot;&quot; + (int)(Math.random() * 10);
        Cookie cookie1 = new Cookie(cookieName1, cookieValue1);  // name-value 쌍으로 Cookie 생성
        cookie1.setMaxAge(30); // 쿠키 파기(expiry) 시간 설정 (생성 시점으로부터 30 초 후)
        response.addCookie(cookie1);  // response 에 Cookie 추가

        // 쿠키는 얼마든지 생성 가능
        String cookieName2 = &quot;datetime&quot;;
        String cookieValue2 = LocalDateTime.now().format(DateTimeFormatter.ofPattern(&quot;yyyyMMddhhmmss&quot;));

        Cookie cookie2 = new Cookie(cookieName2, cookieValue2);
        cookie2.setMaxAge(45);
        response.addCookie(cookie2);

        // response.sendRedirect(&quot;/cookie/list&quot;);
        return &quot;redirect:/cookie/list&quot;;
    }

    @RequestMapping(&quot;/delete&quot;)
    public String delete(HttpServletResponse response){
        String cookieName = &quot;num1&quot;;  // 삭제할 cookie 의 name
        Cookie cookie = new Cookie(cookieName, &quot;&quot;);
        cookie.setMaxAge(0);   // response 되자마자 해당 name 의 cookie 는 삭제됨.
        response.addCookie(cookie);

        return &quot;redirect:/cookie/list&quot;;
    }

    @RequestMapping(&quot;/num1&quot;)
    @ResponseBody
    public String num1(@CookieValue(value = &quot;num1&quot;, required = false) Cookie cookie){

        if(cookie != null){
            return cookie.getName() + &quot; : &quot; + cookie.getValue();
        }

        return &quot;num1 쿠키 없어요&quot;;
    }

    //------------------------------------------------------------------------
    public static final String ADMIN_ID = &quot;admin&quot;;
    public static final String ADMIN_PW = &quot;1234&quot;;

    @GetMapping(&quot;/login&quot;)
    public void login(@CookieValue(name=&quot;username&quot;, required = false) String username, Model model){
        model.addAttribute(&quot;username&quot;, username);
    }

    @PostMapping(&quot;/login&quot;)
    public String loginOk(String username, String password, HttpServletResponse response, Model model){
        // username /password 일치하면 인증성공 -&gt; 쿠키 발급
        if(ADMIN_ID.equalsIgnoreCase(username) &amp;&amp; ADMIN_PW.equals(password)){
            Cookie cookie = new Cookie(&quot;username&quot;, username);
            cookie.setMaxAge(30);
            response.addCookie(cookie);

            model.addAttribute(&quot;result&quot;, true);
        } else {
            Cookie cookie = new Cookie(&quot;username&quot;, username);
            cookie.setMaxAge(0);   // 기존에 혹시 있었을 쿠키도 삭제한다.
            response.addCookie(cookie);
        }

        return &quot;cookie/loginOk&quot;;
    }

    @PostMapping(&quot;/logout&quot;)
    public String logout(HttpServletResponse response){
        Cookie cookie = new Cookie(&quot;username&quot;, &quot;&quot;);
        cookie.setMaxAge(0);   // 기존에 혹시 있었을 쿠키도 삭제한다.
        response.addCookie(cookie);

        return &quot;cookie/logout&quot;;
    }


}
</code></pre>
<h3 id="session">Session</h3>
<ul>
<li>Client의 상태를 기억하는 웹 기술</li>
<li>Client: Request</li>
<li><strong>Server</strong>: <strong>new Client의 상세정보 저장</strong> 위한 (name-value쌍) 테이블 형성 / 오로지 이 Client만을 위한(고유한 Id가 있는) table = session =&gt; Cookie에 비해 보안성이 높고 제한이 크게 없음</li>
<li>Session Reponse값에 쿠기 Session id 끼워서 보냄</li>
<li>클라이언트가 이 쿠키를 보게 됨</li>
<li>Client: 다시 request 보낼 때 cookie의 Session id를 넣어 보냄 (Session id에는 무엇이든 들어갈 수 있음)</li>
</ul>
<p><strong>Spring이 session을 다루는 법</strong></p>
<ol>
<li>HttpSession object 사용</li>
<li>@SessionAttribute 사용</li>
<li>Model에 저장 시, session, request에 저장 (RedirectAttribute도 내부적으로 session 사용)</li>
</ol>
<pre><code class="language-java">package com.lec.spring.controller5;

import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Enumeration;

@Controller
@RequestMapping(&quot;/session&quot;)
public class SessionController {

    // HttpSession 객체
    //  현재 request 한 client 에 대한 Session 정보
    //  session 은 attribute(name-value쌍) 들이 담겨 있다
    //   name: String 타입,  value: Object 타입
    @RequestMapping(&quot;/list&quot;)
    public void list(HttpSession session, Model model){
        Enumeration&lt;String&gt; enumeration = session.getAttributeNames();

        StringBuffer buff = new StringBuffer();

        int i = 0;
        while(enumeration.hasMoreElements()){
            String sessionName = enumeration.nextElement();
            // session.getAttribute(&#39;name&#39;)  &lt;-- 특정 세션 attr value 추출. 리턴타입 Object. 해당 name 이 없으면 null 리턴
            String sessionValue = session.getAttribute(sessionName).toString();

            buff.append((i + 1) + &quot;] &quot; + sessionName + &quot; : &quot; + sessionValue + &quot;&lt;br&gt;&quot;);
            i++;
        }
        if(i == 0){
            buff.append(&quot;세션 안에 attribute 가 없습니다&lt;br&gt;&quot;);
        }

        model.addAttribute(&quot;result&quot;, buff.toString());
    }

    @RequestMapping(&quot;/create&quot;)
    public String create(HttpSession session){
        String sessionName, sessionValue;

        sessionName = &quot;num1&quot;;
        sessionValue = &quot;&quot; + (int)(Math.random() * 100);

        // 세션 attr : name-value 생성
        // setAttribute(String name, Object value) 두번째 매개변수는 Object 타입이다
        session.setAttribute(sessionName, sessionValue);

        sessionName = &quot;datetime&quot;;
        sessionValue = LocalDateTime.now().format(DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd hh:mm:ss&quot;));
        session.setAttribute(sessionName, sessionValue);

        return &quot;redirect:/session/list&quot;;
    }

    @RequestMapping(&quot;/delete&quot;)
    public String delete(HttpSession session){
        // removeAttribute(name) 세션 attribute 삭제
        session.removeAttribute(&quot;num1&quot;);
        return &quot;redirect:/session/list&quot;;
    }

    //-------------------------------------------------
    public static final String ADMIN_ID = &quot;admin&quot;;
    public static final String ADMIN_PW = &quot;1234&quot;;

    @GetMapping(&quot;/login&quot;)
    public void login(HttpSession session, Model model){
        // 현재 로그인 상태인지, 즉 로그인 세션 (name이 &#39;username&#39;인 세션값)이 있는지 확인
        if(session.getAttribute(&quot;username&quot;) != null){
            model.addAttribute(&quot;username&quot;, session.getAttribute(&quot;username&quot;));
        }
    }

    @PostMapping(&quot;/login&quot;)
    public String loginOk(String username, String password, HttpSession session
            , Model model){

        // 세션 name-value 지정
        String sessionName = &quot;username&quot;;
        String sessionValue = username;

        // 제출된 id /pw 값이 일치하면 로그인 성공 + 세션 attr 생성
        if(ADMIN_ID.equalsIgnoreCase(username) &amp;&amp; ADMIN_PW.equals(password)){
            session.setAttribute(sessionName, sessionValue);
            model.addAttribute(&quot;result&quot;, true);
        }else{
            session.removeAttribute(sessionName);
        }

        return &quot;session/loginOk&quot;;
    }

    @PostMapping(&quot;/logout&quot;)
    public String logout(HttpSession session){

        String sessionName = &quot;username&quot;;

        // 세션 삭제
        session.removeAttribute(sessionName);

        return &quot;session/logout&quot;;
    }

}</code></pre>
]]></description>
        </item>
    </channel>
</rss>