<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>raymond_yoon.log</title>
        <link>https://velog.io/</link>
        <description>흘러가되 원하는 방향으로</description>
        <lastBuildDate>Thu, 24 Apr 2025 11:32:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>raymond_yoon.log</title>
            <url>https://velog.velcdn.com/images/raymond_yoon/profile/c7660cc6-94f4-4895-be1c-37c9ca6a6d29/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. raymond_yoon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/raymond_yoon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[개인프로젝트(6) : GPT 챗봇 기능 통합 및 테스트 과정]]></title>
            <link>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B86-GPT-%EC%B1%97%EB%B4%87-%EA%B8%B0%EB%8A%A5-%ED%86%B5%ED%95%A9-%EB%B0%8F-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B86-GPT-%EC%B1%97%EB%B4%87-%EA%B8%B0%EB%8A%A5-%ED%86%B5%ED%95%A9-%EB%B0%8F-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Thu, 24 Apr 2025 11:32:54 GMT</pubDate>
            <description><![CDATA[<p>오늘 하루는 유독 마음이 무거웠다.</p>
<p>며칠 전, <strong>AMR(자율 이동 로봇)</strong>과 디지털 트윈 시스템을 기반으로 하는 회사에 프로그램 개발자 포지션으로 면접을 봤다.
C#과 Python, 그리고 Unity를 활용해 ARM과 API를 연동하는 기술들이 주된 업무였고, 나 역시 그런 기술적 환경에 흥미를 느껴 지원하게 되었다.</p>
<p>면접 자체는 분위기도 나쁘지 않았고, 질문에 차분하게 답변도 잘한 편이었다.
하지만 시간이 지나며 문득 드는 생각은,
“내가 아직 이쪽 전문성을 증명할 수 있는 스펙이 부족했나 보다.”</p>
<p>자격증, 어학점수 같은 기본적인 부분부터, 해당 기술에 대한 실무경험이 적다는 걸 그 자리에서 실감했다.
그 회사가 정말 가고 싶었기 때문에 더 아쉬움이 크게 느껴졌다.</p>
<p>면접에서는 이틀 내 결과를 알려주겠다고 했지만, 오늘로 3일째.
연락이 없다는 건 사실상 탈락이라는 뜻일지도 모르겠다.
아쉬움은 남지만, 다시 마음을 추슬러야겠다고 생각했다.</p>
<p>그래서 오늘은 다시 내 프로젝트로 돌아왔다.
SpringBoot + React 기반의 챗봇 기능을 붙이기 위해 GPT API 연동을 구현했다.
WebClient를 통해 요청을 보내고, 사용자 메시지에 응답하도록 백엔드 로직을 작성하고, 프론트에서도 반응형으로 메시지를 출력하도록 구성했다.</p>
<p>마음 한구석은 아직 무겁지만,
하나씩 구현해가며 중심을 다시 세우는 중이다.</p>
<h1 id="🧪-챗봇-기능-연동-일지-react--spring-boot--openai-api">🧪 챗봇 기능 연동 일지 (React + Spring Boot + OpenAI API)</h1>
<h2 id="🧱-오늘의-목표">🧱 오늘의 목표</h2>
<p>React 프론트에서 질문을 입력하면, Spring Boot 백엔드를 거쳐 GPT가 답변해주는 반려견 상담 챗봇을 구현하는 것!</p>
<hr>
<h2 id="🖼️-프론트-작업">🖼️ 프론트 작업</h2>
<h3 id="1-🧠-chatbotpagejs-구현">1. 🧠 ChatbotPage.js 구현</h3>
<ul>
<li><code>useState</code>로 질문과 답변을 관리</li>
<li>Axios로 POST 요청 전송</li>
<li>GPT 응답이 오면 <code>assistant</code> 역할로 출력</li>
</ul>
<h3 id="2-🔗-라우팅-연결">2. 🔗 라우팅 연결</h3>
<ul>
<li><code>App.js</code>에 <code>/chatbot</code> 경로를 추가하고 <code>ChatbotPage</code> 컴포넌트 렌더링</li>
<li>헤더에 버튼 하나 추가해서 챗봇 페이지 쉽게 접근 가능하게 만듦</li>
</ul>
<h3 id="3-✨-커밋-로그">3. ✨ 커밋 로그</h3>
<pre><code class="language-bash">git add src/App.js
git commit -m &quot;fix: App 컴포넌트에서 라우팅 경로 수정 및 챗봇 페이지 연결&quot;

git add src/components/Header.js
git commit -m &quot;feat: 헤더에 챗봇 페이지로 이동하는 버튼 추가&quot;

git add src/pages/Post.js
git commit -m &quot;refactor: 게시글 상세페이지에 채팅 버튼 추가&quot;

git push origin main</code></pre>
<hr>
<h2 id="⚙️-백엔드-작업">⚙️ 백엔드 작업</h2>
<h3 id="1-🔐-securityconfig-설정">1. 🔐 SecurityConfig 설정</h3>
<ul>
<li><code>/api/chatbot/ask</code> 경로 인증 필요하도록 설정 (<code>requestMatchers</code>)</li>
<li>JWT 필터 적용 (<code>addFilterBefore</code>)</li>
</ul>
<h3 id="2-🧼-jwtauthenticationfilter-수정">2. 🧼 JwtAuthenticationFilter 수정</h3>
<ul>
<li>chatbot 경로는 인증 없이 통과 가능하도록 예외 처리 추가</li>
</ul>
<h3 id="3-🧠-gptservice-연결">3. 🧠 GPTService 연결</h3>
<ul>
<li>WebClient 사용해서 OpenAI API에 질문 전달</li>
<li>model은 <code>gpt-4o</code>, 시스템 프롬프트는 반려견 상담 챗봇으로 설정</li>
</ul>
<h3 id="4-🚨-에러-처리">4. 🚨 에러 처리</h3>
<ul>
<li><code>403 Forbidden</code>: 인증 없이 접근 시 발생 (→ 수정 완료)</li>
<li><code>429 Too Many Requests</code>: GPT API 쿼터 초과 (크레딧 부족으로 결제 필요)</li>
</ul>
<hr>
<h2 id="💰-결제-확인">💰 결제 확인</h2>
<ul>
<li>기존 무료 크레딧 소진되어 <code>$0.00</code></li>
<li>테스트용으로 $5 충전 (OpenAI API는 ChatGPT 유료 플랜과는 별개)
<img src="https://velog.velcdn.com/images/raymond_yoon/post/d7a3e8b6-fd9b-45b4-9735-2e6a95b9808d/image.png" alt=""></li>
</ul>
<hr>
<h2 id="✅-결과">✅ 결과</h2>
<ul>
<li>Postman으로 정상 응답 받음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/f7faa210-b1e3-4181-8fbb-4f70474b1f23/image.png" alt=""></p>
<hr>
<h2 id="📌-회고">📌 회고</h2>
<blockquote>
<p>오늘의 한줄 요약: <strong>백엔드 연결까지는 성공! 이제 챗봇 똑똑하게 만드는 건 과금 싸움이다 💸</strong></p>
</blockquote>
<p>앞으로는 하나씩 구현하면서, 천천히 실력도, 내 마음도 다시 채워나가자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개인프로젝트 (5) : JWT 기반 WebSocket 채팅 구현기]]></title>
            <link>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-5-JWT-%EA%B8%B0%EB%B0%98-WebSocket-%EC%B1%84%ED%8C%85-%EA%B5%AC%ED%98%84%EA%B8%B0</link>
            <guid>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-5-JWT-%EA%B8%B0%EB%B0%98-WebSocket-%EC%B1%84%ED%8C%85-%EA%B5%AC%ED%98%84%EA%B8%B0</guid>
            <pubDate>Thu, 24 Apr 2025 11:19:32 GMT</pubDate>
            <description><![CDATA[<p>요즘은 헬스도 꾸준히 다니고 있고, 탁구 동호회에도 들어가서 몸도 마음도 리프레시 중이다.<br>그런데도 개발에 대한 갈증은 쉽게 사라지지 않아서, 최근 진행 중인 개인프로젝트에 &#39;실시간 채팅&#39; 기능을 붙여보기로 했다.</p>
<p>얼마 전엔 스타트업 기술 면접도 봤는데, 무려 6시간 동안 진행돼서 진이 쏙 빠졌지만, 돌아보면 정말 좋은 경험이었다.<br>준비가 부족했던 부분도 보였고, 놓쳤던 것도 많았지만 덕분에 지금 내가 부족한 게 뭔지 명확히 보였달까.</p>
<p>그래서 이번엔 진짜 하나라도 더 구현해보자!는 마음으로, React + Spring Boot 기반의 게시판 + 결제 프로젝트에 WebSocket 채팅 기능을 직접 구현해봤다.</p>
<h1 id="💬-jwt-기반-websocket-채팅-구현기-feat-성능-개선-3종-세트">💬 JWT 기반 WebSocket 채팅 구현기 (feat. 성능 개선 3종 세트)</h1>
<h2 id="✨-프로젝트-중-실시간-채팅-기능을-추가하게-되었다">✨ 프로젝트 중 실시간 채팅 기능을 추가하게 되었다</h2>
<p>React + Spring Boot로 게시판 + 결제 기능을 구현하고 있었는데, 문득 &quot;게시글 작성자와 실시간으로 대화할 수 있으면 좋겠다&quot;는 생각이 들어 WebSocket 채팅 기능을 붙이게 되었다.</p>
<p>처음엔 막막했지만, 해보니까 생각보다 재미있고, 성능 개선까지 이어지면서 전체적으로 만족스러운 작업이 되었다.</p>
<hr>
<h2 id="🔒-jwt-기반-websocket-인증-처리">🔒 JWT 기반 WebSocket 인증 처리</h2>
<p>WebSocket은 일반 HTTP와 달리 Header에 토큰을 넣을 수 없기 때문에, SockJS 연결 시 쿼리파라미터로 토큰을 전달하고, 백엔드에서는 WebSocket handshake 과정에서 이걸 검증했다.</p>
<pre><code class="language-java">@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.interceptors(new ChannelInterceptor() {
        @Override
        public Message&lt;?&gt; preSend(Message&lt;?&gt; message, MessageChannel channel) {
            StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
            if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                String token = accessor.getFirstNativeHeader(&quot;token&quot;);
                String email = jwtTokenProvider.getUserEmail(token);
                accessor.getSessionAttributes().put(&quot;userEmail&quot;, email);
            }
            return message;
        }
    });
}</code></pre>
<blockquote>
<p>✅ WebSocket에서도 사용자를 식별 가능하게 됨</p>
</blockquote>
<hr>
<h2 id="⚙️-성능-개선-및-구조-최적화">⚙️ 성능 개선 및 구조 최적화</h2>
<p>채팅 기능을 붙이다 보니, 성능이나 구조적으로 아쉬운 부분들이 보여서 리팩토링도 병행했다. 특히 다음 3가지는 눈에 띄게 체감될 정도로 개선이 되었다.</p>
<h3 id="1-websocket-메시지-저장-속도-개선">1. WebSocket 메시지 저장 속도 개선</h3>
<p>처음에는 메시지를 수신할 때마다 동기적으로 DB에 저장하고 있었는데, 이러면 실시간성이 떨어졌다.</p>
<pre><code class="language-java">// 기존: 동기 저장 → 메시지 전송이 느림
chatMessageRepository.save(message);

// 개선: 비동기 저장 처리
@Async
public void saveMessageAsync(ChatMessage message) {
    chatMessageRepository.save(message);
}</code></pre>
<blockquote>
<p>✅ WebSocket 응답 속도 약 35% 향상 (체감 확실)</p>
</blockquote>
<hr>
<h3 id="2-채팅방-목록-조회-시-n1-문제-해결">2. 채팅방 목록 조회 시 N+1 문제 해결</h3>
<p>ChatRoom 조회 후 sender/receiver의 nickname을 각각 호출하면서 N+1 쿼리 폭탄 발생...</p>
<pre><code class="language-java">// 개선 전
room.getSender().getNickname(); // 쿼리 발생

// 개선 후: fetch join
@Query(&quot;SELECT r FROM ChatRoom r JOIN FETCH r.sender JOIN FETCH r.receiver WHERE r.sender = :user OR r.receiver = :user&quot;)
List&lt;ChatRoom&gt; findAllWithUsersByUser(User user);</code></pre>
<blockquote>
<p>✅ 평균 조회 시간 450ms → 260ms로 감소</p>
</blockquote>
<hr>
<h3 id="3-읽음-처리-bulk-update">3. 읽음 처리 Bulk Update</h3>
<p>읽음 처리도 하나하나 save() 하다 보니, 메시지 개수가 많아질수록 성능이 확 떨어졌다.</p>
<pre><code class="language-java">// 기존
messages.forEach(m -&gt; m.markAsRead());

