<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>끄적끄적 기록</title>
        <link>https://velog.io/</link>
        <description>하고싶은게 많은 개발자가 되고싶은</description>
        <lastBuildDate>Fri, 06 Jun 2025 05:51:51 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>끄적끄적 기록</title>
            <url>https://velog.velcdn.com/images/stay_o2o/profile/63546985-3cf6-49da-9b74-b7114de0eee2/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 끄적끄적 기록. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/stay_o2o" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[인덱스로 쿼리 속도 개선하기!]]></title>
            <link>https://velog.io/@stay_o2o/%EC%9D%B8%EB%8D%B1%EC%8A%A4%EB%A1%9C-%EC%BF%BC%EB%A6%AC-%EC%86%8D%EB%8F%84-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@stay_o2o/%EC%9D%B8%EB%8D%B1%EC%8A%A4%EB%A1%9C-%EC%BF%BC%EB%A6%AC-%EC%86%8D%EB%8F%84-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 06 Jun 2025 05:51:51 GMT</pubDate>
            <description><![CDATA[<p>지난번에 겪었던 카드사 고객사의 성능 개선 삽질기를 풀어볼까 해요. 비슷한 경험 있으실 거예요... 분명 예상했던 곳이 아닌 다른 곳에서 병목이 터져버리는... 😩</p>
<p>저도 이번에 애 좀 먹었습니다. <strong>&quot;상담 이력 다운로드&quot;</strong> 때문에 성능 문제가 터질 거라고 예상했는데... 로그인에서부터 삐걱거릴 줄이야!</p>
<hr>
<h2 id="로그인부터-버벅인다고-ft-gatling">로그인부터 버벅인다고? (Ft. Gatling)</h2>
<p>저는 원래 <strong>상담 이력 다운로드</strong> 기능에서 병목이 생길 거라고 확신했어요. 워낙 상담량이 많은 사이트라, 이력을 한 번에 쭉 내려받는 과정에서 부하가 상당할 거라고 봤죠. 그래서 게틀링(Gatling)이라는 녀석을 사용해서 시나리오를 짜봤습니다.</p>
<h3 id="게틀링gatling-테스트-시나리오">게틀링(Gatling) 테스트 시나리오</h3>
<ol>
<li><strong>약 100명 정도의 상담사 동시 로그인</strong></li>
<li><strong>상담 이력 다운로드 요청</strong></li>
<li><strong>동시에 500건의 상담 처리</strong></li>
</ol>
<p>야심 차게 테스트를 돌렸습니다!!
<em>&quot;자, 이제 다운로드 부분에서 그래프가 솟구치겠구만!&quot;</em> 하고 모니터를 뚫어져라 보고 있었는데...</p>
<hr>
<h2 id="다운로드-전부터-부하가-발생한다-왜">&quot;다운로드 전부터 부하가 발생한다...? 왜?&quot;</h2>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/c64722a4-84a5-453d-a45a-ab2cd1aea556/image.png" alt=""></p>
<p>Zabbix 모니터링 그래프를 보니 초반부터 심상치 않은 조짐이 보이기 시작하는 거예요. 상담사 <strong>로그인 부분에서부터 부하가 발생</strong>하기 시작하더라고요.</p>
<p>&quot;에이, 설마 다운로드랑 같이 돌려서 그런 거 아냐?&quot;</p>
<p>저도 처음엔 그렇게 생각했죠. 근데 아니었습니다! 혹시나 해서 <strong>다운로드 요청을 빼고 로그인만 돌려봤는데도 여전히 부하가 발생</strong>하더라고요. 🤦‍♂️ (이때부터 등골이 오싹해지기 시작...)
<img src="https://velog.velcdn.com/images/stay_o2o/post/b41e2a2f-f54f-4e26-9a03-e0fd4b265504/image.png" alt=""></p>
<p>어디서부터 잘못된 걸까... 쿼리 로그를 뒤지기 시작했습니다.</p>
<hr>
<h2 id="너였구나-범인이">너였구나, 범인이!</h2>
<p>저희 솔루션에서는 time over 되는 로그를 남기고 있었기 때문에 오래걸리는 쿼리를 쉽게 분석할 수 있었습니다.
확인 해보니 상담사가 로그인할 때마다 부하가 발생했고, 그중에서도 <strong>상담사의 배분 상태를 로깅하는 부분</strong>이 가장 의심스러웠습니다. 쿼리 실행 시간이 유독 길었거든요.</p>
<p>문제의 테이블은 <code>상담사 배분 상태</code> 테이블이었어요. 상담사의 배분 상태를 기록하는 테이블인데, 구조는 다음과 같습니다.</p>
<ul>
<li><code>log_id</code> - PK</li>
<li><code>log_seq</code></li>
<li><code>account_id</code></li>
<li><code>service_type</code></li>
<li><code>work_status</code></li>
<li><code>created_by</code></li>
<li><code>created_date</code></li>
</ul>
<p>이 테이블에는 <strong>PK인 <code>log_id</code>만 인덱스</strong>가 걸려있었습니다.</p>
<p>문제의 쿼리는 바로 이 녀석이었습니다.</p>
<pre><code class="language-sql">SELECT COALESCE(MAX(log_seq),-1) + 1 FROM account_work_status WHERE account_id = #accountId:CHAR#</code></pre>
<p>상담사가 로그인할 때마다 실행되는 쿼리인데, <strong>인덱싱되어 있지 않은 <code>account_id</code>를 <code>WHERE</code> 조건으로 사용해서 테이블 풀 스캔(Full Scan)이 발생</strong>하고 있었던 거죠. 상담사 수가 많아지고 로그 데이터가 쌓일수록 이 쿼리는 점점 더 느려질 수밖에 없는 구조였던 겁니다.</p>
<p>해결책은 간단했습니다. <strong><code>log_seq</code>와 <code>account_id</code>를 묶어서 복합 인덱스를 추가</strong>해줬습니다.👌</p>
<p>튜닝 후 쿼리 실행 계획을 볼까요?
<img src="https://velog.velcdn.com/images/stay_o2o/post/1459ac3c-d13e-4e53-8563-d751cea0b7b3/image.png" alt=""></p>
<p>보이시나요? <code>log_id</code>만 바라보던 녀석이 이제 <code>account_id</code>를 통해 빠르게 데이터를 찾아낼 수 있게 되었습니다! 이 작은 변화가 로그인 시 발생하는 부하를 크게 줄여주었죠.</p>
<hr>
<h2 id="사용자-경험-기반의-성능-개선-1년-치-상담-목록이-왜-필요해">사용자 경험 기반의 성능 개선: &quot;1년 치 상담 목록이 왜 필요해?&quot;</h2>
<p>로그인 시 발생하는 부하를 줄이는 또 다른 방법은 바로 <strong>사용자 경험 개선</strong>에 있었어요. 상담사가 로그인하면 첫 페이지에 상담 목록이 뜨는데, 이게 <strong>최근 1년 치 데이터를 한 번에 불러오고 있었던 거예요.</strong></p>
<p>상담 목록은 10개씩 페이징 되어있으니, 솔직히 상담사가 로그인하자마자 1년 치 데이터를 전부 볼 필요가 있을까요? 없습니다!😵</p>
<p>그래서 과감하게 판단했습니다. <strong>첫 페이지에 보여주는 상담 목록을 &quot;최근 한 달&quot;로 변경</strong>하는 거죠. 이렇게 하니 로그인 시 불러오는 데이터 양 자체가 확 줄어들면서 서버 부하도 자연스럽게 감소했습니다. 사용자 경험을 고려한 성능 개선이라고 할 수 있겠죠!</p>
<hr>
<p>이번 시스템 성능 개선은 저에게 큰 교훈을 주었습니다. 막연히 예상했던 곳이 아닌, 의외의 지점에서 병목이 발생할 수 있다는 것, 그리고 단순한 인덱스 추가나 데이터 조회 범위 조정만으로도 큰 성능 개선 효과를 볼 수 있다는 것을 다시 한번 깨달았네요.</p>
<p>혹시 여러분들도 시스템이 느리다고 느껴지는데 어디서부터 손대야 할지 모르겠다면, 이번 제 삽질기가 조금이나마 도움이 되었으면 좋겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[버튼 클릭 이벤트를 모아서 로깅하기]]></title>
            <link>https://velog.io/@stay_o2o/%EB%B2%84%ED%8A%BC-%ED%81%B4%EB%A6%AD-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A5%BC-%EB%AA%A8%EC%95%84%EC%84%9C-%EB%A1%9C%EA%B9%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@stay_o2o/%EB%B2%84%ED%8A%BC-%ED%81%B4%EB%A6%AD-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A5%BC-%EB%AA%A8%EC%95%84%EC%84%9C-%EB%A1%9C%EA%B9%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 27 Apr 2025 12:42:30 GMT</pubDate>
            <description><![CDATA[<h1 id="1-요구사항">1. 요구사항</h1>
<p>특정 feature가 추가되면서 <strong>사용성</strong>이 얼마나 되는지를 알기 위해 버튼 클릭 이벤트를 로깅 해야 했습니다.</p>
<p>하지만 클릭할 때마다 서버로 요청을 보내는 방식은 서버 부하가 크고, 네트워크 문제로 실패할 위험도 있었습니다.<br>그래서 <strong>클라이언트에 로그를 모아두었다가</strong>, 일정량 이상 쌓이면 서버로 전송하는 방식을 고민하게 되었습니다.</p>
<ul>
<li>프론트와 백엔드 스펙<ul>
<li>백엔드 : Spring + logback, log4j</li>
<li>프론트 : javascript(ES6불가), juqery </li>
</ul>
</li>
</ul>
<hr>
<h1 id="2-해결-방법">2. 해결 방법</h1>
<p>여러 가지 방법을 검토했습니다.</p>
<ul>
<li><strong>localStorage</strong>: 동기 처리라서 UI에 영향을 줄 수 있고, 용량도 적었습니다.</li>
<li><strong>sessionStorage</strong>: 브라우저를 닫으면 데이터가 사라집니다.</li>
<li><strong>쿠키</strong>: 매 요청마다 서버로 보내져서 오히려 트래픽만 증가시킬 수 있습니다.</li>
<li><strong>IndexedDB</strong>: 비동기로 동작하고, 꽤 큰 용량을 저장할 수 있으며 브라우저를 닫아도 유지됩니다.</li>
</ul>
<p>결론적으로, <strong>IndexedDB</strong>를 선택했습니다.<br><strong>비동기 처리</strong>, <strong>데이터 유지성</strong>, <strong>넉넉한 저장 용량</strong>이라는 점이 결정적인 이유였습니다.</p>
<p>또한, 로그를 남기는 목적은 <strong>개발자가 디버깅하는 용도</strong>가 아니라,<br><strong>사용자의 행동을 분석하고 제품 개선에 활용</strong>하려는 목적이었습니다.</p>
<hr>
<h1 id="3-indexeddb로-로깅">3. IndexedDB로 로깅</h1>
<h2 id="31-indexeddb-초기화">3.1 IndexedDB 초기화</h2>
<pre><code class="language-javascript">var DB_NAME = &#39;AILoggingDB&#39;;
var STORE_NAME = &#39;logs&#39;;
var DB_VERSION = 1;

var AILogger = function() {
    var self = this;
    self.db = null;

    self.initDB = function() {
        var request = indexedDB.open(DB_NAME, DB_VERSION);

        request.onerror = function(event) {
            console.error(&#39;Database error:&#39;, event.target.error);
        };

        request.onupgradeneeded = function(event) {
            var db = event.target.result;
            if (!db.objectStoreNames.contains(STORE_NAME)) {
                db.createObjectStore(STORE_NAME, { keyPath: &#39;timestamp&#39; });
            }
        };

        request.onsuccess = function(event) {
            self.db = event.target.result;
        };
    };

    self.initDB();
};</code></pre>
<h2 id="32-로그를-저장하고-일정량-이상이면-전송">3.2 로그를 저장하고 일정량 이상이면 전송</h2>
<pre><code class="language-javascript">var BATCH_SIZE = 100;

AILogger.prototype.log = function(data) {
    var self = this;
    return new Promise(function(resolve) {
        if (!self.db) {
            resolve();
            return;
        }

        var transaction = self.db.transaction([STORE_NAME], &#39;readwrite&#39;);
        var store = transaction.objectStore(STORE_NAME);

        var logEntry = {
            timestamp: new Date().getTime(),
            data: data
        };

        var addRequest = store.add(logEntry);

        addRequest.onsuccess = function() {
            self.getLogCount().then(function(count) {
                if (count &gt;= BATCH_SIZE) {
                    self.sendLogsToServer().then(resolve);
                } else {
                    resolve();
                }
            });
        };

        addRequest.onerror = function() {
            console.error(&#39;Error logging data&#39;);
            resolve();
        };
    });
};

AILogger.prototype.getLogCount = function() {
    var self = this;
    return new Promise(function(resolve) {
        var transaction = self.db.transaction([STORE_NAME], &#39;readonly&#39;);
        var store = transaction.objectStore(STORE_NAME);
        var countRequest = store.count();
        countRequest.onsuccess = function() {
            resolve(countRequest.result);
        };
    });
};

AILogger.prototype.sendLogsToServer = function() {
    var self = this;
    return new Promise(function(resolve) {
        var transaction = self.db.transaction([STORE_NAME], &#39;readonly&#39;);
        var store = transaction.objectStore(STORE_NAME);
        var getAllRequest = store.getAll();

        getAllRequest.onsuccess = function() {
            var logs = getAllRequest.result;
            if (logs.length === 0) {
                resolve();
                return;
            }
            // 서버로 전송 (예: axios 사용)
            axios.post(&#39;/api/ailogs&#39;, logs).then(function() {
                // 전송 성공 후 로그 삭제
                self.clearLogs().then(resolve);
            }).catch(function() {
                console.error(&#39;Error sending logs&#39;);
                resolve();
            });
        };
    });
};

AILogger.prototype.clearLogs = function() {
    var self = this;
    return new Promise(function(resolve) {
        var transaction = self.db.transaction([STORE_NAME], &#39;readwrite&#39;);
        var store = transaction.objectStore(STORE_NAME);
        var clearRequest = store.clear();
        clearRequest.onsuccess = function() {
            resolve();
        };
    });
};</code></pre>
<h1 id="4-마무리">4. 마무리</h1>
<p>로그를 개발자가 직접 보는 것이 아니라,
<strong>사용자의 행동 흐름을 간단히 파악하는 목적</strong>이라면 이 정도 구조가 충분히 깔끔하고 가벼웠습니다.</p>
<p><strong>IndexedDB</strong>를 사용해서 브라우저 안에 데이터를 쌓고,
부하 없이 서버로 천천히 모아서 보내는 방식은 꽤 만족스러웠습니다.</p>
<p>다음에는 쌓인 로그를 조금 더 쉽게 분석할 수 있는 툴을 붙이거나,
자동으로 CSV로 변환하는 기능도 추가해볼 생각입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[브랜치 정책을 만들어가는 여정 (릴리즈)]]></title>
            <link>https://velog.io/@stay_o2o/%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%A0%95%EC%B1%85%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EA%B0%80%EB%8A%94-%EC%97%AC%EC%A0%95-%EB%A6%B4%EB%A6%AC%EC%A6%88</link>
            <guid>https://velog.io/@stay_o2o/%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%A0%95%EC%B1%85%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EA%B0%80%EB%8A%94-%EC%97%AC%EC%A0%95-%EB%A6%B4%EB%A6%AC%EC%A6%88</guid>
            <pubDate>Sun, 02 Mar 2025 06:02:00 GMT</pubDate>
            <description><![CDATA[<p>이전 글에서는 고객사 사이트 소스에 대한 브랜치 전략을 다뤘다면, 이번에는 <strong>제품 개발</strong>에 대한 브랜치 전략을 이야기해볼까 합니다. 🚀</p>
<h2 id="svn-시절-브랜치-전략-그게-뭔데-먹는-건가요-🤔">SVN 시절: 브랜치 전략? 그게 뭔데 먹는 건가요? 🤔</h2>
<p>SVN을 사용할 때는 <strong>릴리즈라는 개념 자체를 경험하지 못했습니다.</strong></p>
<ul>
<li>버그 패치나 새로운 기능이 추가될 때? 그냥 <strong>바로 commit!</strong> 끝.</li>
<li>SVN에서도 브랜치를 만들 수 있었지만(폴더 복사 방식) 딱히 사용하진 않았어요.</li>
</ul>
<p>그러다가 <strong>GitHub을 도입하고 릴리즈를 처음 경험</strong>하게 되었고, 고객사 사이트를 GitHub에서 관리하면서 <strong>제품 개발에서는 브랜치 전략이 정말 중요하겠구나</strong>를 깨달았습니다.</p>
<hr>
<h2 id="첫-번째-릴리즈-그래도-순탄했던-시작">첫 번째 릴리즈: 그래도 순탄했던 시작</h2>
<p>처음에는 따로 브랜치 전략을 가져가지 않았어요. 기존 고객사 사이트에서 사용하던 방식을 그대로 가져왔습니다.</p>
<ol>
<li><strong>main</strong>과 <strong>develop</strong> 브랜치를 생성</li>
<li>릴리즈를 위해 develop에서 <strong>release 브랜치</strong> 생성</li>
<li>각 <strong>작업(Task)별 브랜치를 release 브랜치에서 파생</strong>시켜 개발 진행</li>
<li>모든 Task가 release 브랜치에 merge되면, <strong>develop 브랜치로 merge</strong><ul>
<li>develop에 <strong>CI/CD가 설정되어 있어서</strong> 테스트를 위해 merge!</li>
</ul>
</li>
<li>테스트 완료 후, <strong>develop → main merge &amp; Tagging &amp; Release!!</strong></li>
</ol>
<p>✅ <strong>첫 번째 릴리즈는 예상보다 순조롭게 끝났습니다.</strong></p>
<hr>
<h2 id="두-번째-릴리즈-고난의-시작">두 번째 릴리즈: 고난의 시작...</h2>
<p>첫 번째 릴리즈 방식이 괜찮아 보여서 동일한 전략을 유지했습니다. </p>
<p>그런데…</p>
<h3 id="첫-번째-문제-develop의-기존-commit-내역이-pr에-포함됨">첫 번째 문제: develop의 기존 commit 내역이 PR에 포함됨</h3>
<p>릴리즈 PR을 생성했는데, <strong>첫 번째 릴리즈 때 develop에 쌓였던 commit 내역들이 모조리 따라온 것…!</strong></p>
<blockquote>
<p>열어보기 무서운 <strong>Files changed</strong>… 🤪</p>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/65cd14c7-fc91-41e5-824d-9b9afae56e6f/image.png" alt=""></p>
</blockquote>
<h3 id="두-번째-문제-merge-conflict-폭탄💥">두 번째 문제: Merge Conflict 폭탄💥</h3>
<p>main 브랜치에서 GitHub Action을 위해 <code>.github</code> 폴더를 직접 수정한 게 화근이었습니다.</p>
<p>develop 브랜치에는 해당 변경 사항이 없었고, 이를 merge하는 과정에서 충돌이 발생…! </p>
<ul>
<li><strong>main 브랜치는 강제 merge가 불가능 했지만, GitHub Action을 위해 잠시 해제하고 작업했던 것 으로 보입니다.</strong></li>
</ul>
<h2 id="해결-방법-🛠️">해결 방법 🛠️</h2>
<h3 id="1️⃣-conflict-해결-main과-develop을-동기화">1️⃣ conflict 해결: main과 develop을 동기화</h3>
<p>merge conflict를 해결하기 위해 <strong>main과 develop 브랜치를 맞춰주는 작업</strong>이 필요했습니다.</p>
<ul>
<li><strong>main에서 새로운 브랜치 생성</strong> <code>fix/conflict</code> → <strong>develop과 merge</strong></li>
<li>이 과정에서 conflict 발생! 하지만 여기서 해결한 후 <strong>develop에 merge</strong></li>
<li>이제 develop이 main과 동기화됨</li>
<li><strong>main으로 다시 merge하면 conflict 없이 정상적으로 반영 가능!</strong></li>
</ul>
<h3 id="2️⃣-새로운-브랜치-전략-수립-git-flow-도입">2️⃣ 새로운 브랜치 전략 수립: Git Flow 도입</h3>
<p>위 문제를 근본적으로 방지하기 위해, <strong>전체적인 제품 개발 릴리즈 브랜치 전략을 재정립</strong>했습니다.</p>
<p>서칭 끝에, <strong>Git Flow 브랜치 전략</strong>이 우리 팀에 적합하다고 판단했어요.</p>
<p>👉 <strong>Git Flow 브랜치 전략을 채택한 이유</strong>:</p>
<ul>
<li><strong>수시 배포가 필요하지 않음</strong> (Release 주기가 명확)</li>
<li><strong>여러 명이 협업하여 릴리즈 진행</strong> (혼자가 아님!)</li>
<li><strong>충분한 테스트 기간이 필요함</strong></li>
</ul>
<p>그래서 제품 개발 온보딩 문서에도 Git Flow 브랜치 정책을 추가했고, 팀원들이 쉽게 따라갈 수 있도록 가이드를 마련했습니다! 🚀</p>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/1bd2ff1d-d32c-4d18-9ab2-5f2477c92d82/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/1a5e59a0-829d-4e20-837c-337f793806d2/image.png" alt=""></p>
<hr>
<h2 id="마무리">마무리</h2>
<p>처음에는 SVN에서 브랜치 전략 없이 개발하다가, GitHub으로 넘어오면서 <strong>릴리즈 브랜치 관리의 중요성</strong>을 깨닫게 되었습니다.</p>
<p>첫 번째 릴리즈는 무난했지만, 두 번째 릴리즈에서 <strong>커밋 내역 누적</strong>과 <strong>Merge Conflict</strong>라는 현실적인 문제를 마주하면서 브랜치 전략의 필요성을 체감했죠.</p>
<p><strong>Git Flow 브랜치 전략 도입을 통해 릴리즈 프로세스를 최적화했고, 다음 릴리즈에서 얼마나 수월하게 진행할 수 있을지 기대가 됩니다!!🎉</strong></p>
<p>다음에는 이번 브랜치 전략을 어떻게 적용했는지 회고할 수 있는 글이 되겠네요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[브랜치 정책을 만들어가는 여정]]></title>
            <link>https://velog.io/@stay_o2o/%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%A0%95%EC%B1%85%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EA%B0%80%EB%8A%94-%EC%97%AC%EC%A0%95</link>
            <guid>https://velog.io/@stay_o2o/%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%A0%95%EC%B1%85%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EA%B0%80%EB%8A%94-%EC%97%AC%EC%A0%95</guid>
            <pubDate>Sun, 03 Nov 2024 12:05:03 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요! 오늘은 저희 팀에서 브랜치 정책을 어떻게 수립했는지에 대해 이야기해보려고 합니다. 최근 SVN에서 GitHub으로 전환하면서 형상 관리에 대한 고민이 많았고, 고객사 사이트 소스 관리를 어떻게 해야 할지 많은 생각을 했어요.</p>
<p>특히 제가 대부분의 운영을 맡고 있었기 때문에, 형상 관리의 중요성을 깊이 느끼고 있습니다. 모든 개발 기록을 잘 남겨야 다음에 동일한 기능이 필요할 때 쉽게 활용할 수 있기 때문입니다!</p>
<h1 id="github을-활용한-브랜치-관리">GitHub을 활용한 브랜치 관리</h1>
<p>저희 팀은 GitHub에서 <strong>main 브랜치</strong>와 <strong>develop 브랜치</strong>를 유지하기로 결정했습니다. 그러나 제품 소스와 고객사 사이트 소스를 따로 관리하다 보니 상황이 복잡해졌습니다.</p>
<ul>
<li>제품 소스</li>
<li>A 고객 사이트 소스</li>
<li>B 고객 사이트 소스</li>
<li>C 고객 사이트 소스
…</li>
</ul>
<p>릴리즈 일정이 아직 멀었고, 고객사의 추가 개발 작업을 진행하고 있었기 때문에 고객사 사이트 소스에 대한 형상 관리 방안을 고민해보았습니다.</p>
<h2 id="고객사-사이트-소스---v10">고객사 사이트 소스 - v1.0</h2>
<p>이전에는 SVN을 사용하여 브랜치를 따로 만들 수 없었는데, GitHub에서는 브랜치를 자유롭게 생성할 수 있어 더 유연한 관리가 가능해졌습니다. 그래서 main과 develop 브랜치를 설정한 후, 고객의 추가 개발 사항을 위한 feature 브랜치를 생성했습니다.</p>
<ul>
<li><strong>main</strong>: 운영에 반영된 최신 소스 브랜치</li>
<li><strong>develop</strong>: 개발이 완료되고 리뷰까지 끝난 후 운영에 반영할 준비가 된 브랜치</li>
<li><strong>feature 브랜치</strong>: 추가 개발 사항을 작업하기 위한 브랜치로, Jira 번호와 작업 이름을 붙여서 생성했습니다.
ex) <code>[TASK-1234]-TASK설명-추가개발</code></li>
<li><strong>epic 브랜치</strong>: 고객사에서 여러 요구사항이 있을 경우, 에픽 단위로 태스크를 만들어 에픽 브랜치를 생성하고 그 하위에 여러 feature 브랜치를 나눔</li>
</ul>
<h2 id="문제점-발생">문제점 발생!</h2>
<p>그런데 몇 가지 문제점이 발생했습니다…
<img src="https://velog.velcdn.com/images/stay_o2o/post/78341673-ec49-46b0-aa99-e7ba01d65942/image.png" alt=""></p>
<figcaption style="text-align:center; font-size:15px; color:#808080; margin-top:0px">
    두 개의 기능이 PR되어 한번에 올라가는 모습<br>
  (commit들을 자세히 남기지 못하는 점 죄송합니다ㅠㅠ)
</figcaption>


<p><strong>문제점 1.</strong> develop에서 feature 브랜치를 생성할 때, _feature → develop → main_으로 PR을 두 번 올려야 했습니다. 반복되는 이 작업이 너무 번거로웠어요.</p>
<p><strong>문제점 2.</strong> develop 브랜치가 계속 남아 있게 되어, 다음 PR을 생성할 때 이전 commit들이 같이 껴들어가게 되었습니다.</p>
<p><strong>문제점 3.</strong> <em>epic → feature</em> 브랜치를 생성했을 때, 여러 개발 사항이 epic 브랜치로 머지되면 개별 feature 브랜치에 대한 개별적인 이력을 파악하기 어려웠습니다. 다른 고객사에서도 동일한 추가 개발 요청이 많았기 때문에, 개발 내역을 잘 기록해두는 것이 중요했습니다.</p>
<h2 id="고객사-사이트-소스---v20">고객사 사이트 소스 - v2.0</h2>
<p>이러한 문제를 해결하기 위해 브랜치 정책을 변경했습니다.
브랜치 전략을 여러개 찾아봤는데, 고객사 사이트 소스에 적합한 브랜치 전략은 <strong>GitHub Flow</strong>라고 생각했어요.
참고 : <a href="https://inpa.tistory.com/entry/GIT-%E2%9A%A1%EF%B8%8F-github-flow-git-flow-%F0%9F%93%88-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%A0%84%EB%9E%B5">[GIT] 📈 깃 브랜치 전략 정리 - Github Flow / Git Flow
</a></p>
<h3 id="develop-브랜치를-없애고-main-→-feature-브랜치만-생성">develop 브랜치를 없애고, <em>main → feature</em> 브랜치만 생성</h3>
<ul>
<li>이를 통해 불필요한 PR을 한 번 더 하지 않아도 되었습니다.<h3 id="개발이-완료된-후-feature-→-main으로-pr을-생성하고-리뷰가-완료되면-feature-브랜치를-삭제">개발이 완료된 후, feature → main으로 PR을 생성하고, 리뷰가 완료되면 feature 브랜치를 삭제</h3>
</li>
<li>이렇게 하면 main 브랜치만 남아 깔끔하게 정리할 수 있고, 모든 개발 이력을 관리할 수 있게 됩니다.</li>
<li>다른 고객사에서도 동일한 기능을 원할 경우 cherry-pick을 통해 쉽게 패치할 수 있습니다.<h3 id="pr-설명과-commit-message를-잘-남기도록-강조했습니다">PR 설명과 commit message를 잘 남기도록 강조했습니다.</h3>
</li>
<li>Jira Task 번호를 commit message에 남겨 이력을 쉽게 확인할 수 있도록 했습니다.</li>
<li>PR 설명을 자세히 기재하여 PR만 봐도 어떤 기능인지, 어떤 의도로 개발했는지 알 수 있도록 했습니다.</li>
</ul>
<hr>
<p>이렇게 브랜치 정책을 변경하게 된 후 main브랜치에는 commit 이력이 많이 남게 되었는데요. 이 방법이 정답은 아니라고 생각합니다. 저는 여러 사이트를 운영하는 입장에서는 이력이 가장 중요하다고 생각하기 때문에 이러한 방법으로 브랜치 정책을 변경하게 되었고 현재 문제 없이 잘 관리하고 있어요!!</p>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/ed2a5a4b-105d-4167-a3b2-fcc4d34f7efd/image.png" alt=""></p>
<figcaption style="text-align:center; font-size:15px; color:#808080; margin-top:0px">
    저희 파트 업무를 도와줄 다른 개발자들을 위해서 온보딩 문서에도 추가했어요.
</figcaption>

<p>Jira에 설명을 적는 것도 중요하지만, 형상 관리에서 commit 내용이 어떤 개발이었고, 어디를 참조하면 되는지를 명확히 하는 것이 필요하다고 생각해요. 이 이력을 참고할 다른 개발자들이 있을테니까요!!</p>
<p>다음 글에서는 제품 소스 형상 관리에 대해 이야기 해 보겠습니다. 감사합니다~~!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Collation - 일본어 정렬]]></title>
            <link>https://velog.io/@stay_o2o/Collation2024.08.13</link>
            <guid>https://velog.io/@stay_o2o/Collation2024.08.13</guid>
            <pubDate>Tue, 13 Aug 2024 05:48:25 GMT</pubDate>
            <description><![CDATA[<h1 id="collation이란">Collation이란?</h1>
<p><strong>Collation</strong>은 문자열 데이터의 정렬 순서와 비교 방법을 정의하는 규칙입니다. 일본어 데이터를 PostgreSQL에서 기본 <code>ORDER BY</code>로 정렬할 때, 현지에서 기대한 순서와 다르게 나오는 문제를 발견하여 이에 대해 조사한 내용을 공유하려고 합니다.</p>
<h2 id="postgresql에서-collation">PostgreSQL에서 Collation</h2>
<h3 id="1-collation-list-조회">1. Collation List 조회</h3>
<p>PostgreSQL에서 사용 가능한 Collation 목록은 다음 쿼리로 확인할 수 있습니다:</p>
<pre><code class="language-sql">SELECT * FROM pg_collation;</code></pre>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/8838c4f3-7404-4d5e-a6bd-f6d2471ab0a3/image.png" alt=""></p>
<h3 id="2-현재-접속한-db의-collation-확인">2. 현재 접속한 DB의 Collation 확인</h3>
<p>현재 사용 중인 데이터베이스의 Collation 정보는 다음과 같이 확인할 수 있습니다:</p>
<pre><code class="language-sql">SELECT datname, datcollate, datctype
FROM pg_database
WHERE datname = current_database();</code></pre>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/1bd6ae2e-6e8f-4157-938a-2b3f8d8f3a40/image.png" alt=""></p>
<h3 id="3-기본-collation-설정">3. 기본 Collation 설정</h3>
<p>DB를 생성할 때 Collation을 지정하지 않으면, 기본적으로 서버의 로케일(locale)에 따라 Collation이 설정됩니다. 서버의 로케일은 아래 명령어로 확인할 수 있습니다:</p>
<pre><code class="language-bash">$ locale</code></pre>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/9a3d30b0-388b-4c5c-8c2e-be2c0c180fcd/image.png" alt=""></p>
<p>현재 서버의 로케일은 <code>ko_KR.UTF-8</code>입니다.</p>
<h3 id="4-이미-생성된-db에-다른-collation-적용-방법">4. 이미 생성된 DB에 다른 Collation 적용 방법</h3>
<p>이미 생성된 데이터베이스에 대해 기본 Collation과 다른 Collation을 적용하는 방법은 두 가지가 있습니다:</p>
<h4 id="41-특정-컬럼의-collation-변경">4.1 특정 컬럼의 Collation 변경</h4>
<p>특정 컬럼의 Collation을 변경하려면 다음과 같이 쿼리를 작성합니다:</p>
<pre><code class="language-sql">ALTER TABLE example_table 
ALTER COLUMN name 
SET COLLATION &quot;ja-JP-x-icu&quot;;</code></pre>
<h4 id="42-조회-시-order-by에-collation-명시">4.2 조회 시 ORDER BY에 Collation 명시</h4>
<p>쿼리에서 ORDER BY에 직접 Collation을 지정하여 정렬할 수 있습니다:</p>
<pre><code class="language-sql">SELECT name 
FROM example_table 
ORDER BY name COLLATE &quot;ja-JP-x-icu&quot;;</code></pre>
<p>위 방법을 통해 일본어 현지에 특화된 Collation(<code>ja-JP-x-icu</code>)으로 데이터를 정렬할 수 있습니다. 일본어의 경우, 기본적으로 숫자 → 영어(소문자) → 영어(대문자) → 히라가나 → 가타카나 → 한자 순으로 정렬됩니다.</p>
<p><strong>ICU(International Components for Unicode)란??</strong>
ICU는 다국어와 지역화를 지원하기 위해 개발된 오픈 소스 소프트웨어 라이브러리입니다. 이 라이브러리는 다양한 운영 체제와 프로그래밍 언어에서 사용할 수 있으며, 특히 다음과 같은 기능을 제공합니다:</p>
<ul>
<li>텍스트 처리 및 유니코드 지원</li>
<li>문자 인코딩 변환</li>
<li>날짜 및 시간 형식 변환</li>
<li>숫자 및 통화 형식 변환</li>
<li>문자열 비교 및 정렬</li>
<li>달력 계산</li>
</ul>
<p>기본 로케일 처리보다 다양한 언어와 지역에 대해 더욱 정확한 정렬 및 비교를 제공할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[모던 자바 인 액션] - 4장 스트림 소개]]></title>
            <link>https://velog.io/@stay_o2o/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-4%EC%9E%A5-%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@stay_o2o/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-4%EC%9E%A5-%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Wed, 08 May 2024 11:13:43 GMT</pubDate>
            <description><![CDATA[<h2 id="스트림이란">스트림이란?</h2>
<ul>
<li><p>JAVA8 API에 새로 추가된 기능.</p>
</li>
<li><p>스트림을 이용하면 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다.</p>
</li>
<li><p>JAVA8의 스트림 API의 특징</p>
<ul>
<li>선언형 : 더 간결하고 가독성이 좋아진다.</li>
<li>조립할 수 있음 : 유연성이 좋아진다.</li>
<li>병렬화 : 성능이 좋아진다.</li>
</ul>
</li>
<li><p>스트림이란?? : 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소.</p>
</li>
<li><p>주요 특징 2가지</p>
<ul>
<li><strong>파이프라이닝</strong> : 스트림 연산끼리 연결해서 커다란 파이프라인을 구성할 수 있도록 스트림 자신을 <strong>반환한다. (5장에서 다시)</strong></li>
<li><strong>내부 반복</strong> : 뒤에서 다시<h2 id="컬렉션과-스트림">컬렉션과 스트림</h2>
</li>
</ul>
</li>
<li><p>컬렉션 : 데이터중심</p>
</li>
<li><p>스트림 : 연산중심</p>
</li>
<li><p>컬렉션은 명시적 반복한다.      </p>
<pre><code class="language-java">//menu 리스트를 순회하면서 dish의 name을 가져와라..!!
for(Dish dish : menu){
  dish.getName();
}</code></pre>
</li>
<li><p>스트림의 내부반복</p>
<pre><code class="language-java">//데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택한다. 반면 외부 반복에서는 병렬성을 스스로 관리해야 한다.(synchronized)
menu.stream()
  .map(Dish::getName)
  .collect(toList());</code></pre>
</li>
<li><p>스트림 연산</p>
<ul>
<li>중간연산 : filter, sorted 같은 중간 연산은 다른 스트림을 반환한다.
이를 연결해서 질의를 만들 수 있다.
스트림의 <strong>게으른 특성</strong> 덕분에 다른 연산들을 한 과정으로 병합될 수 있다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">    List&lt;String&gt; names = menu.stream()
            .filter(dish -&gt; {
              System.out.println(&quot;filtering &quot; + dish.getName());
              return dish.getCalories() &gt; 300;
            })
            .map(dish -&gt; {
              System.out.println(&quot;mapping &quot; + dish.getName());
              return dish.getName();
            })
            .limit(3)
            .collect(toList());
        System.out.println(names);

        filtering:port
        mapping:pork
        filtering:beef ....
        mapping:beef</code></pre>
<ul>
<li>최종연산 : 파이프라인의 결과를 도출한다. List, Map 등 스트림 이외의 결과가 반환된다.</li>
<li>5장에서 더 자세히…!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[모던 자바 인 액션] - 3장 람다 표현식]]></title>
            <link>https://velog.io/@stay_o2o/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-3%EC%9E%A5-%EB%9E%8C%EB%8B%A4-%ED%91%9C%ED%98%84%EC%8B%9D-42hct9w9</link>
            <guid>https://velog.io/@stay_o2o/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-3%EC%9E%A5-%EB%9E%8C%EB%8B%A4-%ED%91%9C%ED%98%84%EC%8B%9D-42hct9w9</guid>
            <pubDate>Wed, 17 Apr 2024 11:49:27 GMT</pubDate>
            <description><![CDATA[<h3 id="람다-표현식">람다 표현식</h3>
<ul>
<li>보통 메서드와 달리 <strong>익명</strong>이다</li>
<li>메서드처럼 특정 클래스에 종속되지 않으므로 <strong>함수</strong>라고 부른다.</li>
<li>람다를 메서드 인수로 <strong>전달</strong>하거나 변수로 저장할 수 있다</li>
<li>익명클래스보다 <strong>간결</strong>하게 구현할 수 있다</li>
<li>람다 코드</li>
</ul>
<pre><code class="language-java"> Comparator&lt;Apple&gt; byWeight = (Apple a1, Apple a2) //람다 파라미터
  -&gt; //화살표
 a1.getWeight().compareTo(a2.getWeight()); //람다 바디</code></pre>
<h3 id="함수형-인터페이스">함수형 인터페이스</h3>
<p>정확히 하나의 추상 메서드를 지정하는 인터페이스이다.</p>
<ul>
<li><strong>Predicate : boolean test(T t)</strong><ul>
<li>T객체를 받아 boolean 반환</li>
<li>negate</li>
<li>and</li>
<li>or</li>
</ul>
</li>
<li><strong>Consumer : void accept(T t)</strong><ul>
<li>T를 받아 void</li>
</ul>
</li>
<li><strong>Function : R apply(T t)</strong><ul>
<li>T를 받아 R반환</li>
<li>antThen</li>
<li>compose</li>
</ul>
</li>
<li><strong>Comparator (Function 기반)</strong><ul>
<li>Comparator comparing(Function) 정적메서드 → 오름차순 정렬시켜줌</li>
<li>reverse() : 디폴트 메서드</li>
<li>thenComparing()</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[모던 자바 인 액션] - 2장 동작 파라미터화 코드 전달하기]]></title>
            <link>https://velog.io/@stay_o2o/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-2%EC%9E%A5-%EB%8F%99%EC%9E%91-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%ED%99%94-%EC%BD%94%EB%93%9C-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@stay_o2o/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-2%EC%9E%A5-%EB%8F%99%EC%9E%91-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%ED%99%94-%EC%BD%94%EB%93%9C-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 17 Apr 2024 11:43:40 GMT</pubDate>
            <description><![CDATA[<h2 id="동작파라미터화">동작파라미터화</h2>
<ul>
<li><p>동작 파라미터화란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미한다.</p>
</li>
<li><p>자주 바뀌는 요구사항에 대해 유연하게 대응할 수 있음</p>
<ul>
<li>사과의 색 / 무게 등을 필터링 할 때<ul>
<li>두가지 모두 필터링 할때.</li>
</ul>
</li>
</ul>
</li>
<li><p>유연하게 대응하기 위해서 <code>interface</code>를 사용한다.</p>
<pre><code class="language-java">  public List&lt;Apple&gt; filterApples(List&lt;Apple&gt; inventory, Color color, int weight, boolean flag) {
      //두가지 필터링이 존재
      List&lt;Apple&gt; result = new ArrayList&lt;&gt;();
      for (Apple apple : inventory) {
          if ((flag &amp;&amp; apple.getColor().equals(color)) ||
                  !flag &amp;&amp; apple.getWeight() &gt; weight) {
              result.add(apple);
          }
      }
      return result;
  }
      public void 모든_속성으로_필터링() {
          //초록 사과만 필터링
          List&lt;Apple&gt; greenApples = filterApples(inventory, Color.GREEN, 0, true);
          //무게 150이상만 필터링
          List&lt;Apple&gt; heavyApples = filterApples(inventory, null, 150, false);
      }</code></pre>
<ul>
<li>위 같이 무게 또는 색으로 하나만 필터링 하고 싶지만, 파라미터에는 모두 넣어줘야 한다.</li>
</ul>
</li>
<li><p>Predicate : 참 또는 거짓을 반환하는 함수</p>
</li>
</ul>
<pre><code class="language-java">public interface ApplePredicate {
    boolean test (Apple apple);
}

public class AppleHeavyWeightPredicate implements ApplePredicate{
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight() &gt; 150;
    }
}
public class AppleGreenColorPredicate implements ApplePredicate{
    @Override
    public boolean test(Apple apple) {
        return Color.GREEN.equals(apple.getColor());
    }
}</code></pre>
<ul>
<li>위 predicate 함수를 사용해서 유연하게 필터링을 만들 수 있다.</li>
<li>3장에서 계속,,,</li>
<li>동작 파라미터화 방법<ul>
<li>클래스</li>
<li>익명 클래스</li>
<li>람다</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[모던 자바 인 액션] - 1장 JAVA8, 9, 10의 등장]]></title>
            <link>https://velog.io/@stay_o2o/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-1%EC%9E%A5-JAVA8-9-11%EC%9D%98-%EB%93%B1%EC%9E%A5</link>
            <guid>https://velog.io/@stay_o2o/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-1%EC%9E%A5-JAVA8-9-11%EC%9D%98-%EB%93%B1%EC%9E%A5</guid>
            <pubDate>Wed, 17 Apr 2024 11:35:07 GMT</pubDate>
            <description><![CDATA[<p><em>1장의 모든 내용은 뒤에서 자세히 다룹니다,,,</em></p>
<h3 id="스트림-처리">스트림 처리</h3>
<ul>
<li>한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임</li>
<li>Java8의 java.util.stream에 스트림 API 추가. (Stream<T>)</li>
</ul>
<h3 id="동작-파라미터화로-메서드에-코드-전달">동작 파라미터화로 메서드에 코드 전달</h3>
<h3 id="병렬성과-공유-가변-데이터">병렬성과 공유 가변 데이터</h3>
<p>스트림 메서드로 전달하는 코드는 다른 코드와 동시에 실행하더라도 안전하게 실행될 수 있어야 한다. 이러한 코드를 만드려면 공유된 가변 데이터(shared mutable data)에 접근하지 않아야 한다.</p>
<p><code>synchronized</code>를 사용할 수 있지만 시스템 성능에 악영향을 미친다.</p>
<h3 id="java8">Java8</h3>
<ul>
<li><strong>메서드 참조</strong></li>
</ul>
<pre><code class="language-java">//Java8 이전
File[] hiddenFiles = new File(&quot;.&quot;).listFiles(new FileFilter(){
    public boolean accept(File file) {
        return file.isHidden(); 
    }
});

//Java8
File[] hiddenFiles = new File(&quot;.&quot;).listFiles(File::isHidden);</code></pre>
<p>메서드 참조를 이용해서 <code>listFiles</code>에 직접 함수를 전달할 수 있다.</p>
<ul>
<li><strong>람다 : 익명함수</strong></li>
</ul>
<p>위의 <code>File::isHidden</code>이 람다 형식.</p>
<pre><code class="language-java">public static List&lt;Apple&gt; filterApples(List&lt;Apple&gt; inventory, Predicate&lt;Apple&gt; p) {
  List&lt;Apple&gt; result = new ArrayList&lt;&gt;();
  for (Apple apple : inventory) {
    if (p.test(apple)) {
      result.add(apple);
    }
  }
  return result;
}
    public interface Predicate&lt;T&gt;{
    boolean test(T t);
}
public static boolean isGreenApple(Apple apple) {
  return &quot;green&quot;.equals(apple.getColor());
}

public static boolean isHeavyApple(Apple apple) {
  return apple.getWeight() &gt; 150;
}</code></pre>
<p>filterApples 메서드를 다음처럼 호출할 수 있다.</p>
<ul>
<li><code>filterApples(inventory, Apple:isGreenApple)</code></li>
<li><code>filterApples(inventory, Apple:isHeavyApple)</code></li>
</ul>
<p><strong>Predicate란?</strong>
수학에서 인수로 값을 받아 true나 false를 반환하는 함수를 프레디케이트라고 한다.</p>
<ul>
<li><strong>Stream</strong><ul>
<li>for문처럼 외부 반복이 아닌, 내부 반복.</li>
<li>주요 기능<ul>
<li>필터링</li>
<li>추출</li>
<li>그룹핑</li>
</ul>
</li>
<li>필터링 예시: <code>inventory.stream().filter((Apple a) -&gt; a.getWeight() &gt; 150)).collect(toList());</code></li>
</ul>
</li>
<li><strong>디폴트 메서드</strong><ul>
<li>java8 이전에는 List를 구현하는 모든 클래스가 sort를 구현해야 했지만 지금은 따로 구현하지 않아도 됨.</li>
</ul>
</li>
</ul>
<h3 id="그래서-java-8-9-10-11-많이-달라졌는가"><strong>그래서 java 8, 9, 10, 11 많이 달라졌는가?</strong></h3>
<ul>
<li>JAVA8 - 메소드전달, 람다 … 기본</li>
<li>JAVA9 - 대규모 컴포넌트를 정의하고 사용하는 기능 → 리액티브 프리그래밍</li>
<li>JAVA10 - 지역변수 추론</li>
<li>JAVA11 - CompletableFuture와 리액티브 프로그래밍 기능을 활용한 비동기 HTTP 클라이언트 라이브러리</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[GitHub subtree로 Repository 분할]]></title>
            <link>https://velog.io/@stay_o2o/GitHub-subtree%EB%A1%9C-Repository-%EB%B6%84%ED%95%A0</link>
            <guid>https://velog.io/@stay_o2o/GitHub-subtree%EB%A1%9C-Repository-%EB%B6%84%ED%95%A0</guid>
            <pubDate>Sat, 30 Mar 2024 10:46:55 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/stay_o2o/post/f4e72a1b-571e-49a5-8572-66e1599c2650/image.png" alt=""></p>
<h1 id="svn---github🙊">SVN -&gt; GitHub🙊...</h1>
<p>기존에 SVN을 사용하다 최근 사내 모든 형상관리를 GitHub으로 이전했다. 다른 팀에서 이 작업을 진행하다 보니 크게 신경쓰지 못했다. 
분명 SVN에서 GitHub으로 이전하니, 이전되는 SVN의 프로젝트(폴더)를 확인하고 문제 이상 있으면 말하라고 공지까지 됐었는데 DeadLine이 있는 작업들을 하다 보니 별일 있겠어...? 하고 넘어갔었다...</p>
<h2 id="기존-svn-폴더-구조">기존 SVN 폴더 구조</h2>
<p>SVN에서는 제품 버전별로 폴더들이 있고, 그 아래에 고객사별로 소스가 존재했다.</p>
<pre><code>ROOT
--V.1
    --고객사A
    ...
--v.2
    --고객사B
    --고객사C
    --고객사D
    ...</code></pre><p>이런 구조로 되어있다 보니, GitHub으로 이전하게 되면 고객사 소스별로 Repository가 구성되어야 했다. 하지만,,,</p>
<h2 id="github-repository-구조문제">GitHub Repository 구조(문제)</h2>
<p>GitHub으로 이전되면서 이런 구조로 이전 되었다.</p>
<pre><code>V.1 Repository
--고객사A
...
V.2 Repository
--고객사B
--고객사C
--고객사D
...</code></pre><p>이렇게 구조가 만들어지면서, V.2 버전의 고객사 중 하나를 수정했을 때 V.2 Repository에 Commit 내용이 생겼을 것이다.</p>
<ul>
<li>문제    <ul>
<li>고객사 별로 형상관리가 어렵게 된다.</li>
<li>V.2 Repository를 Clone 했을 때 해당 Repository의 모든 고객사를 내려받는다. 그러면 불필요한 소스들을 내려받게 되며, 내려 받는데 시간도 오래걸린다.</li>
</ul>
</li>
</ul>
<p>이러한 문제들이 발생하기 전에 Repository를 분리하여 해결이 필요했다.</p>
<h1 id="git-subtree로-해결😎">git subtree로 해결😎</h1>
<p>subtree 명령어로 Repository를 분리 할 수 있었다.</p>
<ol>
<li>분리 할 Repository 폴더로 이동</li>
<li>subtree
<code>git subtree split -P &lt;분리할 하위 폴더&gt; -b &lt;새로운 브랜치 이름&gt;</code></li>
<li>새 폴더 만들기(분리할 폴더의 루트가 됨)</li>
<li>해당 폴더로 이동
<code>git init</code>
<code>git pull &lt;기존 프로젝트 폴더 위치&gt; &lt;생성한 브랜치 이름&gt;</code></li>
<li>GitHub에서 새로운 Repository 생성
<code>git remote add origin &lt;분리한 리포지토리 주소&gt;</code>
<code>git branch -M main</code>
<code>git push -u origin main</code></li>
</ol>
<p>이렇게 Repository를 고객사 별로 분리 할 수 있었고, 다른 고객사들의 Commit 내용이 겹치는 불상사를 막을 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SQL] - 재귀쿼리/계층형쿼리 Oracle vs PostgreSQL]]></title>
            <link>https://velog.io/@stay_o2o/SQL-%EC%9E%AC%EA%B7%80%EC%BF%BC%EB%A6%AC-Oracle-vs-PostgreSQL</link>
            <guid>https://velog.io/@stay_o2o/SQL-%EC%9E%AC%EA%B7%80%EC%BF%BC%EB%A6%AC-Oracle-vs-PostgreSQL</guid>
            <pubDate>Sun, 11 Dec 2022 09:46:05 GMT</pubDate>
            <description><![CDATA[<h2 id="서론">서론</h2>
<p>현재 Oracle 쿼리를 PostgreSQL로 변경하는 작업을 하고있다. 처음에는 Oracle과의 호환성이 좋다고 하여 무지성 복붙을 하고 쿼리를 날려봤더니 에러가 났다... 알고보니 몇 가지 다른 함수들이 있었다. 그 중 <strong>재귀쿼리</strong>에 대하여 정리를 해보고자 한다. 재귀쿼리가 쓰이는 곳이 꽤나 많아서 제대로 이해하고 넘어가도록 하자.</p>
<h2 id="oracle---start-with--connect-by-prior">Oracle - START WITH ... CONNECT BY PRIOR</h2>
<p>우선 테스트 할 테이블과 데이터를 넣어줬다.</p>
<pre><code class="language-sql">CREATE TABLE DEP (
     DEP_CD NUMBER NOT NULL, -- 부서코드
     PARENT_CD NUMBER, -- 상위부서 코드
     DEPT_NAME VARCHAR2(100) NOT NULL, -- 부서이름
     PRIMARY KEY (DEP_CD)
);

INSERT INTO DEP VALUES ( 101, NULL, &#39;총괄개발부&#39;);
INSERT INTO DEP VALUES ( 102, 101, &#39;모바일개발센터&#39;);
INSERT INTO DEP VALUES ( 103, 101, &#39;웹개발센터&#39;);
INSERT INTO DEP VALUES ( 104, 101, &#39;시스템개발센터&#39;);

INSERT INTO DEP VALUES ( 105, 102, &#39;쇼핑몰(모바일)&#39;);
INSERT INTO DEP VALUES ( 106, 103, &#39;외주SI&#39;);
INSERT INTO DEP VALUES ( 107, 103, &#39;쇼핑몰&#39;);
INSERT INTO DEP VALUES ( 108, 105, &#39;전산지원팀&#39;);
INSERT INTO DEP VALUES ( 109, 106, &#39;구축1팀&#39;);
INSERT INTO DEP VALUES ( 100, 106, &#39;구축2팀&#39;);
INSERT INTO DEP VALUES ( 111, 104, &#39;ERP시스템&#39;);</code></pre>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/40fa7567-6b80-40bf-a0bc-021dbc66d32d/image.png" alt=""></p>
<p>그리고 Oracle의 계층형 쿼리 형식이다.</p>
<pre><code class="language-sql">SELECT [컬럼...]
FROM [테이블]
WHERE [조건...]
START WITH [최상위 조건]
CONNECT BY [NOCYCLE] [PRIOR 계층형 구조 조건]</code></pre>
<p>위 테이블 구조는 최상위 부서가 &#39;총괄개발부 - 101&#39;이고, 총괄개발부 밑으로는 부서번호-[102,103,104]가 있다. 이후 다른 부서들도 계층형 구조로 이루어져 있다.
이제 계층형 쿼리로 작성해보자.
<img src="https://velog.velcdn.com/images/stay_o2o/post/1985b849-ab31-4ba1-b781-15858d4a8074/image.png" alt=""></p>
<pre><code class="language-sql">SELECT DEPT_NAME, DEP_CD, PARENT_CD, LEVEL
FROM DEP 
START WITH PARENT_CD IS NULL --최상위 컬럼 조건
CONNECT BY PRIOR DEP_CD = PARENT_CD --계층형 구조 조건
ORDER BY LEVEL;</code></pre>
<p>이제 구문을 하나씩 보도록 하겠다.</p>
<blockquote>
</blockquote>
<pre><code class="language-sql">SELECT DEPT_NAME, DEP_CD, PARENT_CD, LEVEL
FROM DEP</code></pre>
<p>SELECT 절에 컬럼들을 나열해 주었고, LEVEL이란 컬럼은 루트 계층 부터의 단계를 나타내는데 계층형 쿼리에서만 사용하는 모조 컬럼이다. 따라서 &#39;총괄개발부&#39;가 최상위 계층이기 때문에 LEVEL이 1인 것을 볼 수 있다. 그리고, &#39;총괄개발부&#39;밑의 계층인 부서번호-[102,103,104]의 LEVEL은 2인 것을 볼 수 있다.</p>
<blockquote>
</blockquote>
<pre><code class="language-sql">START WITH PARENT_CD IS NULL</code></pre>
<p>START WITH 에는 최상위 계층이 될 조건이다. PARENT_CD컬럼이 부모컬럼이기 때문에 모든 행의 계층구조를 보기 위해 &#39;총괄개발부&#39;를 기준으로 봐야한다. 따라서 &#39;총괄개발부&#39;의 PARENT_CD는 NULL 이기 때문에 PARENT_CD IS NULL 이라는 조건을 넣어준다.</p>
<blockquote>
</blockquote>
<pre><code class="language-sql">CONNECT BY PRIOR DEP_CD = PARENT_CD</code></pre>
<p>먼저 START WITH에서 조건에 맞는 최상위 행(위 예에서는 &#39;총괄개발부&#39;)을 가져온 후 이 행의 DEP_CD를 PARENT_CD로 갖는 계층 데이터를 끝까지 가져온다.
만약 PRIOR 반대로 설정하게 된다면? - <code>CONNECT BY PRIOR PARENT_CD = DEP_CD</code>
START WITH에서 최상위 행을 구한 후 이 행의 PARENT_CD를 DEP_CD로 갖는 계층 데이터를 끝까지 가져온다. 하지만 이때 최상위 행인 &#39;총괄개발부&#39;의 PARENT_CD는 NULL이기 때문에 쿼리 결과는 최상위 행 하나만 출력되게 된다.<img src="https://velog.velcdn.com/images/stay_o2o/post/1fe6c23a-849f-43e8-a03e-46c45f8bd8cc/image.png" alt=""></p>
<blockquote>
<pre><code class="language-sql">ORDER BY LEVEL</code></pre>
</blockquote>
<p>```
LEVEL을 기준으로 오름차순 정렬을 했다. 쿼리 결과에서 해당 행이 몇 번째 계층인지 명확하게 보기 위해서이다.</p>
<h2 id="마치며">마치며</h2>
<p>이번에는 간단하게 Oracle의 계층형 쿼리에 대해 정리해 보았다. 계층형 쿼리를 응요하여 사용하게 된다면 무궁무진하게 사용 할 수 있을 것 같다. 다음에는 PostreSQL 재귀쿼리를 정리해 보도록 하겠다.</p>
<h2 id="참조">참조</h2>
<blockquote>
</blockquote>
<p><a href="https://heavenlake.tistory.com/45">https://heavenlake.tistory.com/45</a>
<a href="https://coding-factory.tistory.com/461">https://coding-factory.tistory.com/461</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JAVA] 약수의 개수와 덧셈 - Lv.1]]></title>
            <link>https://velog.io/@stay_o2o/JAVA-%EC%95%BD%EC%88%98%EC%9D%98-%EA%B0%9C%EC%88%98%EC%99%80-%EB%8D%A7%EC%85%88-Lv.1</link>
            <guid>https://velog.io/@stay_o2o/JAVA-%EC%95%BD%EC%88%98%EC%9D%98-%EA%B0%9C%EC%88%98%EC%99%80-%EB%8D%A7%EC%85%88-Lv.1</guid>
            <pubDate>Sat, 15 Oct 2022 07:52:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/stay_o2o/post/3a8756b9-73da-48b0-83bc-8499eba6cb3f/image.png" alt=""><img src="https://velog.velcdn.com/images/stay_o2o/post/ed4af47b-63f8-42f0-af2d-c267aadbf9e6/image.png" alt="">
<img src="https://velog.velcdn.com/images/stay_o2o/post/8a677b44-07c7-47d1-a605-fef112b1cf91/image.png" alt=""></p>
<h2 id="문제">문제</h2>
<blockquote>
<p>문제 링크 : <a href="https://school.programmers.co.kr/learn/courses/30/lessons/77884">https://school.programmers.co.kr/learn/courses/30/lessons/77884</a></p>
</blockquote>
<p>두 정수 <strong>left</strong>와 <strong>right</strong>가 매개변수로 주어집니다. <strong>left</strong>부터 <strong>right</strong>까지의 모든 수들 중에서, 약수의 개수가 짝수인 수는 더하고, 약수의 개수가 홀수인 수는 뺀 수를 return 하도록 solution 함수를 완성해주세요.</p>
<h2 id="생각-생각">생각 생각</h2>
<p>N의 약수의 개수를 구할 때 반복 횟수는 1부터 N까지 for문을 모두 순회하는 것이 아니라 N의 제곱근 만큼 반복하면 된다.
-&gt; N의 약수의 개수를 구하기 위한 반복 횟수 = <code>Math.sqrt(N)</code></p>
<p>약수의 개수가 짝수인 수 - <code>Math.sqrt(N) != 0</code>
약수의 개수가 홀수인 수 - <code>Math.sqrt(N) == 0</code></p>
<h2 id="코드">코드</h2>
<pre><code class="language-java">class Solution {
    public int solution(int left, int right) {
        int answer = 0;
         for(int num=left; num&lt;=right; num++){
                if(num % Math.sqrt(num)==0){
                    answer-=num;
                }
                else
                    answer+=num;


            }
        return answer;
    }
}</code></pre>
<h2 id="풀이">풀이</h2>
<p>위에서 말했다 싶이 반복문은 left~right 만큼 순회하고,
약수의 개수가 짝수인경우(<code>Math.sqrt(N) !=0</code>)에는 answer에 더해주고, 약수의 개수가 홀수인 수(<code>Math.sqrt(N) == 0</code>)에는 answer에 값은 빼준다.</p>
<h2 id="review">REVIEW</h2>
<p>문제를 천천히 읽고 깊이 생각해보면 해결책은 쉽게 나온다. 문제를 쓱 보고 간단하다고 쉬운문제라고 판단하지 말자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JAVA] 문자열 내림차순으로 배치하기 - Lv.1]]></title>
            <link>https://velog.io/@stay_o2o/JAVA-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%82%B4%EB%A6%BC%EC%B0%A8%EC%88%9C%EC%9C%BC%EB%A1%9C-%EB%B0%B0%EC%B9%98%ED%95%98%EA%B8%B0-Lv.1</link>
            <guid>https://velog.io/@stay_o2o/JAVA-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%82%B4%EB%A6%BC%EC%B0%A8%EC%88%9C%EC%9C%BC%EB%A1%9C-%EB%B0%B0%EC%B9%98%ED%95%98%EA%B8%B0-Lv.1</guid>
            <pubDate>Sat, 15 Oct 2022 07:19:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/stay_o2o/post/78b41322-b44b-47e3-b322-b4e38e40911d/image.png" alt=""></p>
<h2 id="문제설명">문제설명</h2>
<blockquote>
<p>문제 링크 : <a href="https://school.programmers.co.kr/learn/courses/30/lessons/12917">https://school.programmers.co.kr/learn/courses/30/lessons/12917</a></p>
</blockquote>
<p>주어진 문자열 s를 내림차순으로 배치한다. s는 영어 대소문자로 이뤄져있고, 대문자는 소문자보다 작은것으로 간주한다.</p>
<h2 id="생각-생각">생각 생각</h2>
<p>사실 쉬운문제라 생각할 것도 없다. 아스키문자대로 내림차순 정렬하면 대문자는 소문자 뒤로 오기 때문에 간단하다.
문자열을 배열로 만든 후 정렬 시키고 다시 문자열로 바꾸면 끝.</p>
<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.util.*;
class Solution {
    public String solution(String s) {
        String answer = &quot;&quot;;
        char[] chars = s.toCharArray();
        Arrays.sort(chars);

        answer = new StringBuilder(new String(chars)).reverse().toString();
        return answer;
    }
}</code></pre>
<h2 id="풀이">풀이</h2>
<ol>
<li>문자열을 배열로 만든다.</li>
<li>Arrays.sort()를 사용하여 정렬 - 오름차순 정렬</li>
<li>StringBuilder에 정렬된 배열을 문자열로 만들어 넣어준다.</li>
<li>StringBuilder에 있는 reverse()메서드를 통해 오름차순 정렬된 문자열을 뒤집는다.</li>
<li>toString()으로 문자열로 변환.</li>
</ol>
<h2 id="review">REVIEW</h2>
<p>StringBuilder에 reverse()메서드가 존재하는지 몰랐다. 찾아보니 StringBuffer에도 reverse()메서드가 존재하더라.
다음에 문자열 정렬을 할 때가 온다면 한번 사용해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA - 상속관계 매핑]]></title>
            <link>https://velog.io/@stay_o2o/JPA-%EC%83%81%EC%86%8D%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91</link>
            <guid>https://velog.io/@stay_o2o/JPA-%EC%83%81%EC%86%8D%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91</guid>
            <pubDate>Tue, 27 Sep 2022 05:05:55 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>영한님의 강의들(JPA 기본, QueryDsl ...)을 들으면서 사내서비스를 리팩토링 중이었다. 
개발 도중 JPA기본편 강의에서 들었던 <strong>상속관계 매핑</strong>을 사용할 기회가 있어 적용해보았다. </p>
<h2 id="상속관계-매핑이란">상속관계 매핑이란.</h2>
<p>우선 간단하게 JPA 상속관계 매핑의 전략들에 대해 알아보겠다.</p>
<ul>
<li><strong>JOIN 전략</strong> - 부모 및 자식 테이블들을 각각 두어 정규화를 사용한 전략<ul>
<li>장점 : 외래키 제약조건 활용 / 테이블 정규화</li>
<li>단점 : 조회 시 join 사용 / 성능저하 우려 / 조회쿼리 복잡 / INSERT 시 2번 호출</li>
</ul>
</li>
<li><strong>SINGLE_TABLE 전략</strong> - 부모 테이블 하나로 통합 테이블 관리.<ul>
<li>장점 : 조인이 필요없어 조회가 빠름 / 조회 쿼리 단순</li>
<li>단점 : 자식 엔티티가 매핑한 컬럼은 null 허용 / 한 테이블에 모든 데이터를 저장하여 오히려 성능저하를 유발 할 수 있음.</li>
</ul>
</li>
</ul>
<h2 id="구조">구조</h2>
<p>상속관계의 구조는 아래와 같다.
<img src="https://velog.velcdn.com/images/stay_o2o/post/c00cf73e-7564-44a3-8774-bcefb22a9173/image.png" alt=""></p>
<p>Server를 상속하는 App서버와 DB서버가 있다.
서버 IP, 서버 이름, OS버전 등은 공통적으로 사용되는 필드들이라 Server에 두었고, APP과 DB 서버에서 필요한 필드들은 각각 두었다. 이 상속관계를 이용하여 JPA에서 관계를 만들었다.</p>
<hr>
<h2 id="엔티티-코드">엔티티 코드</h2>
<ul>
<li><p><strong>Server 엔티티</strong></p>
<pre><code class="language-java">@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = &quot;s_dtype&quot;)
@Getter
@Setter
public abstract class Server {
 @Id
 @GeneratedValue
 private int serverKey;

 @Column(name=&quot;s_dtype&quot;,insertable = false,updatable = false)
 private String dtype;

 private String ip;
 private String os;
 private String serverName;
 .....(생략)
}
</code></pre>
</li>
</ul>
<pre><code>위는 **Server** 엔티티이다. 하위 엔티티들의 컬럼들이 많지 않고, 복잡한 엔티티가 아니라 **SINGLE TABLE** 전략을 사용했다. 

---

* **App / DB 엔티티**
```java
@Getter
@Setter
@Entity
@DiscriminatorValue(&quot;APP&quot;)
public class App extends Server{

    private String ssl;
    private String apache;

}

@Getter
@Setter
@Entity
@DiscriminatorValue(&quot;DB&quot;)
public class DB extends Server {

    private String dbVersion;
    private String dbName;
    private String dbUserName;
    private String dbUserPwd;
}

</code></pre><p>위는 <strong>App/DB</strong> 엔티티이다. <strong>Server</strong>를 상속받고 각각의 필드들이 존재한다.</p>
<hr>
<ul>
<li><strong>테이블 **
이렇게 객체를 구성하고 실행하게 되면 **SINGLE_TABLE</strong> 전략이기 때문에 DB에 한 테이블에 3개의 엔티티들이 들어가게 된다. (통합 테이블!!)
<img src="https://velog.velcdn.com/images/stay_o2o/post/6719b2ac-29ba-4711-9b5d-3fbf755162e4/image.png" alt=""> </li>
</ul>
<h1 id="본론">본론</h1>
<p><strong>Server</strong>에 대한 API 중 <strong>Server</strong>의 List를 DTO로 변환하여 return하고 싶었다. 그래서 아래와 같이 코드를 작성했다.</p>
<h2 id="조회-쿼리-작성">조회 쿼리 작성</h2>
<ol>
<li><p>JPQL </p>
<blockquote>
<p><code>List&lt;Server&gt; serversByEm = em.createQuery(&quot;select s from Server s&quot;, Server.class).getResultList();</code>
쿼리의 결과를 보면 List안의 객체들은 <strong>자식타입(App/DB)</strong>이지만 다형성으로 <strong>Server</strong>에 담겨져 나왔다.
<img src="https://velog.velcdn.com/images/stay_o2o/post/6ff8f255-18e2-45f3-92e7-45345c42173c/image.png" alt="">
문제는 여기 부터였다. 
나는 조회된 <strong>List&lt;Server&gt;</strong>를 <strong>Dto</strong>로 변환하고 싶었는데 그게 생각처럼 쉽지 않았다.
간단하게 보자면, 각 객체에는 자식 엔티티의 값들이 들어있지만 <strong>Server타입</strong>(부모타입)으로 조회가 되기때문에 <strong>자식타입(App,DB)</strong>으로 <em>다운캐스팅_을 하지 않는이상 자식 값에 접근 할 수 없었다. (_다운캐스팅을 한다고 해도 List안의 객체가 App인지 DB인지 조건문을 걸어서 다운캐스팅을 해야 될 것 같다.... 괜한 로직이 발생하는 것 같다..! 또는 자식 타입 각각 조회하는 방법도 있다..!</em>)
<img src="https://velog.velcdn.com/images/stay_o2o/post/66ae26eb-5214-45b8-b08b-0529bb9d6454/image.png" alt=""></p>
</blockquote>
</li>
<li><p>QueryDsl</p>
<blockquote>
<p>QueryDsl도 위와 같은 결과가 나왔지만 다른방법은 적용해봤다. 
바로 부모엔티티와 자식엔티티를 Join하는 방법이다.... 위에서도 언급했지만 SINGLE_TALBE 의 장점은 JOIN을 사용하지 않는 것 이다. 그래도 테스트를 위해  한 번 작성해봤다. </p>
<pre><code class="language-java">List&lt;ServerResponseDto&gt; serverList = queryFactory
         .select(Projections.bean(ServerResponseDto.class,
             server.serverKey,
             server.dtype,
             server.ip,
             server.serverName,
             server.os,
             app.ssl,
               app.apache,
             dB.dbVersion,
             dB.dbName,
               db.dbUserName,
               db.dbUserPwd,
         ))
         .from(server)
         .leftJoin(app).on(app.eq(server))
         .leftJoin(dB).on(dB.eq(server))
         .fetch(); </code></pre>
</blockquote>
<pre><code>&gt; FROM절은 이러하다. ` from t_server server0_ left outer join t_server app1_ on (app1_.s_server_key=server0_.s_server_key) left outer join t_server db2_ on (db2_.s_server_key=server0_.s_server_key)`
위처럼 **App**과 **DB**를 **JOIN**하여 **DTO**로 바로 조회했더니 원하는 결과를 가져오긴 했다.
하지만 _JOIN을 사용하지 않는 것이_ **SINGLE_TABLE전략**의 장점이기 때문에 이 방법은 좀 꺼림직 했다.
</code></pre></li>
</ol>
<h2 id="그럼-해결책은">그럼 해결책은?</h2>
<p>어렵게 찾은 해결책....<a href="https://inf.run/UYqS">상속관계에서 DTO로 바로 조회하는법</a>
정말 거짓말 안하고 하루 꼬박 해서 이 질문에 대한 답을 서칭했다. 구글과 영한님 JPA강의의 커뮤니티까지 많이 찾아봤는데 결국 QueryDsl 강의 커뮤니티에서 찾을 수 있었다....
  결국 위에서 JPQL로 했던 것 처럼 Server 엔티티를 <strong>DTO로 직접변환</strong> 하거나, <strong>네이티브쿼리</strong>를 사용해야만 한다... 이러고보니<img src="https://velog.velcdn.com/images/stay_o2o/post/e44ddbac-f001-40ef-bf5d-932da427847d/image.png" alt="">
 네이티브쿼리로 하는게 가장 적합한 방법이지 않나 싶다.
  위 답변에서 가장 눈에 띄는 말은 <strong>&quot;만약 비즈니스 로직에 큰 차이가 없고, 단순히 데이터의 차이만 있다면, 상속 관계를 사용하지 말고, 한 테이블에 합치는 것을 권장합니다.&quot;</strong> 이었다,,, 지금까지 상속관계 매핑을 생각해서 설계했던 구조와 삽질했던 순간이 머릿속을 지나갔다. 물론 <strong>꼭 사용해야 하는 곳에는 사용해라.</strong> 라고 하셨고 약간의 허탈함이 나를 채웠다.</p>
<h1 id="결론">결론</h1>
<p>  이후 나도 영한님 강의에 질문을 올렸다.</p>
<p>  Q. 상속관계 매핑을 실무에서 잘 사용하지 않는 이유는 뭔가요?
      <strong>A. JPA에서 상속관계 매핑은 매핑이 복잡하고, 성능까지 고민하면서 사용하기가 쉽지 않기 때문에 꼭 필요한 경우에 부분적으로 사용하는 것을 권장합니다.</strong></p>
<p>  Q. 상속관계 매핑을 사용하지 않고 한 테이블에 데이터를 모두 넣으면 컬럼에 null을 허용하는 것은 상속관계 매핑과 다를게 없을텐데 여기서 오는 이점이 무엇일까요?
 ** A. 이 부분은 객체의 상속관계를 사용할 것인가? 아니면 객체 내부에 타입을 두고 해당 타입으로 구분할 것인가 하는 객체와 자료구조의 문제로 보시면 됩니다.**</p>
<p>  <strong>결론적으로</strong> 상속관계 매핑은 지양해야 하고, 꼭 필요로 할 때만 사용해야 된다는 것이다. 또 JOIN 전략이 정석적이며, SINGLE_TABLE전략을 사용 하고자 할 때 단순히 데이터의 차이만 있다면 상속관계 없이 한 테이블로 구현하는 것이 좋을 것 같다.</p>
<h1 id="추가">추가</h1>
<p>  상속에 관한 레퍼런스를 찾다보니 &quot;상속(Inheritance) 보단 조합(Composition)을&quot; 이라는 말이 있더라. 나중에 공부해보고 적용하고 추가로 작성하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Enum과 AttributeConverter]]></title>
            <link>https://velog.io/@stay_o2o/Enum%EA%B3%BC-AttributeConverter</link>
            <guid>https://velog.io/@stay_o2o/Enum%EA%B3%BC-AttributeConverter</guid>
            <pubDate>Mon, 18 Jul 2022 06:22:27 GMT</pubDate>
            <description><![CDATA[<h1 id="이-글의-배경">이 글의 배경</h1>
<p>JPA를 사용하면서 서버정보를 담고있는 <strong><em>Server</em></strong> Entity의 필드중 하나인 <strong><em>type</em></strong>을 enum타입으로 사용하려고 했다.
<img src="https://velog.velcdn.com/images/stay_o2o/post/d4624c89-6b7e-4d72-b06f-b5f1135d1ade/image.png" alt=""></p>
<p>위는 <em><strong>ServerType</strong>_이라는 enum클래스이다. _code_와 _desc</em> 두 개의 필드를 가지고있고, 현재 DB의 컬럼에는 _code_가 저장되어 있다. 따라서 DB에 INSERT 할 때는 _code_를 사용하고, SELECT 할 때는 해당 _code_를 가지고있는 enum객체를 담아주길 원했다.</p>
<ul>
<li>INSERT 할 때 : Server테이블 - type컬럼 = <strong>P</strong></li>
<li>SELECT 할 때 : Server.type == <strong>ServerType.PRODUCT</strong>;</li>
</ul>
<p>이대로 <strong><em>Server</em></strong> Entity에 enum _<strong>ServerType</strong>_을 컬럼으로 정의하고 실행해 보았다.</p>
<hr>
<h1 id="발생-문제">발생 문제</h1>
<p>특정 조건 없이 _findOne_을 했을 때 DB에서 가져오는 _<strong>ServerType</strong>_의 값이 Entity와 매핑이 되지 않았다. </p>
<blockquote>
<pre><code>org.springframework.dao.InvalidDataAccessApiUsageException: No enum constant com.spectra.store.jpo.enums.ServerType.P;
nested exception is java.lang.IllegalArgumentException: No enum constant com.spectra.store.jpo.enums.ServerType.P</code></pre></blockquote>
<p>```</p>
<ul>
<li>DB에서 값을 가져와 Entity로 변환 할 때 기본적으로 enum클래스의 필드 이름인 <strong><em>ServerType.P</em></strong>로 매핑하려 하는데 해당 enum은 <strong><em>ServerType.PRODUCT</em></strong> 이기 때문에 발생하는 에러인 것 같다.</li>
</ul>
<h2 id="attributeconverterxy">AttributeConverter&lt;X,Y&gt;</h2>
<p>원하는 값을 매핑하기 위해서는 <em>ServerType.PRODUCT_의 _code_에 매핑을 시켜줘야 한다. 
이를 간단하게 해결해 주는 <strong>AttributeConverter</strong>가 존재한다!!
<img src="https://velog.velcdn.com/images/stay_o2o/post/6d22dc9b-be6c-47b7-9bc9-132af871db56/image.png" alt="">
**_AttributeConverter</em>**는 인터페이스로 2개의 메서드가 선언되어있다. 이를 구현하는  자식 클래스를 만들어 재정의 하면 된다.</p>
<ul>
<li><strong><em>Y</em> convertToDatabaseColum(<em>X var1</em>)</strong> : X타입을 받아 Y(DB컬럼)타입으로 리턴</li>
<li><strong><em>X</em> convertToEntityAttribute(<em>Y var1</em>)</strong> : Y타입을 받아 X(Entity)타입으로 리턴</li>
</ul>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/d9eadbd5-d9e3-4e01-8e59-b5607de89c68/image.png" alt=""><img src="https://velog.velcdn.com/images/stay_o2o/post/41c42305-4fe8-4f21-ba44-2364d820f06c/image.png" alt=""></p>
<p><strong>AttributeConverter</strong>를 상속하는 <strong>ServerTypeConverter</strong>를 만들었고 메서드들을 재정의했다. DB로 저장하기 위해 해당 <strong>ServerType.getCode()</strong>를 하여 <em>code_를 DB에 저장하고, Entity로 변환하기 위해 ServerType(enum)에 _<strong>ofCode(String code)</strong></em> static 메서드를 만들었다.</p>
<p>그리고 Entity의 <em>ServerType_필드에는 **</em>@Convert_<strong>,
_ServerTypeConverter_에는 _</strong>@Converter**_ Annotaion을 붙여주면 끝이다!!
<img src="https://velog.velcdn.com/images/stay_o2o/post/0e3b1fc0-25e8-4f39-a777-b4081323e6e0/image.png" alt=""></p>
<hr>
<h1 id="해치웠나-아직-개선-필요">해치웠나...? 아직 개선 필요!!</h1>
<p>이대로 간단하게 끝나면 정말 좋겠지만... 생각을 조금 더 해보면 Enum클래스 마다 <em><strong>ofCode()</strong></em> 메서드와 <strong><em>Converter</em></strong>를 만들어 줘야한다. 필요한 enum클래스가 100개라면 Converter도 100개가 만들어져야한다!!</p>
<p><em>&quot;불필요하고 반복되는 작업은 개발자를 힘들게해요!!&quot;</em>
<img src="https://velog.velcdn.com/images/stay_o2o/post/a2bff791-3eda-4ef3-b8a6-f2edcb063123/image.png" alt=""></p>
<h2 id="추상화가-필요해">추상화가 필요해</h2>
<p>현재 반복되는 작업인 <strong><em>AttributeConverter</em></strong> 클래스의 메소드 재정의 부분과 enum의 <strong><em>ofCode()</em></strong> 메서드를 <strong>추상화</strong> 하여 자식클래스에서 간편하게 사용하도록 만들 것이다.</p>
<h3 id="interface-enumfild-와-servertype-enum-클래스">interface EnumFild 와 ServerType enum 클래스</h3>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/cb160b70-3733-4485-ad99-9fd5b125fd61/image.png" alt=""><img src="https://velog.velcdn.com/images/stay_o2o/post/cf0e074f-2492-4b28-ad6f-3675dc3ac17d/image.png" alt=""></p>
<p>우선 <strong><em>EnumField</em></strong> 인터페이스를 만들어 <strong>getter</strong>를 강제하고 enum클래스들이 이를 상속하도록 한다. 이는 밑의 추상화 Converter 클래스에서 다형성을 활용하기 위함이다.</p>
<h3 id="추상화-클래스-abstractenumcodeattributeconverter">추상화 클래스 AbstractEnumCodeAttributeConverter</h3>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/25e15275-ce5d-441d-b4e2-da6fe6d70e51/image.png" alt=""></p>
<p>AbstractEnumCodeAttributeConverter 클래스는 이전에 각각의 enum에서 재정의하던 기능들을 추상화 시킨 클래스이다.
반환타입을 보면 <em><strong>&lt;T extends Enum<T> &amp; EnumField&gt;</strong>_인데 이 의미는 Enum과 EnumField를 동시에 상속받고 구현하는 타입이라는 뜻이다. 결국 **_ServerType</em><strong>이 반환타입이 되겠다.
이렇게 불특정한 타입<T>이지만 어느정도의 경계는 정해주고 추론 할 수 있도록 해주는 것이 &quot;</strong>경계가 있는 타입 파라미터(Bounded Type Parameter)**&quot;라고 한다.</p>
<h3 id="servertypeconverter-클래스">ServerTypeConverter 클래스</h3>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/0d51d0d1-8b0a-4fd3-8b73-fe61ebaa8574/image.png" alt=""> </p>
<p><em><strong>ServerTypeConverter</strong>_클래스는 부모클래스인 **_AbstractEnumCodeAttributeConverter</em>**클래스에 자기 자신을 넘겨 타입을 정의한다.</p>
<h3 id="enumvalueutils-클래스">EnumValueUtils 클래스</h3>
<p><img src="https://velog.velcdn.com/images/stay_o2o/post/0240d412-c7ff-499c-a2ed-e68b3348dcbe/image.png" alt="">마지막으로 <em><strong>AbstractEnumCodeAttributeConverter</strong>_클래스에서 호출하는 **_EnumBalueUtils</em>** 클래스이다. 이는 <strong>Util</strong>클래스로 <strong>final</strong>클래스로 정의하고 메서드를 <strong>static</strong>으로 선언한다. 
메서들의 기능을 하나씩 보겠다.</p>
<ul>
<li><strong>toDBCode(<em>T db</em>)</strong> : 파라미터로 ServerType이 들어오면 해당 필드의 code를 반환해주고, 파라미터가 null로 들어오면 빈문자열&quot;&quot;을 반환한다.</li>
<li><strong>toEntityCode(<em>Class<T> enumClass, String dbCode</em>)</strong> : 파라미터로 ServerType과 code가 넘어오면 ServerType을 EnumSet으로 만들어 stream으로 순회하면서 파라미터 code와 맞는 필드를 찾아 반환한다. 역시 파라미터로 들어온 code가 비어있으면 null을 반환한다.</li>
</ul>
<hr>
<h1 id="흐름-정리">흐름 정리</h1>
<ol>
<li>Repository에서 INSERT 혹은 SELECT를 하기위해 쿼리 실행.</li>
<li>Entity에 <em><strong>@Convert</strong></em> 어노테이션이 있는 필드가 있으면 해당 Converter 호출. -&gt; <strong><em>ServerTypeConverter클래스</em></strong></li>
<li>AttributeConverter클래스를 구현하는 <strong>AbstractEnumCodeAttributeConverter</strong>를 호출.</li>
<li>Entity -&gt; DB컬럼 인 경우 : <strong><em>convertToDatabaseColumn()</em></strong> 실행
 DB컬럼 -&gt; Entity 인 경우 : <strong><em>convertToEntityAttribute()</em></strong> 실행</li>
<li>EnumValueUtils클래스의 Bounded Type Parameter로 되어있는 static 메서드 실행.</li>
</ol>
<hr>
<h1 id="마치며">마치며....</h1>
<p>처음에 무지성으로 찾아보며 개발하다가 이렇게 글로 정리하려니 꽤나 힘든 일이다. 하지만 이렇게 정리하니 Converter가 어떤 동작을 하는지 조금은 이해가 가고, 사실 Converter도 중요하지만 Bounded Type Parameter라는 엄청난 수확이 있었던 것 같다. </p>
<p>여기까지 이 글을 읽어주신 분들께 정말 감사하고 피드백 환영입니다!! 글 쓰는 연습좀 더 해야겠습니다...</p>
<hr>
<h2 id="참조">참조</h2>
<p><a href="https://techblog.woowahan.com/2600/">https://techblog.woowahan.com/2600/</a>  -&gt; AttributeConverter
<a href="https://thecodinglog.github.io/java/2020/12/09/java-generic-class.html">https://thecodinglog.github.io/java/2020/12/09/java-generic-class.html</a> -&gt; Bounded Type Parameter</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[고객사 제품관리 사내서비스 리팩토링]]></title>
            <link>https://velog.io/@stay_o2o/%EA%B3%A0%EA%B0%9D%EC%82%AC-%EC%A0%9C%ED%92%88%EA%B4%80%EB%A6%AC-%EC%82%AC%EB%82%B4%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@stay_o2o/%EA%B3%A0%EA%B0%9D%EC%82%AC-%EC%A0%9C%ED%92%88%EA%B4%80%EB%A6%AC-%EC%82%AC%EB%82%B4%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Sat, 16 Jul 2022 06:35:46 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>현재 팀 내에서 고객사의 제품을 관리하는 사내서비스를 리팩토링 하는 중이다. 편하게 Admin페이지라고 하겠다. 이 Admin페이지는 입사하고 얼마 안되서 만들어 져서 내가 개발에 참여하진 않았는데 JPA를 공부해 보고 싶었던 터였고 Admin페이지의 Back단은 Springboot와 JPA로 개발되었다고 해서 이번 기회로 주니어 개발자들끼리 리펙토링을 해보기로 했다.</p>
<p>Front는 React로 두명의 팀원이 맡게 되었고, 나는 Back을 맡기로 했다.</p>
<p>Admin페이지의 리팩토링을 진행하는 이유는 다음과 같다.</p>
<blockquote>
<ol>
<li>반복되는 작업과 불필요한 기능 제거.</li>
<li>UI/UX 개선</li>
<li>사내서비스이고 빠르고, 편하게 개발하다보니 정리(naming, 폴더구조...)가 되어 있지 않음.</li>
</ol>
</blockquote>
<p>시간 날 때마다 눈여겨 보곤 하는데, JPA를 공부하면서 개발하다 보니 좀 개발이 더딘 감이 있는 것 같다.
개발 하면서 또 공부하면서 얻었던 지식들을 정리해보려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이직준비 - 포트폴리오 작성]]></title>
            <link>https://velog.io/@stay_o2o/%EA%B2%BD%EB%A0%A5-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%EC%9E%91%EC%84%B1</link>
            <guid>https://velog.io/@stay_o2o/%EA%B2%BD%EB%A0%A5-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%EC%9E%91%EC%84%B1</guid>
            <pubDate>Tue, 17 May 2022 01:42:52 GMT</pubDate>
            <description><![CDATA[<h2 id="현재-맡은-업무">현재 맡은 업무</h2>
<p>현재 본인은 솔루션회사에 재직중인데 주요 업무는 이러하다.</p>
<pre><code>1. 고객사 서버에 신규 솔루션 구축
2. 솔루션 구축 시 고객사의 커스터마이징 요청이 있으면 해당 커스터마이징 구현 하여 구축.
3. 고객사가 솔루션 사용중 원하는 기능 추가 개발.
4. ~~고객사 서버 운영 (서버리소스 모니터링/버그픽스...)~~ -&gt; 후에 운영/개발이 분리되며 본인은 개발만 맡게 됨.</code></pre><h2 id="미리미리">미리미리</h2>
<p>내가 10월에 입사했으니 10월 ~ 5월 이면 약 8개월이 다 되어간다. 8개월이 절대 짧은시간이 아니라는걸 알고있다. 하지만 8개월동안 뭐를 배우고 뭐를 해봤나라고 누군가 묻는다면 솔직히 신나서 말할거리가 없다. 대부분의 업무가 커스터마이징 혹은 기능 추가개발이다 보니 프로젝트를 하나 잡아서 진행하는 것도 아니라서 자랑스럽게 말할거리도, 결과물도 내놓을게 없다는 생각이 들었다.
그래도 아무 기록도 없는 것 보다 조금이라도 기록을 남겨야 나중에 이력서에 끄적이기라도 할 것 같다.</p>
<h2 id="이런-상황에-대한-조언">이런 상황에 대한 조언</h2>
<p>본인은 오픈카톡방 중 백엔드 개발자 방에 들어가있다. 여기서 다양한 정보를 얻을 수 있고 개발하면서 또는 취업에 관련한 조언들을 들을 수 있었다. 감사하게도 정말 친절하게 조언해 주셔서 위와같은 상황에 대해 어떻게 대처할 것인지 방향을 잡을 수 있을 것 같다. 간단하지만 정리 해보자면,</p>
<blockquote>
<p>커스터마이징 할때 특정한 부분을 고려해서 구현를 했고 다른 방법과 이런 부분이 달라서 좋다.<br>
추가개발할때 <del>문제(또는 요구사항)를 해결하기 위해 ~</del>를 고민했고 ......</p>
</blockquote>
<blockquote>
<p>개선한 일 위주로 적고 그보단 제가 할줄 아는거를 기초 활용, 실무 활용, 학습중 이런식으로 나누고 각 단계가 어느 정도인지를 자세하게 적음<br>
예시) 
기초 활용 가능 : 자주 사용하는 명령어를 익숙하게 사용 할 수 있고 모르는 내용은 공식 문서를 참고하여 수행 할 수 있다.
기본 적인 기능 구현을 외부 도움 없이 할 수 있다.</p>
</blockquote>
<p>이런 방향으로 조언해 주셨다. 지금 생각해 보니 추가개발 할 때는 기존에 있던 기능들과 충돌이 나지 않아야 하기 때문에 조심했던 기억이 있다.
지금까지 개발했던 부분들 중에 가물가물한 부분들이 몇 있는데, SVN에 Log들을 보면서 기억을 살려 어떤 부분들을 조심하고 생각하면서 구현했는지, 왜 이렇게 구현했는지를 이제 기록해보려고 한다.</p>
<p>일찍 일어나는 새가 벌레를 먹는다고 미리미리 준비해서 일이 닥쳤을때 당황하지 않고 해결하자!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA - OSIV]]></title>
            <link>https://velog.io/@stay_o2o/JPA-OSIV</link>
            <guid>https://velog.io/@stay_o2o/JPA-OSIV</guid>
            <pubDate>Fri, 13 May 2022 00:46:52 GMT</pubDate>
            <description><![CDATA[<p>spring.jpa.open-in-view: true (default)
-&gt; 장점 : API호출이 끝나는 시점까지 커넥션을 들고있어서(영속성 컨텍스트가 살아있어서) 컨트롤러 또는 view단에서도 LAZY로딩이 가능하다. 
-&gt; 단점 : 커넥션을 계속 들고있게되면 로직이 오래걸리는경우 커넥션일 말라버릴 수 있음.</p>
<p>spring.jpa.open-in-view: false
-&gt; 장점 : 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고, 커넥션도 반환한다. 그러므로 커넥션 리소스를 낭비하지 않는다.
-&gt; 단점 : OSIV를 끄면 모든 지연로딩을 트랜잭션(Service) 안에서 처리해야한다.</p>
<p>성능을 중요하게 생각한다면 OSIV를 끄고 비즈니스 로직(Command)과 view용 쿼리를 분리하는 것이다.</p>
<p>OrderService
    OrderService: 핵심 비즈니스 로직
    OrderQueryService: 화면이나 API에 맞춘 서비스 (주로 읽기 전용 트랜잭션 사용)</p>
<p>쉽게 생각한다면,
고객서비스의 실시간 API는 OSIV를 끄고,
ADMIN처럼 커넥션을 많이 사용하지 않는다면 OSIV를 켜는것이 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA 8 - 메소드 레퍼런스]]></title>
            <link>https://velog.io/@stay_o2o/JAVA-8-%EB%A9%94%EC%86%8C%EB%93%9C-%EB%A0%88%ED%8D%BC%EB%9F%B0%EC%8A%A4</link>
            <guid>https://velog.io/@stay_o2o/JAVA-8-%EB%A9%94%EC%86%8C%EB%93%9C-%EB%A0%88%ED%8D%BC%EB%9F%B0%EC%8A%A4</guid>
            <pubDate>Thu, 12 May 2022 05:45:37 GMT</pubDate>
            <description><![CDATA[<h2 id="메소드-레퍼런스">메소드 레퍼런스</h2>
<p>람다가 하는일이 기존 메소드 또는 생성하는 호출하는 거라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현할 수 있음.</p>
<pre><code>//Greeting 클래스
public class Greeting
{
    private String name;

    public Greeting(){

    }

    public Greeting(String name){
        this.name = name;
    }
    public String hello(String name){
        return &quot;hello&quot; + name;

    }
    public static String hi(String name){
        return &quot;hi &quot; + name;
    }
}</code></pre><h3 id="스태틱-메소드-참조">스태틱 메소드 참조</h3>
<pre><code>UnaryOperator&lt;String&gt; hi = Greeting::hi;
hi.apply(&quot;Stay&quot;);
// 출력 : &quot;hi Stay&quot;</code></pre><h3 id="특정-객체의-인스턴스-메소드-참조">특정 객체의 인스턴스 메소드 참조</h3>
<pre><code>Greeting greeting = new Greeting();
UnaryOperator&lt;String&gt; hello = greeting::hello;
hello.apply(&quot;Stay&quot;);
//출력 : &quot;hello Stay&quot;</code></pre><h3 id="생성자-참조">생성자 참조</h3>
<pre><code>Supplier&lt;Greeting&gt; newGreeting = Greeting::new;
Greeting greeting = newGreeting.get();
//Greeting 타입의 greeting이 생성.</code></pre><h3 id="임의-객체의-인스턴스-메소드-참조">임의 객체의 인스턴스 메소드 참조</h3>
<pre><code>String[] names = {&quot;KEE&quot;,&quot;WHIE&quot;,&quot;TOBY&quot;};
Arrays.sort(names, String::compareToIgnoreCase);</code></pre><p>compareToIgnoreCase는 현재 인스턴스와 파라미터로 들어오는 문자열을 대소문자 상관없이 비교하여 문자열파라미터가 작은경우 0보다 큰 값 반환, 문자열 파라미터가 큰 경우 0보다 작은 값 반환, 같은 경우 0이 반환됨.
이때 인스턴스란 names배열에 있는 요소들 하나하나를 말한다. 그래서 &quot;KEE&quot;와 &quot;WHIE&quot;를 처음에 비교하고 또 &quot;TOBY&quot;를 비교하는 형식이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA 8 - 함수형 인터페이스]]></title>
            <link>https://velog.io/@stay_o2o/JAVA-8-%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@stay_o2o/JAVA-8-%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Thu, 12 May 2022 04:49:29 GMT</pubDate>
            <description><![CDATA[<h1 id="시작하며">시작하며</h1>
<p>백기선님의 &quot;더 자바, JAVA 8&quot; 인프런 강의를 들으면서 정리한 내용이다.
이전에 한번 강의를 쭉 봤었는데, 그때는 &quot;아... 저런게 있나보다&quot; 싶었다. 근데 이제 슬슬 써보려고 하니 가물가물해서 이제는 정리하려고 한다. 다시 한번 강의를 들으면서 정리하는 글이다.</p>
<h2 id="함수형-인터페이스">함수형 인터페이스</h2>
<h3 id="functiont-r">Function&lt;T, R&gt;</h3>
<blockquote>
<p>T 타입을 받아서 R타입을 리턴하는 함수 인터페이스</p>
</blockquote>
<pre><code>Function&lt;Integer, Integer&gt; plus10 = (i) -&gt; i+10;
plus10.apply(5);
// 입력값 : 5 -&gt; 반환값 : 15</code></pre><p>함수 조합용 메서드
    1. andThen : fn1.anThen(fn2).plus10 -&gt; fn1를 먼저 실행하고 fn2를 실행.
    2. compose : fn1.compose(fn2).plus10 -&gt; fn2를 먼저 실행하고 fn1을 실행.</p>
<h3 id="consumer-t">Consumer&lt; T &gt;</h3>
<blockquote>
<p>T타입을 받아서 아무값도 리턴하지 않는 함수 인터페이스</p>
</blockquote>
<pre><code>Consumer&lt;Integer&gt; printT = (i) -&gt; System.out.println(i);
printT.accept(10); 
//10 출력</code></pre><h3 id="supplier-t">Supplier&lt; T &gt;</h3>
<blockquote>
<p>T타입의 값을 제공하는 함수 인터페이스 -&gt; 입력값이 없음.</p>
</blockquote>
<pre><code>Supllier&lt;Integer&gt; get10 = () -&gt; 10;
  System.out.println(get10.get())
  //10출력</code></pre><h3 id="predicate-t">Predicate&lt; T &gt;</h3>
<blockquote>
<p>T타입을 받아서 Boolean을 리턴하는 함수 인터페이스</p>
</blockquote>
<pre><code>Predicate&lt;String&gt; startsWithABC = (s) -&gt; s.startWith(&quot;ABC&quot;);
startsWithABC.test(&quot;ABCDEFG&quot;)
// TRUE!!</code></pre><h3 id="unaryoperator-t">UnaryOperator&lt; T &gt;</h3>
<blockquote>
<p>Function&lt;T, R&gt;의 특수한 형태로, 입력값 하나를 받아서 동일한 타입을 리턴하는 함수 인터페이스
Function&lt;T, R&gt;을 상속받으므로 Function에서 사용하는 메서드들을 사용 가능함.</p>
</blockquote>
<pre><code>UnaryOperator&lt;Integer&gt; plus10 = (i) -&gt; i+10;
plus10.apply(5);
// 입력값 : 5 -&gt; 반환값 : 15</code></pre><p>백기선님 인프런 강의
출처 : <a href="https://inf.run/QUGA">https://inf.run/QUGA</a></p>
]]></description>
        </item>
    </channel>
</rss>