<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>junju404</title>
    <link>https://junju404.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 21 Jul 2026 14:49:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>junju404</managingEditor>
    <item>
      <title>[Project] AI 직무 추천 서비스, Node.js에서 Spring Boot로의 마이그레이션과 RAG 도입기 (Part 2. 기술 후보군 탐색과 가설 수립)</title>
      <link>https://junju404.tistory.com/33</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 서론 : 기술 스택, 답을 정하기 전에 질문부터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;논리적인, 설득력 있는 코드를 작성하자.&lt;br /&gt;&lt;/span&gt;항상, &quot;왜&quot;를 생각하자.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://junju404.tistory.com/32&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Part 1&lt;/a&gt;에서는 프로젝트를 다시 시작하게 된 배경과 기존 시스템의 문제점을 회고했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 Part 2에서는 본격적인 개발에 앞서, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;과거에는 어떻게 구현했는지(AS-IS)&lt;/b&gt;를 되돌아보고, &lt;b&gt;새로운 시스템에 도입할 만한 기술 후보군(To-BE s)&lt;/b&gt;들을 리서치한 내용을 정리해 보고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 당장 &quot;이 기술을 쓰겠다!&quot;고 확정하는 것이 아니다. 현재 내 프로젝트의 목표(안정성, 정확성, 성능)에 부합할 것으로 예상되는 기술들을 나열하고, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;&quot;왜 이 기술을 후보로 올렸는지&quot;&lt;/b&gt;에 대한 가설을 수립하는 단계&lt;/span&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 각 파트별(DB, 캐시, 검색 등) 상세 구현 단계에서 &lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;직접 성능을 비교/검증하며 최종 스택을 확정해 나가는 과정을 기록할 예정&lt;/span&gt;&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 본론 : 영역별 기술 후보군 탐색&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. 데이터 수집 (폐쇄적인 학사 정보 수집)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 학교의 강의 계획서는 외부에 공개되지 않고, 학사정보시스템에 로그인해야만 볼 수 있다. 이 폐쇄적인 데이터를 어떻게 가져올지를 생각해봐야된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 과거의 방식 (Legacy)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;- 방식 : 수동 입력 (직접 DB에서 INSERT)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 비용이 '0'이다. 초기 데이터를 가장 빠르게 구축할 수 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;확장성 Zero&lt;/b&gt; : 우리 학과(소프트웨어학과) 외 타 학과로 서비스를 &lt;b&gt;확장&lt;/b&gt;하려면, 결국엔 그 학과 강의 계획서를 일일이 타이핑해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 최신화 번거로움&lt;/b&gt; : 교수님이 강의 내용을 바꾸거나 강의의 교수님이 바뀐다면 &lt;b&gt;기존 DB를 사람(개발자)이 다시 일일이 수정&lt;/b&gt;해야된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 기술 스택 후보&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동화를 위해 고려 중인 크롤링 기술들은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;- Jsoup (Java HTML Parser)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 없이 HTTP 요청만으로 HTML을 가져와 파싱한다.&lt;/li&gt;
&lt;li&gt;예상 특징 : 리소스 소모가 가장 적고 빠르다. 하지만 로그인이 필요한 페이지나, JS로 동적 렌더링되는 페이지는 수집이 불가능할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;- Selenium WebDriver&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 브라우저를 띄워 사람처럼 동작하게 한다.&lt;/li&gt;
&lt;li&gt;예상 특징 : 로그인, 팝업, 버튼 클릭 등 복잡한 상호작용이 가능하다. 하지만 메모리와 CPU를 많이 점유하여 속도가 느릴 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;- Playwright&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MS에서 만든 최신 브라우저 자동화 도구로, Selenium보다 빠르고 안정적이라 알려져 있다.&lt;/li&gt;
&lt;li&gt;예상 특징 : Node.js 진영에서 강세지만 Java 생태계에서도 호환성이 좋을지, 레퍼런스가 충분한지 검증이 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;  1차 가설 수립&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;&quot;로그인 세션 처리는 Selenium으로 뚫고, 이후 세션 쿠키를 활용한 대량 데이터 파싱은 Jsoup으로 처리하는 하이브리드 방식을 사용하면 리소스 효율을 극대화할 수 있지 않을까?&quot;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. Main Database (데이터 저장소의 선택)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 과거의 방식 (Legacy)&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;기술 : MySQL&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;선택 이유&lt;/b&gt; : 솔직히 말하면 &quot;이전 프로젝트에서 사용했던 DB는 이것뿐이라서&quot;, &quot;남들이 다 쓰니까&quot; 였다. &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;기술적인 비교나 고민은 없었다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 기술 후보군&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터의 성격&lt;b&gt;(관계형 vs 비정형)&lt;/b&gt;과 &lt;b&gt;읽기/쓰기 패턴&lt;/b&gt;을 고려해 다양한 DB를 검토한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;A. RDBMS (관계형 데이터베이스) - 메인 저장소&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의, 학생, 수강 이력 등은 &lt;b&gt;명확한 관계&lt;/b&gt;를 가지며 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;데이터 무결성&lt;/b&gt;&lt;/span&gt;이 가장 중요한 특징이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. MySQL 8.0&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 대중적인 오픈소스 DB이며 방대한 레퍼런스를 보유하고 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가설&lt;/b&gt; : 익숙하기에 러닝 커브가 낮다. 국내 IT 기업 표준에 가까워 실무 적응에 유리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. PostgreSQL&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;가장 진보된 오픈소스 RDBMS&quot;로 불리며, 복잡한 쿼리와 Vector(pgvector) 확장 기능이 강력하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가설&lt;/b&gt; : 벡터 검색 기능이 필요할 때 별도의 벡터 DB 없이 포스트그레스 하나로 해결할 수 있을지도 모른다. MySQL 대비 성능 차이가 관건이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. MariaDB&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL의 오픈소스 포크(Fork) 버전으로, MySQL과 호환성이 높다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;B. NoSQL (비관계형 데이터베이스) - 조회 및 캐시용&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의 계획서처럼 수정은 거의 없고 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;'조회'만 빈번하게 일어나는&lt;/b&gt;&lt;/span&gt; 데이터나, 로그 데이터 처리를 위해 고려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. MongoDB&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSON(Document) 형태로 데이터를 저장하며 스키마가 유연하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가설&lt;/b&gt; : 강의 계획서 항목이 학과마다 제각각일 때 유연하게 대처 가능하다. 하지만 트랜잭션 관리가 RDBMS보다 까다로울 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Firebase (Firestore)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구글의 서버리스 DB로 실시간 동기화가 강점이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가설&lt;/b&gt; : 프론트엔드와 직접 연동하기 편하지만, 백엔드 로직이 복잡해질 경우 제약이 있을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;  1차 가설 수립&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;&quot;기본적인 데이터 무결성을 위해 RDBMS(MySQL or PostgreSQL)를 메인으로 가되, 단순 조회 성능이나 유연성이 필요한 부분은 NoSQL 도입을 검토해 볼 예정이다.&quot;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-3. Caching &amp;amp; Vector (속도와 비용 효율화)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성형 AI API는 호출당 비용이 발생하고 응답 속도가 느리다. 이를 보완할 기술이 필수적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 과거의 방식 (Legacy)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;방식 : &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;MySQL을 캐시처럼 사용 (단순 조회)&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제점&lt;/b&gt; : Disk I/O가 발생하므로 아주 빠르지 않고, 오래된 데이터를 자동으로 지우는 로직을 직접 구현해야 해서 번거로웠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 기술 후보군&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. Redis (Remote Dictionary Server)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Key-Value 구조의 인메모리 저장소이다. &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;RAM에 저장하므로 처리 속도가 압도적으로 빠르다&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;가설 (&lt;b&gt;Caching&lt;/b&gt;) : 사용자가 동일한 질문을 하면 DB 접근 없이(컨텍스츠 스위칭 X) Redis에서 바로 반환하여 0.1초 내 응답이 가능할 것이다.&lt;/li&gt;
&lt;li&gt;가설 (&lt;b&gt;TTL&lt;/b&gt;) : &quot;1시간 뒤 삭제&quot; 같은 만료 설정을 통해 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;데이터 생명주기 관리가 편리&lt;/b&gt;&lt;/span&gt;할 것이다.&lt;/li&gt;
&lt;li&gt;가설 (&lt;b&gt;Vector&lt;/b&gt;) : Redis Stack을 활용하면 임베딩 벡터 저장소로도 사용 가능하여, 인프라를 일원화할 수 있을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. Memcached&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis보다 단순하지만 &lt;b&gt;멀티스레드를 지원&lt;/b&gt;해 극강의 처리량을 자랑한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가설&lt;/b&gt; : 단순 캐싱만 한다면 좋겠지만, '벡터 검색'이나 '다양한 자료구조'가 필요한 이번 프로젝트에는 Redis보다 범용성이 떨어질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-4. Application Architecture (코드를 어떻게 짤 것인가?)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 과거의 방식 (Legacy)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;아키텍처&lt;/b&gt; : Router / Controller / Service / DAO&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ORM&lt;/b&gt; : 사용 안 함 (Raw SQL)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제점&lt;/b&gt; : DAO 파일 안에서 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;SQL 문자열을 직접 + 연산자로 합쳐서 쿼리를 날렸다&lt;/b&gt;&lt;/span&gt;. 오타가 나면 실행해봐야 알 수 있었고, SQL이 코드에 섞여 있어 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;유지보수가 매우 힘들었다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 기술 후보군&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트 환경에서 DB에 접근하는 방식들은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;1. MyBatis (SQL Mapper)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL을 XML 파일로 분리해서 관리한다. 쿼리를 직접 짤 수 있어 튜닝에 유리하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가설&lt;/b&gt; : 복잡한 통계 쿼리는 편하겠지만, 단순 CRUD 작업도 일일이 쿼리를 짜야 해서 생산성이 낮을 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;2. Spring Data JPA (ORM)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 객체와 DB 테이블을 자동으로 매핑해준다. 기본적인 쿼리는 메소드 이름만으로 생성된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가설&lt;/b&gt; : &quot;SQL 중심&quot;에서 &quot;객체 중심&quot;으로 개발 패러다임을 바꿀 수 있어 생산성이 압도적으로 높을 것으로 기대된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;3. QueryDSL&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 코드로 쿼리를 작성하게 해주는 빌더다. 컴파일 시점에 문법 오류를 잡을 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가설&lt;/b&gt; : JPA로 해결하기 힘든 동적 쿼리(검색 조건이 많을 때)를 짤 때, 오타 걱정 없이 안전하게 짤 수 있는 최적의 도구로 보인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 결론 : 꼬리에 꼬리를 무는&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;바이브 코딩&lt;/b&gt;, 하지만 &lt;b&gt;논리적으로 검증하고 학습하는&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이번 프로젝트에서 &lt;b&gt;&quot;바이브 코딩&quot;&lt;/b&gt;을 지향한다. AI(Copilot, GPT)가 짜주는 코드(코드리뷰 포함)를 적극 활용할 것이다. 하지만, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;&quot;AI가 짰으니까 맞겠지&quot;라고 맹목적으로 수용하는 일은 과거처럼 &quot;왜?&quot;를 생각하지 않은 것이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;앞으로 이어질 포스팅에서는 위에서 나열한 후보군들을 하나씩 직접 구현하고 부딪혀보며 내 프로젝트에 최적화된 기술을 찾아가는 과정을 기록할 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  Next Topics&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[Data Collection] Selenium vs Jsoup, 폐쇄된 포털을 뚫어라&lt;/li&gt;
&lt;li&gt;[Database] MySQL vs PostgreSQL, 벡터 검색은 누가 더 잘할까?&lt;/li&gt;
&lt;li&gt;[Cache] Redis 도입 전후 성능 차이 측정 (JMeter)&lt;/li&gt;
&lt;li&gt;[Search] 단순 매칭을 넘어선 유사도 검색 구현하기&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>ProJect/QJ(수강신청 추천 서비스)</category>
      <author>junju404</author>
      <guid isPermaLink="true">https://junju404.tistory.com/33</guid>
      <comments>https://junju404.tistory.com/33#entry33comment</comments>
      <pubDate>Sun, 25 Jan 2026 16:55:48 +0900</pubDate>
    </item>
    <item>
      <title>[Project] AI 직무 추천 서비스, Node.js에서 Spring Boot로의 마이그레이션과 RAG 도입기 (Part 1. 설계와 이유)</title>
      <link>https://junju404.tistory.com/32</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 서론 : 2023년의 QJ, 그 시작과 한계&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 기획 배경 : &quot;이 과목, 진짜 내 진로에 도움이 될까?&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학 생활을 하며 매 학기 수강신청 기간마다 겪었던 고질적인 고민이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;백엔드 개발자가 목표인데, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;'시스템 프로그래밍' 과목을 듣는 게 도움이 될까? &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아니면 차라리 다른 웹 관련 과목을 듣는 게 나을까?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;학과 커리큘럼은 방대했지만, 정작 내가 희망하는 '&lt;b&gt;구체적인 직무&lt;/b&gt;'와 연결해 주는 가이드는 부족했다. 선배들에게 물어보거나 강의 계획서를 일일이 찾아보는 것도 한계가 있었고 번거로웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하지만, 2023년에는 마침 &quot;&lt;b&gt;ChatGPT 열풍&lt;/b&gt;&quot;이 불고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;나 역시 GPT에게 조언을 구했지만, 과정은 순탄치 않았다. 매번 전공 과목 강의계획서 텍스트를 복사해서 프롬프트에 붙여넣고, &lt;i data-index-in-node=&quot;111&quot; data-path-to-node=&quot;11&quot;&gt;&quot;나 이런 상황인데 이거 들을까?&quot;&lt;/i&gt; 라고 물어보는 과정을 반복해야 했다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;귀찮게 계속 내 정보를 입력하고, 전공 설명서를 복사 붙여넣기 해야 하나?&lt;br /&gt;내 관심 직무만 딱 입력하면, 우리 학교 커리큘럼을 싹 분석해서 &lt;br /&gt;우선순위를 추천해 주는 서비스가 있다면 어떨까?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 지극히 개인적인 니즈와 불편함에서 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;13&quot;&gt;'QJ(Question Job)'&lt;/b&gt; 프로젝트가 시작되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 내가 가장 잘 아는 '소프트웨어학과' 커리큘럼을 중심으로 MVP를 구축하고, 점차 타 학과로 확장하려는 목표를 가지고 개발에 착수했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 기존 서비스(Legacy) 기능 및 아키텍처&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 팀원들과 나에게 가장 익숙하고 진입 장벽이 낮은 &lt;b data-index-in-node=&quot;60&quot; data-path-to-node=&quot;15&quot;&gt;Node.js 생태계&lt;/b&gt;를 기반으로 아키텍처를 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 서비스 아키텍처&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;qj.png&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WtKFG/dJMcadnbsWs/1eJv4HKjLkqKRsWKmvB9H0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WtKFG/dJMcadnbsWs/1eJv4HKjLkqKRsWKmvB9H0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WtKFG/dJMcadnbsWs/1eJv4HKjLkqKRsWKmvB9H0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWtKFG%2FdJMcadnbsWs%2F1eJv4HKjLkqKRsWKmvB9H0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;939&quot; height=&quot;360&quot; data-filename=&quot;qj.png&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 기술 스택&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;Runtime:&lt;/b&gt; Node.js (v18)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;Framework:&lt;/b&gt; Express.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0&quot;&gt;Database:&lt;/b&gt; MySQL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,3,0&quot;&gt;AI Model:&lt;/b&gt; OpenAI GPT-3.5-turbo-16k (Context Stuffing 방식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,4,0&quot;&gt;Crawling:&lt;/b&gt; Puppeteer (Headless Chrome) + node-schedule&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,5,0&quot;&gt;Infrastructure:&lt;/b&gt; AWS EC2 (Ubuntu) + PM2 (무중단 배포 및 프로세스 관리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) 기능&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;로그인/회원가입&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아이디/닉네임 중복 검사 및 비밀번호 해싱&lt;/li&gt;