// 개선
@Modifying
@Query(&quot;UPDATE ChatMessage m SET m.isRead = true WHERE m.chatRoom = :room AND m.sender &lt;&gt; :user AND m.isRead = false&quot;)
void markMessagesAsReadBulk(ChatRoom room, User user);</code></pre>
<blockquote>
<p>✅ 수백 건도 1~2ms 내 처리 가능</p>
</blockquote>
<hr>
<h2 id="🎯-최종적으로-구현된-흐름">🎯 최종적으로 구현된 흐름</h2>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/b025f1f6-e568-4c72-b778-7be086c14ece/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/91f1a44e-d64e-44b0-a84c-bc467b6c8400/image.png" alt=""></p>
<ul>
<li>게시글 상세페이지에서 &quot;💬 채팅하기&quot; 버튼 클릭  </li>
</ul>
<ul>
<li>해당 게시글 작성자와 1:1 채팅방 생성</li>
</ul>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/6dd50a23-f80a-4bab-9b9c-11063621daa2/image.png" alt=""></p>
<ul>
<li>실시간 메시지 송수신, 읽음 처리, 채팅 목록 UI까지 연동
<img src="https://velog.velcdn.com/images/raymond_yoon/post/75a6b0f4-b1c6-4e73-ac9d-6afeb573f160/image.png" alt=""></li>
</ul>
<hr>
<h2 id="🧠-배운-점">🧠 배운 점</h2>
<ul>
<li>WebSocket에서 JWT 인증 처리 방법을 직접 다뤄봄</li>
<li>N+1 문제는 정말 자주 발생한다는 걸 다시 느꼈고, fetch join을 적극 활용해야 함</li>
<li>실시간 처리는 꼭 비동기 처리와 배치 처리를 고려해야 한다</li>
</ul>
<hr>
<h2 id="💡-다음-계획">💡 다음 계획</h2>
<ul>
<li>open api 이용해서 chat bot 만들어보기</li>
</ul>
<hr>
<p>이제 실시간 채팅도 잘 붙었고, 성능 개선까지 했으니 다음은 사용자 경험을 좀 더 다듬는 일만 남았다.<br>오늘도 개발하면서 또 한 걸음 나아간 느낌이다 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[👨🏻‍💻Spring에서 MyBatis와 JPA, 무엇을 써야 할까?]]></title>
            <link>https://velog.io/@raymond_yoon/Spring%EC%97%90%EC%84%9C-MyBatis%EC%99%80-JPA-%EB%AC%B4%EC%97%87%EC%9D%84-%EC%8D%A8%EC%95%BC-%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@raymond_yoon/Spring%EC%97%90%EC%84%9C-MyBatis%EC%99%80-JPA-%EB%AC%B4%EC%97%87%EC%9D%84-%EC%8D%A8%EC%95%BC-%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Fri, 21 Mar 2025 12:57:09 GMT</pubDate>
            <description><![CDATA[<p>Spring으로 백엔드 개발을 하다 보면 자연스럽게 MyBatis와 JPA를 모두 접하게 된다. 처음에는 “뭐가 더 좋은 거지?”, “내가 언제 뭘 써야 할까?” 같은 고민이 있다. </p>
<hr>

<h4 id="mybatis">MyBatis</h4>
<p>장점</p>
<ul>
<li>SQL을 직접 작성 -&gt; 쿼리 제어가 자유롭다</li>
<li>복잡한 JOIN, 동적 쿼리 처리에 강하다</li>
<li>어떤 SQL이 실행되는지 명확하게 보인다</li>
</ul>
<p>단점</p>
<ul>
<li>SQL을 전부 직접 작성해야 해서 생산성이 떨어질 수 있다.</li>
<li>테이블 변경 시 SQL도 수정해야 하므로 유지보수가 힘들 수 있다.</li>
<li>객체지향적인 설계에는 조금 약한 느낌..?<pre><code class="language-xml">&lt;!-- UserMapper.xml --&gt;
&lt;select id=&quot;findByUsername&quot; resultType=&quot;User&quot;&gt;
SELECT * FROM users WHERE username = #{username}
&lt;/select&gt;</code></pre>
<pre><code class="language-java">// UserMapper.java
User findByUsername(String username);</code></pre>
</li>
</ul>
<p>MyBatis는 이런 곳에서 유용한듯</p>
<ul>
<li>통계, 분석 등 복잡한 쿼리가 많은 기능</li>
<li>성능 튜닝이 중요한 조회 중심 서비스</li>
<li>SQL 작성이 익숙한 팀원들과 협업할 때<hr>

</li>
</ul>
<h4 id="jpa">JPA</h4>
<p>장점</p>
<ul>
<li>기본적인 CRUD는 쿼리 없이도 가능 -&gt; 생산성이 좋다</li>
<li>엔티티 중심의 객체지향 설계가 가능하다</li>
<li>지연 로딩, 변경 감지 같은 고급 기능도 지원한다</li>
</ul>
<p>단점</p>
<ul>
<li>처음엔 개념이 많고 어렵다(영속성 컨텍스트, 지연 로딩 등)</li>
<li>복잡한 쿼리 작성이 불편하고 성능 튜닝이 까다로울 수 있음</li>
<li>어떤 쿼리가 나가는지 감이 안 잡힐 때가 있을수도..?<pre><code class="language-java">// UserRepository.java
public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
  Optional&lt;User&gt; findByUsername(String username);
}</code></pre>
</li>
</ul>
<p>JPA는 이런 곳에서 유용한 것 같다.</p>
<ul>
<li>CRUD가 주로 이루어지는 일반적인 웹 서비스</li>
<li>도메인 중심의 객체지향 모델링이 중요한 경우</li>
<li>빠르게 개발하고 유지보수까지 고려해야 할 때<hr>

</li>
</ul>
<h4 id="정리하며">정리하며</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>MyBatis</th>
<th>JPA</th>
</tr>
</thead>
<tbody><tr>
<td>쿼리 방식</td>
<td>직접 작성</td>
<td>자동 생성 or JPQL</td>
</tr>
<tr>
<td>학습 난이도</td>
<td>쉬운 편</td>
<td>개념이 많아 어려움</td>
</tr>
<tr>
<td>생산성</td>
<td>낮음 (쿼리 직접 작성)</td>
<td>높음 (자동 처리)</td>
</tr>
<tr>
<td>복잡한 쿼리</td>
<td>강함</td>
<td>제한적</td>
</tr>
<tr>
<td>객체지향 설계</td>
<td>약함</td>
<td>강함</td>
</tr>
<tr>
<td>유지보수</td>
<td>어렵게 느껴질 수 있음</td>
<td>구조화되어 편함</td>
</tr>
<tr>
<td><hr></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h4 id="결국-선택은-상황에-따라서">결국 선택은 상황에 따라서</h4>
<p>개인적으로 CRUD 위주의 서비스는 JPA, 
복잡한 통계성 쿼리나 성능이 중요한 부분은 MyBatis로 나눠서 쓰는게 좋았다.
둘 중 하나만 고집할 필요는 없고, 상황에 따라 선택하거나 혼합해서 사용하는 것도 좋은 방법이었다.</p>
<hr>
<h2 id="그럼-mybatis랑-jpa는-어떻게-돌아가는-걸까">그럼 MyBatis랑 JPA는 어떻게 돌아가는 걸까?</h2>
<ul>
<li><h4 id="mybatis는-xml에-sql을-직접-작성하고-인터페이스랑-연결해서-사용하는-방식이다">MyBatis는 XML에 SQL을 직접 작성하고, 인터페이스랑 연결해서 사용하는 방식이다</h4>
</li>
</ul>
<p><code>resources/mapper/</code> 같은 경로에 XML 파일을 만들고, SQL을 <code>&lt;select&gt;</code>, <code>&lt;insert&gt;</code> 등으로 작성해준다.<br>그리고 인터페이스 파일에서 XML의 <code>id</code> 값과 같은 메서드를 선언하면 된다.</p>
<pre><code class="language-xml">&lt;!-- UserMapper.xml --&gt;
&lt;select id=&quot;findByUsername&quot; resultType=&quot;User&quot;&gt;
  SELECT * FROM users WHERE username = #{username}
&lt;/select&gt;</code></pre>
<pre><code class="language-java">// UserMapper.java
@Mapper
public interface UserMapper {
    User findByUsername(String username);
}</code></pre>
<p>이렇게 매핑하면 UserMapper의 메서드를 호출할 때 XML 안의 쿼리가 실행된다.
SQL을 내가 다 짜야 하긴 하지만, 그만큼 컨트롤도 확실히 가능하다.
복잡한 조건이 필요한 쿼리나 성능 튜닝이 필요할 때 직접 건드릴 수 있다는 점이 꽤 큰 장점이다.</p>
<hr>
<h4 id="jpa는-repository-인터페이스만-만들어도-기본적인-crud는-다-된다"><strong>JPA는 Repository 인터페이스만 만들어도 기본적인 CRUD는 다 된다</strong></h4>
<p>Spring Data JPA에서 제공하는 JpaRepository를 상속받으면
별다른 설정 없이도 데이터를 저장하고 조회할 수 있다.</p>
<pre><code class="language-java">public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
    Optional&lt;User&gt; findByUsername(String username);
}</code></pre>
<p>메서드 이름만 지어도 내부적으로 쿼리가 생성돼서 실행된다.
만약 복잡한 쿼리가 필요하다면 @Query 어노테이션을 써서 JPQL을 직접 작성할 수도 있다.</p>
<p>코드를 객체 중심으로 작성할 수 있다는 게 가장 큰 장점이고,
설계가 잘 되어 있다면 유지보수나 확장도 수월하게 할 수 있다.</p>
<hr>
<p>이렇게 동작 방식까지 살펴보니,
JPA는 편리함과 구조적인 설계를 지향하고,
MyBatis는 쿼리 제어의 유연함과 성능 중심의 설계에 가깝다는 게 확실히 보인다.</p>
<p>둘 다 장단점이 확실해서 프로젝트 상황에 따라 적절히 고르는 게 중요한 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개인 프로젝트(4)]]></title>
            <link>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B84</link>
            <guid>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B84</guid>
            <pubDate>Thu, 20 Mar 2025 06:35:36 GMT</pubDate>
            <description><![CDATA[<p>서류를 엄청 많이 넣었지만 면접도 못가고 서류탈락만 하게된다.. 면접이라도 볼 수 있지 않을까 해서 이력서를 준비하고 넣고 하지만 서류열람 알림이 뜨고 기대하고 실망하기만 반복된다.
그래도 꾸준히 어제보다 나은 내일을 위해 더 열심히 공부하고 스팩 쌓고 이력서를 계속 넣어야겠다. 언젠간 날 알아봐줄 회사가 나타날거라 믿는다! 날 알아봐준 그 회사에 성장에 모든걸 쏟아부을것이다.</p>
<h2 id="🔥-카카오페이-결제-승인-오류-해결-과정">🔥 카카오페이 결제 승인 오류 해결 과정</h2>
<h4 id="🛑-문제-발생">🛑 문제 발생</h4>
<p>카카오페이 결제 후 승인 요청 시, pg_token 값이 백엔드에서 정상적으로 전달되지 않아 403 Forbidden 오류가 발생했다.
또한, MissingServletRequestParameterException이 발생하며 백엔드에서 &#39;pg_token&#39;이 누락되었다고 인식했다.</p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/a547bf15-b5c6-4351-9cb9-7e41fc0a29af/image.png" alt=""></p>
<p>현재 이런식으로 한번 더 결제가 반복되는 현상과 pg_token이 프론트에서 백엔드로 가지않는 현상이 발생된다!! </p>
<p>생각을 해봤다.. 음..아니 전달하는 코드인데 왜 전달이 안될까? 혹시 이럴때 마다 가능성이있는 보내는 형식 문제인가? 그래서 못 받는거지 라고 생각이 됐다</p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/00817f70-654c-4e12-8fff-413b5c3c08f5/image.png" alt=""></p>
<p>POSTMAN으로 백엔드 테스트를 해봤더니 403에러가 뜬다.. 보통 Security 문제일 확률이 높다고 생각하는데 아니 그럴 일이 없다 CORS설정등 다했기 때문에! </p>
<p>그러면 내 코드에서 문제점이 있나 살펴봤더니 웬걸 백엔드에서 RequestParam으로 받고있었네?</p>
<p>보안적으로도 RequestBody가 더 안전하다 (쿼리 파라미터는 URL에 남아서 노출될 가능성 있음)</p>
<p>그리고 POST/PUT은 RequestBody를 주로 사용하는데 왜 자연스럽게 RequestParam을 썼을까 생각한다. 그래도 금방 발견해서 정말 뿌듯했다.</p>
<ul>
<li><p>프론트엔드 문제 해결
pg_token을 정상적으로 가져오는지 console.log()를 통해 확인</p>
</li>
<li><p>백엔드 문제 해결
@RequestParam으로 pg_token을 받는 구조였지만, 프론트에서 Body로 전송했기 때문에 @RequestBody로 변경하여 해결</p>
</li>
</ul>
<p>백엔드 (PaymentController.java)</p>
<pre><code class="language-java">@PostMapping(&quot;/success&quot;)
public ResponseEntity&lt;String&gt; paymentSuccess(@RequestBody PaymentRequestDTO request, 
                                             @RequestHeader(&quot;Authorization&quot;) String token) {
    String pgToken = request.getPgToken();
    Long postId = request.getPostId();
    String tid = request.getTid();

    System.out.println(&quot;Received pg_token: &quot; + pgToken);
    System.out.println(&quot;Received postId: &quot; + postId);
    System.out.println(&quot;Received tid: &quot; + tid);

    kakaoPayService.approvePayment(postId, userId, tid, pgToken);

    return ResponseEntity.ok(&quot;결제가 성공적으로 완료되었습니다.&quot;);
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/0e6376f2-1dca-4884-a0de-8bea1b06ff80/image.png" alt=""></p>
<p>결국 되어버린 결제..! </p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/0fca9a96-8d92-4f52-8c2f-755ee9fb934b/image.png" alt=""></p>
<p>데이터가 들어오는게 이렇게 행복한 일이라니!
<img src="https://velog.velcdn.com/images/raymond_yoon/post/a217b46c-80f2-4f8d-b1b5-df46ea24b87b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/de29fe78-3a7a-41f4-a9a7-88eaa3770ee7/image.png" alt=""></p>
<p>테스트이고, 개인프로젝트지만 정말 신기하고 재미있는 경험이다... 스타트업쪽은 포트원이라는 온라인 간편결제로 한다니깐 나중에 이걸 바탕으로 시도해봐야겠다</p>
<p>그럼 이제 남은건 중복 요청 문제이다
웹에서 시도해봤더니 한번 승인되고 한번 거절되고 했다</p>
<p>POSTMAN으로 테스트했을때, 됐으니 백엔드 문제가 아닌 프론트에서 중복 요청을 하겠구나 라고 생각하고 일단 난 백엔드니깐 백엔드에서 중복요청을 방지하는 로직을 추가하면 더욱더 보안에 좋을 것 같아서 백엔드 중복 요청 방지하는 로직을 추가했다</p>
<p>백엔드 추가 내용:</p>
<ul>
<li>PaymentRepository에 중복 결제 여부를 확인하는 메서드 추가<pre><code class="language-java">boolean existsByPostIdAndUserIdAndPaymentStatus(Long postId, Long userId, String paymentStatus);</code></pre>
</li>
<li>KakaoPayService.approvePayment() 내부에서 이미 결제된 경우 승인 요청을 보내지 않도록 처리<pre><code class="language-java">boolean isAlreadyPaid = paymentRepository.existsByPostIdAndUserIdAndPaymentStatus(postId, userId, &quot;SUCCESS&quot;);
if (isAlreadyPaid) {
  throw new RuntimeException(&quot;이미 결제된 내역입니다.&quot;);
}</code></pre>
</li>
</ul>
<p>역시 예상했던 것 처럼 해결은 안됐지만, 혹시 모를 방지도 되니 로직을 추가하였다.</p>
<p>🧐 원인 분석
✔ 백엔드에서 중복 요청을 방지하는 로직을 추가했지만, 애초에 중복 요청이 발생하는 원인이 프론트에 있었음.
✔ useEffect가 searchParams를 감지하며 두 번 실행됨 → 같은 요청이 두 번 전송됨
✔ 결국, 프론트에서 중복 실행을 막아야 해결 가능</p>
<p>🔍 해결 방법 (프론트 수정)
✅ useRef를 사용해 중복 요청 방지
✅ useCallback을 활용하여 useEffect 내부에서 중복 실행되지 않도록 처리</p>
<pre><code class="language-javascript">import React, { useEffect, useRef, useCallback } from &quot;react&quot;;
import { useNavigate, useSearchParams } from &quot;react-router-dom&quot;;
import axios from &quot;axios&quot;;

const PaymentSuccess = () =&gt; {
    const [searchParams] = useSearchParams();
    const navigate = useNavigate();
    const isRequestSent = useRef(false); // 중복 요청 방지용 useRef 추가

    // API 호출 로직을 useCallback으로 감싸기
    const handlePaymentApproval = useCallback(() =&gt; {
        if (isRequestSent.current) return; // 이미 요청을 보냈다면 실행하지 않음
        isRequestSent.current = true; // 첫 번째 실행 이후 true로 변경

        const pgToken = searchParams.get(&quot;pg_token&quot;);
        const postId = searchParams.get(&quot;postId&quot;);
        const tid = localStorage.getItem(&quot;tid&quot;);
        const token = localStorage.getItem(&quot;token&quot;);

        if (!pgToken || !postId || !tid) {
            alert(&quot;결제 정보가 올바르지 않습니다.&quot;);
            navigate(&quot;/&quot;);
            return;
        }

        axios.post(
            `http://localhost:8080/payment/success`,
            { pg_token: pgToken, postId: postId, tid: tid },
            { headers: { Authorization: `Bearer ${token}` } }
        )
        .then(() =&gt; {
            alert(&quot;결제가 완료되었습니다!&quot;);
            localStorage.removeItem(&quot;tid&quot;);
            navigate(`/post/${postId}`);
        })
        .catch((error) =&gt; {
            console.error(&quot;결제 승인 실패:&quot;, error.response ? error.response.data : error);
            alert(&quot;결제 승인에 실패했습니다.&quot;);
            navigate(&quot;/&quot;);
        });
    }, [searchParams, navigate]); // 의존성 배열 추가 (ESLint 경고 방지)

    useEffect(() =&gt; {
        handlePaymentApproval();
    }, [handlePaymentApproval]); // useEffect에서 useCallback 사용

    return &lt;div&gt;결제 승인 중...&lt;/div&gt;;
};

export default PaymentSuccess;</code></pre>
<hr>

<p>✅ 해결된 점
✔ 백엔드, 프론트에서 중복 실행 방지
✔ useEffect가 한 번만 실행됨 → 중복 승인 요청 사라짐
✔ 카카오페이 결제 승인 정상 작동</p>
<hr>

<p>📌 결론
✅ &quot;프론트에서 발생한 문제이지만, 백엔드에도 로직을 추가하기!&quot;
✅ 앞으로 useEffect 사용 시 중복 실행을 방지하는 방법을 고려해야 함.
✅ useRef와 useCallback을 적극 활용하면 중복 요청을 방지할 수 있음.</p>
<p>이제 결제 승인 요청이 한 번만 실행되며, 정상적으로 완료되었다!</p>
<hr>

<p>이제 다음 예정은 is_Paid 가 1이면 게시물이 결제하기버튼 안보이고, 결제가 된건데
 이게 user 마다 그래야하는데 게시물 is_Paid가 true가 되면, 모든 user들이 통합적이로 보이는지? 이거에 대한 의문을 해결하는 것과, 오류없는지 확인한번 다 돌리고 게시글부분 댓글과 좋아요 되면 구현 더 하고 </p>
<ul>
<li>챗봇 or 채팅</li>
</ul>
<p>둘중 선택해서 하거나 둘다 할것같다.</p>
<p>그리고 나는 java개발자가 아닌 백엔드 개발자로서 golang도 설치하여 맛만보고, node.js로 앱이나 만들까 고민중이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개인 프로젝트(3)]]></title>
            <link>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B83</link>
            <guid>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B83</guid>
            <pubDate>Tue, 18 Mar 2025 18:55:44 GMT</pubDate>
            <description><![CDATA[<p>하..이번에 정말 가고싶고 나의 긍정적인 마인드를 선보일 수 있는 기업에 지원했는데 지원자수도 너무 많고, 서류열람만 되고 또 연락안올거 같아서 우울할거 같아서 오예스 하나를 먹고 버텼다.. 오예스랑 칙촉 너무 맛있는듯; 오늘 등 운동도 하고 도서관가서 할 것도 하고 내일은 오늘보다 더 나은 내가 되어야겠다!!  </p>
<h2 id="카카오페이-결제-승인-오류-해결-과정">카카오페이 결제 승인 오류 해결 과정</h2>
<h3 id="문제-발생">문제 발생</h3>
<p>카카오페이 결제 API를 연동하는 과정에서 결제 요청(Ready) → 결제 승인(Approve) 순서로 진행해야 하지만, 결제 승인 과정에서 아래와 같은 오류가 발생했다.</p>
<h3 id="발생한-오류">발생한 오류:</h3>
<p>pg_token이 백엔드로 전달되지 않음</p>
<p>프론트에서는 URL에 pg_token이 포함되어 있었지만, 백엔드에서는 해당 값을 받지 못하고 MissingServletRequestParameterException이 발생.
백엔드에서 System.out.println 로그가 찍히지 않음</p>
<p>정상적인 API 호출이 이루어지지 않았을 가능성이 있음.
프론트에서 결제 승인 요청을 보냈으나 실패</p>
<p>프론트에서 백엔드 /payment/success 엔드포인트로 pg_token, postId, tid를 전달했지만, 응답이 400(Bad Request) 또는 오류 메시지가 발생함.</p>
<h3 id="원인-분석">원인 분석</h3>
<ol>
<li>pg_token이 프론트에서 잘 전달되고 있는지 확인</li>
</ol>
<ul>
<li>console.log를 추가하여 프론트에서 pg_token, postId, tid가 올바르게 찍히는지 확인했다.</li>
<li>결과적으로, 프론트에서는 정상적으로 URL에서 pg_token을 추출하고 있었음.</li>
</ul>
<ol start="2">
<li>백엔드에서 요청을 올바르게 받고 있는지 확인</li>
</ol>
<ul>
<li>System.out.println(&quot;Received pg_token: &quot; + pgToken); 추가했으나 콘솔에서 찍히지 않았음.</li>
<li>즉, 프론트에서 백엔드로 요청이 제대로 가지 않았을 가능성이 높음.</li>
</ul>
<ol start="3">
<li>Axios 요청의 Content-Type 문제</li>
</ol>
<ul>
<li>axios.post(...) 요청 시, headers: { Authorization: Bearer ${token} }에서 Bearer를 백틱을 사용한 템플릿 리터럴로 감싸는 대신, 일반적인 문자열로 사용해야 함.</li>
</ul>
<ol start="4">
<li>pg_token 전달 방식 문제</li>
</ol>
<ul>
<li>axios.post 요청을 보낼 때, params를 body로 보냈기 때문에 Spring에서 @RequestParam으로 받을 수 없었음.</li>
<li>백엔드에서 @RequestParam 대신 @RequestBody를 사용해야 하거나, 프론트에서 올바르게 params로 전달해야 함.</li>
</ul>
<h3 id="해결-방법">해결 방법</h3>
<p>프론트 수정</p>
<ol>
<li>pg_token, postId, tid를 정상적으로 백엔드로 전송하도록 수정</li>
<li>Bearer 토큰을 올바르게 전달하도록 수정</li>
<li>요청 데이터를 query params가 아니라 body로 보내도록 수정</li>
</ol>
<p>수정된 프론트 코드 (PaymentSuccess.js)</p>
<pre><code class="language-javascript">import React, { useEffect } from &quot;react&quot;;
import { useNavigate, useSearchParams } from &quot;react-router-dom&quot;;
import axios from &quot;axios&quot;;

const PaymentSuccess = () =&gt; {
    const [searchParams] = useSearchParams();
    const navigate = useNavigate();

    useEffect(() =&gt; {
        const pgToken = searchParams.get(&quot;pg_token&quot;);
        const postId = searchParams.get(&quot;postId&quot;);
        const tid = localStorage.getItem(&quot;tid&quot;);
        const token = localStorage.getItem(&quot;token&quot;);

        console.log(&quot;URL:&quot;, window.location.href);
        console.log(&quot;pg_token:&quot;, pgToken);
        console.log(&quot;postId:&quot;, postId);
        console.log(&quot;tid:&quot;, tid);

        if (!pgToken || !postId || !tid) {
            alert(&quot;결제 정보가 올바르지 않습니다.&quot;);
            navigate(&quot;/&quot;);
            return;
        }

        axios
            .post(
                `http://localhost:8080/payment/success`,
                { pg_token: pgToken, postId: postId, tid: tid },
                {
                    headers: {
                        Authorization: `Bearer ${token}`,
                        &quot;Content-Type&quot;: &quot;application/json&quot;,
                    },
                }
            )
            .then(() =&gt; {
                alert(&quot;결제가 완료되었습니다!&quot;);
                localStorage.removeItem(&quot;tid&quot;);
                navigate(`/post/${postId}`);
            })
            .catch((error) =&gt; {
                console.error(&quot;결제 승인 실패:&quot;, error.response ? error.response.data : error);
                alert(&quot;결제 승인에 실패했습니다.&quot;);
                navigate(&quot;/&quot;);
            });
    }, [searchParams, navigate]);

    return &lt;div&gt;결제 승인 중...&lt;/div&gt;;
};

export default PaymentSuccess;
</code></pre>
<p>백엔드 수정</p>
<ol>
<li>System.out.println을 사용하여 요청을 받는지 확인</li>
<li>프론트에서 전달하는 방식에 맞게 @RequestParam을 @RequestBody로 변경</li>
<li>카카오페이 승인 API 호출 시, 올바른 헤더를 전달하도록 수정</li>
</ol>
<p>수정된 백엔드 코드 (PaymentController.java)</p>
<pre><code class="language-java">@PostMapping(&quot;/success&quot;)
public ResponseEntity&lt;String&gt; paymentSuccess(
        @RequestBody Map&lt;String, String&gt; requestData,
        @RequestHeader(&quot;Authorization&quot;) String token) {

    String pgToken = requestData.get(&quot;pg_token&quot;);
    Long postId = Long.valueOf(requestData.get(&quot;postId&quot;));
    String tid = requestData.get(&quot;tid&quot;);

    System.out.println(&quot;Received pg_token: &quot; + pgToken);
    System.out.println(&quot;Received postId: &quot; + postId);
    System.out.println(&quot;Received tid: &quot; + tid);

    try {
        String email = jwtTokenProvider.getUserEmail(token.replace(&quot;Bearer &quot;, &quot;&quot;));
        Long userId = userService.getUserIdByEmail(email);

        kakaoPayService.approvePayment(postId, userId, tid, pgToken);

        return ResponseEntity.ok(&quot;결제가 성공적으로 완료되었습니다.&quot;);
    } catch (Exception e) {
        System.out.println(&quot;결제 승인 오류 발생: &quot; + e.getMessage());
        e.printStackTrace();
        return ResponseEntity.badRequest().body(&quot;결제 승인 중 오류 발생: &quot; + e.getMessage());
    }
}</code></pre>
<h3 id="카카오페이-리다이렉트-설정-추가">카카오페이 리다이렉트 설정 추가</h3>
<p>카카오 디벨로퍼에서 결제 승인, 실패, 취소 리다이렉트 URL 설정 필요.
초기 설정 누락으로 승인 요청 실패함..^^ 바보 </p>
<p>설정 경로:
카카오 디벨로퍼 →  애플리케이션 → 플랫폼</p>
<ul>
<li><p>추가한 URL:
<a href="http://localhost:3000/payment-success">http://localhost:3000/payment-success</a>
<a href="http://localhost:3000/payment-cancel">http://localhost:3000/payment-cancel</a>
<a href="http://localhost:3000/payment-fail">http://localhost:3000/payment-fail</a></p>
</li>
<li><p>백엔드 approval_url 수정
params.put(&quot;approval_url&quot;, &quot;<a href="http://localhost:3000/payment-success?postId=&quot;">http://localhost:3000/payment-success?postId=&quot;</a> + postId);</p>
</li>
</ul>
<h3 id="결론">결론</h3>
<p>주요 원인</p>
<ul>
<li>pg_token이 query parameter로 전달되지 않아 백엔드에서 제대로 받지 못함.</li>
<li>axios.post 요청 시 @RequestParam이 아닌 @RequestBody로 데이터를 보내야 함.</li>
<li>Bearer 토큰 전달 방식이 잘못되어 인증 문제 발생 가능성.</li>
</ul>
<p>해결 방법</p>
<ul>
<li>@RequestBody로 데이터 받기.</li>
<li>axios.post 요청 시 headers를 올바르게 설정.</li>
<li>console.log와 System.out.println을 적극 활용하여 디버깅.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[개인 프로젝트 이야기(2)]]></title>
            <link>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9D%B4%EC%95%BC%EA%B8%B02</link>
            <guid>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9D%B4%EC%95%BC%EA%B8%B02</guid>
            <pubDate>Mon, 17 Mar 2025 07:56:56 GMT</pubDate>
            <description><![CDATA[<p>요즘 웬만한 곳도 400:1 이고, 매출액 100 이상인 곳은 700명까지 지원자가 많다... 나도 어디 들어가서 잘할 수 있고, 열심히 할 수 있는데 자소서로 나를 어필하는건 면접보다 어려운거같다.
계속 나의 스팩을 올리고 걸어가다 보면 나를 찾아주는 회사를 발견하지않을까 생각한다.</p>
<p>저번에 그림 구상까지 한걸 보면
그 결과로 
<img src="https://velog.velcdn.com/images/raymond_yoon/post/6b7a47df-67b1-4726-8783-6105c8ba6629/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/e9454df7-3efc-480b-a3b8-0e8d2591fec0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/55834777-ca0b-4b7d-bce9-5cdeb2e0909c/image.png" alt=""></p>
<p>이런식으로 ui가 변했다. 눈으로 보기 편하게 녹색계열로 해서 나름 나쁘지 않다고 생각했지만, 디자인이랑 프론트 관련된 분들이 보면 회초리를 들거같다고 생각했다..ㅎ </p>
<p>리엑트를 처음 만져보면서 음 물론 혼자 짜면 어렵겠지만, 서칭하고 하면서 함수의 구조도 파악하며 하니깐 이런 느낌이구나 생각했다. </p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/34390617-0c93-444f-afc8-f95f2f4021de/image.png" alt=""></p>
<p>(폭죽효과 제대로 안찍혀서 새로찍음..)</p>
<p>카카오쪽이나 tistory 같은 곳을 보면 클릭하면 폭죽효과 같은것도 터지게 하길래 신기해서 넣어봤다. css 쪽과 따로 firework.js 파일을 만들어서 클릭하면 event가 발생하도록 색깔등 생각보다 자세하게 넣을 수 있구나 신기했다. 코딩은 알면 알수록 신기한게 정말 많고 세세하다는게 느껴진다.</p>
<p>근데 보다보니 ui가 좀 더 혁신이 필요하겠다고 생각하여 진지하게 생각해보면서 당근이라던가 velog도 검은바탕의 흰글씨 느낌이 유행인것 같았다. 당근마켓의 PC버전을 보면 단순하지만 눈에 보기도 편하고 확 들어오는 느낌이 있다. 정말 제일 어려운게 가장 단순하면서 잘보이는거라고 생각하는데 참 대단하다고 느꼈다.</p>
<p>또 뭐가있을까 생각하다가 나는 보통 Web을 이용할때 다크모드를 좋아해서 
다크모드도 추가했다. F12를 통해 개발자모드도 보며 다크모드를 on/off등 jwt token이든 이렇게 보는거구나 다시한번 보면서 신기해했다.
<img src="https://velog.velcdn.com/images/raymond_yoon/post/8f2057d0-8e78-41d5-9454-7656a7011b2b/image.png" alt=""></p>
<p>다크 모드를 추가도 해보고 위쪽에다가 상단바에 넣어보기도 하고~</p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/f160bc0c-90ac-4f71-b516-380c63c4d29d/image.png" alt=""></p>
<p>결국 전체적인 ui를 처음보단 엄청 깔끔하게 바뀌었다! 
R은 내 영어 이름인 Raymond 에서 R을 따서 레몬의 색 노랑색을 입혔다. 기능은 home버튼이다~</p>
<p>ui도 게시판쪽 부분만 좀 수정하면 더 이쁠 것 같다.</p>
<p>일단 현재까지는 ui 부분 수정 및 기능 확인들을 했다.
다음 글에서는 카카오페이 결제를 넣었는데 그걸 볼 생각이다.</p>
<p>바로 이것! 현재까지는 자기 게시물이면 결제하기가 안뜨게 하고 추가하는게 간단할 줄 알았는데 생각보다 복잡해서 좀 걸렸지만 여튼저튼 잘 추가했다.</p>
<p>결제하기 버튼을 누르면 카카오페이지 url응답이 오며 결제가 시작되어야하는데, 
이 부분이 백엔드 api응답으로 url을 들어가면 카카오페이 테스트까지 다 정상적인데 프론트에서 받아왔을때, 주소까지는 오는데 그 주소를 들어가면 기다려달라는 페이지가 떠서 그거에 대한 것을 해결하는것이 다음 게시글 내용일것 같다!! 얼른 해결됐음 좋겠다~ </p>
<p>이 부분에서 생각해야하는것 궁금하것등이 많아서 더 알아보고 쓸 생각이다. 
is_Paid 가 1이면 게시물이 결제하기버튼 안보이고, 결제가 된건데
 이게 user 마다 그래야하는데 게시물 is_Paid가 true가 되면, 모든 user들이 통합적으로 보이는것 같아서 이것에 대한 수정?
.
.
.
.
.
나와 함께 할 팀은 누구일까? 취직하고 싶다 같이 개발하고싶다~ </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개인 프로젝트 이야기(1)]]></title>
            <link>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9D%B4%EC%95%BC%EA%B8%B01</link>
            <guid>https://velog.io/@raymond_yoon/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9D%B4%EC%95%BC%EA%B8%B01</guid>
            <pubDate>Fri, 14 Mar 2025 14:32:11 GMT</pubDate>
            <description><![CDATA[<p>빠르게 취업을 하고 실무 경험을 쌓아서 성장하고 싶었는데, 최근 넣은 모든 곳은 다 서류탈락이었다.. 자신감도 바닥을 찍고 그러지만 힘들어도 앞으로 걸어가는거고 나를 알아주는 회사가 있지 않을까 라는 생각이 있다! 몇 주 전에 여의도쪽 스타트업 다니는 친구네 회사도 구경갔는데 가슴이 정말 두근거렸다. 거기서 일하는 개발자들이나 미팅하는 사람들을 보면서 &#39;와.. 진짜 멋있다 나도 취직해서 일 하고 싶다&#39; 라는 생각이 들었다. 물론 취직을 한 친구들은 취업하면은 생각이 달라진다고 하지만 백수인 입장에서 취직한 사람들은 동경의 대상이다. 요즘 부트캠프도 많이 하는 사람들이 생겨 나는 경쟁력이 없나 라는 생각도 가졌지만, 혼자 스스로 개인 프로젝트를 하고 실력을 키우면은 오히려 더 나를 좋게 봐주는 회사가 있을거라고 생각한다! 그래서 내가 만들고 싶은 개인 프로젝트에 기술은 프론트엔드는 React.js 로 백엔드는 Spring Boot 로 
(1) 게시판 구현
(2) 로그인 구현
(3) 결제 시스템 구현
(4) 채팅 or 챗봇 구현
이렇게 구성하고 있다. 요즘 인스타에서도 자기 게시물 구독자 전용이 나오는 것처럼 이 프로젝트에서는 아직 생각중이지만, 구독을 하거나 아니면 게시판 하나를 사거나 해서 결제 시스템을 이용하는 것이다.</p>
<p>솔직히 그냥 무턱대고 AI의 도움을 받아서 코드를 보고, 생각하고, 작성하는데 
쉬는동안 이런 생각이 들었다..
&#39;아니 그냥 무턱대고 만들지 말고 진짜 구상하는 것처럼..? 그렇게 설계를 해보자&#39; 라는 생각이 들었다.</p>
<p>일단 무턱대고 구현을 해서
<img src="https://velog.velcdn.com/images/raymond_yoon/post/0610742a-c491-4ce1-8e9a-252071d33ebb/image.png" alt=""></p>
<p>형식적인 게시판을 만들었다.
물론 회원가입도 되고 </p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/f41131ec-7fcd-439d-b6d4-2bc1cb598488/image.png" alt=""></p>
<p>로그인도 된다.</p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/bc4a92e0-f154-46ff-b28c-ad16703012b8/image.png" alt=""></p>
<p>그러면 이제 상단바에 나의 이름과 로그아웃이 뜬다.</p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/1cb003f0-2265-4f73-ab8c-ee9c4772588f/image.png" alt=""></p>
<p>로그인 구현은 이번에 프로젝트에 써본 jwt 토큰을 이용해서 해보고, 게시판은 CRUD 를 작성하여 프론트, 백엔드 API를 구현하였다.</p>
<p>솔직히 아무것도 안보고 구현하라하면 못할 것 같지만, AI와 구글링 서칭으로 코드도 보고 생각하면서 복사 붙여넣기를 하지않고 수월하게 하면서 배우고 있다.</p>
<p>백엔드지만, ui가 너무 별로고 그래서 ui도 구상하면서 생각했다. 어차피 풀스택 개발자가 목표기도 하고 스타트업이던 어디던 다른 직무의 사람들과 의사소통을 하며 일하고 싶어서 전체적으로 기획, 프론트, 디자인, 백엔드를 조금이라도 내가 생각하면서 해보려고 한다. 물론 많이 부족하지만 이런 경험은 값지다고 생각한다. 나의 노력은 누군가 알아주지 않을까? 일단 내가 알아주니깐 나를 믿고 나아간다.</p>
<p>마지막으로 ui를 구상한 그림을 그려봤다...물론 똥이다 ^^
<img src="https://velog.velcdn.com/images/raymond_yoon/post/4de0ffbb-f156-4b42-b93e-cda5af210c53/image.png" alt="">
첫 번째 나의 개인프로젝트 이야기는 끝이고, ui는 확실히 좋아진거를 볼 수 있을 것이다!! </p>
<p>취준생 나 자신 파이팅..</p>
<p>PS. 대표 레몬 이미지는 디자인 친구한테 부탁했지만 바빠서 gpt로 만들어봤는데 매우 만족.. 근데 역시 인공적인 느낌이 있다! 사람 최고! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[👨🏻‍💻String은 왜 대문자 일까?]]></title>
            <link>https://velog.io/@raymond_yoon/String%EC%9D%80-%EC%99%9C-%EB%8C%80%EB%AC%B8%EC%9E%90-%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@raymond_yoon/String%EC%9D%80-%EC%99%9C-%EB%8C%80%EB%AC%B8%EC%9E%90-%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Sat, 01 Mar 2025 09:44:14 GMT</pubDate>
            <description><![CDATA[<p>스프링 프로젝트를 진행하다 보면 다양한 데이터 타입을 다루게 되는데, 그중에서도 int는 소문자로 시작하는 반면 String은 대문자로 시작하는 점이 눈에 띄었다. 처음에는 단순한 문법 차이라고 생각했지만, 이를 이해하려면 Java의 기본 타입(Primitive Type)과 참조 타입(Reference Type)에 대한 개념을 알아야 했다.</p>
<hr>

<h2 id="기본-타입primitive-type과-참조-타입reference-type">기본 타입(Primitive Type)과 참조 타입(Reference Type)</h2>
<p>스프링에서 엔티티(Entity)를 만들 때, 필드 타입을 지정하는 과정에서 기본 타입과 참조 타입 중 어떤 것을 선택해야 할지 고민할 때가 있다. 이 둘의 차이를 알면 더 적절한 선택을 할 수 있다.</p>
<h3 id="기본-타입-primitive-type">기본 타입 (Primitive Type)</h3>
<p>기본 타입은 가장 단순한 데이터 타입으로, 메모리에서 직접 값을 저장하는 방식으로 동작한다. 대표적인 기본 타입은 다음과 같다.</p>
<ul>
<li>byte, short, int, long (정수형)</li>
<li>float, double (실수형)</li>
<li>char (문자형)</li>
<li>boolean (논리형)</li>
</ul>
<p>기본 타입은 고정된 크기의 메모리를 차지하며, 값을 직접 저장한다. 예를 들어 int a = 10;이라고 선언하면, a에는 실제 숫자 10이 저장된다.</p>
<h3 id="참조-타입-reference-type">참조 타입 (Reference Type)</h3>
<p>반면, String과 같은 참조 타입은 실제 값을 저장하는 것이 아니라, 객체의 주소(Reference)를 저장한다. 즉, String str = &quot;Hello&quot;; 라고 선언하면, &quot;Hello&quot;라는 문자열 객체가 메모리에 생성되고, str 변수는 그 객체의 주소를 가리킨다.</p>
<p>Java에서 String은 클래스이므로 대문자로 시작하며, 이는 객체 지향 프로그래밍에서 일반적인 네이밍 규칙이다. 반면 int와 같은 기본 타입은 더 가볍고 메모리를 효율적으로 관리하기 위해 소문자로 지정되어 있다.</p>
<hr>

<h2 id="스프링에서의-적용">스프링에서의 적용</h2>
<p>스프링 프로젝트에서 엔티티(Entity) 클래스의 필드를 정의할 때, 기본 타입과 참조 타입 중 어떤 것을 사용할지 고민할 수 있다. 예를 들어:</p>
<pre><code class="language-java">@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private int age; // 기본 타입
    private String name; // 참조 타입
}</code></pre>
<p>위 코드에서 age는 기본 타입 int를 사용했으며, name은 String 참조 타입을 사용했다. int와 같은 기본 타입은 null 값을 가질 수 없지만, Integer와 같은 래퍼 클래스(Wrapper Class)를 사용하면 null 처리가 가능하다.</p>
<pre><code class="language-java">private Integer age; // null 값 허용 가능</code></pre>
<h3 id="정리">정리</h3>
<table>
<thead>
<tr>
<th>타입</th>
<th align="left">종류</th>
<th align="center">시작 문자</th>
<th align="right">저장 방식</th>
</tr>
</thead>
<tbody><tr>
<td>int</td>
<td align="left">기본 타입</td>
<td align="center">소문자</td>
<td align="right">값을 직접 저장</td>
</tr>
<tr>
<td>String</td>
<td align="left">참조 타입(클래스)</td>
<td align="center">대문자</td>
<td align="right">객체의 주소를 저장</td>
</tr>
</tbody></table>
<p>스프링 프로젝트를 진행하면서 이런 차이를 이해하면 더 적절한 타입을 선택할 수 있다. 앞으로도 프로젝트를 하면서 궁금했던 점들을 정리해보려고 한다. 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🦁멋쟁이사자처럼 12기 중앙 해커톤]]></title>
            <link>https://velog.io/@raymond_yoon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-12%EA%B8%B0-%EC%A4%91%EC%95%99%ED%95%B4%EC%BB%A4%ED%86%A4</link>
            <guid>https://velog.io/@raymond_yoon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-12%EA%B8%B0-%EC%A4%91%EC%95%99%ED%95%B4%EC%BB%A4%ED%86%A4</guid>
            <pubDate>Sat, 01 Feb 2025 12:34:15 GMT</pubDate>
            <description><![CDATA[<p>매우 늦은 해커톤 후기와 나의 첫 백엔드로써 프로젝트에서 내 코드리뷰를 해볼 생각이다.
지금은 갓태어난 새 라면 이때는 알에 있었던 시기라고 할 수 있다. 그래도 운이 좋아서 이렇게 친구덕에 해커톤 경험도 해보고 너무 좋은 경험이였다. 성장 할 수 있는 곳에 가서 나의 실력을 매우 성장하고 싶다. 오히려 늦은 후기가 지금은 더 좋은 것 같다. 그 이유는 그때 보다 성장한 내가 그때 내가 했던 것을 보는건 좋은 경험이 될 것 같기 때문이다.</p>
<hr>

<h1 id="해커톤-후기">해커톤 후기</h1>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/c2d2950d-e586-4e6c-8309-8c812157b6ce/image.jpg" alt=""></p>
<p>해커톤을 처음 가봤는데 사람이 진짜 진짜 많았다.. 다른 학교들 사람들도 보면서 내가 이런 경험도 해보다니 너무 좋은 경험이라고 생각했다. </p>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/fb257253-9074-4d1e-9239-4659baf76165/image.jpg" alt=""></p>
<p>친구들이랑 이런 사진도 찍구 
<img src="https://velog.velcdn.com/images/raymond_yoon/post/78e0093b-6d44-4ea1-b182-3fb12c35c527/image.jpg" alt=""></p>
<p>날씨도 너무너무 좋았다~ 
여러가지 부스 체험도 해보고 팀원끼리 이야기도 나누면서 새벽을 보내고 좋은 경험이였다~</p>
<p>내가 백엔드로 가고 첫 프로젝트였는데 간단한 CRUD 구현과 네이버 뉴스 API 활용하는 담당을 맡았다. 
솔직히 지금도 모르겠지만, 저 때는 내가 할 수 있을까? 라는 생각이 들었던것 같다.
간단한 CRUD 구현조차 구글링과 gpt의 도움을 받고, 네이버 뉴스 API 활용하는것은 구글링과 네이버 공식 문서 및 gpt를 쓰는데도 시간이 많이 걸렸다.
하지만 이렇게 하면서 성장을 많이 한것 같다.</p>
<p>내가 맡은 부분 중 2가지를 봐볼건데 좋아요 기능과 네이버뉴스 API를 활용하는 것이다. </p>
<hr>
<h2 id="첫-해커톤-프로젝트-리뷰--좋아요-기능과-네이버-뉴스-api-활용기">첫 해커톤 프로젝트 리뷰 : 좋아요 기능과 네이버 뉴스 API 활용기</h2>
<h3 id="code-review--좋아요like-기능-구현-및-개선-포인트">Code Review : 좋아요(Like) 기능 구현 및 개선 포인트</h3>
<ul>
<li>프로젝트 구조<blockquote>
<p>like/<br>├── common/
│    └── ResponseResult.java<br>├── controller/
│    └── LikeController.java<br>├── dto/
│    └── LikeRequestDTO.java<br>├── entity/
│    └── Like.java<br>├── exception/ 
│    └── CustomException.java<br>├── repository/
│    └── LikeRepository.java<br>└── service/
ㅒ    └── LikeService.java  </p>
</blockquote>
</li>
</ul>
<p>프로젝트 구조는 각 계층별 역할을 분리하여 유지보수와 확장에 용이한 구조가 아닐까 생각한다! (GPT 최고!)
근데 나중에 프로젝트할때 같이 하는 사람마다 다르게 분류할 수 도 있다는 것을 깨달았다! </p>
<p><strong>&lt;각 파일 및 코드 리뷰&gt;</strong></p>
<p><strong>(common)</strong></p>
<ul>
<li><strong>ResponseResult.java</strong></li>
<li><em>역할:*</em> API 응답 구조(성공 여부, 데이터, 에러 메시지) 통일 관리</li>
<li><em>아쉬운 점:*</em> 응답에 타임스탬프 등 추가 메타 정보 부족</li>
</ul>
<p><strong>(controller)</strong></p>
<ul>
<li><strong>LikeController.java</strong></li>
<li><em>역할:*</em> 클라이언트의 좋아요 추가/삭제 요청을 받아 서비스 호출 및 결과 반환</li>
<li><em>아쉬운 점:*</em> 중복 try-catch로 인한 코드 반복, 글로벌 예외 처리 미적용</li>
</ul>
<p><strong>(dto)</strong></p>
<ul>
<li><strong>LikeRequestDTO.java</strong></li>
<li><em>역할:*</em> 좋아요 요청에 필요한 postId 데이터 캡슐화</li>
<li><em>아쉬운 점:*</em> 향후 확장 대비 추가 데이터 고려 부족</li>
</ul>
<p><strong>(entity)</strong></p>
<ul>
<li><strong>Like.java</strong></li>
<li><em>역할:*</em> 좋아요 정보를 DB에 매핑, Board와 Member와의 연관관계 관리</li>
<li><em>아쉬운 점:*</em> 양방향 연관관계 설정 미검토, 로깅 기능 부재</li>
</ul>
<p><strong>(exception)</strong></p>
<ul>
<li><strong>CustomException.java</strong></li>
<li><em>역할:*</em> 좋아요 기능 관련 예외 상황 전달</li>
<li><em>아쉬운 점:*</em> 에러 코드 등 상세 예외 정보 미포함</li>
</ul>
<p><strong>(repository)</strong></p>
<ul>
<li><strong>LikeRepository.java</strong></li>
<li><em>역할:*</em> DB에서 Like 관련 CRUD 및 존재 여부 확인</li>
<li><em>아쉬운 점:*</em> 복잡한 쿼리 최적화나 커스텀 쿼리 고려 미흡</li>
</ul>
<p><strong>(service)</strong></p>
<ul>
<li><strong>LikeService.java</strong></li>
<li><em>역할:*</em> 좋아요 생성/삭제, 중복 체크, 트랜잭션 관리 및 추천수 업데이트</li>
<li><em>아쉬운 점:*</em> boardService 의존성으로 인한 도메인 간 결합, 이벤트 기반 처리 미고려, 로그 기록 미흡</li>
</ul>
<h3 id="code-review--좋아요like-기능-구현-및-개선-포인트-1">Code Review : 좋아요(Like) 기능 구현 및 개선 포인트</h3>
<ul>
<li>프로젝트 구조<blockquote>
<p>healthnews/
├── client/<br>│    └── JsoupCrawling.java
├── controller/<br>│    └── HealthNewsController.java
├── dto/<br>│    ├── HealthNewsResponseDto.java
│    ├── NewsApiResponseDto.java
│    └── NewsSummaryDto.java
├── entity/<br>│    └── HealthNews.java 
├── exception/<br>│    ├── NaverApiCallException.java
│    ├── NewsControllerAdvice.java
│    └── NewsNullException.java 
├── repository/<br>│    ├── HealthNewsRepository.java
│    └── NewsApiResponseRepository.java
└── service/<br>ㅐ  └── HealthNewsService.java</p>
</blockquote>
</li>
</ul>
<p><strong>&lt;각 파일 및 코드 리뷰&gt;</strong></p>
<p><strong>(client)</strong></p>
<ul>
<li><strong>JsoupCrawling.java</strong></li>
<li><em>역할:*</em> Jsoup을 사용해 URL의 HTML을 파싱하고, 지정된 쿼리로 요소(Elements)를 추출하여 반환</li>
<li><em>아쉬운 점:*</em> 예외 발생 시 단순 e.printStackTrace()에 의존, 로깅 및 재시도 메커니즘 미구현</li>
</ul>
<p><strong>(controller)</strong></p>
<ul>
<li><strong>HealthNewsController.java</strong></li>
<li><em>역할:*</em> 키워드 기반 건강 뉴스 검색과 사용자 건강 상태 기반 뉴스 제공 API 엔드포인트 관리</li>
<li><em>아쉬운 점:*</em> 검색과 사용자 뉴스 조회 기능이 혼재, 입력 검증 및 예외 처리 보완 필요</li>
</ul>
<p><strong>(dto)</strong></p>
<ul>
<li><p><strong>HealthNewsResponseDto.java</strong></p>
</li>
<li><p><em>역할:*</em> 뉴스 응답 데이터(제목, 링크, 설명, 이미지 URL)를 캡슐화하고 이미지 링크 유효성 검사 제공</p>
</li>
<li><p><em>아쉬운 점:*</em> 추가적인 메타 정보나 확장 시 고려할 필드 부족</p>
</li>
<li><p><strong>NewsApiResponseDto.java</strong></p>
</li>
<li><p><em>역할:*</em> 뉴스 API 응답 데이터를 담고, Naver 뉴스만 필터링 및 HealthNewsResponseDto 변환 제공</p>
</li>
<li><p><em>아쉬운 점:*</em> JsoupCrawling 인스턴스를 직접 생성하여 DI(의존성 주입) 미적용, 테스트 용이성 저하</p>
</li>
<li><p><strong>NewsSummaryDto.java</strong></p>
</li>
<li><p><em>역할:*</em> 뉴스 요약 정보를 담고, 이미지 크롤링을 통해 HealthNewsResponseDto로 변환</p>
</li>
<li><p><em>아쉬운 점:*</em> JsoupCrawling 의존성을 외부 주입 대신 내부 처리하여 유연성 부족</p>
</li>
</ul>
<p><strong>(entiry)</strong></p>
<ul>
<li><strong>HealthNews.java</strong></li>
<li><em>역할:*</em> 건강 뉴스 엔티티로, DB 테이블과 매핑되어 뉴스 저장 관리</li>
<li><em>아쉬운 점:*</em> 기본 CRUD 외에 추가 검색이나 비즈니스 로직 반영 미흡</li>
</ul>
<p><strong>(exception)</strong></p>
<ul>
<li><p><strong>NaverApiCallException.java</strong></p>
</li>
<li><p><em>역할:*</em> 네이버 API 호출 실패 시 발생하는 커스텀 예외 정의</p>
</li>
<li><p><em>아쉬운 점:*</em> 디버깅을 위한 추가 정보 제공 미흡</p>
</li>
<li><p><strong>NewsControllerAdvice.java</strong></p>
</li>
<li><p><em>역할:*</em> NewsNullException과 NaverApiCallException에 대한 전역 예외 처리</p>
</li>
<li><p><em>아쉬운 점:*</em> 추가 예외 유형 처리 및 통일된 응답 포맷 적용 필요</p>
</li>
<li><p><strong>NewsNullException.java</strong></p>
</li>
<li><p><em>역할:*</em> 뉴스 결과가 없을 경우 발생하는 커스텀 예외 정의</p>
</li>
<li><p><em>아쉬운 점:*</em> 상세 메시지 및 예외 코드 부여 미흡</p>
</li>
</ul>
<p><strong>(repository)</strong></p>
<ul>
<li><p><strong>HealthNewsRepository.java</strong></p>
</li>
<li><p><em>역할:*</em> HealthNews 엔티티에 대한 CRUD 작업을 수행하는 Repository</p>
</li>
<li><p><em>아쉬운 점:*</em> 기본 CRUD 외 추가 검색 조건 및 페이징 기능 미구현</p>
</li>
<li><p><strong>NewsApiResponseRepository.java</strong></p>
</li>
<li><p><em>역할:*</em> 네이버 뉴스 API 호출 및 응답 데이터를 Optional 형태로 반환</p>
</li>
<li><p><em>아쉬운 점:*</em> RestTemplate 사용 시 커넥션 타임아웃, 에러 핸들링 개선 필요</p>
</li>
</ul>
<p><strong>(service)</strong></p>
<ul>
<li><strong>HealthNewsService.java</strong></li>
<li><em>역할:*</em> 네이버 뉴스 API를 통해 뉴스 데이터를 수집, 필터링, 가공하여 최종 HealthNewsResponseDto로 반환 및 DB저장</li>
<li><em>아쉬운 점:*</em> 반복 API 호출 시 매직넘버(2000, 100, 4)사용, API 제한 고려 및 효율성 개선 필요</li>
</ul>
<hr>

<h2 id="마무리-한마디"><strong>마무리 한마디</strong></h2>
<p>이번 리뷰를 통해서 좋아요 기능과 네이버 뉴스 API 모두에서 전역 예외 처리, 로깅, 의존성 관리 및 API 호출 최적화 등 개선할 부분이 많음을 확인했으니 앞으로 이러한 점들을 보완해 더 안정적이고 확장 가능한 시스템을 만들어보자잇~ 나의 코드를 보는 눈을 더 키워 봅시다! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[👨🏻‍💻자료구조란 무엇일까?]]></title>
            <link>https://velog.io/@raymond_yoon/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@raymond_yoon/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Tue, 05 Nov 2024 06:43:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/aac432cf-7ecb-4468-9696-92ede575ace5/image.png" alt=""></p>
<p>취업을 하여 실무를 경험하고 싶은 요즘 스펙도 없고 요즘 자존감이 떨어지고 불안한 시기인 아무것도 아닌 나를 위해 좀 더 성실하고 열심히 살기위해 글을 쓴다. 스티브 잡스분께서 말씀하신 <strong>&quot;어제를 뒤돌아보는 건 그만하자. 그 대신 내일을 발전시켜 나가자.&quot;</strong> 라는 말을 생각하며 나는 더 나은 내일을 위해 발전시켜 나가는것이다! 취업하고싶다아</p>
<hr>
<hr>

<h1 id="자료구조란-무엇">자료구조란 무엇?</h1>
<blockquote>
<p>프로그래밍에서 효율적인 데이터 관리와 처리를 위해 필수적으로 알아야 하는 개념이 바로 자료구조라고 생각한다.
자료구조는 데이터를 저장하고 조직화하는 방법을 의미하고 알고리즘과 결합하여 프로그램의 성능을 최적화할 수 있다.
이 글에서는 자료구조의 기본 개념과 몇 가지 주요 자료구조를 살펴볼 것이다.</p>
</blockquote>
<hr>

<h2 id="1-자료구조의-필요성">1. 자료구조의 필요성</h2>
<p>자료구조는 다양한 형태의 데이터를 적절히 조직화하여 더 효율적인 연산을 가능하게 한다. 예를 들어 특정 데이터를 빠르게 검색해야 할 때 적합한 자료구조를 사용하면 검색 속도를 크게 향상시킬 수 있다.
효율적인 자료구조를 선택함으로써 시간과 메모리를 절약하고 프로그램의 성능을 최적화할 수 있다.</p>
<hr>

<h2 id="2-자료구조의-종류">2. 자료구조의 종류</h2>
<h3 id="2-1-배열-array">2-1 배열 (Array)</h3>
<p>배열은 같은 데이터 타입의 요소들을 연속된 메모리 공간에 저장하는 자료구조이다. 각 요소는 고유한 인덱스를 통해 접근할 수 있다.
<img src="https://velog.velcdn.com/images/raymond_yoon/post/4e0d988c-cfad-49a1-a75d-bcdd37942e90/image.png" alt=""></p>
<h4 id="장점">장점</h4>
<ul>
<li><strong>빠른 접근 속도:</strong> 인덱스를 통해 O(1) 시간 복잡도로 빠르게 특정 요소에 접근할 수 있다.</li>
<li><strong>간단한 구조:</strong> 메모리 구조가 단순하고 이해하기 쉬워 자주 사용된다.</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li><strong>고정된 크기:</strong> 선언 시 크기를 지정해야 하므로 크기 변경이 어렵다.</li>
<li><strong>비효율적인 삽입/삭제:</strong> 중간에 데이터를 삽입하거나 삭제할 경우에 데이터를 이동해야 하므로 시간 복잡도가 O(n)이다.<hr>

</li>
</ul>
<h3 id="2-2-연결-리스트-linked-list">2-2 연결 리스트 (Linked List)</h3>
<p>연결 리스트는 각각의 노드가 데이터와 다음 노드를 가리키는 포인터를 포함하는 구조이다. 노드를 동적으로 추가 및 삭제할 수 있어 유연성이 높다.
<img src="https://velog.velcdn.com/images/raymond_yoon/post/89c36a78-e5ad-4b06-a93b-1ee820bc390a/image.png" alt=""></p>
<h4 id="장점-1">장점</h4>
<ul>
<li><strong>동적 크기 조절:</strong> 필요에 따라 크기를 유연하게 변경할 수 있다.</li>
<li><strong>효율적인 삽입/삭제:</strong> 중간 삽입 및 삭제 시 데이터를 이동할 필요가 없어 O(1) 시간 복잡도로 처리할 수 있다.</li>
</ul>
<h4 id="단점-1">단점</h4>
<ul>
<li><strong>느린 접근 속도:</strong> 배열과 달리 인덱스로 접근할 수 없기 때문에 특정 노드에 접근하려면 처음부터 순회해야 한다.</li>
<li><strong>추가 메모리 필요:</strong> 각 노드마다 다음 노드를 가리키는 포인터를 저장해야 하므로 메모리 사용량이 증가한다.<hr>

</li>
</ul>
<h3 id="2-3-스택-stack">2-3 스택 (Stack)</h3>
<p>스택은 데이터를 한쪽 끝에서만 삽입하고 제거하는 자료구조로, LIFO(Last In, First Out) 원칙을 따른다. 대표적으로 함수 호출, 되돌리기 기능 등에 사용된다.
<img src="https://velog.velcdn.com/images/raymond_yoon/post/05447d4f-8fd5-4698-8cfb-43a41b06da41/image.png" alt=""></p>
<h4 id="장점-2">장점</h4>
<ul>
<li><strong>간단한 구현:</strong> 삽입 및 삭제가 한쪽 끝에서만 이렁나기 때문에 구조가 단순하다.</li>
<li><strong>빠른 삽입/삭제:</strong> O(1) 시간 복잡도로 삽입과 삭제를 할 수 있다.</li>
</ul>
<h4 id="단점-2">단점</h4>
<ul>
<li><strong>제한된 접근 방식:</strong> 마지막에 삽입된 요소만 접근 가능하여 중간 요소를 접근할 수 없다.<hr>

</li>
</ul>
<h3 id="2-4-큐-queue">2-4 큐 (Queue)</h3>
<p>큐는 FIFO(First In, First Out) 원칙을 따르는 자료구조이다. 데이터를 앞쪽에서 제거하고 뒤쪽에 삽입한다. 대기열과 같은 구조에 적합하다.
<img src="https://velog.velcdn.com/images/raymond_yoon/post/0e110f45-c6d3-4d9e-a919-14bc50625fb2/image.png" alt=""></p>
<h4 id="장점-3">장점</h4>
<ul>
<li><strong>순서 유지:</strong> 먼저 들어온 데이터가 먼저 나가는 구조로, 순서가 중요한 작업에 유리하다.</li>
<li><strong>빠른 삽입/삭제:</strong> 삽입과 삭제가 양쪽 끝에서만 일어나므로 O(1) 시간 복잡도로 효율적이다.</li>
</ul>
<h4 id="단점-3">단점</h4>
<ul>
<li><strong>제한된 접근 방식:</strong> 중간에 있는 요소에 직접 접근할 수 없다.<hr>

</li>
</ul>
<h3 id="2-5-트리-tree">2-5 트리 (Tree)</h3>
<p>트리는 계층 구조를 가지는 비선형 자료구조로, 노드 간 부모-자식 관계가 존재한다. 이진 트리, AVL 트리, B-트리 등 다양한 형태가 있으며 데이터 구조와 알고리즘 문제에서 자주 사용된다.
<img src="https://velog.velcdn.com/images/raymond_yoon/post/008ef6a0-dd50-41eb-91bd-759aa44fa2fe/image.png" alt=""></p>
<h4 id="장점-4">장점</h4>
<ul>
<li><strong>빠른 탐색:</strong> 정렬된 트리 구조에서는 원하는 데이터를 빠르게 탐색할 수 있다. 예를 들면 이진 탐색 트리는 O(log n) 시간 복잡도를 가진다.</li>
<li><strong>계층적 데이터 표현:</strong> 트리 구조를 통해 계층적으로 데이터를 나타낼 수 있어 파일 시스템이나 조직도 표현에 적합하다.</li>
</ul>
<h4 id="단점-4">단점</h4>
<ul>
<li><strong>복잡한 구현:</strong> 삽입, 삭제, 균형 조정이 복잡할 수 있고 메모리와 연산 성능이 요구된다.</li>
<li><strong>비용이 많이 드는 균형 유지:</strong> 자가 균형 트리(BST)의 경우 삽입과 삭제 시 균형을 맞추기 위해 추가 연산이 필요하다.<hr>

</li>
</ul>
<h3 id="2-6-그래프-graph">2-6 그래프 (Graph)</h3>
<p>그래프는 노드(정점)와 그 노드들을 연결하는 간선(엣지)으로 이루어진 자료구조이다. 방향 그래프, 무방향 그래프, 가중치 그래프 등 다양한 종류가 있고 네트워크, 경로찾기 등의 문제에 활용된다.
<img src="https://velog.velcdn.com/images/raymond_yoon/post/8b724feb-fc6e-425c-af4c-430e4edfcccb/image.png" alt=""></p>
<h4 id="장점-5">장점</h4>
<ul>
<li><strong>복잡한 관계 표현 가능:</strong> 그래프는 네트워크, 지도 등 복잡한 데이터 간의 관계를 표현하는데 유리하다.</li>
<li><strong>유연한 구조:</strong> 장점과 간선의 관계를 자유롭게 설정할 수 있어 다양한 문제를 해결할 수 있다.</li>
</ul>
<h4 id="단점-5">단점</h4>
<ul>
<li><strong>높은 메모리 사용량:</strong> 노드와 간선을 모두 저장해야 하므로 메모리 소모가 크다.</li>
<li><strong>복잡한 구현:</strong> 경로 탐색, 최단 거리 등 다양한 알고리즘이 필요해 구현이 복잡할 수 있다.</li>
</ul>
<hr>

<p>이처럼 각각의 자료구조는 특정한 장점과 단점을 가지고 있으므로 용도에 따라 최적의 자료구조를 선택하는 것이 중요하다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🦁멋쟁이사자처럼 백엔드 스터디 (4)]]></title>
            <link>https://velog.io/@raymond_yoon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%8A%A4%ED%84%B0%EB%94%94-4</link>
            <guid>https://velog.io/@raymond_yoon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%8A%A4%ED%84%B0%EB%94%94-4</guid>
            <pubDate>Sun, 29 Sep 2024 03:37:40 GMT</pubDate>
            <description><![CDATA[<p>어느덧 마지막 학기를 시작한지 거의 한달이 되어간다. 추석도 있고 가족여행도 갔다와서 금방 지나간것 같다. 졸업을 해야되어서 지금 준비중인 논문 프로젝트와 졸업시험, 자격증, 취업 같은 걱정들이 너무 많다. 지금 진행중인 프로젝트도 잘됐음 좋겠고 모든게 다 잘되길 그리고 취업을 할 수 있었음 좋겠다. 그리고 내 주위 모두가 건강했으면 좋겠다.</p>
<h1 id="백엔드-스터디-4">백엔드 스터디 (4)</h1>
<hr>
이번 포스팅에서는 Spring Boot에서 자주 사용되는 주요 어노테이션과 REST API에서 자주 활용되는 컨트롤러 어노테이션을 정리했다. 기본 키(PK) 설정부터, 필드와 객체 매핑, 그리고 각 HTTP 메소드에 대응하는 컨트롤러 어노테이션을 설명한다.
<hr>

<h2 id="목차">목차</h2>
<ol>
<li>기본 키(PK) 어노테이션</li>
<li>필드/컬럼 어노테이션</li>
<li>객체와 테이블 매핑</li>
<li>REST API 개념 </li>
<li>컨트롤러에서 사용하는 어노테이션</li>
</ol>
<hr>

<h3 id="1-기본-키pk-어노테이션">1. 기본 키(PK) 어노테이션</h3>
<ul>
<li>@Id: 엔티티의 기본 키를 선언하는 어노테이션</li>
<li>@GeneratedValue: 기본 키 값을 자동으로 생성하는 전략을 설정하는 어노테이션<ul>
<li>GenerationType.AUTO : 자동으로 키를 생성</li>
<li>GenerationType.IDENTITY : 데이터베이스에 의해 키를 생성<pre><code class="language-java">@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="2-필드컬럼-어노테이션">2. 필드/컬럼 어노테이션</h3>
<ul>
<li>@Column : 필드를 테이블 컬럼에 매핑할 때 사용되는 어노테이션<ul>
<li>name : 테이블의 컬럼명</li>
<li>nullable : null 값을 허용할지 여부</li>
<li>length : 문자열의 최대 길이 설정</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@Column(name = &quot;username&quot;, nullable = false, length = 50)
private String name;
</code></pre>
<ul>
<li>@Enumerated : 자바의 enum 타입을 데이터베이스에 매핑할 때 사용<pre><code class="language-java">@Enumerated(EnumType.STRING)
private RoleType roleType;
</code></pre>
</li>
</ul>
<pre><code>
### 3. 객체와 테이블 매핑
Hibernate의 hbm2ddl.auto 설정을 통해 엔티티와 테이블 간의 매핑 및 자동 생성 여부를 설정할 수 있다.
* create, update, validate 등 다양한 옵션을 선택할 수 있으며, 실제 개발 환경에서 주의가 필요하다.

&lt;hr&gt;

### 4. REST API 개념
REST API는 웹 시스템에서 분산된 하이퍼미디어 시스템을 위한 소프트웨어 아키텍쳐이다. HTTP 프로토콜을 사용해 다양한 리소스를 주고받으며 각 리소스는 URL로 식별된다.

### 5. 컨트롤러에서 사용하는 어노테이션

@Controller
* @Controller와 @ResponseBody를 결합한 어노테이션으로 JSON/XML 형식의 데이터를 반환할때 사용된다.
```java
@RestController
public class MyRestController {
    @GetMapping(&quot;/greeting&quot;)
    public String greeting() {
        return &quot;Hello, World!&quot;;
    }
}</code></pre><p>@RequestParam</p>
<ul>
<li>URL 쿼리 파라미터로 전달된 값을 메소드 매개변수에 바인딩한다.<pre><code class="language-java">@GetMapping(&quot;/test&quot;)
public void test(@RequestParam String name, @RequestParam int price) {
  System.out.println(name);  // 출력: spring
  System.out.println(price); // 출력: 20000
}</code></pre>
</li>
</ul>
<p>@RequestBody</p>
<ul>
<li>HTTP 요청의 바디 부분을 자바 객체로 변환해준다. 주로 POST 메소드에서 사용된다.<pre><code class="language-java">@PostMapping(&quot;/new&quot;)
public void createQuestion(@RequestBody QuestionDTO dto) {
  // 처리 로직
}</code></pre>
</li>
</ul>
<p>@PathVariable</p>
<ul>
<li>URL 경로에서 값을 가져올때 사용된다. 경로에 있는 변수를 메소드 매개변수에 바인딩한다.<pre><code class="language-java">@GetMapping(&quot;/champions/{name}&quot;)
public List&lt;String&gt; getChampionByName(@PathVariable String name) {
  return championService.getChampionByName(name);
}</code></pre>
</li>
</ul>
<hr>

<h3 id="마무리">마무리</h3>
<p>Spring Boot에서 제공하는 어노테이션들은 REST API 개발에서 매우 유용하게 활용된다. 
이번 포스팅에서는 그중에서 자주 사용되는 기본 어노테이션들과 컨트롤러 어노테이션을 알아보았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🦁멋쟁이사자처럼 백엔드 스터디 (3)]]></title>
            <link>https://velog.io/@raymond_yoon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%8A%A4%ED%84%B0%EB%94%94-3</link>
            <guid>https://velog.io/@raymond_yoon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%8A%A4%ED%84%B0%EB%94%94-3</guid>
            <pubDate>Wed, 04 Sep 2024 11:38:25 GMT</pubDate>
            <description><![CDATA[<p>정보처리기사 필기도 보고, 해커톤도 하고, 운동도 하면서 방학을 보냈다. 뭔가 좀 더 바쁘게 살았어야했다는 생각이 들었지만, 내 인생에서 학생때의 마지막 방학이여서 뭔가 이제 정말 시작이구나 라고 생각했다. 항상 걱정은 많은 것 같다. 첫번째로 괜찮은 기업에 들어가서 많이 배우고 성장하고 싶다는 생각에 나를 좀 더 성장시켜야겠다고 생각했다. 내 학생 인생의 마지막 학기가 시작된다. 졸업요건도 생각하고 물론 배울수있는 좋은 회사에 들어가서 취업계 내서 졸업하면 최고Best라고 생각한다. 마지막 4학년 2학기가 끝나고 내가 어떻게 됐을지 궁금하면서 걱정된다. 잘 됐음 좋겠네. </p>
<h1 id="백엔드-스터디-3">백엔드 스터디 (3)</h1>
<hr>
이번 포스팅에서는 Spring에서 JPQL(Java Persistence Query Language)과 Spring Data JPA를 활용하여 데이터베이스와 상호작용하는 방법을 스터디한 내용을 공유하고자 한다. JPQL은 SQL과 유사한 문법을 가지면서도 엔티티 객체를 대상으로 쿼리를 작성할 수 있어 객체지향적인 프로그래밍에 적합하다. 또한 Spring Data JPA는 간단한 쿼리에서부터 복잡한 데이터 접근까지 편리하게 처리할 수 있는 기능을 제공한다. 
<hr>

<h2 id="구조">구조</h2>
<h3 id="애플리케이션-구조">애플리케이션 구조</h3>
<p>Spring 애플리케이션은 다음과 같은 주요 구성들로 이루어져 있다. 여기서 각 구성 요소가 어떻게 상호작용하는지 이해하는것이 중요하다.</p>
<ul>
<li><p>Controller : 클라이언트로부터 HTTP 요청을 받아 처리한다.</p>
</li>
<li><p>Service : 비즈니스 로직을 구현하며, 데이터를 처리하고 저장소와 상호작용한다.</p>
</li>
<li><p>Repository : 실제 데이터베이스와 상호작용하며, CRUD 작업을 처리한다.</p>
</li>
<li><p>DTO : 데이터 전송 객체로, 계층 간 데이터를 전달하는 역할을 한다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/9d274563-884e-44b5-b5e5-776210d60f1c/image.png" alt=""></p>
<h3 id="패키지-구조">패키지 구조</h3>
<p>다음은 일반적인 Spring 프로젝트의 패키지 구조다.</p>
<ul>
<li><p>config : 애플리케이션 설정 파일이 위치하는 패키지 </p>
</li>
<li><p>controller : HTTP 요청을 처리하는 컨트롤러 클래스가 위치</p>
</li>
<li><p>domain : 도메인 객체들이 정의된 곳 </p>
</li>
<li><p>repository : 데이터베이스와의 상호작용을 담당하는 리포지토리 클래스가 위치</p>
</li>
<li><p>service : 비즈니스 로직을 담당하는 서비스 클래스가 위치</p>
</li>
</ul>
<hr>

<h2 id="jpql-java-persistence-query-language">JPQL (Java Persistence Query Language)</h2>
<p>JPQL은 객체를 대상으로 쿼리를 작성하는 Java Persistence API의 쿼리 언어이다. SQL과 문법이 비슷하지만 테이블이 아닌 엔티티 객체에 대한 쿼리를 수행한다.</p>
<h3 id="jpql-기본-예시">JPQL 기본 예시</h3>
<pre><code class="language-java">@PersistenceContext
EntityManager em;

public void main save(Board board) {
    em.persist(board);
}</code></pre>
<p>EntityManager를 통해 Board 엔티티를 데이터베이스에 저장하는 방법을 보여준다.
JPQL을 사용하면 SQL과 유사하게 엔티티 객체를 조회하고 수정할 수 있다.</p>
<h3 id="jpql로-데이터-조회">JPQL로 데이터 조회</h3>
<p>JPQL을 사용해 데이터를 조회할 때는 @Query 어노테이션을 사용할 수 있다.</p>
<pre><code class="language-java">@Query(&quot;SELECT c FROM ChatRoom c WHERE c.roomId = :roomId&quot;)
ChatRoom findChatRoomByRoomId(@Param(&quot;roomId&quot;) String roomId);</code></pre>
<p>위 코드는 ChatRoom 엔티티에서 특정 roomId 값을 기준으로 데이터를 조회하는 JPQL 쿼리이다.</p>
<hr>

<h2 id="연결">연결</h2>
<p>Spring Data JPA와 데이터베이스를 연결하기 위해서는 설정 파일을 사용해 데이터베이스 관련 정보를 설정해야한다. </p>
<pre><code>dependencies {
    implementation&#39;org.mariadb.jdbc:mariadb-java-client:3.1.2&#39;
    implementation&#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
}
-----------------------------------------------
spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://localhost:8080/yourdb
    username: 사용자이름
    password: 비밀번호</code></pre><p>Spring Boot 애플리케이션이 MariaDB와 연결되도록 설정한다.</p>
<hr>

<h2 id="jpa-실습">JPA 실습</h2>
<h3 id="엔티티-클래스-정의">엔티티 클래스 정의</h3>
<pre><code class="language-java">@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Gyumin {
    @Id
    @Column(name = &quot;Mccc_id&quot;)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String roomId;
    private String fileName;
    private String code;

    // 생성자
    public Gyumin(String roomId, String fileName, String code) {
        this.roomId = roomId;
        this.fileName = fileName;
        this.code = code;
    }
}
</code></pre>
<p>위 클래스는 Gyumin 이라는 엔티티로 데이터베이스 테이블의 컬럼과 매핑된다. 각 필드는 id, roomId, fileName, code와 같이 정의되었고 @Id로 기본 키가 지정되었다.</p>
<h3 id="repository-인터페이스">Repository 인터페이스</h3>
<p>JPA에서 데이터를 조회할 때 Repository 패턴을 사용한다. SnapshotRespository 인터페이스는 MongoDB와 JPA를 함께 사용하여 데이터를 다루는 방법을 보여준다.</p>
<pre><code class="language-java">public interface SnapshotRepository extends MongoRepository&lt;Snapshot, String&gt; {
    Snapshot findByRoomId(String roomId);
    List&lt;Snapshot&gt; findSnapshotsByRoomId(String roomId);
    Snapshot findByRoomIdAndFileName(String roomId, String fileName);
}</code></pre>
<p>위 코드는 SnapshotRepository를 통홰 roomId로 데이터를 조회하는 쿼리를 작성하는 방법을 보여준다. findByRoomId는 roomId 값으로 데이터를 조회하는 기능을 수행하며 findSnapshotsByRoomId는 동일한 roomId로 여러 개의 Snapshot 리스트를 반환한다.</p>
<h3 id="jpql을-사용한-쿼리-작성">JPQL을 사용한 쿼리 작성</h3>
<p>JPA에서는 @Query 어노테이션을 사용하여 JPQL 쿼리를 정의할 수 있다.</p>
<pre><code class="language-java">public interface ManageRepository extends JpaRepository&lt;Manage, Long&gt; {

    @Query(&quot;select m from Manage m &quot; +
           &quot;where m.team.id = :teamId &quot; +
           &quot;order by m.createDate&quot;)
    List&lt;Manage&gt; findManagesByTeamId(@Param(&quot;teamId&quot;) Long teamId);
}</code></pre>
<p>위 코드에서는 @Query 어노테이션을 통해 직접 JPQL 쿼리를 작성하였고, teamId 파라미터를 사용하여 team.id에 해당하는 데이터를 조회한다. @Param 어노테이션을 사용하여 쿼리 매개변수를 바인딩한다.</p>
<hr>

<h2 id="벌크성-쿼리-builk-queries">벌크성 쿼리 (Builk Queries)</h2>
<p>벌크성 쿼리는 다수의 데이터를 한번에 처리하는데 사용된다. 대용량 데이터를 업데이트하거나 삭제할 때 성능 최적화에 유리하다.</p>
<h3 id="벌크-업데이트-예시">벌크 업데이트 예시</h3>
<pre><code class="language-java">@Modifying
@Query(&quot;UPDATE Member m SET m.age = m.age + 1 WHERE m.age &gt;= :age&quot;)
int bulkUpdateAge(@Param(&quot;age&quot;) int age);</code></pre>
<p>위 쿼리는 age 값이 일정 수준 이상인 회원들의 나이를 일괄적으로 1씩 증가시키는 벌크 업데이트 쿼리이다. @Modifying 어노테이션을 사용하여 업데이트 쿼리를 처리할 수 있다.</p>
<hr>
이번에는 Spring에서 JPQL과 JPA를 사용하여 객체지향적으로 데이터를 처리하는 방법을 알았다. JPQL을 통해 복잡한 쿼리도 쉽게 작성할 수 있으며 벌크성 쿼리를 통해 대량 데이터를 효율적으로 처리할 수 있다. 앞으로 더 복잡한 JPQL 쿼리 작성법과 성능 최적화 방법을 더 알아보자 ]]></description>
        </item>
        <item>
            <title><![CDATA[🦁멋쟁이사자처럼 백엔드 스터디 (2)]]></title>
            <link>https://velog.io/@raymond_yoon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%8A%A4%ED%84%B0%EB%94%94-2</link>
            <guid>https://velog.io/@raymond_yoon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%8A%A4%ED%84%B0%EB%94%94-2</guid>
            <pubDate>Wed, 14 Aug 2024 07:35:07 GMT</pubDate>
            <description><![CDATA[<p>스터디를 한지 시간이 오래 지나긴 했지만, 기록이 진짜 중요하고, 이런 기록 하나하나가 나에게 도움 된다고 생각해서 나를 위해 쓴다고 생각하여 늦게라도 쓴다.</p>
<p>지금은 멋쟁이사자처럼 대학 12th HACKATHON을 경험하고 온 후여서 그때 스터디할때는 이게 어떻게 쓰이지? 뭐지? 라는 생각이 해커톤을 경험한 후, 아 이렇게 쓰이는구나 이런식이구나 라는걸 많이 깨달았다. 
역시 나는 몸으로 직접 부딪치면서 깨달아야한다는것을 알았다. 참 많은 도움이 된 해커톤이여서 빠른 시일 내에 회고를 해야겠다는 생각이 들었다. </p>
<h1 id="백엔드-스터디-2">백엔드 스터디 (2)</h1>
<hr>

<h2 id="용어">용어</h2>
<h3 id="연관관계-association">연관관계 (Association)</h3>
<p>엔티티 간의 관계를 의미하며, 데이터베이스의 테이블 간 외래 키 관계에 해당한다.</p>
<h3 id="매핑-mapping">매핑 (Mapping)</h3>
<p>객체 모델과 데이터베이스 모델 간의 관계를 정의하는 작업. JPA에서는 엔티티와 테이블을 매핑하고, 연관관계 매핑에서는 엔티티 간의 관계를 정의한다.</p>
<h3 id="단방향-연관관계-unidirectional-association">단방향 연관관계 (Unidirectional Association)</h3>
<p>한 쪽 엔티티만 다른 엔티티를 참조하는 관계</p>
<h3 id="양방향-연관관계-bidirectional-association">양방향 연관관계 (Bidirectional Association)</h3>
<p>두 엔티티가 서로를 참조하는 관계</p>
<h3 id="일대일-one-to-one">일대일 (One-to-One)</h3>
<p>한 엔티티가 하나의 다른 엔티티와 연관된 관계</p>
<h3 id="일대다-one-to-many">일대다 (One-to-Many)</h3>
<p>한 엔티티가 여러 엔티티와 연관된 관계</p>
<h3 id="다대일-many-to-one">다대일 (Many-to-One)</h3>
<p>여러 엔티티가 한 엔티티와 연관된 관계</p>
<h3 id="다대다-many-to-many">다대다 (Many-to-Many)</h3>
<p>여러 엔티티가 여러 엔티티와 연관된 관계</p>
<h3 id="주인-엔티티-owner-entity">주인 엔티티 (Owner Entity)</h3>
<p>연관관계에서 외래키를 소유하고 있는 엔티티</p>
<h3 id="주인-아닌-엔티티-non-owner-entity">주인 아닌 엔티티 (Non-Owner Entity)</h3>
<p>외래키를 소유하고 있지 않은 엔티티</p>
<hr>


<h2 id="연관관계의-필요성">연관관계의 필요성</h2>
<blockquote>
<p>객체 모델에서는 실제 비즈니스 로직을 구현하기 위해 엔티티간의 관계를 명확히 정의해야 한다. 데이터베이스의 테이블 간 외래키와 같은 관계를 객체 지향적으로 다루기 위해 연관관계가 필요하다.</p>
</blockquote>
<h2 id="매핑의-목적">매핑의 목적</h2>
<blockquote>
<p>객체 지향 코드와 관계형 데이터베이스의 구조를 일치시켜 데이터의 일관성을 유지하기 위함이다.
이를 통해 객체 모델을 통해 자연스럽게 데이터베이스 상호작용을 구현할 수 있다.</p>
</blockquote>
<hr>

<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/825f288d-a80f-4bae-8386-8e8dbbe7c209/image.png" alt=""></p>
<p>객체 연관관계는 간접적이고, 테이블 연관관계는 외래 키로 명확히 표현되며, JPA 매핑을 통해 이를 일관되게 연결할 수 있다.</p>
<hr>

<h2 id="연관관계-매핑의-종류">연관관계 매핑의 종류</h2>
<h3 id="일대일one-to-one-관계">일대일(One-to-One) 관계</h3>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/27e66a9d-bf98-4eb3-96d1-8abed478b0f8/image.png" alt=""></p>
<h4 id="객체-연관관계">객체 연관관계</h4>
<p>Member 객체와 Locker 객체가 1:1로 매핑되어 있다. 
즉, Member는 하나의 Locker를 가질 수 있고, Locker도 하나의 Member에만 연결된다.</p>
<h4 id="테이블-연관관계">테이블 연관관계</h4>
<p>데이터베이스에서는 MEMBER 테이블이 LOCKER_ID를 외래 키로 가지며, 이 키를 통해 LOCKER 테이블과 1:1 관계를 맺는다.
LOCKER_ID 필드는 외래 키(FK)이면서 유니크(UNIQUE) 제약조건이 설정되어 있어, 각 Locker는 하나의 Member와만 연관될 수 있다.</p>
<blockquote>
<p>일대일 관계는 두 엔티티가 서로 1:1로 매핑되는 관계로, 하나의 객체가 다른 하나의 객체에만 연관되는 구조이다. 
데이터베이스에서는 외래 키와 유니크 제약조건을 사용해 이를 구현하며, 객체 모델에서는 필드 참조를 통해 연관관계를 표현한다.</p>
</blockquote>
<h3 id="다대일many-to-one-관계">다대일(Many-to-One) 관계</h3>
<h4 id="단방향">&lt;단방향&gt;</h4>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/dbb9abb9-2a55-4ae4-aa12-e07902b838aa/image.png" alt=""></p>
<h4 id="객체-연관관계-1">객체 연관관계</h4>
<p>Member 객체와 Team 객체가 다대일 관계를 가진다. 
즉, 여러 Member 객체가 하나의 Team 객체와 연관될 수 있다.
Member 클래스는 Team team 필드를 통해 Team 객체를 참조한다. 
이는 여러 Member가 동일한 Team 객체에 속할 수 있음을 나타낸다.</p>
<h4 id="테이블-연관관계-1">테이블 연관관계</h4>
<p>데이터베이스에서는 MEMBER 테이블이 TEAM_ID를 외래 키(FK)로 가지며, 이를 통해 TEAM 테이블과의 관계를 정의한다.
TEAM_ID 필드는 MEMBER 테이블에 속하며, 이는 여러 MEMBER가 동일한 TEAM에 연결될 수 있도록 한다.</p>
<blockquote>
<p>다대일 관계는 여러 엔티티가 하나의 엔티티와 연관되는 관계로, 객체 모델에서는 Member가 Team을 참조하는 방식으로 표현된다. 
데이터베이스에서는 외래 키를 통해 이 관계를 구현하며, 여러 Member가 같은 Team에 속할 수 있다. 이 구조는 팀과 소속된 멤버들 간의 관계를 효율적으로 관리할 수 있게 해준다.</p>
</blockquote>
<h4 id="양방향">&lt;양방향&gt;</h4>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/d2b8fe66-eb0a-470f-8e93-cc90884ace4d/image.png" alt=""></p>
<h4 id="객체-연관관계-2">객체 연관관계</h4>
<p>Member와 Team 객체가 서로 양방향으로 참조하고 있다. Member 객체는 Team team 필드를 통해 Team 객체를 참조하며, Team 객체는 List&lt; Member &gt; members 필드를 통해 여러 Member 객체들을 참조한다.
즉, 여러 Member가 하나의 Team에 속하고, Team은 자신에게 속한 모든 Member들을 참조할 수 있다.</p>
<h4 id="테이블-연관관계-2">테이블 연관관계</h4>
<p>데이터베이스에서는 MEMBER 테이블이 TEAM_ID 외래 키(FK)를 통해 TEAM 테이블과 연관되어 있다.
사진에서 표시된 것처럼, Team 객체에 추가된 members 필드는 테이블 구조에는 영향을 주지 않으며, 이는 객체 모델 상에서만 관리된다.</p>
<p>이 관계에서 Member 엔티티가 연관관계의 주인이다. 왜냐하면, 외래 키(TEAM_ID)가 MEMBER 테이블에 존재하기 때문이다. 연관관계 주인은 데이터베이스에서 실제로 외래 키를 관리하고, 해당 키를 업데이트하는 역할을 한다.</p>
<blockquote>
<p>이 구조는 Member와 Team 간의 관계를 양방향으로 설정하여, Member가 Team을 참조할 뿐만 아니라 Team도 자신에게 속한 Member들을 참조할 수 있도록 한다. 
이러한 양방향 관계는 데이터베이스 상에서 외래 키를 관리하는 주인 엔티티(Member)와 비주인 엔티티(Team)로 구분되어 구현된다.</p>
</blockquote>
<h3 id="일대다one-to-many-관계">일대다(One-to-Many) 관계</h3>
<h4 id="단방향-1">&lt;단방향&gt;</h4>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/5390e563-49f1-4c84-92c4-dbe1510d902f/image.png" alt="">
<img src="https://velog.velcdn.com/images/raymond_yoon/post/0b191e95-6005-4952-ba50-71ff9fe1d594/image.png" alt=""></p>
<p>코드 구성을 보면 Team 클래스가 @OneToMany 어노테이션을 사용하여 Member들과 일대다 관계를 형성하고 있다.
@JoinColumn(name = &quot;TEAM_ID&quot;) 어노테이션은 members 리스트가 TEAM_ID 외래 키를 통해 Member들과 연관된다는 것을 나타낸다.
List&lt; Member &gt; members 필드를 통해 Team 객체는 여러 Member 객체를 관리할 수 있다.</p>
<h4 id="객체-연관관계-3">객체 연관관계</h4>
<p>Team 객체는 여러 Member 객체와 연결되며, 이 관계는 단방향이다. 즉, Team은 Member들을 참조하지만, Member는 Team을 참조하지 않는다.
관계가 단방향이기 때문에 Member 객체에서는 Team에 대한 정보가 없다.</p>
<h4 id="테이블-연관관계-3">테이블 연관관계</h4>
<p>데이터베이스에서 TEAM_ID는 MEMBER 테이블에 외래 키로 존재한다. 따라서 Member가 어떤 Team에 속해 있는지는 MEMBER 테이블의 TEAM_ID 컬럼을 통해 관리된다.</p>
<ul>
<li><p>주의사항 
&lt;외래 키 관리&gt;
@JoinColumn을 통해 지정된 외래 키는 반드시 MEMBER 테이블에 존재해야 하며, 이를 통해 Team과 Member 간의 연관관계가 설정된다.
team.getMembers().add(member) 메서드를 호출하지 않으면, Member의 TEAM_ID 필드가 null로 저장되어 관계가 설정되지 않는다.</p>
<p>&lt;Update 쿼리 발생&gt;
Team 객체에 Member 객체를 추가할 때마다 외래 키(TEAM_ID)를 업데이트하기 위한 쿼리가 발생합니다. 이로 인해 성능에 영향을 줄 수 있으므로, 이를 주의하여 관리해야 한다.</p>
<p>&lt;실수로 양방향 연관관계로 변환될 가능성&gt;
만약 Member 객체가 Team 객체를 참조해야 하는 상황이 발생한다면, 단방향에서 양방향 연관관계로 변경해야 할 수 있다. 하지만 이 경우 Member 클래스에 Team 필드를 추가하고 @ManyToOne 어노테이션을 붙여야 한다.</p>
<blockquote>
<p>이 구조는 Team 객체가 여러 Member 객체와 일대다 단방향 관계를 가지도록 설정한다. 데이터베이스 상에서 외래 키는 MEMBER 테이블에 존재하며, 이 관계는 객체 모델에서 단방향으로 관리된다. 이 방식은 단순하지만, 외래 키 관리 및 업데이트 쿼리에 주의해야 하며, 필요에 따라 양방향 관계로 확장할 수 있다.</p>
</blockquote>
</li>
</ul>
<p>&lt;양방향&gt;
<img src="https://velog.velcdn.com/images/raymond_yoon/post/5f4222df-695d-49ca-b61f-c1626f2eef63/image.png" alt="">
<img src="https://velog.velcdn.com/images/raymond_yoon/post/03f578ae-8e76-464e-ab55-29543cfd39a6/image.png" alt=""></p>
<p>코드구성을 보면 Member 클래스는 Team 클래스와 다대일(Many-to-One)로 연결되어 있으며, @ManyToOne 어노테이션을 통해 이를 설정한다.
@JoinColumn(name = &quot;TEAM_ID&quot;) 어노테이션은 Member 테이블에서 외래 키로 TEAM_ID를 사용함을 나타낸다.
insertable = false, updatable = false 설정은 이 필드를 읽기 전용으로 만들어, 데이터베이스에 값을 삽입하거나 수정할 수 없도록 제한한다. 이는 해당 관계가 양방향이지만, 데이터의 일관성을 유지하기 위해 한쪽에서만 외래 키를 관리하게 하려는 목적이다.</p>
<h4 id="객체-연관관계-4">객체 연관관계</h4>
<p>Team과 Member 간의 관계는 양방향이다. Team 클래스는 List&lt; Member &gt; 필드를 통해 여러 Member 객체들을 참조하고, Member 클래스는 Team 객체를 참조한다.
즉, Member가 어느 Team에 속하는지를 알 수 있으며, Team도 자신에게 속한 Member들을 알 수 있다.</p>
<h4 id="테이블-연관관계-4">테이블 연관관계</h4>
<p>데이터베이스에서는 MEMBER 테이블이 TEAM_ID 외래 키를 통해 TEAM 테이블과 연결되어 있다. 이 외래 키는 Member 엔티티에 의해 관리되지만, 해당 필드는 읽기 전용으로 설정되어있다.</p>
<blockquote>
<p>일대다 양방향 연관관계는 Team이 여러 Member를 참조하고, Member도 Team을 참조하는 구조를 가진다. 이 관계에서 Member 쪽에서 외래 키를 관리하지만, 읽기 전용으로 설정하여 데이터의 일관성을 유지하고, 특정 상황에서 관계를 안전하게 처리할 수 있도록 한다. 이를 통해 양방향 관계를 구현하되, 외래 키 관리를 한쪽에서만 책임지는 방식으로 설정할 수 있다.</p>
</blockquote>
<h3 id="다대다many-to-many-관계">다대다(Many-to-Many) 관계</h3>
<p><img src="https://velog.velcdn.com/images/raymond_yoon/post/3629dc9a-db01-4849-96bd-c391e714d95d/image.png" alt="">
<img src="https://velog.velcdn.com/images/raymond_yoon/post/dd834e9a-659f-4e65-ab21-6c8833006a05/image.png" alt=""></p>
<h4 id="객체-연관관계-5">객체 연관관계</h4>
<p>객체 모델에서는 Member와 Product 간의 다대다 관계를 List&lt; Product &gt;와 List&lt; Member &gt;를 사용하여 구현한다. 이를 통해 Member는 여러 Product를, Product는 여러 Member를 참조할 수 있다.
@ManyToMany 어노테이션을 사용하고, @JoinTable 어노테이션을 통해 중간 테이블(MEMBER_PRODUCT)을 설정하여 다대다 관계를 구현한다.</p>
<h4 id="테이블-연관관계-5">테이블 연관관계</h4>
<p>데이터베이스에서는 다대다 관계를 직접적으로 표현할 수 없기 때문에, 중간 테이블(연결 테이블)을 사용한다. 이 중간 테이블은 두 엔티티의 외래 키로 구성되며, 각각의 외래 키는 양쪽 엔티티와 일대다[1:N]관계를 맺는다.
예시에서는 Member_Product라는 중간 테이블을 통해 MEMBER와 PRODUCT 간의 다대다 관계를 구현하고 있다.</p>
<ul>
<li>주의사항 
&lt;실무에서의 사용&gt; 
실무에서는 다대다 관계를 직접 사용하는 것보다, 중간 테이블을 만들어 다대일 관계로 풀어내는 방식이 더 일반적이다. 이는 관리와 확장성을 높이고, 추가적인 컬럼을 필요로 할 때 유리하다.
&lt;성능 이슈&gt; 
다대다 관계를 직접 사용할 경우, 조인 쿼리가 복잡해지거나 성능 이슈가 발생할 수 있으므로, 필요에 따라 중간 테이블을 명시적으로 관리하는 것이 좋다.</li>
</ul>
<blockquote>
<p>다대다 관계는 두 엔티티 간의 다대다 연결을 설정하는 구조로, 이를 위해 중간 테이블을 사용하여 관계를 관리한다. 객체 모델에서는 @ManyToMany 어노테이션을 사용하며, 실무에서는 이를 다대일 관계로 풀어내어 관리하는 것이 일반적이다.</p>
</blockquote>
<hr>

<h2 id="요약">요약</h2>
<p>연관관계 매핑은 객체 지향 설계에서 엔티티 간의 관계를 명확하게 표현하고, 이를 데이터베이스의 관계형 모델과 일치시켜 데이터의 일관성과 무결성을 유지하는 데 필수적이다. 올바른 연관관계 매핑을 통해 코드의 가독성과 유지보수성을 높일 수 있으며, 복잡한 비즈니스 로직을 효율적으로 구현할 수 있다.</p>
<p>특히, 적절한 매핑 설계는 성능 최적화에 큰 영향을 미친다. 잘못된 매핑은 불필요한 쿼리 발생이나 N+1 문제를 초래할 수 있지만, 올바른 매핑은 쿼리의 효율성을 극대화하고 데이터베이스와의 상호작용을 최소화할 수 있다. 따라서 연관관계 매핑은 시스템의 성능과 유지보수성을 고려하여 신중하게 설계해야 한다. 이를 통해 안정적이고 확장 가능한 시스템을 구축할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🦁멋쟁이사자처럼 백엔드 스터디 (1)]]></title>
            <link>https://velog.io/@raymond_yoon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%8A%A4%ED%84%B0%EB%94%94-1</link>
            <guid>https://velog.io/@raymond_yoon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%8A%A4%ED%84%B0%EB%94%94-1</guid>
            <pubDate>Sun, 02 Jun 2024 15:45:12 GMT</pubDate>
            <description><![CDATA[<p>이번년에 내 미래를 위해 뭐라도 해야한다는 생각으로 
작년에 기숙사 룸메이트로 만난 친구 덕분에 멋사라는 동아리를 알게되고, 
많이 부족한 실력이지만 들어가게 되었다. 
아무것도 모르고 방황하던 나에겐 그 친구가 시작점이 된 느낌이여서 고마웠다.</p>
<p>여튼 그래서 3월달부터 시작된 백엔드 스터디에 대해서 기록, 복습을 위해 velog에 기록하려고 한다.
내 미래, 꿈, 직업같은 것들은 velog에 기록하고, 취미인 클라이밍등등은 instagram, 일상은 blog에 저장하자는 목표가 있다. 추억하고 경험은 기록으로 남기는게 좋을 것 같다고 생각을 했다.</p>
<h1 id="백엔드-스터디-1">백엔드 스터디 (1)</h1>
<hr>

<h2 id="용어">용어</h2>
<h3 id="api">API</h3>
<p>API (Application Programing Interface)는 다른 소프트웨어 시스템과 통신하기 위해 따라야 하는 규칙을 정의하는 것을 말한다.</p>
<p>한마디로 어떠한 응용프로그램에서 데이터를 주고 받기 위한 방법을 의미하는 것 같다.</p>
<h3 id="인터페이스">인터페이스</h3>
<p>인터페이스는 어떤 기계간의 장치끼리 정보를 교환하기 위한 수단이나 방법을 의미한다.
일종의 매개체로 스마트폰과 사람간의 어떠한 신호를 주고 받는 매개체가 되는 소프트웨어인것같다.
UI(USER INTERFACE) 우리가 눈으로 보는 화면을 UI라 한다.</p>
<h3 id="jpa">JPA</h3>
<p>JPA (Java Persistence API)는 자바 애플리케이션에서 관계형 데이터베이스를 사용하는 표준방법을 제공하는 API이다.
JPA는 객체-관계 매핑(ORM) 기술을 사용해서 자바 객체를 데이터베이스 테이블에 매핑한다.</p>
<h3 id="jdbc">JDBC</h3>
<p>JDBC (Java Database Connectivity)는 자바 애플리케이션이 데이터베이스에 연결하고 SQL 쿼리를 실행하여 데이터를 검색하고 조작할 수 있게 하는 저수준 API 라고한다.
JDBC는 데이터베이스에 저장된 데이터를 자바 애플리케이션에서 사용할 수 있게 한다.</p>
<h3 id="orm">ORM</h3>
<p>ORM (Objcect-Relational Mapping)는 객체 지향 프로그래밍 언어의 객체를 관계형 데이터베이스의 테이블에 매핑하는 기술이다.
데이터베이스의 데이터를 자바 객체로 매핑하고, 자바 객체를 데이터베이스의 데이터로 매핑하여 프로그래밍 패러다임의 불일치를 해결한다.</p>
<hr>

<h4 id="jpa의-사용이유">JPA의 사용이유</h4>
<ol>
<li><p>패러다임의 불일치 해결</p>
<ul>
<li>객체 지향 프로그래밍 언어와 관계형 데이터베이스의 데이터 모델 간의 불일치를 해소한다.</li>
</ul>
</li>
<li><p>객체 중심적 개발</p>
<ul>
<li>SQL 중심의 개발 방식에서 벗어나 객체 중심으로 개발할 수 있게 한다.</li>
</ul>
</li>
<li><p>생산성 향상</p>
<ul>
<li>반복적인 SQL 코드 작성에서 해방되어 비즈니스 로직에 집중할 수 있게 한다.</li>
</ul>
</li>
<li><p>성능 개선</p>
<ul>
<li>1차 캐시, 지연로딩, 쓰기 지연등의 최적화 기능을 통해 성능을 개선한다.</li>
</ul>
</li>
<li><p>유지보수 용이</p>
<ul>
<li>객체 지향 모델을 사용하여 코드의 가독성과 유지보수성을 높인다.</li>
</ul>
</li>
</ol>
<h2 id="구동방식-gpt예시">구동방식 (GPT예시)</h2>
<h4 id="1-프로젝트-설정">1. 프로젝트 설정</h4>
<p>build.gradle:</p>
<pre><code>plugins {
    id &#39;org.springframework.boot&#39; version &#39;2.6.6&#39;
    id &#39;io.spring.dependency-management&#39; version &#39;1.0.11.RELEASE&#39;
    id &#39;java&#39;
}

group = &#39;com.example&#39;
version = &#39;0.0.1-SNAPSHOT&#39;
sourceCompatibility = &#39;11&#39;

repositories {
    mavenCentral()
}

dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    runtimeOnly &#39;org.mariadb.jdbc:mariadb-java-client&#39;
    testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;
}

test {
    useJUnitPlatform()
}
</code></pre><h4 id="2-applicationproperties-설정">2. application.properties 설정</h4>
<p>src/main/resources/application.properties 파일을 생성하고, MariaDB 데이터베이스 연결 정보를 설정한다.</p>
<p>application.properties:</p>
<pre><code>spring.datasource.url=jdbc:mariadb://localhost:3306/your_database
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
</code></pre><h4 id="3-엔티티-클래스-정의">3. 엔티티 클래스 정의</h4>
<p>데이터베이스 테이블에 매핑될 자바 클래스를 정의한다.</p>
<p>User.java:</p>
<pre><code>package com.example.demo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

    // Getters and setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
</code></pre><h4 id="4-리포지토리-인터페이스-생성">4. 리포지토리 인터페이스 생성</h4>
<p>JPA를 사용하여 CRUD 작업을 수행할 리포지토리를 정의한다.</p>
<p>UserRepository.java:</p>
<pre><code>package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
}
</code></pre><h4 id="5-서비스-클래스-구현">5. 서비스 클래스 구현</h4>
<p>비즈니스 로직을 처리하는 서비스 클래스를 구현한다.</p>
<p>UserService.java:</p>
<pre><code>package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User saveUser(User user) {
        return userRepository.save(user);
    }

    public List&lt;User&gt; getAllUsers() {
        return userRepository.findAll();
    }

    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}
</code></pre><h4 id="6-컨트롤러-클래스-구현">6. 컨트롤러 클래스 구현</h4>
<p>HTTP 요청을 처리하는 컨트롤러 클래스를 구현한다.</p>
<p>UserController.java:</p>
<pre><code>package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping(&quot;/users&quot;)
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseEntity&lt;User&gt; createUser(@RequestBody User user) {
        User createdUser = userService.saveUser(user);
        return new ResponseEntity&lt;&gt;(createdUser, HttpStatus.CREATED);
    }

    @GetMapping
    public ResponseEntity&lt;List&lt;User&gt;&gt; getAllUsers() {
        List&lt;User&gt; users = userService.getAllUsers();
        return new ResponseEntity&lt;&gt;(users, HttpStatus.OK);
    }

    @GetMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;User&gt; getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return user != null ? new ResponseEntity&lt;&gt;(user, HttpStatus.OK) : new ResponseEntity&lt;&gt;(HttpStatus.NOT_FOUND);
    }

    @DeleteMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;Void&gt; deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return new ResponseEntity&lt;&gt;(HttpStatus.NO_CONTENT);
    }
}
</code></pre><h4 id="7-애플리케이션-실행">7. 애플리케이션 실행</h4>
<p>DemoApplication.java:</p>
<pre><code>package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
</code></pre><p>이제 애플리케이션을 실행하면, Spring Boot 애플리케이션이 시작되고 MariaDB 데이터베이스에 연결됩니다. Postman 또는 다른 HTTP 클라이언트를 사용하여 CRUD 작업을 수행할 수 있다.</p>
<h4 id="postman을-사용한-예제">Postman을 사용한 예제</h4>
<ol>
<li><p>Create User (POST 요청)</p>
<p>URL: <a href="http://localhost:8080/users">http://localhost:8080/users</a>
Method: POST
Body (raw, JSON)</p>
<pre><code>{
  &quot;name&quot;: &quot;John Doe&quot;,
  &quot;email&quot;: &quot;john.doe@example.com&quot;
}
</code></pre></li>
<li><p>Get All Users (GET 요청)</p>
<p>URL: <a href="http://localhost:8080/users">http://localhost:8080/users</a>
Method: GET
Get User by ID (GET 요청)</p>
<p>URL: <a href="http://localhost:8080/users/1">http://localhost:8080/users/1</a>
Method: GET
Delete User (DELETE 요청)</p>
<p>URL: <a href="http://localhost:8080/users/1">http://localhost:8080/users/1</a>
Method: DELETE</p>
</li>
</ol>
<p>이와 같은 방법으로 Gradle과 MariaDB를 사용하여 Spring Boot 애플리케이션을 설정하고, JPA를 활용하여 간단한 CRUD 애플리케이션을 구현할 수 있다.</p>
<p>나중에는 스터디에서 한 프로젝트로 활용해보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[물이 흐르기 시작했다.]]></title>
            <link>https://velog.io/@raymond_yoon/%EB%AC%BC%EC%9D%B4-%ED%9D%90%EB%A5%B4%EA%B8%B0-%EC%8B%9C%EC%9E%91%ED%96%88%EB%8B%A4</link>
            <guid>https://velog.io/@raymond_yoon/%EB%AC%BC%EC%9D%B4-%ED%9D%90%EB%A5%B4%EA%B8%B0-%EC%8B%9C%EC%9E%91%ED%96%88%EB%8B%A4</guid>
            <pubDate>Tue, 16 Jan 2024 16:08:33 GMT</pubDate>
            <description><![CDATA[<p>이제 4학년이 되어버린 나</p>
<p>준비된 포폴도 없고 실력도 없는 만큼 더 열심히 해야한다.
시작하기를 두려워 했지만 시간은 흐르고 이제 물도 엎질러졌다.
오늘부로 물은 흐르기 시작한다.</p>
<p>첫 게시글은 미래의 내가 볼 수 있고, 동기부여가 될 수 있게 자유 형식으로 쓰고싶다.
나의 최종목표는 사람좋은 기업들어가기... 꿈은 크게 가질수록 좋다고 했다.</p>
<p>목표를 크게 잡아 실패하는 것은 문제가 되지 않는다고 생각한다.
오히려 목표를 낮게 잡아 실패하는것이 문제라고 생각한다.</p>
<p>이번년 나의 목표</p>
<ol>
<li>마지막 나의 학창시절을 잘 즐기는것</li>
<li>사회로 나가기전에 준비 단단히 하기</li>
<li>포트폴리오 쌓고, 실력 쌓고, 추억 쌓기</li>
<li>인간관계 힘들어하지 않기</li>
<li>부모님께 잘하기</li>
</ol>
<p>나중에 학교를 졸업하고 다시보면 새로운 느낌일거라 생각한다.</p>
<p>다들 엄청난 속도로 나의 앞에서 가고있지만, 
조금은 의식을 하되, 
나의 속도에 맞춰서 날 믿고 가자.</p>
<p>이 글을 기점으로 다시 일어나자
파이팅..!!</p>
<p>사진은 내가 좋아하는 망그러진 곰 행운부적</p>
]]></description>
        </item>
    </channel>
</rss>