&lt;li&gt;JWT(Access Token)와 Cookie를 활용한 인증 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;취업 게시판&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;학교 대학일자리센터의 채용 공고를 매일 정해진 시간에 수집&lt;/li&gt;
&lt;li&gt;Headless Chrome(Puppeteer)을 띄워 실제 브라우저처럼 페이지를 순회하며 데이터 적재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AI 강의 추천&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 '관심 직무(예: 백엔드 개발자)'를 입력하면 서버가 이를 수신&lt;/li&gt;
&lt;li&gt;DB에 저장된 '학교 강의 정보' 전체와 '프롬프트(답변 예시, 상황 설명)'를 조합하여 ChatGPT API로 전송&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,2,1,2,0&quot;&gt;Logic&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;21,2,1,2,1&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DB에 해당 직무로 조회된 기록이 있는지 확인&lt;/li&gt;
&lt;li&gt;있다면? DB에 저장된 추천 결과(JSON)를 반환 (Cache Hit)&lt;/li&gt;
&lt;li&gt;없다면? GPT API를 호출하여 결과를 생성하고, DB에 저장 후 반환 (Cache Miss)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 문제 인식(본론1) : 왜 2년 만에 다시 뜯어고치는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 돌아갔던 서비스를 굳이 2년 만에 꺼내어 처음부터 다시 만드는 데에는 이유가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거의 개발 일지(주간 보고서)를 다시 살펴보니, 당시에는 '기능 구현'에 급급해 놓쳤던 &lt;b data-index-in-node=&quot;99&quot; data-path-to-node=&quot;5&quot;&gt;구조적 한계&lt;/b&gt;와 &lt;b data-index-in-node=&quot;107&quot; data-path-to-node=&quot;5&quot;&gt;엔지니어링의 부재&lt;/b&gt;가 명확히 보였기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) &quot;응답 시간 2분&quot;과 무늬만 캐싱 (Week 13 이슈)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;산학 프로젝트 진행 당시, 멘토님께 피드백을 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;GPT API 호출이 2분 정도 걸리면 사용자 입장에서 너무 느리다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 나는 이 문제를 해결하기 위해 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;25&quot;&gt;RDBMS(MySQL)를 캐시 저장소처럼 사용&lt;/b&gt;하는 전략을 택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직은 단순했다. 누군가 &quot;프론트엔드&quot;를 검색해서 GPT 답변을 받으면, 그 JSON 결과를 MySQL 테이블에 저장해 두는 것이다. 다음 사람이 똑같이 &quot;프론트엔드&quot;를 검색하면 DB에서 꺼내주니 0.1초 만에 응답이 가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 방식은 치명적인 &lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;26&quot;&gt;논리적 결함&lt;/b&gt;을 안고 있었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;27&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,0,0&quot;&gt;Cache Miss의 대기 시간 :&lt;/b&gt; 누군가 처음 검색하는 직무라면 여전히 2분을 기다려야 한다. 캐싱은 '자주 찾는 데이터'를 빠르게 줄 뿐, 근본적인 생성 속도를 개선해 주진 못했다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,0&quot;&gt;데이터의 최신성(Freshness) 문제 :&lt;/b&gt; 만약 학교의 강의 계획서가 업데이트되거나, 추천 로직 프롬프트가 수정되면 어떻게 될까? MySQL에 저장된 과거의 추천 데이터는 &lt;b&gt;상한 데이터(Stale Data)&lt;/b&gt;가 된다. 이를 갱신하려면 DB 데이터를 일일이 지우거나 복잡한 업데이트 로직을 짜야 했다. 결국 TTL(유효 기간) 없는 DB 저장은 진정한 의미의 캐싱이라 보기 어려웠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 토큰 제한과 할루시네이션 (Week 9~10 이슈)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;GPT 답변 내용이 중간에 잘리거나, 없는 과목을 추천하는 환각 현상이 발생합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시에는 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;33&quot;&gt;'Context Stuffing(문맥 밀어 넣기)'&lt;/b&gt; 방식을 사용했다. 쉽게 말해, DB에 있는 170여 개의 과목 정보를 프롬프트 텍스트에 다 때려 넣고 질문하는 방식이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;34&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;34,0,0&quot;&gt;비용 문제 :&lt;/b&gt; 과목 데이터가 많아질수록 토큰 사용량이 선형적으로 증가했다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;34,1,0&quot;&gt;할루시네이션 :&lt;/b&gt; 정보량이 너무 많아지니, GPT가 맥락을 잃고 우리 학교에 없는 과목을 지어내거나, 중요한 필수 과목을 누락하는 현상이 발생했다. 당시엔 프롬프트에 &lt;i data-index-in-node=&quot;135&quot; data-path-to-node=&quot;34,1,0&quot;&gt;&quot;거짓말하지 마!&quot;&lt;/i&gt; 라고 강조 문구를 넣는 것으로 떼웠지만, 이는 공학적인 해결책이 아니었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 불안정한 데이터 수집 (Stateless Crawling)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js의 node-schedule과 puppeteer를 이용한 무한 루프 방식의 크롤러는 운영 환경에서 매우 불안정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;37&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,0,0&quot;&gt;상태 관리 부재 :&lt;/b&gt; 100페이지를 크롤링하다가 50페이지에서 네트워크 에러가 나면, 어디서 멈췄는지 알 수 없어 다음 스케줄에 다시 1페이지부터 시작해야 했다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,1,0&quot;&gt;리소스 누수 :&lt;/b&gt; 단순 텍스트 수집에 무거운 Headless Browser(Chrome)를 띄우다 보니 EC2 메모리 점유율이 치솟았고, 종종 서버가 멈추는 원인으로 추측된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) &quot;백엔드&quot;와 &quot;Server Developer&quot;는 다르다? (텍스트 매칭의 한계)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 시스템은 DB 조회 시 단순 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;39&quot;&gt;문자열 일치(=)&lt;/b&gt; 방식을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 &quot;Server Developer&quot;라고 입력하면, 이미 DB에 &quot;백엔드 개발자&quot;에 대한 완벽한 추천 결과가 있어도 이를 활용하지 못하고(Cache Miss), 다시 GPT를 호출해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람에겐 같은 의미지만 컴퓨터에겐 다른 문자열이었기 때문이다. 이 또한 막대한 API 비용 낭비이자 비효율이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) 운영의 사각지대 (No Monitoring)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 문제는 &lt;b&gt;&quot;서버 상태를 알 수 없다&quot;&lt;/b&gt;는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2가 가끔 멈출 때마다 단순히 재부팅을 했지만, 왜 멈췄는지 알 수 없었다. 크롬(Puppeteer)이 메모리를 얼마나 잡아먹는지, CPU 점유율이 언제 튀는지 측정하는 &lt;b&gt;지표&lt;/b&gt;가 전혀 없었다. 눈을 감고 운전하는 것과 다를 바 없었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;37&quot; data-ke-size=&quot;size26&quot;&gt;3. 학습 목표 : Java Spring Boot 딥 다이브&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;단순히 기존 기능을 옮기는 것이 아니다. 이번 마이그레이션의 가장 큰 목적 중 하나는 &quot;&lt;b&gt;Java 생태계로의 깊이 있는 학습&lt;/b&gt;&quot;이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;39&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;39,0,0&quot;&gt;Single Thread vs Multi Thread:&lt;/b&gt; Node.js의 이벤트 루프(Event Loop) 모델과 달리, Spring Boot의 요청 당 스레드(Thread-per-Request) 모델을 직접 경험한다. 스레드 풀(Thread Pool) 설정이 성능에 어떤 영향을 미치는지, 동시성 이슈는 어떻게 제어하는지 학습한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;39,1,0&quot;&gt;객체지향과 디자인 패턴:&lt;/b&gt; Java의 강점인 OOP를 활용해, 기존의 절차지향적 코드를 어떻게 유지보수하기 좋은 구조로 개선할 수 있는지 실험한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;39,2,0&quot;&gt;Career Alignment:&lt;/b&gt; 내가 목표로 하는 백엔드 엔지니어로서의 커리어 패스에 맞춰, 실무에서 가장 많이 사용되는 기술 스택(Spring Boot, JPA, QueryDSL 등)의 내부 동작 원리를 파고든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;4. 기술적 해결 전략(본론2) : 가설과 검증&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 분석한 기술적 부채들을 청산하기 위해, 나는 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;39&quot;&gt;Spring Boot 생태계로의 마이그레이션&lt;/b&gt;과 &lt;b data-index-in-node=&quot;55&quot; data-path-to-node=&quot;39&quot;&gt;아키텍처 재설계&lt;/b&gt;를 결정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 언어 변경이 아닌, &lt;b data-index-in-node=&quot;86&quot; data-path-to-node=&quot;39&quot;&gt;시스템의 체질 개선&lt;/b&gt;이 목표다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 특정 기술을 맹목적으로 도입하는 것이 아니라, &lt;b data-index-in-node=&quot;136&quot; data-path-to-node=&quot;39&quot;&gt;&quot;가설&quot;을 세우고 &quot;실험&quot;을 통해 최적의 기술 스택을 결정&lt;/b&gt;한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;40&quot; data-ke-size=&quot;size23&quot;&gt;1) Node.js -&amp;gt; Spring Boot (안정성과 배치)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;이 부분은 명확한 이유를 가지고 확정했다. 기존의 불안정성을 해결하기 위해 &lt;b data-index-in-node=&quot;42&quot; data-path-to-node=&quot;41&quot;&gt;Spring Batch&lt;/b&gt; 도입이 필수적이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;42&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;42,0,0&quot;&gt;재시작성 :&lt;/b&gt; Batch의 JobRepository는 작업의 성공/실패 지점을 DB에 기록한다. 크롤링이 50페이지에서 실패하더라도, 다음 실행 시 정확히 51페이지부터 이어서 수행할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;42,1,0&quot;&gt;트랜잭션 관리 :&lt;/b&gt; 대량의 공고 데이터를 읽고 쓸 때(Chunk Processing), 데이터 정합성을 보장하기 훨씬 유리하다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;42,2,0&quot;&gt;Jsoup 도입 :&lt;/b&gt; 무거운 브라우저(Puppeteer) 대신 가벼운 HTML 파서인 Jsoup을 사용하여 메모리 사용량을 획기적으로 줄인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;43&quot; data-ke-size=&quot;size23&quot;&gt;2) MySQL Cache -&amp;gt; In-Memory Caching Strategy (성능 실험)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;&quot;무조건 Redis가 정답인가?&quot;에 대한 물음을 던지고 실험을 진행할 예정이다. RDBMS를 캐시로 쓰던 방식은 버리되, 어떤 캐시 전략이 효율적인지 검증한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;45&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,0,0&quot;&gt;가설 1 (Local Cache):&lt;/b&gt; 단일 서버 환경에서는 네트워크 비용이 없는 HashMap이나 EhCache가 Redis보다 빠를 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,1,0&quot;&gt;가설 2 (Global Cache - Redis):&lt;/b&gt; 서버 확장성(Scale-out)과 데이터의 영속성, 그리고 &lt;b data-index-in-node=&quot;62&quot; data-path-to-node=&quot;45,1,0&quot;&gt;TTL(Time To Live)&lt;/b&gt; 관리 측면에서는 Redis가 유리할 것이다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,2,0&quot;&gt;검증 계획 :&lt;/b&gt; 두 가지 방식을 모두 구현해 보고, 부하 테스트를 통해 응답 속도와 리소스 효율을 비교하여 최종 스택을 결정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;46&quot; data-ke-size=&quot;size23&quot;&gt;3) Context Stuffing -&amp;gt; RAG &amp;amp; Vector Search (정확도 실험)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size16&quot;&gt;모든 정보를 프롬프트에 밀어 넣는 방식을 버리고, &lt;b data-index-in-node=&quot;28&quot; data-path-to-node=&quot;53&quot;&gt;RAG(검색 증강 생성)&lt;/b&gt; 아키텍처 도입을 시도한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;54&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54,0,0&quot;&gt;Vector Search (유사도 검색):&lt;/b&gt; 과목 데이터와 직무 키워드를 임베딩(Embedding)하여 Vector DB에 저장한다. &quot;Server Developer&quot;라고 검색해도 &quot;백엔드 개발자&quot;와 유사함을 인지하고 결과를 찾아내는 &lt;b&gt;의미 기반 검색(Semantic Search)&lt;/b&gt;을 구현한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54,1,0&quot;&gt;Grounding:&lt;/b&gt; 전체 과목이 아닌, 유사도가 높은 상위 N개 과목만 추출하여 GPT에게 제공함으로써 할루시네이션을 줄이고 API 비용을 절감한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 모니터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 이상 감으로 운영하지 않는다. 애플리케이션의 상태를 투명하게 볼 수 있는 환경을 구축한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;57&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;57,0,0&quot;&gt;Actuator &amp;amp; Prometheus:&lt;/b&gt; Spring Boot Actuator를 통해 JVM 메모리, 스레드 상태, HTTP 요청 수 등의 메트릭을 수집한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;57,1,0&quot;&gt;Grafana:&lt;/b&gt; 수집된 데이터를 시각화 대시보드로 만들어, CPU 스파이크나 메모리 누수 조짐을 &lt;b data-index-in-node=&quot;54&quot; data-path-to-node=&quot;57,1,0&quot;&gt;눈으로 확인&lt;/b&gt;하고 대응하는 경험을 쌓는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 향후 계획(결론)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트는 단순히 &quot;취업 잘 되는 자바 스프링을 써보자&quot;는 차원이 아니다. 2023년의 내가 겪었던 &lt;b&gt;운영상의 한계(속도, 불안정성, 부정확함)&lt;/b&gt;를, 2025년의 내가 &lt;b&gt;현대적인 아키텍처 기술(Batch, Redis, RAG)&lt;/b&gt;을 통해 논리적으로 해결해 나가는 과정이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;61&quot; data-ke-size=&quot;size16&quot;&gt;또한, 나는 아직 RAG나 벡터 검색의 전문가가 아니다. 그렇기에 &lt;b&gt;'바이브 코딩(Vibe Coding)'&lt;/b&gt;과 적극적인 &lt;b data-index-in-node=&quot;69&quot; data-path-to-node=&quot;61&quot;&gt;AI 활용&lt;/b&gt;을 통해 모르는 기술을 빠르게 학습하고, 내 서비스에 맞게 최적화하는 과정을 가감 없이 기록할 것이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;61&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;62&quot; data-ke-size=&quot;size23&quot;&gt;  Future Roadmap&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;63&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;63,0,0&quot;&gt;환경 구축:&lt;/b&gt; Docker Compose로 MySQL, Redis, Spring Boot, Prometheus, Grafana가 연동된 통합 환경 구축.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;63,1,0&quot;&gt;배치 구현:&lt;/b&gt; Spring Batch + Jsoup을 활용하여 '중단되어도 다시 살아나는' 크롤러 구현.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;63,2,0&quot;&gt;캐시 전략:&lt;/b&gt; Local Cache vs Redis 성능 비교 실험 및 최종 아키텍처 결정.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;63,3,0&quot;&gt;AI 고도화:&lt;/b&gt; Spring AI와 Vector Store를 활용한 RAG 파이프라인 구축 및 할루시네이션 감소율 검증.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;64&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;64&quot;&gt;&quot;남들이 쓰니까 쓰는 기술&quot;은 없다.&lt;/b&gt; 모든 기술 스택의 변경에는 합당한 이유가 있어야 하며, 이번 프로젝트를 통해 그 '이유'를 코드로 증명해 보이고자 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;64&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;64&quot; data-ke-size=&quot;size16&quot;&gt;깃허브 : &lt;a href=&quot;https://github.com/kau-qj/backend&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kau-qj/backend&lt;/a&gt;&lt;/p&gt;</description>
      <category>ProJect/QJ(수강신청 추천 서비스)</category>
      <author>junju404</author>
      <guid isPermaLink="true">https://junju404.tistory.com/32</guid>
      <comments>https://junju404.tistory.com/32#entry32comment</comments>
      <pubDate>Mon, 19 Jan 2026 05:08:13 +0900</pubDate>
    </item>
    <item>
      <title>[Java] String은 참조형인데 왜 값이 안 바뀔까? &amp;ndash; Pass by Value 와 Immutable</title>
      <link>https://junju404.tistory.com/31</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 왜 이 글을 쓰게 되었는가&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 글을 쓰게 된 계기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;span style=&quot;color: #000000; text-align: center;&quot;&gt;자바의 신 Vol 1: 기초 문법편 3rd Edition&quot;을 읽던 중에 &quot;&lt;b&gt;Pass by value&lt;/b&gt;&quot; 주제에서 시작됐다. &lt;b&gt;String 문자열 객체&lt;/b&gt;가 메서드의 매개변수로 전달되어, 해당 메서드에서 객체의 값이 수정될 거라고 생각했다. 하지만, 생각했던 것과 달리 String 문자열 객체가 변경되지 않아서 &lt;b&gt;해당 개념을 바로잡고자 글을 쓰게 됐다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 필자의 배경 수준&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Java : 중&lt;br /&gt;&lt;/b&gt;&amp;nbsp; - 기본 문법 이해 (코딩테스트 주력 언어)&lt;br /&gt;&amp;nbsp; - 프로젝트 경험 보유&lt;br /&gt;&amp;nbsp; - 기본형 / 참조형 자료형 구분&lt;br /&gt;&amp;nbsp; - &lt;a href=&quot;https://junju404.tistory.com/26&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JVM 메모리 구조(T구조 - 스택, 힙, 스태틱) 얕게 이해&lt;/a&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 문제 상황과 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1767877681179&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ReferencePass {
    public static void main(String[] args) {
        ReferencePass referencePass = new ReferencePass();
        referencePass.callPassByValue();
    }

    public void callPassByValue() {
        int a = 10;
        String b = &quot;b&quot;; // 2-1
        
        passByValue(a, b); // 2-2
        
        System.out.println(a); // 2-4
        System.out.println(b);
    }

    public void passByValue(int a, String b) {
        a = 20;
        b = &quot;z&quot;; // 2-3
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과&lt;/p&gt;
&lt;pre id=&quot;code_1767881845805&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;before passByValue
a = 10
b = b
in passByValue
a = 20
b = z
after passByValue
a = 10
b = b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해당 예제에서 헷갈렸던 점은 String 이 참조 자료형인데, &lt;/b&gt;&lt;br /&gt;&lt;b&gt;메서드 내부에서 값을 변경했을 때 호출한 쪽의 값이 변경되지 않는다는 점이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 자료형의 경우 값이 복사되어 전달되기 때문에 변경되지 않는다는 것은 알고 있었지만, &lt;br /&gt;&lt;b&gt;String 은 객체라서 메서드 내부에서 값을 변경하면 호출한 쪽에서도 변경될 것이라고 예상했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 실제 실행 결과는 예상과 달랐고,&lt;br /&gt;이 차이가 어디에서 발생하는지 JVM 메모리 관점에서 확인해볼 필요가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. JVM 메모리 관점에서의 실행 흐름&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) callPassByValue() 초기 상태&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;905&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ITSr4/dJMcab3XYKD/NUznGcKoqmxhLxoFdxBoQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ITSr4/dJMcab3XYKD/NUznGcKoqmxhLxoFdxBoQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ITSr4/dJMcab3XYKD/NUznGcKoqmxhLxoFdxBoQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FITSr4%2FdJMcab3XYKD%2FNUznGcKoqmxhLxoFdxBoQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;455&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;905&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;callPassByValue&lt;/b&gt;&lt;/span&gt; 메서드의 스택 프레임이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 정수형 자료형(기본 자료형)인 a 변수에는 10을 가리키고, 문자열 객체(참조 자료형)인 b 의 주소값은 String Pool(Heap)에 존재하는 &quot;b&quot; 객체를 가리키는 참조값을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) passByValue() 호출 직후&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;905&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMhKIB/dJMcaiBYuuF/RrcOUya1gg5glVrmRpnQ2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMhKIB/dJMcaiBYuuF/RrcOUya1gg5glVrmRpnQ2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMhKIB/dJMcaiBYuuF/RrcOUya1gg5glVrmRpnQ2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMhKIB%2FdJMcaiBYuuF%2FRrcOUya1gg5glVrmRpnQ2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;455&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;905&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;passByValue&lt;/b&gt;&lt;/span&gt; 메서드의 스택 프레임이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 매개변수로 전달받아서, 해당 스택 프레임의 a 라는 정수형 자료형 지역변수가 존재하고, 값은 10으로 초기화한다. b 라는 지역변수도 존재하는데, 문자열 객체로써 주소값은 Heap 의 &quot;b&quot; 를 참조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) passByValue() 내부에서 재할당&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;905&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWmaaz/dJMcahpADVx/2v6pqn6DwmDJTWouM0R171/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWmaaz/dJMcahpADVx/2v6pqn6DwmDJTWouM0R171/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWmaaz/dJMcahpADVx/2v6pqn6DwmDJTWouM0R171/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWmaaz%2FdJMcahpADVx%2F2v6pqn6DwmDJTWouM0R171%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;455&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;905&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;passByValue&lt;/b&gt;&lt;/span&gt; 메서드에서 지역변수 a의 값을 20으로 수정하고, 지역변수 b의 주소값을 &quot;z&quot; 라는 객체로 재할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4) passByValue() 종료 후&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;905&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/83B8k/dJMcac9AUnO/MwkzvKM5fV4dleK3q901Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/83B8k/dJMcac9AUnO/MwkzvKM5fV4dleK3q901Ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/83B8k/dJMcac9AUnO/MwkzvKM5fV4dleK3q901Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F83B8k%2FdJMcac9AUnO%2FMwkzvKM5fV4dleK3q901Ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;455&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;905&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;passByValue&lt;/b&gt;&lt;/span&gt; 메서드가 종료되어, 해당 스택 프레임은 소멸된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 다시 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;callPassByValue&lt;/b&gt;&lt;/span&gt; 메서드의 스택 프레임에서 코드를 진행하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Mutable 참조 자료형&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제에서는 String 객체를 매개변수로 전달했을 때 값이 변경되지 않는 이유를 확인했다. 하지만, 참조 자료형이지만 배열이나 DTO와 같은 객체를 매개변수로 전달하면 메서드 호출 이후 값이 변경되는 경우가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Java의 Pass by Value 가 지켜지지 않았다&quot;가 아니라, 객체의 가변성 여부에 따른 차이이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 두 가지 코드를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 자바 패턴 중 하나인 &quot;DTO&quot;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1767881728135&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ReferencePass2 {
    public static void main(String[] args) {
        ReferencePass2 referencePass2 = new ReferencePass2();
        referencePass2.callPassByReference();
    }

    public void callPassByReference() {
        MemberDTO MemberDTO = new ReferencePass2(&quot;Sangmin&quot;);
        System.out.println(&quot;before passByReference&quot;);;
        System.out.println(&quot;member.name : &quot; + MemberDTO.name);
        passByReference(MemberDTO);
        System.out.println(&quot;after passByReference&quot;);
        System.out.println(&quot;memberDTO.name : &quot; + MemberDTO.name);
    }

    public void passByReference(MemberDTO member) {
        member.name = &quot;sungChoon&quot;;
        System.out.println(&quot;in passByReference&quot;);
        System.out.println(&quot;memberDTO.name : &quot; + member.name);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과&lt;/p&gt;
&lt;pre id=&quot;code_1767881945067&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;before passByReference
member.name = Sangmin
in passByReference
member.name = SungChoon
after passByReference
member.name = SungChoon&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO 객체 역시 내부 필드를 변경할 수 있는 가변 객체일 경우에 가능하다. 메서드 내부에서 member.name = &quot;xxx&quot; 처럼 참조 변수를 재할당하는 것이 아니라, 참조가 가리키는 객체의 필드를 직접 수정하는 연산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 메서드 호출 이후에도 호출자 측에서 변경된 필드 값을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 자바의 &quot;정수형 배열&quot;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1767882131285&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ReferencePass3 {
    public static void main(String[] args) {
        int[] arr = {0, 1, 2};
        ReferencePass3 referencePass3 = new ReferencePass3();
        for (int num : arr) System.out.print(num + &quot; &quot;);
        System.out.println();
        referencePass3.callPassByReference(arr);
        for (int num : arr) System.out.print(num + &quot; &quot;);
        System.out.println();
    }

    public void callPassByReference(int[] arr) {
        System.out.println(&quot;method start!!&quot;);
        arr[0] = 100;
        System.out.println(&quot;method end!!&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과&lt;/p&gt;
&lt;pre id=&quot;code_1767882144660&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0 1 2 
method start!!
method end!!
100 1 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 또한 참조 자료형이므로, 메서드 호출 시 배열 객체 자체가 전달되는 것이 아니라 배열을 가리키는 참조값이 복사되어 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, arr[0] = 100 과 같은 코드는 참조 변수 arr 를 다른 객체로 재할당하는 것이 아니라, arr 가 가리키는 배열 객체 내부의 상태를 변경하는 연산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 메서드 호출이 종료된 이후에도 동일한 배열 객체를 참조하고 있는 호출자 측에서는 변경된 결과를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;이처럼 Java 에서는 참조 자료형이더라도 메서드 호출 시 참조 자체가 전달되는 것은 아니다. 전달되는 것은 항상 참조값의 복사본이다. (Pass By Value)&lt;br /&gt;다만, 해당 참조값이 가리키는 객체가 내부 상태를 변경할 수 있는 Mutable 객체라면 메서드 호출 이후에도 결과가 변경될 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;참조 자료형인 String이 메서드 내부에서 값이 변경되지 않는 이유를 Pass by Value 와 JVM 메모리 구조 관점에서 정리했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java는 메서드 호출 시 항상 인자를 값으로 전달하며, 참조 자료형 역시 객체 자체가 아닌 참조값이 복사되어 전달된다.&lt;br /&gt;이로 인해, 메서드 내부에서 참조 변수를 다른 객체로 재할당하더라도 호출한 쪽의 참조에는 영향을 주지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 배열이나 DTO처럼 내부 상태를 변경할 수 있는 mutable 객체의 경우,&lt;br /&gt;메서드 내부에서 객체의 필드를 수정하면 동일한 객체를 참조하고 있는 호출자 측에서도 변경된 결과를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결국, 참조 자료형의 변경 여부는 Pass by Value 규칙의 예외가 아니라,&lt;/b&gt;&lt;br /&gt;&lt;b&gt;객체의 가변성 여부에 따른 결과 차이라고 이해할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;깃허브 주소 : &lt;a href=&quot;https://github.com/ParkJunGyu26/TIL/tree/main/Java/java-pass-by-value&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/ParkJunGyu26/TIL/tree/main/Java/java-pass-by-value&lt;/a&gt;&lt;/p&gt;</description>
      <category>CS/Java</category>
      <category>immutable</category>
      <category>Java</category>
      <category>JVM</category>
      <category>mutable</category>
      <category>PassByValue</category>
      <category>string</category>
      <category>til</category>
      <category>개발공부</category>
      <category>메모리구조</category>
      <category>참조자료형</category>
      <author>junju404</author>
      <guid isPermaLink="true">https://junju404.tistory.com/31</guid>
      <comments>https://junju404.tistory.com/31#entry31comment</comments>
      <pubDate>Thu, 8 Jan 2026 23:50:47 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 34204번 가방</title>
      <link>https://junju404.tistory.com/30</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/34204&quot;&gt;가방&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 난이도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;골드 4&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알고리즘 태그&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투포인터, 슬라이딩윈도우, 그리디(정렬)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이 문제를 왜 풀게 됐는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 지문이 길거나 또는 문제 지문에서 생각을 요구하는 문제를 풀고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 전에는 문제의 지문이 짧아서, '이 문제의 알고리즘은 무엇일까?'라는 생각을 먼저 했다.&lt;br /&gt;하지만, 2025년부터 기업의 코딩테스트는 문제의 독해력도 요구하는 것 같았다. 기업에서 코딩테스트를 통해 이제는 요구하는 바가 '요구사항을 정확하게 이해하고 있는가?'와 '이 문제의 알고리즘을 알고 있는가?' 인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, 프로그래머스를 풀면서 더 많은 문제를 풀어보고 싶었다. 프로그래머스와 가장 유사하게 &quot;부분 점수&quot;도 있기 때문에 연습하기 가장 좋다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;독해력 연습과 부분 점수를 고려하고자 &lt;a href=&quot;https://www.acmicpc.net/category/55&quot;&gt;백준의 한국정보올림피아드 문제&lt;/a&gt; 를 풀어보게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 소개&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상훈은 최대한 김기범이 최댓값 가지도록 최선을 다 한다.&lt;br /&gt;기범은 주어진 K 개를 훔치지만, 그 값이 최솟값 가지도록 최선을 다 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상훈이가 가방에 담을 수 있는 물건은 무게 합이 x 이하만 가능하다.&lt;br /&gt;기범이는 훔쳐가는 물건 무게 합 최대값 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남겨진 물건의 개수가 K 개 미만인 경우에는 기범이는 모두 훔친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제를 어떻게 접근했는가?&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;지문을 명확하게 이해해야되고, 예제 케이스 3개가 어떠한 원리로 출력되는지 이해하는 게 가장 중요했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;도둑 김기범은 가게에서 K개의 물건을 훔쳐갈 것인데, 
물건이 무거울 경우 훔쳐가기 어렵고 경찰한테 걸릴 가능성이 높다. 
고로, 도둑 김기범은 훔쳐가는 물건의 무게의 합을 최소화한다. 
만약 가게에 있는 물건의 개수가 
K개 미만일 경우, 도둑 김기범은 가게에 있는 모든 물건을 훔쳐간다.

상훈이는 도둑 김기범이 가게를 도착하기 전에, 
가방에 상점의 물건들을 몇 개 담아서 들고 갈 것이다. 
이후, 도둑 김기범은 상훈이가 들고 가지 않은 물건들에 대해 위에 설명한 방식으로 범죄를 저지른다. 
상훈이는 가방에 물건을 적당히 담아서 도둑 김기범이 훔쳐가는 물건의 무게 합을 최대화하려고 한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 지문에 앞 부분에는 &lt;code&gt;기범의 무게 합을 최소화&lt;/code&gt;한다고 나와있고, 지문의 뒷 부분에는 &lt;code&gt;기범의 무게 합을 최대화&lt;/code&gt; 한다고 주어졌다.&lt;br /&gt;그래서 이를 각 주체마다 목표점을 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;기범은 주어진 상황에서 훔칠 물건의 무게를 최소화할 것이다.&lt;/code&gt;&lt;br /&gt;&lt;code&gt;상훈은 주어진 상황에서 남겨진 물건의 무게를 최대화할 것이다.&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 결론은 최댓값의 기범의 무게를 각 무게 제한별로 출력하는 것으로 이해했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;N 개의 물건은 오름차순으로 주어진다는 보장이 없기 때문에 오름차순으로 정렬했다.&lt;/li&gt;
&lt;li&gt;투포인터 역할을 하는 left, right 를 설정한다. left 는 기범이가 훔칠 물건의 가장 작은 인덱스를 뜻하고, right 는 기범이가 훔칠 물건의 가장 큰 인덱스를 뜻한다. 그렇다면, 문제 지문에 적용한다면 상훈이는 무게 제한이 x일 때, 물건을 0번 물건부터 left-1번 물건까지 누적해서 가방에 넣을 것이다. 반면에, 기범이는 무조건 k 개를 훔치되, 주어진 물건 중에서 가장 작은 k 개를 훔칠 것이다.&lt;/li&gt;
&lt;li&gt;right 가 마지막 물건을 가리키고 있을 때는 더 이상 right 를 증가할 수 없기 때문에, 그 미만인 경우에만 left 와 right 를 수정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 헷갈렸던 부분은 예제 3의 출력값이었다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[input]
3 2 3
1 1 7

[output]
8
8
8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x 가 1일 때는, 8인 걸 이해했지만, x 가 2일 때도 왜 8이 출력되는지 이해가 안됐다.&lt;br /&gt;&lt;b&gt;첫 번째 나의 생각은 '1 1 을 가져가면, 7 아닌가?' 이었다.&lt;/b&gt;&lt;br /&gt;하지만, 이것은 잘못됐다. 우리가 출력해야되는 건 &quot;상훈&quot;이 입장에서는 &quot;기범&quot;이의 값이 최대한 값이 커야된다. 따라서, 상훈이가 1 1 을 &quot;가져갈 순 있지만, 1만 가져간다면 기범이는 무조건 8을 가져갈 수 밖에 없다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째 생각은 '그러면, 하나도 안가져가면 9 아닌가?' 이었다.&lt;/b&gt;&lt;br /&gt;하지만, 이것도 잘못됐다. 이번에는 &quot;기범&quot;이의 입장에서 무조건 2개를 가져갈 것이고, 그렇다면 기범이는 최선을 다 해서 가장 작은 값 2개를 가져갈 것이다. 그러면, 1 1 을 기범이가 훔쳐서 2가 출력될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 고민을 통해서 반례에 대해 조금 더 꼼꼼하게 생각해볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 번에 맞추지 못한 이유는?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 출력의 범위는 &lt;code&gt;C&lt;/code&gt; 만큼 출력해야되는데, 그 출력을 루프에서 진행했기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정답 코드(StringBuilder / StringBuffer / BufferedWriter)&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

// 상훈이 가방 담을 수 있는 물건 무게 합 x 이하
// 도둑 김기범이 훔쳐가는 물건 무게 합 최대값 출력

// 상훈은 최대한 김기범이 최댓값 가지도록 최선을 다 한다.
// 기범은 최솟값 가지도록 최선을 다 한다.
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken()); // 상점의 물건 개수 (배열) &amp;lt;= 5000
        int k = Integer.parseInt(st.nextToken()); // 도둑 김기범이 훔쳐갈 물건 개수 &amp;lt;= 5000
        int c = Integer.parseInt(st.nextToken()); // 상훈이 가방 무게 최댓값 &amp;lt;= 1_000_000
        int[] A = new int[n];
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &amp;lt; n; i++) A[i] = Integer.parseInt(st.nextToken());
        Arrays.sort(A); // 오름차순 정렬

        long kibeom = 0L;
        int left = 0;
        int right = k-1;
        for (int i = 0; i &amp;lt; Math.min(k, n); i++) kibeom += A[i];

        StringBuffer sb = new StringBuffer();
        // StringBuffer sb = new StringBuffer();
        // BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        long sanghoon = 0L;
        for (int x = 1; x &amp;lt;= c; x++) {
            // 상훈이가 먼저 가방 세팅
            if (sanghoon + A[left] &amp;lt;= x) { // 가방에 더 넣을 수 있음(can)
                // 기범이 물건 훔치기
                if (right - left + 1 == k) {
                    if (right &amp;lt; n-1) {
                        sanghoon += A[left];
                        kibeom -= A[left++];
                        kibeom += A[++right];
                    }
                }
            }
            sb.append(kibeom).append(&quot;\n&quot;);
            // bw.write(String.valueOf(kibeom));
            // bw.write(&quot;\n&quot;);
        }
        System.out.println(sb);
        // bw.flush();
        // bw.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오답 코드(루프에서 바로 출력)&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

// 상훈이 가방 담을 수 있는 물건 무게 합 x 이하
// 도둑 김기범이 훔쳐가는 물건 무게 합 최대값 출력

// 상훈은 최대한 김기범이 최댓값 가지도록 최선을 다 한다.
// 기범은 최솟값 가지도록 최선을 다 한다.
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken()); // 상점의 물건 개수 (배열) &amp;lt;= 5000
        int k = Integer.parseInt(st.nextToken()); // 도둑 김기범이 훔쳐갈 물건 개수 &amp;lt;= 5000
        int c = Integer.parseInt(st.nextToken()); // 상훈이 가방 무게 최댓값 &amp;lt;= 1_000_000
        int[] A = new int[n];
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &amp;lt; n; i++) A[i] = Integer.parseInt(st.nextToken());
        Arrays.sort(A); // 오름차순 정렬

        long kibeom = 0L;
        int left = 0;
        int right = k-1;
        for (int i = 0; i &amp;lt; Math.min(k, n); i++) kibeom += A[i];

        long sanghoon = 0L;
        for (int x = 1; x &amp;lt;= c; x++) {
            // 상훈이가 먼저 가방 세팅
            if (sanghoon + A[left] &amp;lt;= x) { // 가방에 더 넣을 수 있음(can)
                // 기범이 물건 훔치기
                if (right - left + 1 == k) {
                    if (right &amp;lt; n-1) {
                        sanghoon += A[left];
                        kibeom -= A[left++];
                        kibeom += A[++right];
                    }
                }
            }
            System.out.println(kibeom);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xecjp/dJMcacV4y4D/wrLylZ1lbna0uHlKRNelrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xecjp/dJMcacV4y4D/wrLylZ1lbna0uHlKRNelrk/img.png&quot; data-alt=&quot;1. StringBuffer 사용 / 2. BufferedWriter 사용 / 3. System.out.println() 사용 (53점) / 4. StringBuilder 사용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xecjp/dJMcacV4y4D/wrLylZ1lbna0uHlKRNelrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXecjp%2FdJMcacV4y4D%2FwrLylZ1lbna0uHlKRNelrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1794&quot; height=&quot;394&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1. StringBuffer 사용 / 2. BufferedWriter 사용 / 3. System.out.println() 사용 (53점) / 4. StringBuilder 사용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주어진 케이스 외의 반례&lt;/h3&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;5 2 20
2 3 5 7 11

1   5
2   8
3   8
4   8
5   12
6   12
7   12
8   12
9   12
10  18
11  18
12  18
13  18
14  18
15  18
16  18
17  18
18  18
19  18
20  18


3 2 9
1 1 7

1   8
2   8
3   8
4   8
5   8
6   8
7   8
8   8
9   8


5 1 15
1 2 3 4 5

1   2
2   2
3   3
4   3
5   3
6   4
7   4
8   4
9   4
10  5
11  5
12  5
13  5
14  5
15  5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 후기&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;타입 주의 (long 인가? int 인가?)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누적하는 값이 1_000_000 * 5_000 는 5_000_000_000 가 된다. Java 에서 int 범위는 약 21억까지 가능하므로, int 보다는 long 으로 타입을 설정했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;문제 중심으로 이해하기 (나의 생각이 맞는데? 보다는 주어진 예시가 어떠한 원리로 나왔고, 그 원리가 모두 동일한지)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀고나서 보니 현대오토에버 3분기 코테, 싸피 14기 코테에서 나의 독해력의 개선점과 유사했다.&lt;br /&gt;'나의 풀이가 맞는데, 왜 결과가 틀렸지(다르지)?' 라는 식으로 생각하며 시간을 많이 낭비했다. 그래서, 그 이후부터는 주어진 예제 케이스를 중심으로 먼저 이해하도록 노력하고 있다.&lt;/p&gt;</description>
      <category>Algorithm/BaekJoon</category>
      <category>2025 현대오토에버 코테</category>
      <category>Java</category>
      <category>백준</category>
      <category>백준 34204</category>
      <category>백준 34204 풀이</category>
      <category>알고리즘</category>
      <category>투포인터</category>
      <author>junju404</author>
      <guid isPermaLink="true">https://junju404.tistory.com/30</guid>
      <comments>https://junju404.tistory.com/30#entry30comment</comments>
      <pubDate>Sun, 4 Jan 2026 08:18:00 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 15900번 나무 탈출</title>
      <link>https://junju404.tistory.com/29</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/15900&quot;&gt;백준 15900번 나무 탈출&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 난이도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실버 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알고리즘 태그&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프 탐색, BFS, 트리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 소개&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성원이와 형석 두 사람이 게임을 진행한다. 게임은 성원이가 먼저 시작한다.&lt;br /&gt;게임 방식은 리프 노드에서 게임말을 한 칸씩 부모 노드&lt;b&gt;(루트 노드는 반드시 1)&lt;/b&gt;로 이동한다. 만약, 리프 노드에서 출발한 게임말이 1번 노드를 만나게 되면 해당 게임말은 사라진다. 이러한 방식을 진행 중에, 모든 게임말이 사라졌을 때, 해당 차례의 사람은 게임에서 지는 것이다.&lt;br /&gt;특정 노드에서는 게임말이 여러 개가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력은 처음에 N 을 입력받고(노드 개수),&lt;br /&gt;그 다음 (N-1)개 만큼의 두 숫자를 입력받는다. 이 두 숫자는 노드를 뜻하고, 서로 연결됨을 뜻한다.(엣지 개수 = (N-1))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력은 성원이가 게임에서 항상 이긴다면 &quot;Yes&quot;를,&lt;br /&gt;성원이가 게임에서 항상 진다면 &quot;No&quot;를 출력한다.&lt;br /&gt;(성원이와 형석이 모두 최선의 결과를 나타내도록 할 것이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이 문제를 왜 풀게 됐는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/388354&quot;&gt;프로그래머스 홀짝트리&lt;/a&gt; 문제를 못 풀어서, 이와 유사한 문제를 제미나이에게 추천해달라고 했다. 그래서, 유사한 문제 중 백준에서 풀게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제를 어떻게 접근했는가?&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;리프 노드의 개수와 엣지(경로) 개수의 합이 홀짝인 경우에 Yes/No 인가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 아래에 있는 반례 케이스 때문에 아닌 것을 알게 됐다. 주어진 예제 케이스에는 단순하게 {리프 노드 개수 + 엣지 개수(n-1)} 가 짝수라면 &quot;No&quot;, 홀수라면 &quot;Yes&quot;가 출력됐다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 리프 노드에서 1번 노드까지의 거리의 합이 홀짝인 경우에 Yes/No 인가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 이 경우가 문제의 정답이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 리프노드에서 1번 노드까지 올라가는 경우 중복되는 경우가 있다. 그렇다면, O(리프노드 수 * N) 은 시간초과가 발생할 수 있을 것 같다.&lt;/li&gt;
&lt;li&gt;1번 노드(루트 노드)에서 리프 노드까지 단 한 번만 탐색하는 경우에는 시간복잡도가 O(N) 만 발생할 것이다. 그렇다면, 이렇게 풀면 시간복잡도는 안전하다.&lt;/li&gt;
&lt;li&gt;각 노드에는 1번 노드와의 거리를 저장한다.&lt;/li&gt;
&lt;li&gt;각 리프 노드의 1번 노드와의 거리를 모두 누적하여, 홀/짝을 판별하여 답을 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정답 코드(BFS)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간복잡도 : O(N)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
    static int n;
    static int[] visited;
    static ArrayList&amp;lt;ArrayList&amp;lt;Integer&amp;gt;&amp;gt; edge;

    // 엣지 수 + 리프 수 가 홀수라면, 성원이가 진다?

    // 루트 노드 : 1번 노드
    // 리프 노드 : 자식 없는 노드
    // 성원이가 먼저 시작
    // 루트 노드의 값이 짝수면 지는거임
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        visited = new int[n+1];
        Arrays.fill(visited, -1);
        edge = new ArrayList&amp;lt;&amp;gt;();
        for (int i = 0; i &amp;lt;= n; i++) edge.add(new ArrayList&amp;lt;&amp;gt;());

        for (int i = 1; i &amp;lt; n; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());

            edge.get(a).add(b);
            edge.get(b).add(a);
        }

        ArrayDeque&amp;lt;Integer&amp;gt; q = new ArrayDeque&amp;lt;&amp;gt;();
        q.offer(1);
        visited[1] = 0;
        ArrayList&amp;lt;Integer&amp;gt; leaf = new ArrayList&amp;lt;&amp;gt;();
        while (!q.isEmpty()) {
            int cur = q.poll();
            boolean isLeaf = true;

            for (int next : edge.get(cur)) {
                if (visited[next] != -1) continue;

                isLeaf = false;
                visited[next] = visited[cur]+1;
                q.offer(next);
            }

            if (isLeaf) leaf.add(cur);
        }

        int answer = 0;
        for (int l : leaf) answer += visited[l];
        if (answer % 2 == 0) System.out.println(&quot;No&quot;);
        else System.out.println(&quot;Yes&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오답 코드(BFS)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간복잡도 : O(N * l(리프 노드 수))&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
    static int n;
    static ArrayList&amp;lt;ArrayList&amp;lt;Integer&amp;gt;&amp;gt; edge;

    // 엣지 수 + 리프 수 가 홀수라면, 성원이가 진다?

    // 루트 노드 : 1번 노드
    // 리프 노드 : 자식 없는 노드
    // 성원이가 먼저 시작
    // 루트 노드의 값이 짝수면 지는거임
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());

        edge = new ArrayList&amp;lt;&amp;gt;();
        for (int i = 0; i &amp;lt;= n; i++) edge.add(new ArrayList&amp;lt;&amp;gt;());

        for (int i = 1; i &amp;lt; n; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());

            edge.get(a).add(b);
            edge.get(b).add(a);
        }

        int answer = 0;
        for (int i = 2; i &amp;lt;= n; i++) {
            // 리프 노드의 경우
            if (edge.get(i).size() == 1) {
                int[] visited = new int[n+1];
                Arrays.fill(visited, -1);
                ArrayDeque&amp;lt;Integer&amp;gt; q = new ArrayDeque&amp;lt;&amp;gt;();

                q.offer(i);
                visited[i] = 0;
                while (!q.isEmpty()) {
                    int cur = q.poll();

                    if (cur == 1) {
                        answer += visited[cur];
                        break;
                    }

                    for (int next : edge.get(cur)) {
                        if (visited[next] != -1) continue;

                        visited[next] = visited[cur]+1;
                        q.offer(next);
                    }
                }
            }
        }

        if (answer % 2 == 0) System.out.println(&quot;No&quot;);
        else System.out.println(&quot;Yes&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1802&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bugFBB/dJMcacaGYYT/DjmIclbqmUUJxo9yt22Qdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bugFBB/dJMcacaGYYT/DjmIclbqmUUJxo9yt22Qdk/img.png&quot; data-alt=&quot;시간초과 : 두번째 풀이&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bugFBB/dJMcacaGYYT/DjmIclbqmUUJxo9yt22Qdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbugFBB%2FdJMcacaGYYT%2FDjmIclbqmUUJxo9yt22Qdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1802&quot; height=&quot;244&quot; data-origin-width=&quot;1802&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;시간초과 : 두번째 풀이&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/board/view/164430&quot;&gt;주어진 케이스 외의 반례&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;5
1 2
2 3
2 4
3 5

-&amp;gt; Yes

5
1 2
1 3
3 4
4 5

-&amp;gt; No&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 후기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우연히, 해당 문제는 최근에 풀었던 현대오토에버 4분기 공채의 코테에서 &lt;b&gt;나의 풀이 중 개선점&lt;/b&gt;과 유사했다.&lt;br /&gt;자세하게 얘기할 순 없지만, &quot;트리&quot;라는 것은 방향이 정해져있긴 하지만, &lt;b&gt;루트 노드에서 출발할 지? 또는 리프노드에서 출발할 지?&lt;/b&gt; 에 따라 시간복잡도가 달라진다.&lt;br /&gt;해당 문제에서는 루트 노드에서 시작했고, 단 한 번의 탐색만 진행하기 때문에 시간복잡도는 &lt;b&gt;O(N)&lt;/b&gt;이다.&lt;br /&gt;하지만, 리프 노드에서 시작할 경우에는 &lt;b&gt;O(N * 리프노드 수)&lt;/b&gt; 가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 현대오토에버 코테에서는 시야가 좁았던 것 같다. &lt;b&gt;조금 더 시야를 넓혀서, 무조건적인 시야 보다는 조금 더 유연하게 접근하자.&lt;/b&gt;&lt;/p&gt;</description>
      <category>Algorithm/BaekJoon</category>
      <category>2025 현대오토에버 코테</category>
      <category>Java</category>
      <category>백준 15900 Java 풀이</category>
      <category>백준 15900번</category>
      <category>백준 15900번 BFS</category>
      <category>백준 15900번 트리</category>
      <category>백준 15900번 풀이</category>
      <category>백준 나무 탈출 풀이</category>
      <category>알고리즘</category>
      <author>junju404</author>
      <guid isPermaLink="true">https://junju404.tistory.com/29</guid>
      <comments>https://junju404.tistory.com/29#entry29comment</comments>
      <pubDate>Thu, 1 Jan 2026 21:20:15 +0900</pubDate>
    </item>
    <item>
      <title>[자격증] PCCP Java Lv5 후기</title>
      <link>https://junju404.tistory.com/28</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 결과&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvKy2S/dJMcabpeViW/Rrew0V9jUOjbh2YQdvuNO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvKy2S/dJMcabpeViW/Rrew0V9jUOjbh2YQdvuNO1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;707&quot; data-filename=&quot;blob&quot; width=&quot;600&quot; height=&quot;848&quot; style=&quot;width: 49.4276%; margin-right: 10px;&quot; data-widthpercent=&quot;50.01&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvKy2S/dJMcabpeViW/Rrew0V9jUOjbh2YQdvuNO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvKy2S%2FdJMcabpeViW%2FRrew0V9jUOjbh2YQdvuNO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzPZY0/dJMcab3QYPQ/BFUexTDgpmRjKQgTSadHWk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzPZY0/dJMcab3QYPQ/BFUexTDgpmRjKQgTSadHWk/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;1754&quot; data-filename=&quot;PCCP Certification of Achievement 박준규_프로그래머스인증시험_page-0001.jpg&quot; width=&quot;600&quot; height=&quot;849&quot; style=&quot;width: 49.4096%;&quot; data-widthpercent=&quot;49.99&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzPZY0/dJMcab3QYPQ/BFUexTDgpmRjKQgTSadHWk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzPZY0%2FdJMcab3QYPQ%2FBFUexTDgpmRjKQgTSadHWk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1240&quot; height=&quot;1754&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;필자의 알고리즘 상태는 &lt;b&gt;백준 기준(solved)&lt;/b&gt;으로는 &lt;a href=&quot;https://solved.ac/profile/junju404&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;플레티넘4&lt;/a&gt;이고, 백준 문제 기준으로 평균적으로 골드4까지는 풀 수 있다(?) 고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;알고리즘 유형별로 DP, 그리디, 누적합 같은 문제는 골드5...?까지인 것 같고, 그래프(DFS, BFS, 최단경로)는 원래부터 난이도가 높아서 골드3 정도..? 그래서 평균 골드 4까지는 풀 만 하다고 생각했다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;앳코더&lt;/b&gt;나 &lt;b&gt;코드포스&lt;/b&gt;는 Div4 기준으로는 평균적으로 3문제 정도 푸는 것 같다. (앳코더로는 갈색..? 고점 뜨면 초록..ㅠ)&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 응시 배경&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;프로그래머스에서 PCCP(Java) 를 응시한 가장 큰 이유는 &quot;&lt;b&gt;코테 실전 경험&lt;/b&gt;&quot;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 2025년 하반기 종료&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;코딩테스트에 대한 무료(?) 실전 경험을 위해서 여러 기업들의 공채에 많이 지원했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 프로그래머스 : 토스, 카카오, 현대캐피탈, 한국자금중개, 현대오토에버&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 잡다 : 마이다스아이티&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- YBM COS Pro : 다우기술&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;내가 응시했던 코딩테스트 플랫폼만 봐도, 대부분 프로그래머스에서 진행하는 것을 알 수 있다. 하지만, 이제 2025년 하반기는 끝이 났고, 더이상 응시할 수 있는 기업은 없다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;어쩔 수 없이 이제는 유료(?) 실전 경험을 위해서 5만원을 결제해서 응시하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 코딩테스트 플랫폼 적응&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에서 보듯이, 이제는 코딩테스트 플랫폼은 프로그래머스가 독보적인 것 같다. 실전 감각을 꾸준히 익히기 위해서 응시했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 프로그래머스에서 코딩 테스트를 응시해보면 알 수 있지만, 학창 시절의 국어 비문학 독해 느낌이 많이 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;더군다나 필자는 비문학 독해 능력이 부족하다고 생각해서 이제는 프로그래머스에서만 연습하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;백준도 충분히 문제 은행(양치기)에서는 정말 많은 득을 봤다. 하지만, 이제는 실전 감각과 독해 능력(문제 읽는)을 기르기 위해서 프로그래머스에서 실전 감각을 익히고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 유일한? 코테 역량 자격증&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;작년까지만 해도 &quot;소프티어&quot;에 꾸준히 도전했었다. 하지만, 올해부터 서비스가 종료되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서, 앳코더, 코드포스 등 여러 모의 테스트를 쳐봤지만 영어로 된 걸 다시 한국어로 번역하는 것도 귀찮고, 시간 분배에는 도움이 됐지만 뭔가 모를 아쉬움이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서, 우연히 &lt;b&gt;프로그래머스의 PCCP 자격증&lt;/b&gt; 이라는 걸 알게 됐고, 처음이자 마지막으로 도전해보고 싶었고 현재 나의 알고리즘 레벨 상태도 궁금하기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 시험 준비 과정&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;따로 &quot;PCCP 를 위해 준비해야겠다!&quot; 라는 건 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;최근 일주일 동안, 코드트리에서 &quot;DP&quot;, &quot;BFS&quot;, &quot;DFS&quot; 부분에 대해 특정 유형별(HARD)로 학습 중이긴 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;해당 유형들이 다 출제되진 않았지만 어느 정도는 도움이 된 것 같고, 감각도 조금씩 돌아오고 있는 상태인 것 같다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;시험 전날, 프로그래머스에서 보니깐 &quot;&lt;a href=&quot;https://school.programmers.co.kr/learn/challenges?order=recent&amp;amp;partIds=56389&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PCCP 기출 문제&lt;/a&gt;&quot; 로 따로 공개된 것을 발견했다. 만약 준비하시는 분들이 있다면 해당 문제들을 한 번씩 풀어보면 도움이 될 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;1622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HLqIw/dJMcaihB1rm/Q08eZsK043PcdKZlKlCN6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HLqIw/dJMcaihB1rm/Q08eZsK043PcdKZlKlCN6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HLqIw/dJMcaihB1rm/Q08eZsK043PcdKZlKlCN6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHLqIw%2FdJMcaihB1rm%2FQ08eZsK043PcdKZlKlCN6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;525&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;1622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. 시험 후기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어떤 문제가 나왔는지 구체적으로 얘기할 순 없지만, 위의 공개된 문제처럼 Lv1, Lv2, Lv2, Lv3 으로 배치된 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;수학적인 계산 및 구현, 자료구조, 그래프 탐색, DP&lt;/b&gt; 이 정도로 나온 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코테에서는 항상 &quot;DP&quot;는 어렵지만, 요즘은 단골로 출제되는 것 같다. 그래서, DP에도 여러 유형들이 나뉘어지는 것 같은데 여러 DP 를 풀어보는 것도 중요할 것 같다!!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;문제를 풀 때는, 종이를 적극적으로 활용하고, 그리고 주석으로 한 번 더 방향성을 잡는 것도 중요한 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;무작정 문제를 풀기 보다는 문제 지문(요구사항)을 한 번 더 정리하고, 문제를 천천히 이해하는 것이 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고, 문제에서는 어떠한 알고리즘을 사용할 수 있을지 후보군으로 한 번 필터링한다. (ex) 완전탐색은 안된다 등)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;점수는 1000점 만점인데, 910점으로 나름 커트라인에 걸치긴 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;아마, 900점 이상인 이유는 시간복잡도나 공간복잡도 등 효율성 및 정확도는 정답인 거 같긴한데.. 감점된 이유는 조금 더 최적화된 방법이 있는 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다른 사람들 후기에는 1000점(만점), 945점 등 부분점수를 주는데, Lv5 이상 범위를 적은 것 보니 정답이긴 한데 조금 더 노력해보라 라는 느낌이지 않을까 개인적인 생각이다..!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;시험 치기 전에도 느꼈고, 시험이 끝난 후에도 느낀 점은 &lt;b&gt;'이제 문제에 대해 어떻게 접근하고, 이러한 알고리즘을 쓰면 풀릴 거 같다. 근데, 이걸 어떻게 구현해야될까?'&lt;/b&gt; 이다. 확실히 &quot;구현력&quot;에 대한 부족함이 있다는 걸 느꼈지만, 조금 더 구체적으로 파고든다면, '해당 알고리즘을 어떻게 구현할까?' 라는 부분도 보완해야된다고 느꼈다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코드트리(HSAT 기출, 삼성 기출, 유형별 학습)으로 조금 더 보완해나가면 더 괜찮아질 것 같다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여담이긴 하지만, 최근에 hsat을 검색했다가 &quot;소프티어&quot;에서 &quot;&lt;a href=&quot;https://exam.hyundai-ngv.com/challenge/HSAT/list&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NGV 캠퍼스&lt;/a&gt;&quot;로 다시 오픈(?)된 것 같다..! 아마 조만간 소프티어 자격증도 돌아올 거 같다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>기타 자격증/PCCP</category>
      <category>Java</category>
      <category>Lv5</category>
      <category>PCCP</category>
      <category>문제해결능력</category>
      <category>자격증</category>
      <category>코딩역량</category>
      <category>프로그래머스</category>
      <author>junju404</author>
      <guid isPermaLink="true">https://junju404.tistory.com/28</guid>
      <comments>https://junju404.tistory.com/28#entry28comment</comments>
      <pubDate>Sun, 21 Dec 2025 18:06:11 +0900</pubDate>
    </item>
    <item>
      <title>[자료구조] 우선순위 큐 삽입 순서 - &amp;ldquo;소 잃고 외양간 고치지 말자!&amp;rdquo;</title>
      <link>https://junju404.tistory.com/27</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1005&quot;&gt;백준 1005번 문제&lt;/a&gt;를 풀다가 우선순위 큐를 사용해서 위상정렬을 구현했다. 코드를 완성하고 팀원과 비교해보니 둘 다 같은 답을 출력하는데, 우선순위 큐에 값을 넣는 부분의 코드 순서가 다르더라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 그냥 스타일 차이라고 생각했는데, 자세히 보니 정렬 기준이 되는 값을 초기화하는 시점이 달랐다. 나의 실수를 기록하고, 이를 파고들어 정리하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드 비교 : 어디가 다른가&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 내 코드
for (int i = 1; i &amp;lt;= n; i++) {
    if (inDegree[i] == 0) {
        q.offer(i);           // 우선순위 큐에 먼저 삽입
        answer[i] = res[i];   // 나중에 정렬 기준값 초기화
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 팀원의 코드
for (int i = 1; i &amp;lt;= n; i++) {
    if (inDegree[i] == 0) {
        answer[i] = res[i];   // 먼저 정렬 기준값 초기화
        q.offer(i);           // 나중에 우선순위 큐에 삽입
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 코드 모두 결과적으로 같은 동작을 하는 것처럼 보인다. 하지만 이 순서 차이는 생각보다 중요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;근본 원인 : PriorityQueue와 힙의 관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 의문을 풀려면 Java의 &lt;code&gt;PriorityQueue&lt;/code&gt;가 어떻게 구현되어 있는지 알아야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) PriorityQueue의 내부 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 &lt;code&gt;PriorityQueue&lt;/code&gt;는 내부적으로 &lt;b&gt;이진 힙(binary heap)&lt;/b&gt; 자료구조를 사용한다. 우선순위를 효율적으로 관리하기 위해 배열 기반의 완전 이진 트리 형태로 구현된 것인데, 이것이 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;힙의 특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;완전 이진 트리 형태&lt;/b&gt;: 모든 레벨이 왼쪽부터 차곡차곡 채워짐&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Min-heap &lt;/b&gt;: 부모 노드 &amp;le; 자식 노드 (루트가 항상 최솟값)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배열 표현 &lt;/b&gt;: 인덱스 &lt;code&gt;i&lt;/code&gt;에서
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왼쪽 자식 : &lt;code&gt;2*i + 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;오른쪽 자식 : &lt;code&gt;2*i + 2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;부모 : &lt;code&gt;(i-1) / 2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 최솟값을 O(1)에 찾고, 삽입과 삭제도 O(log n)에 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 힙이 우선순위를 유지하는 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기가 핵심이다. 힙이 항상 우선순위 조건을 만족하려면 &lt;b&gt;두 가지 핵심 연산&lt;/b&gt;을 수행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;삽입할 때(Sift-Up)&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;새 원소를 배열 끝에 추가&lt;/li&gt;
&lt;li&gt;부모와 비교해서 조건을 만족할 때까지 위로 올린다&lt;/li&gt;
&lt;li&gt;올바른 위치를 찾을 때까지 반복&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;삭제할 때(Sift-Down)&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;루트(최솟값)를 제거&lt;/li&gt;
&lt;li&gt;배열 마지막 원소를 루트 자리에 이동&lt;/li&gt;
&lt;li&gt;자식들과 비교해서 조건을 만족할 때까지 아래로 내린다&lt;/li&gt;
&lt;li&gt;올바른 위치를 찾을 때까지 반복&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가장 중요한 포인트는 이것이다&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 연산들은 &lt;b&gt;삽입과 삭제 순간에만&lt;/b&gt; 일어난다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번 배치가 끝나면, 외부에서 아무리 값이 바뀌어도 힙은 그 변화를 감지하지 않는다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내가 놓친 부분&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 내 실수가 무엇인지 명확해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 무의식적으로 이렇게 가정했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;인덱스 &lt;code&gt;i&lt;/code&gt;를 큐에 넣고, 나중에 &lt;code&gt;answer[i]&lt;/code&gt; 값을 업데이트하면 힘이 그 변화를 감지해서 자동으로 재배치하지 않을까?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이건 완전한 오해였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내 코드의 실행 흐름&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[초기 상태]
answer = [0, 0, 0, ...]

[Step 1] q.offer(i) 호출
  ├─ PriorityQueue: &quot;이제 삽입합니다&quot;
  ├─ answer[i]의 현재값 확인: 0
  ├─ Sift-Up 수행: 0 값 기준으로 배치
  └─ 배치 완료 ✓

[Step 2] answer[i] = res[i] 실행
  ├─ answer 배열 값 변경 (0 &amp;rarr; 5)
  ├─ PriorityQueue: (감지 안 함)
  └─ 힙 구조 유지 (그대로)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;팀원 코드의 실행 흐름&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;[Step 1] answer[i] = res[i] 실행
  └─ answer 배열 값 세팅: 5

[Step 2] q.offer(i) 호출
  ├─ PriorityQueue: &quot;이제 삽입합니다&quot;
  ├─ answer[i]의 현재값 확인: 5
  ├─ Sift-Up 수행: 5 값 기준으로 배치
  └─ 배치 완료 ✓&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이가 극명하다. 내 코드는 &lt;b&gt;잘못된 기준(0)&lt;/b&gt; 으로 배치했고, 팀원 코드는 &lt;b&gt;올바른 기준(5)&lt;/b&gt; 으로 배치했다. 나중에 내가 값을 바꿔도, 이미 정해진 힙 구조는 변하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 이 차이가 눈에 띄지 않았나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흥미로운 점은 두 코드 모두 같은 정답을 출력했다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제(ACM Craft)의 특수한 성질 때문인 것 같다. 위상정렬에서 여러 원소가 동시에 처리될 수 있는 상황에서, 초기 배치 순서가 조금 이상해도 최종 결과는 같을 수 있기 때문이다. 즉, 내 코드도 잘못된 배치에도 불구하고 &lt;b&gt;우연히&lt;/b&gt; 맞는 답을 도출한 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이건 운이 좋았던 것이지, 근본적인 해결책이 아니다. 다른 문제나 다른 상황에서는 이 순서 차이가 명백한 오류로 드러날 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;올바른 사용 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 답은 명확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정렬 기준이 되는 값을 먼저 초기화한 후, 우선순위 큐에 삽입한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;for (int i = 1; i &amp;lt;= n; i++) {
    if (inDegree[i] == 0) {
        answer[i] = res[i];   // 기준값 준비
        q.offer(i);           // 올바른 값 기준으로 삽입
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Sift-Up이 수행될 때, 정렬 조건이 이미 올바르게 반영된 상태에서 시작된다. 의도한 대로 정확하게 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론 : 자료구조의 동작 원리를 무시하면 안 된다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 경험을 통해 얻은 교훈을 정리하면 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. PriorityQueue는 정렬된 리스트가 아니다&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;완전히 정렬된 상태를 유지하지 않는다&lt;/li&gt;
&lt;li&gt;오직 루트가 최솟값이라는 조건만 보장한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 힙의 배치는 삽입/삭제 순간에만 결정된다&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부에서 값이 바뀌어도 자동으로 재배치되지 않는다&lt;/li&gt;
&lt;li&gt;한 번 Sift-Up이 끝나면, 그 이후 구조는 변하지 않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 삽입 시점이 모든 것을 좌우한다&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그 순간의 값이 힙 구조 전체를 결정한다&lt;/li&gt;
&lt;li&gt;정렬 기준이 되는 값은 반드시 삽입 전에 준비되어야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;대충&quot; 코드를 짜면 우연히 맞을 수도 있다. 하지만 자료구조의 원리를 제대로 이해하지 못하면, 언제든 그 우연이 깨질 수 있다. 이번 발견 덕분에 PriorityQueue를 더 정확하게 사용할 수 있게 되었고, 앞으로 비슷한 함정에 빠지지 않을 것 같다.&lt;/p&gt;</description>
      <category>CS/자료구조</category>
      <category>Java</category>
      <category>priorityqueue</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>우선순위큐</category>
      <category>자료구조</category>
      <category>힙</category>
      <author>junju404</author>
      <guid isPermaLink="true">https://junju404.tistory.com/27</guid>
      <comments>https://junju404.tistory.com/27#entry27comment</comments>
      <pubDate>Tue, 12 Aug 2025 19:31:10 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 스프링 입문을 위한 자바 객체 지향의 원리와 이해 2장 - 자바와 절차적/구조적 프로그래밍 - T메모리</title>
      <link>https://junju404.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 이번 글에서는 자바의 프로그램 실행 구조에서 &lt;b&gt;메모리는 어떻게 사용되는가&lt;/b&gt;, 그리고 그 기반이 되는 &lt;b&gt;절차적/구조적 프로그래밍 유산&lt;/b&gt;에 대해 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 자바의 실행 환경(JVM)이 어떻게 메모리를 나누고 관리하는지 이해하는 것은,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 객체지향과 스프링의 구조를 파악하는 데도 큰 도움이 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  T메모리란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;T메모리는 &lt;b&gt;JVM이 자바 프로그램을 실행할 때 사용하는 메모리 구조&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조는 크게 세 가지 영역으로 나뉘며, &lt;b&gt;스레드(Thread) 단위&lt;/b&gt;로 생성되기 때문에 T메모리(Thread Memory)라고 부릅니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;메모리 영역&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;static 영역&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;클래스와 static 멤버들이 올라가는 공간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;stack 영역&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;메서드 호출 시 생성되는 지역 변수 공간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;heap 영역&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;new로 생성한 객체들이 저장되는 공간&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  static 영역 &amp;ndash; 클래스의 놀이터&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Counter {
    static int count = 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스나 static 멤버는 &lt;b&gt;처음 호출될 때 JVM의 static 영역&lt;/b&gt;에 로드됩니다.&lt;/li&gt;
&lt;li&gt;static 멤버는 프로그램 어디서든 접근 가능하여, &lt;b&gt;전역 변수처럼&lt;/b&gt; 사용됩니다.&lt;/li&gt;
&lt;li&gt;단, 이로 인해 스레드 간 공유되어 &lt;b&gt;동시성 문제&lt;/b&gt; 발생 가능성이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  static 변수는 되도록 사용을 최소화하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 전용으로 사용할 경우 &lt;code&gt;final static&lt;/code&gt; 상수로 선언하는 것이 좋습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  stack 영역 &amp;ndash; 메서드의 놀이터&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    int a = 10;
    String name = &quot;Java&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메서드가 호출되면 &lt;b&gt;스택 프레임&lt;/b&gt;이 생성되어, 지역 변수들이 저장됩니다.&lt;/li&gt;
&lt;li&gt;메서드가 종료되면 해당 스택 프레임은 &lt;b&gt;자동으로 소멸&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;기본형 타입은 &lt;b&gt;값 자체가 저장&lt;/b&gt;, 참조형은 &lt;b&gt;주소값이 저장&lt;/b&gt;됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;int x = 5;          // 값 저장
String str = &quot;Hi&quot;;  // 주소값 저장 &amp;rarr; 실제 객체는 heap에 존재
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  내부 블록(if, for 등)에서 선언된 변수는 해당 블록의 스택 프레임에만 존재합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  heap 영역 &amp;ndash; 객체의 놀이터&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;User user = new User(&quot;Alice&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;new&lt;/code&gt; 키워드를 통해 생성된 객체 인스턴스가 저장됩니다.&lt;/li&gt;
&lt;li&gt;인스턴스 필드들은 모두 heap 영역에 존재하며, &lt;b&gt;GC(Garbage Collector)&lt;/b&gt;가 관리합니다.&lt;/li&gt;
&lt;li&gt;더 이상 참조되지 않는 객체는 &lt;b&gt;자동으로 소멸&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  상속 관계에 있는 객체는 상위 클래스의 인스턴스도 함께 생성되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Object&lt;/code&gt; 클래스까지 포함됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  절차적/구조적 프로그래밍, 그리고 Java&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 객체지향 언어이지만, 그 뿌리는 절차적/구조적 프로그래밍에서 출발했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이해하면 객체지향 개념이 왜 필요한지 자연스럽게 와닿습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  절차적 프로그래밍&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램을 &lt;b&gt;위에서 아래로 순서대로 실행&lt;/b&gt;하는 방식&lt;/li&gt;
&lt;li&gt;&lt;code&gt;goto&lt;/code&gt; 사용은 복잡한 흐름을 만들기 때문에 Java에선 &lt;b&gt;예약어지만 사용 불가&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;// 이런 식의 흐름이 복잡해지기 때문에 지양
goto line14;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  구조적 프로그래밍 = 함수 중심&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복 코드 제거, 유지보수 용이&lt;/li&gt;
&lt;li&gt;논리를 함수 단위로 모듈화&lt;/li&gt;
&lt;li&gt;Java에서는 이 개념이 &lt;b&gt;메서드(method)&lt;/b&gt;로 이어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  함수는 클래스와 독립적이지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메서드는 클래스 내부에 반드시 존재&lt;/b&gt;해야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚙️ main() 실행부터 메모리까지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Hello, World!&lt;/code&gt;를 출력하는 간단한 코드도, 실제로는 복잡한 실행 구조를 가집니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Hello {
    public static void main(String[] args) {
        System.out.println(&quot;Hello, World!&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 순서 요약&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;JRE&lt;/b&gt;가 프로그램 실행 요청&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JVM 부팅&lt;/b&gt; 및 static 영역 준비&lt;/li&gt;
&lt;li&gt;&lt;code&gt;java.lang&lt;/code&gt; 등 필수 패키지 static에 적재&lt;/li&gt;
&lt;li&gt;클래스 로딩 &amp;rarr; static 영역에 등록&lt;/li&gt;
&lt;li&gt;&lt;code&gt;main()&lt;/code&gt; 메서드 호출 &amp;rarr; &lt;b&gt;stack 프레임 생성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;지역 변수 확보 &amp;rarr; 코드 실행&lt;/li&gt;
&lt;li&gt;&lt;code&gt;main()&lt;/code&gt; 종료 &amp;rarr; 스택 프레임 소멸&lt;/li&gt;
&lt;li&gt;JVM 종료 &amp;rarr; static, heap 메모리 해제&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;[여기에 JVM 실행 다이어그램 삽입]&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  변수는 어디에 저장될까?&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;변수 종류&lt;/th&gt;
&lt;th&gt;저장 위치&lt;/th&gt;
&lt;th&gt;소멸 시점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;전역 변수&lt;/td&gt;
&lt;td&gt;static 영역&lt;/td&gt;
&lt;td&gt;JVM 종료 시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지역 변수&lt;/td&gt;
&lt;td&gt;stack 영역&lt;/td&gt;
&lt;td&gt;메서드/블록 종료 시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;객체 필드&lt;/td&gt;
&lt;td&gt;heap 영역&lt;/td&gt;
&lt;td&gt;참조 해제 시 (GC)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  스레드와 메모리 구조&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;멀티 스레드&lt;/th&gt;
&lt;th&gt;멀티 프로세스&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;스택 영역&lt;/td&gt;
&lt;td&gt;스레드마다 별도&lt;/td&gt;
&lt;td&gt;프로세스마다 별도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;static / heap 영역&lt;/td&gt;
&lt;td&gt;공유&lt;/td&gt;
&lt;td&gt;공유하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 효율&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;안전성&lt;/td&gt;
&lt;td&gt;동시성 이슈 가능&lt;/td&gt;
&lt;td&gt;상대적으로 안전&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  서블릿(Servlet)은 요청마다 스레드를 생성 &amp;rarr; 멀티 프로세스보다 메모리 효율성 높음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  전역 변수보다 전역 상수!&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Shared {
    static int value = 55;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전역 변수는 여러 스레드에서 동시에 수정 가능&lt;/li&gt;
&lt;li&gt;&amp;rarr; 데이터 무결성 문제 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 올바른 예시&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Constants {
    public static final int MAX_COUNT = 100;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;읽기 전용 상수로 사용 &amp;rarr; 안전 + 공유 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  해당 단원을 읽고 배운 점 (TIL)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM이 프로그램 실행을 위해 메모리를 &lt;b&gt;static, stack, heap&lt;/b&gt;으로 나누어 관리한다는 점을 알게 되었다.&lt;/li&gt;
&lt;li&gt;메서드 호출과 블록 스코프의 변수 소멸 타이밍을 이해하면, &lt;b&gt;예상치 못한 변수 접근 에러&lt;/b&gt;를 방지할 수 있다.&lt;/li&gt;
&lt;li&gt;멀티 스레드 환경에서는 static 영역과 heap을 공유한다는 점에서 &lt;b&gt;전역 변수 사용 시 동기화 이슈&lt;/b&gt;에 주의가 필요하다.&lt;/li&gt;
&lt;li&gt;전역 변수는 피하되, 전역 &lt;b&gt;상수&lt;/b&gt;는 적극 활용하자!&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 장에서는 본격적으로 객체지향 개념이 어떻게 자바에 녹아있는지를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스, 인스턴스, 메서드, 필드 등 객체지향의 핵심 요소들을 차근히 정리해볼게요.  &lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료: 스프링 입문을 위한 자바 객체 지향의 원리와 이해 (김종민 저)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>CS/Java</category>
      <author>junju404</author>
      <guid isPermaLink="true">https://junju404.tistory.com/26</guid>
      <comments>https://junju404.tistory.com/26#entry26comment</comments>
      <pubDate>Mon, 23 Jun 2025 22:38:59 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 스프링 입문을 위한 자바 객체 지향의 원리와 이해 1장 - 사람을 사랑한 기술 - Java 역사 이야기</title>
      <link>https://junju404.tistory.com/25</link>
      <description>&lt;h1&gt;스프링 입문을 위한 자바 객체 지향의 원리와 이해 1장 - 사람을 사랑한 기술, Java 역사 이야기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 오늘은 자바의 탄생 배경과 발전 과정에 대해 이야기해보려고 합니다. 프로그래밍 언어의 역사를 통해 자바가 왜 '사람을 사랑한 기술'이라고 불리는지 함께 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 기계어에서 객체 지향 프로그래밍 언어로&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기계어 - 0과 1의 행진&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍의 시작은 &lt;b&gt;기계어&lt;/b&gt;였습니다. 인류 최초의 프로그래머들은 꺼진 상태와 켜진 상태를 0과 1로 해석할 수 있는 '백열전구'를 이용해 프로그램을 작성했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;역사적 의미&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;애드삭(EDSAC)&lt;/b&gt;: 2진법을 적용한 세계 최초의 프로그램 내장 방식 전자 계산기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유니박(UNIVAC)&lt;/b&gt;: 최초의 상업용 컴퓨터&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흥미로운 점은 프로그래밍 언어들이 서로를 &lt;b&gt;모방하고 창조&lt;/b&gt;하면서 발전했다는 것입니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;애드삭&lt;/b&gt;: 기계어 &lt;code&gt;ADD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유니박&lt;/b&gt;: 기계어 &lt;code&gt;PLUS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Java 진영&lt;/b&gt;: &lt;code&gt;System.out.println()&lt;/code&gt;, &lt;code&gt;JDBC&lt;/code&gt;, &lt;code&gt;ResultSet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Microsoft 진영&lt;/b&gt;: &lt;code&gt;Console.WriteLine()&lt;/code&gt;, &lt;code&gt;ODBC&lt;/code&gt;, &lt;code&gt;RecordSet&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;용어 정리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JDBC&lt;/b&gt;: Java Database Connectivity&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ODBC&lt;/b&gt;: Open Database Connectivity&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어셈블리어 - 인간 지향으로의 첫 걸음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기계어의 0과 1 행진을 벗어나 &lt;b&gt;어셈블리어&lt;/b&gt;가 등장했습니다. 이는 기계어의 니모닉(기억하기 쉬운 기호)으로, 인간이 이해하기 쉬운 형태로 발전했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어셈블러&lt;/b&gt;는 어셈블리어를 기계어로 번역해주는 소프트웨어입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  컴퓨터의 데이터 저장 방식: 엔디안(Endian)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터가 메모리에 데이터를 저장하는 방식에는 두 가지가 있습니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 빅엔디안 (Big-Endian)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RISC CPU에서 사용&lt;/li&gt;
&lt;li&gt;낮은 주소에 데이터의 높은 바이트(MSB)부터 저장&lt;/li&gt;
&lt;li&gt;평소 우리가 숫자를 사용하는 선형 방식과 동일 (이해하기 쉬움)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;예시: 0x12345678
&amp;rarr; 0x12 / 0x34 / 0x56 / 0x78 (순서대로 저장)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 리틀엔디안 (Little-Endian)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인텔 CPU에서 사용&lt;/li&gt;
&lt;li&gt;낮은 주소에 데이터의 낮은 바이트(LSB)부터 저장&lt;/li&gt;
&lt;li&gt;평소 우리가 사용하는 방식과 반대로 거꾸로 읽음&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;실무에서의 중요성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인텔 기반 운영체제: 리틀 엔디안 방식 사용&lt;/li&gt;
&lt;li&gt;네트워크 데이터 전송: 빅 엔디안 방식 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C언어 - 강력한 이식성의 시작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C언어는 &lt;b&gt;&quot;One Source Multi Object Use AnyWhere&quot;&lt;/b&gt;라는 철학을 가지고 등장했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 소스 파일을 각 기계에 맞는 컴파일러로 컴파일하여 다양한 플랫폼에서 실행할 수 있게 했습니다. 다만, 운영체제마다 소스 수정 과정이 필요했다는 한계가 있었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C++언어 - 객체 지향의 도입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C언어에 &lt;b&gt;객체 지향 개념&lt;/b&gt;을 도입한 C++언어가 등장했습니다. 이는 인간적인 사고의 프로그래밍 방식으로, 프로그래밍 패러다임의 큰 전환점이 되었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;☕ Java - 진정한 객체 지향 언어&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&quot;Write Once One Object Use AnyWhere&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java는 &lt;b&gt;&quot;사람을 사랑한 방법&quot;&lt;/b&gt;으로 프로그래밍을 더욱 인간적이고 안전하게 만들었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  Java의 혁신적 특징&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;컴파일러를 기종별로 준비할 필요 없음&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단, JRE(Java Runtime Environment)만 설치하면 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JVM(Java Virtual Machine)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플랫폼 독립성 보장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;포인터 제거&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 관리의 복잡성과 위험성 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  C언어 vs Java 비교&lt;/h4&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;Java&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;소스 파일&lt;/td&gt;
&lt;td&gt;One Source&lt;/td&gt;
&lt;td&gt;One Source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;목적 파일&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Multi Object&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;One Object&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실행&lt;/td&gt;
&lt;td&gt;Use AnyWhere&lt;/td&gt;
&lt;td&gt;Use AnyWhere&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ JDK, JRE, JVM의 관계&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zXFBC/btsOG5xWCT8/F6JBZH4tKA3DOmqwkmxNWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zXFBC/btsOG5xWCT8/F6JBZH4tKA3DOmqwkmxNWK/img.png&quot; data-alt=&quot;chat gpt 가 만들어준 다이어그램&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zXFBC/btsOG5xWCT8/F6JBZH4tKA3DOmqwkmxNWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzXFBC%2FbtsOG5xWCT8%2FF6JBZH4tKA3DOmqwkmxNWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;chat gpt 가 만들어준 다이어그램&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 생태계를 이해하기 위해 핵심 구성 요소들의 관계를 알아보겠습니다:&lt;/p&gt;
&lt;table style=&quot;height: 114px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;th style=&quot;height: 38px;&quot;&gt;구분&lt;/th&gt;
&lt;th style=&quot;height: 38px;&quot;&gt;JDK&lt;br /&gt;(Java Development Kit)&lt;/th&gt;
&lt;th style=&quot;height: 38px;&quot;&gt;JRE&lt;br /&gt;(Java Runtime Environment)&lt;/th&gt;
&lt;th style=&quot;height: 38px;&quot;&gt;JVM&lt;br /&gt;(Java Virtual Machine)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;목적&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Java 개발 도구 제공&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Java 실행 환경 제공&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Java 바이트 코드 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;구성 요소&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;JRE + 컴파일러(javac), 디버거 등&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;JVM + 라이브러리(API 클래스)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;클래스 로더, 실행 엔진, 메모리 영역&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;포함 관계&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;JDK &amp;sup; JRE &amp;sup; JVM&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;JRE &amp;sup; JVM&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;예시&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Java 코드 컴파일 및 실행&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;.jar 파일 실행&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;바이트코드를 기계어로 변환하여 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;간단 정리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JVM&lt;/b&gt;: Java 프로그램이 돌아가는 가상 컴퓨터 (핵심 엔진)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JRE&lt;/b&gt;: JVM + 자바 프로그램 실행에 필요한 기본 라이브러리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JDK&lt;/b&gt;: JRE + 개발도구 (개발자용 풀 패키지)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Java 프로그램 실행 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 프로그램이 어떻게 실행되는지 단계별로 살펴보겠습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;소스 코드 작성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Main.java&lt;/code&gt; 파일 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴파일 과정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일러가 Java 소스 코드를 클래스 파일로 생성&lt;/li&gt;
&lt;li&gt;컴파일된 클래스 파일은 JVM이 인식 가능한 바이트 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행 과정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM이 클래스 파일의 바이트 코드를 해석&lt;/li&gt;
&lt;li&gt;바이너리 코드로 변환하여 프로그램 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;백문이 불여일타 - 실제 확인해보기&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Java 소스 코드 작성 (Hello 클래스, main 메서드, System.out.println)&lt;/li&gt;
&lt;li&gt;소스 코드 파일을 컴파일 &amp;rarr; &lt;code&gt;javac&lt;/code&gt; 명령어 사용&lt;/li&gt;
&lt;li&gt;생성된 클래스 파일을 JVM으로 실행 &amp;rarr; &lt;code&gt;java&lt;/code&gt; 명령어 사용&lt;/li&gt;
&lt;li&gt;클래스 파일을 바이너리 코드로 변환 &amp;rarr; 프로그램 수행&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/raTk8/btsOFMfaXOW/bcZSkQzOZJQulk2aMOJtOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/raTk8/btsOFMfaXOW/bcZSkQzOZJQulk2aMOJtOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/raTk8/btsOFMfaXOW/bcZSkQzOZJQulk2aMOJtOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FraTk8%2FbtsOFMfaXOW%2FbcZSkQzOZJQulk2aMOJtOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1616&quot; height=&quot;764&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 역사를 통해 우리는 프로그래밍 언어가 어떻게 '기계 중심'에서 '사람 중심'으로 발전해왔는지 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기계어의 0과 1에서 시작해 어셈블리어, C언어, C++을 거쳐 Java에 이르기까지, 각 단계마다 프로그래머의 편의성과 안전성을 높이려는 노력이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Java는 &lt;b&gt;&quot;Write Once, Run Anywhere&quot;&lt;/b&gt;라는 철학으로 플랫폼 독립성을 실현했고, 포인터 제거를 통해 메모리 안전성을 확보했습니다. 이러한 특징들이 Java를 '사람을 사랑한 기술'로 만든 핵심 요소라고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트에서는 객체 지향 프로그래밍의 핵심 개념들에 대해 더 자세히 다뤄보겠습니다. 기대해 주세요!  &lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;참고 자료: 스프링 입문을 위한 자바 객체 지향의 원리와 이해 (김종민 저)&lt;/i&gt;&lt;/p&gt;</description>
      <category>CS/Java</category>
      <author>junju404</author>
      <guid isPermaLink="true">https://junju404.tistory.com/25</guid>
      <comments>https://junju404.tistory.com/25#entry25comment</comments>
      <pubDate>Wed, 18 Jun 2025 17:06:46 +0900</pubDate>
    </item>
    <item>
      <title>[Git] git pull 브랜치 충돌 해결 방법</title>
      <link>https://junju404.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;팀프로젝트를 진행하다보면 git 에서 여러 에러를 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;내가 이번에 만난 에러는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상황을 조금 구체적으로 설명하자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;default 브랜치인 &quot;develop&quot;과 각자 기능을 담당하는 feature/n 브랜치가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;나는 feature/8 를 사용 중이었고, commit을 모두 마친 후 PR을 요청했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서 그동안 팀원들이 바꾼 develop 브랜치의 코드를 업데이트 하기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;git pull origin develop을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그런데&lt;/p&gt;
&lt;pre id=&quot;code_1707778701236&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST git-upload-pack (323 bytes)

POST git-upload-pack (943 bytes)

POST git-upload-pack (gzip 2543 to 1322 bytes) remote: Total 228 (delta 84),

reused 217 (delta 76),

pack-reused 0 From https://github.com/Homeat/Server * branch develop -&amp;gt; FETCH_HEAD 1fe1f65..70c7b21 develop -&amp;gt; origin/develop

hint: You have divergent branches and need to specify how to reconcile them.

hint: You can do so by running one of the following commands sometime before

hint: your next pull: hint: hint: git config pull.rebase false # merge

hint: git config pull.rebase true # rebase

hint: git config pull.ff only # fast-forward only hint:

hint: You can replace &quot;git config&quot; with &quot;git config --global&quot; to set a default hint: preference for all repositories.

You can also pass --rebase, --no-rebase, hint: or --ff-only on the command line to override the configured default per hint: invocation. Need to specify how to reconcile divergent branches.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;어후.. 끔찍하다.. 이게 무슨 말인지 이해는 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사실 이전에 git 충돌 때문에 이 문제를 포기했던 적이 있다.. 같이 공부하던 친구도 pull 을 하려는 브랜치에 굴복(?)당하는 건 싫다고 해서 어째저째 하다가 어영부영 넘어갔던 거 같다.. 그 땐 fetch 진행 후, merge를 rebase로 진행했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 때는 git에 대해 조금 더 공부하려고 했으나 프로젝트 진행이 급하기 때문에 잠시 보류해두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;근데, 이제 어느 정도 프로젝트 구현은 마무리가 되어가고 깃에 대한 기록을 남기기 위해 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: GungSeo, serif;&quot;&gt;&lt;b&gt;위 문제 원인은 로컬 브랜치와 remote 원격 브랜치 사이에 변경 사항 충돌 때문에 발생한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서 이 문제를 해결하기 위한 방법은 3가지가 있다고 친절히 힌트를 주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. git config pull.rebase false&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. git config pull.rebase true&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. git config pull.ff only&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선, 위 3가지 방법을 알아보기 전에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;git pull에 대해서 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;git pull (origin ~~~)은 두 단계를 포함하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- git fetch (origin ~~~) 리모트 저장소(origin ~~~)에서 변경사항을 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- git merge (origin ~~~) 변경 사항을 현재 작업 중인 브랜치에 병합한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼 이제, 위의 3가지 방법을 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;git config pull.rebase false&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 옵션은 git pull이 fetch 이후 merge를 실행하도록 설정한다. 즉, git pull을 실행하면 원격(remote) 브랜치의 변경 사항을 가져와서 현재(local) 브랜치에 병합한다.&lt;br /&gt;&lt;br /&gt;병합(merge)는&amp;nbsp;두&amp;nbsp;브랜치의&amp;nbsp;변경&amp;nbsp;사항을&amp;nbsp;모두&amp;nbsp;포함하는&amp;nbsp;새로운&amp;nbsp;커밋을&amp;nbsp;생성한다.&amp;nbsp;이&amp;nbsp;새로운&amp;nbsp;커밋에는&amp;nbsp;병합된&amp;nbsp;두&amp;nbsp;브랜치의&amp;nbsp;커밋이&amp;nbsp;부모로&amp;nbsp;설정된다.&amp;nbsp;이렇게&amp;nbsp;하면&amp;nbsp;두&amp;nbsp;브랜치에서&amp;nbsp;독립적으로&amp;nbsp;이루어진&amp;nbsp;작업을&amp;nbsp;모두&amp;nbsp;유지하면서&amp;nbsp;한&amp;nbsp;브랜치로&amp;nbsp;합칠&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;하지만,&amp;nbsp;이&amp;nbsp;방식은&amp;nbsp;여러&amp;nbsp;사람이&amp;nbsp;동시에&amp;nbsp;작업을&amp;nbsp;진행할&amp;nbsp;때&amp;nbsp;복잡한&amp;nbsp;커밋&amp;nbsp;히스토리를&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;그&amp;nbsp;이유는&amp;nbsp;병합&amp;nbsp;과정에서&amp;nbsp;새로운&amp;nbsp;커밋이&amp;nbsp;생성되기&amp;nbsp;때문이다.&amp;nbsp;또한,&amp;nbsp;병합&amp;nbsp;과정에서&amp;nbsp;충돌이&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있으며,&amp;nbsp;이&amp;nbsp;충돌은&amp;nbsp;수동으로&amp;nbsp;해결해야&amp;nbsp;한다.&lt;br /&gt;&lt;br /&gt;따라서,&amp;nbsp;병합은&amp;nbsp;작업&amp;nbsp;내용을&amp;nbsp;명확하게&amp;nbsp;구분하고&amp;nbsp;싶을&amp;nbsp;때&amp;nbsp;유용하다.&amp;nbsp;하지만,&amp;nbsp;커밋&amp;nbsp;히스토리를&amp;nbsp;간결하게&amp;nbsp;유지하고&amp;nbsp;싶다면,&amp;nbsp;rebase&amp;nbsp;옵션을&amp;nbsp;고려해&amp;nbsp;보는&amp;nbsp;것이&amp;nbsp;좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1707779161056&quot; class=&quot;prolog&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;친구와 함께 팀 프로젝트를 하고 있다고 생각해보세요. 
당신과 친구는 각각 자신의 부분을 작성하고, 이후에 모두 합쳐서 최종 보고서를 만들어야 합니다. 
이 때, 각자가 작성한 부분을 그대로 모아서 하나의 보고서를 만드는 것이 'merge' 방식입니다. 
각자 작성한 부분이 그대로 보존되지만, 적절한 순서나 흐름을 만들기 위해 추가적인 편집이 필요할 수 있습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;30cb94d6-ae8b-4211-a5bb-6642b82a7921.svg&quot; data-origin-width=&quot;72&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u3YIi/btsEFTBsry3/zQ9GXUF4dg3wtbKQzu9Qp0/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u3YIi/btsEFTBsry3/zQ9GXUF4dg3wtbKQzu9Qp0/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u3YIi/btsEFTBsry3/zQ9GXUF4dg3wtbKQzu9Qp0/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu3YIi%2FbtsEFTBsry3%2FzQ9GXUF4dg3wtbKQzu9Qp0%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;72&quot; height=&quot;150&quot; data-filename=&quot;30cb94d6-ae8b-4211-a5bb-6642b82a7921.svg&quot; data-origin-width=&quot;72&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;git config pull.rebase true&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 옵션은 fetch 이후 'rebase'를 실행하도록 git pull 옵션을 설정한다. 따라서, git pull을 실행하면 local(현재) 브랜치의 변경 사항이 임시적으로 &quot;저장&quot;되고, remote(원격) 브랜치의 최신 변경 사항으로 local 브랜치가 업데이트 된다. 그 다음, 앞서 &quot;저장&quot;했던 local 브랜치의 변경 사항이 다시 적용된다.&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;과정을&amp;nbsp;통해,&amp;nbsp;여러&amp;nbsp;사람이&amp;nbsp;동시에&amp;nbsp;작업을&amp;nbsp;진행하더라도&amp;nbsp;각자의&amp;nbsp;작업이&amp;nbsp;순차적으로&amp;nbsp;이루어진&amp;nbsp;것처럼&amp;nbsp;보이게&amp;nbsp;된다.&amp;nbsp;이는&amp;nbsp;커밋&amp;nbsp;히스토리를&amp;nbsp;깔끔하게&amp;nbsp;유지할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;해주며,&amp;nbsp;코드&amp;nbsp;변경&amp;nbsp;사항&amp;nbsp;추적에&amp;nbsp;있어서&amp;nbsp;혼란을&amp;nbsp;줄여&amp;nbsp;준다.&lt;br /&gt;&lt;br /&gt;하지만&amp;nbsp;주의할&amp;nbsp;점이&amp;nbsp;있다.&amp;nbsp;rebase는&amp;nbsp;기본적으로&amp;nbsp;기존의&amp;nbsp;커밋&amp;nbsp;히스토리를&amp;nbsp;변경한다.&amp;nbsp;따라서,&amp;nbsp;이미&amp;nbsp;원격&amp;nbsp;저장소에&amp;nbsp;push한&amp;nbsp;커밋을&amp;nbsp;rebase를&amp;nbsp;통해&amp;nbsp;변경하는&amp;nbsp;것은&amp;nbsp;다른&amp;nbsp;사람들과의&amp;nbsp;작업에&amp;nbsp;혼란을&amp;nbsp;줄&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;그러므로,&amp;nbsp;원격&amp;nbsp;저장소에&amp;nbsp;push한&amp;nbsp;커밋은&amp;nbsp;가능한&amp;nbsp;변경하지&amp;nbsp;않는&amp;nbsp;것이&amp;nbsp;좋다.&lt;br /&gt;&lt;br /&gt;또한,&amp;nbsp;rebase를&amp;nbsp;사용하면&amp;nbsp;병합된&amp;nbsp;커밋이&amp;nbsp;생성되지&amp;nbsp;않는다.&amp;nbsp;이는&amp;nbsp;각&amp;nbsp;브랜치에서의&amp;nbsp;작업이&amp;nbsp;병합으로&amp;nbsp;인해&amp;nbsp;'섞이지'&amp;nbsp;않게&amp;nbsp;되므로,&amp;nbsp;각&amp;nbsp;브랜치에서&amp;nbsp;어떤&amp;nbsp;작업이&amp;nbsp;이루어졌는지&amp;nbsp;더&amp;nbsp;명확하게&amp;nbsp;파악할&amp;nbsp;수&amp;nbsp;있다는&amp;nbsp;장점이&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1707779171061&quot; class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;같은 팀 프로젝트 상황에서, 이번에는 당신이 친구의 부분을 먼저 읽고, 그 내용에 맞춰서 자신의 부분을 이어서 작성한다고 생각해보세요. 
이렇게 하면, 친구의 부분이 바뀌더라도 그 변경 사항을 빠르게 반영하고, 자신의 부분을 순차적으로 이어서 작성할 수 있습니다. 
이것이 'rebase' 방식입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;30cb94d6-ae8b-4211-a5bb-6642b82a7921 (1).svg&quot; data-origin-width=&quot;79&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kUooH/btsEEZoRdkC/6hLHwm7yjMTiGLSfRPGOEk/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kUooH/btsEEZoRdkC/6hLHwm7yjMTiGLSfRPGOEk/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kUooH/btsEEZoRdkC/6hLHwm7yjMTiGLSfRPGOEk/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkUooH%2FbtsEEZoRdkC%2F6hLHwm7yjMTiGLSfRPGOEk%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;79&quot; height=&quot;150&quot; data-filename=&quot;30cb94d6-ae8b-4211-a5bb-6642b82a7921 (1).svg&quot; data-origin-width=&quot;79&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;git config pull.ff only&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이&amp;nbsp;옵션은&amp;nbsp;git&amp;nbsp;pull이&amp;nbsp;Fast-forward&amp;nbsp;방식만을&amp;nbsp;사용하도록&amp;nbsp;설정한다.&amp;nbsp;Fast-forward는&amp;nbsp;현재(local)&amp;nbsp;브랜치가&amp;nbsp;가리키는&amp;nbsp;커밋이&amp;nbsp;원격(remote)&amp;nbsp;브랜치가&amp;nbsp;가리키는&amp;nbsp;커밋의&amp;nbsp;직접적인&amp;nbsp;선행&amp;nbsp;커밋일&amp;nbsp;때만&amp;nbsp;적용&amp;nbsp;가능한&amp;nbsp;방식이다.&lt;br /&gt;&lt;br /&gt;Fast-forward&amp;nbsp;방식을&amp;nbsp;사용하면,&amp;nbsp;현재&amp;nbsp;브랜치의&amp;nbsp;포인터만&amp;nbsp;원격&amp;nbsp;브랜치가&amp;nbsp;가리키는&amp;nbsp;커밋으로&amp;nbsp;이동시키고,&amp;nbsp;별도의&amp;nbsp;병합&amp;nbsp;커밋을&amp;nbsp;생성하지&amp;nbsp;않는다.&amp;nbsp;이렇게&amp;nbsp;하면&amp;nbsp;커밋&amp;nbsp;히스토리가&amp;nbsp;선형적으로&amp;nbsp;유지되고,&amp;nbsp;병합으로&amp;nbsp;인한&amp;nbsp;혼란을&amp;nbsp;방지할&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;하지만,&amp;nbsp;현재&amp;nbsp;브랜치에서&amp;nbsp;새로운&amp;nbsp;커밋이&amp;nbsp;추가되었거나,&amp;nbsp;원격&amp;nbsp;브랜치의&amp;nbsp;커밋이&amp;nbsp;현재&amp;nbsp;브랜치의&amp;nbsp;선행&amp;nbsp;커밋이&amp;nbsp;아닌&amp;nbsp;경우에는&amp;nbsp;Fast-forward가&amp;nbsp;불가능하다.&amp;nbsp;이런&amp;nbsp;경우에는&amp;nbsp;병합(merge)나&amp;nbsp;리베이스(rebase)를&amp;nbsp;사용해야&amp;nbsp;한다.&lt;br /&gt;&lt;br /&gt;따라서,&amp;nbsp;'git&amp;nbsp;config&amp;nbsp;pull.ff&amp;nbsp;only'&amp;nbsp;옵션은&amp;nbsp;협업&amp;nbsp;환경에서&amp;nbsp;커밋&amp;nbsp;히스토리를&amp;nbsp;깔끔하게&amp;nbsp;유지하고&amp;nbsp;싶을&amp;nbsp;때&amp;nbsp;유용하다.&amp;nbsp;하지만,&amp;nbsp;이&amp;nbsp;옵션을&amp;nbsp;사용할&amp;nbsp;때는&amp;nbsp;현재&amp;nbsp;브랜치의&amp;nbsp;변경&amp;nbsp;사항을&amp;nbsp;자주&amp;nbsp;원격&amp;nbsp;저장소에&amp;nbsp;푸시하거나,&amp;nbsp;원격&amp;nbsp;브랜치의&amp;nbsp;변경&amp;nbsp;사항을&amp;nbsp;자주&amp;nbsp;가져와야&amp;nbsp;한다는&amp;nbsp;점을&amp;nbsp;유의해야&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 설명만으로는 이해가 되지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1707779126523&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;이번에는 당신이 친구의 부분을 기다리지 않고 먼저 보고서를 작성했다고 생각해봅시다. 
그리고 친구가 자신의 부분을 완성하면, 그 부분을 당신이 이미 작성한 부분 뒤에 바로 이어붙입니다. 
이 경우, 당신이 작성한 부분을 다시 수정하거나 조정할 필요 없이, 친구의 부분을 그대로 추가할 수 있습니다. 
이것이 'fast-forward' 방식입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;30cb94d6-ae8b-4211-a5bb-6642b82a7921 (2).svg&quot; data-origin-width=&quot;151&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dK19V6/btsEGgQIuql/nkEhmwzmRfYrnffRRalkqK/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dK19V6/btsEGgQIuql/nkEhmwzmRfYrnffRRalkqK/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dK19V6/btsEGgQIuql/nkEhmwzmRfYrnffRRalkqK/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdK19V6%2FbtsEGgQIuql%2FnkEhmwzmRfYrnffRRalkqK%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;151&quot; height=&quot;150&quot; data-filename=&quot;30cb94d6-ae8b-4211-a5bb-6642b82a7921 (2).svg&quot; data-origin-width=&quot;151&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 방법으로 git 충돌 문제를 해결할 수 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;나는&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1707779870204&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git fetch origin
git merge origin/develop&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 문제를 해결했다. 그래서 리모트 브랜치와 내 작업 브랜치를 합쳐서 변경 사항을 수동으로 작업한 후에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR을 해보니 충돌이 해결됐다.&lt;/p&gt;</description>
      <category>ProJect/Git</category>
      <category>branch</category>
      <category>GIT</category>
      <category>git pull</category>
      <category>Github</category>
      <category>Merge</category>
      <category>rebase</category>
      <category>remote</category>
      <category>충돌</category>
      <category>팀프로젝트</category>
      <author>junju404</author>
      <guid isPermaLink="true">https://junju404.tistory.com/21</guid>
      <comments>https://junju404.tistory.com/21#entry21comment</comments>
      <pubDate>Tue, 13 Feb 2024 08:19:10 +0900</pubDate>
    </item>
  </channel>
</rss>