<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sangkyu-bae.log</title>
        <link>https://velog.io/</link>
        <description>기록에 성장을</description>
        <lastBuildDate>Tue, 06 Jan 2026 06:07:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sangkyu-bae.log</title>
            <url>https://velog.velcdn.com/images/sangkyu-bae/profile/e073c850-31ac-4395-8bfd-729e51072bc3/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sangkyu-bae.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sangkyu-bae" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Virtual Thread 도입기: 적용 시 고려사항]]></title>
            <link>https://velog.io/@sangkyu-bae/Virtual-Thread-%EB%8F%84%EC%9E%85%EA%B8%B0-%EC%A0%81%EC%9A%A9-%EC%8B%9C-%EA%B3%A0%EB%A0%A4%EC%82%AC%ED%95%AD</link>
            <guid>https://velog.io/@sangkyu-bae/Virtual-Thread-%EB%8F%84%EC%9E%85%EA%B8%B0-%EC%A0%81%EC%9A%A9-%EC%8B%9C-%EA%B3%A0%EB%A0%A4%EC%82%AC%ED%95%AD</guid>
            <pubDate>Tue, 06 Jan 2026 06:07:13 GMT</pubDate>
            <description><![CDATA[<h3 id="🤔-배치-처리-그냥-멀티스레드로-돌리면-되는-거-아닐까">🤔 배치 처리, 그냥 멀티스레드로 돌리면 되는 거 아닐까?</h3>
<p>10시간 걸리던 배치 처리를 30분으로 줄였다고 하면, 많은 분들이 이렇게 질문합니다.</p>
<p>“그냥 멀티스레드 쓰면 되는 거 아니야?”</p>
<p>저 역시 처음엔 그렇게 생각했습니다.
스레드 풀을 키우고, 병렬 처리하면 해결될 문제라고 생각했었습니다.</p>
<p>하지만 외부 API 호출이 중심인 배치 작업에서는
플랫폼 스레드 기반 멀티스레드가
생각보다 빠르게 한계에 부딪힌다는 걸 직접 경험했습니다.</p>
<p>이 글에서는 Java 21의 Virtual Thread를 도입하면서
왜 기존 멀티스레드 방식이 잘 맞지 않았는지,
그리고 Virtual Thread가 <strong>빛을 발한 조건과 그렇지 않은 조건</strong>을
이론이 아닌 실무 관점에서 정리해보려 합니다.</p>
<hr>
<h3 id="1-문제-상황-10시간-걸리는-배치">1. 문제 상황: 10시간 걸리는 배치</h3>
<p><strong>비즈니스 요구사항</strong>
저희는 매일 새벽, 차량 담보대출 약 30,000건에 대해 외부 기관 API를 호출해 차량 원부 정보를 조회합니다. 대출 리스크 관리를 위한 필수 작업이죠.</p>
<pre><code class="language-java">// 기존 코드 (단순화)
for (Loan loan : loans) {
    VehicleInfo info = externalApiClient.getVehicleInfo(loan.getVehicleNumber());
    loan.updateVehicleInfo(info);
    loanRepository.save(loan);
}</code></pre>
<p>30,000건 × 평균 1.2초 = 약 10시간
00시에 시작해도 오전 10 시가 넘어야 끝나는 상황.</p>
<h3 id="2--1차-시도-platform-thread-기반-멀티스레드">2.  1차 시도: Platform Thread 기반 멀티스레드</h3>
<pre><code class="language-java">ExecutorService executor = Executors.newFixedThreadPool(50);

for (Loan loan : loans) {
    executor.submit(() -&gt; {
        VehicleInfo info = externalApiClient.getVehicleInfo(loan.getVehicleNumber());
        loanRepository.save(loan);
    });
}</code></pre>
<p><strong>Platform Thread의 한계</strong></p>
<ul>
<li>Context Switching 비용 증가</li>
<li>I/O 대기 시간 동안 스레드가 블로킹되어 낭비</li>
</ul>
<p><strong>현재우리의 상황</strong></p>
<blockquote>
</blockquote>
<ul>
<li><p>I/O 대기 : 95% 이상 (외부 API 응답대기)
-&gt; CPU-bound가 아닌 I/O bound작업
-&gt; Platform Thread는 비효율적</p>
<hr>
<h3 id="3-virtural-thread-도입">3. Virtural Thread 도입</h3>
</li>
</ul>
<p><strong>Virtual Thread란?</strong></p>
<ul>
<li>Java 21에서 정식 도입 (Project Loom)</li>
<li>경량 스레드: 1개당 수 KB 메모리</li>
<li>수백만 개 생성 가능</li>
<li>I/O 대기 시 자동으로 다른 작업 수행 (Non-blocking)</li>
</ul>
<p><strong>제약사항</strong></p>
<blockquote>
<p>외부 기관 API 제약:</p>
</blockquote>
<ul>
<li>동시 호출 제한</li>
<li>Rate Limit 초과 시: 429 에러</li>
</ul>
<p>→ Virtual Thread로 10,000개 동시 호출하면 안 됨
→ Rate Limiter 필수</p>
<hr>
<h3 id="4-실제구현">4. 실제구현</h3>
<p>4-1. Virtual Thread Executor 생성</p>
<pre><code class="language-java">@Configuration
public class VirtualThreadConfig {

    @Bean(name = &quot;vehicleBatchExecutor&quot;)
    public ExecutorService vehicleBatchExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }
}</code></pre>
<p>핵심: newVirtualThreadPerTaskExecutor()는 작업마다 새 Virtual Thread 생성
→ 스레드 풀 크기 걱정 없음
4-2. Rate Limiter 적용</p>
<pre><code class="language-java">@Component
public class VehicleInfoBatchProcessor {

    private final RateLimiter rateLimiter;
    private final ExecutorService virtualThreadExecutor;

    public VehicleInfoBatchProcessor(
            ExecutorService executor,
            double rateLimiter) {
        this.virtualThreadExecutor = executor;
        // 초당 요청 설정
        this.rateLimiter = RateLimiter.create(rateLimiter);
    }

    public void processBatch(List&lt;Loan&gt; loans) {
        List&lt;CompletableFuture&lt;Void&gt;&gt; futures = loans.stream()
            .map(loan -&gt; CompletableFuture.runAsync(() -&gt; {
                rateLimiter.acquire(); // Rate Limit 대기
                processLoan(loan);
            }, virtualThreadExecutor))
            .toList();

        // 모든 작업 완료 대기
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .join();
    }

    private void processLoan(Loan loan) {
        try {
            VehicleInfo info = externalApiClient.getVehicleInfo(
                loan.getVehicleNumber()
            );
            loan.updateVehicleInfo(info);
            loanRepository.save(loan);

        } catch (Exception e) {
            log.error(&quot;Failed to process loan: {}&quot;, loan.getId(), e);
            // 실패 건은 별도 기록
            failureRepository.save(new FailedLoan(loan.getId(), e.getMessage()));
        }
    }</code></pre>
<hr>
<h4 id="5-결과-및-성능비교">5. 결과 및 성능비교</h4>
<p><strong>처리시간비교</strong></p>
<table>
<thead>
<tr>
<th><strong>방식</strong></th>
<th><strong>처리시간</strong></th>
<th><strong>개선율</strong></th>
</tr>
</thead>
<tbody><tr>
<td>동기방식</td>
<td>10시간</td>
<td>-</td>
</tr>
<tr>
<td>Virtual Thread</td>
<td>30분</td>
<td>96%</td>
</tr>
</tbody></table>
<hr>
<h4 id="6-실무-적용-시-주의사항">6. 실무 적용 시 주의사항</h4>
<p><strong>Virtual Thread가 만능은 아니다</strong>
<strong>1. CPU-bound 작업에는 효과 없음</strong></p>
<pre><code class="language-java">나쁜 예: CPU 집약적 작업
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i &lt; 10000; i++) {
    executor.submit(() -&gt; {
        // 복잡한 암호화 연산
        heavyCryptography();
    });
}
// → Platform Thread보다 느릴 수 있음</code></pre>
<p>*<em>Virtual Thread도 결국 Platform Thread *</em> 위에서 실행됨
CPU 작업은 대기 시간이 없어서 Context Switching만 증가</p>
<p><strong>2. Synchronized 블록 주의</strong></p>
<pre><code class="language-java">주의: Virtual Thread에서 synchronized
public synchronized void updateCounter() {
    counter++;
}</code></pre>
<p>synchronized는 Platform Thread를 Pinning(고정)시킴
→ Virtual Thread의 장점 상실
해결책: ReentrantLock 사용</p>
<pre><code class="language-java">private final ReentrantLock lock = new ReentrantLock();

public void updateCounter() {
    lock.lock();
    try {
        counter++;
    } finally {
        lock.unlock();
    }
}</code></pre>
<hr>
<p><strong>적용하기 좋은 케이스</strong>
<strong>1. 외부 API 호출이 많은 경우</strong>
<strong>2. DB I/O가 많은 경우</strong></p>
<hr>
<h4 id="트러블-슈팅">트러블 슈팅</h4>
<p><strong>1. OutOfMemoryError</strong>
원인:</p>
<pre><code class="language-java">Future를 모두 메모리에 보관
List&lt;CompletableFuture&lt;Void&gt;&gt; futures = loans.stream()
    .map(loan -&gt; CompletableFuture.runAsync(...))
    .toList(); // 30,000개의 Future 객체</code></pre>
<p>해결 :
<strong>Chunk 단위 처리</strong></p>
<pre><code class="language-java">개선: 1000개씩 나눠서 처리
int chunkSize = 1000;
for (int i = 0; i &lt; loans.size(); i += chunkSize) {
    List&lt;Loan&gt; chunk = loans.subList(
        i, 
        Math.min(i + chunkSize, loans.size())
    );

    List&lt;CompletableFuture&lt;Void&gt;&gt; futures = chunk.stream()
        .map(loan -&gt; CompletableFuture.runAsync(...))
        .toList();

    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
        .join(); // 1000개 완료 후 다음 Chunk
}</code></pre>
<hr>
<h4 id="결론">결론</h4>
<p>Virtual Thread를 써야 하는 경우</p>
<ul>
<li>I/O 대기 시간이 긴 작업 (외부 API, DB 조회)</li>
<li>동시 처리가 필요한 대량 작업</li>
</ul>
<p>Virtual Thread를 쓰면 안 되는 경우</p>
<ul>
<li>CPU 집약적 연산 (암호화, 이미지 처리)</li>
<li>짧은 작업 (수 ms 이내)</li>
<li>레거시 코드에 ThreadLocal이 많은 경우</li>
</ul>
<hr>
<h4 id="마지막으로">마지막으로</h4>
<p>15시간 걸리던 배치를 30분으로 줄인 건 단순히 Virtual Thread를 적용했기 때문이 아닙니다.
문제의 본질을 파악하고 (I/O-bound)
적절한 도구를 선택하고 (Virtual Thread)
실무 제약사항을 고려한 (Rate Limiter, Chunk 처리)
덕분입니다.
새로운 기술을 도입할 때는 항상 &quot;왜?&quot;를 먼저 습관을 들이는게 중요한것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[에러 격벽 처리: 외부 API 예외로부터 도메인을 보호하라]]></title>
            <link>https://velog.io/@sangkyu-bae/%EC%97%90%EB%9F%AC-%EA%B2%A9%EB%B2%BD-%EC%B2%98%EB%A6%AC-%EC%99%B8%EB%B6%80-API-%EC%98%88%EC%99%B8%EB%A1%9C%EB%B6%80%ED%84%B0-%EB%8F%84%EB%A9%94%EC%9D%B8%EC%9D%84-%EB%B3%B4%ED%98%B8%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/%EC%97%90%EB%9F%AC-%EA%B2%A9%EB%B2%BD-%EC%B2%98%EB%A6%AC-%EC%99%B8%EB%B6%80-API-%EC%98%88%EC%99%B8%EB%A1%9C%EB%B6%80%ED%84%B0-%EB%8F%84%EB%A9%94%EC%9D%B8%EC%9D%84-%EB%B3%B4%ED%98%B8%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sat, 10 May 2025 06:48:22 GMT</pubDate>
            <description><![CDATA[<h3 id="🤔-외부-api-예외-그대로-던져도-괜찮을까">🤔 외부 API 예외, 그대로 던져도 괜찮을까?</h3>
<p>실무에서 외부 시스템과 통신하는 코드는 필연적으로 다양한 예외를 마주합니다.</p>
<p>하지만 종종 이런 예외를 아래와 같이 직접적으로 던지는 경우를 봅니다:</p>
<pre><code class="language-java">java
복사편집
try {
    // 외부 API 호출
} catch (IOException e) {
    throw e; 
}
</code></pre>
<p>이렇게 되면 무슨 문제가 생길까요?</p>
<hr>
<h3 id="🧱-문제점">🧱 문제점</h3>
<ul>
<li><p><strong>도메인 계층이 외부 시스템의 예외 타입에 오염</strong>됨</p>
<p>  → <code>IOException</code> 같은 라이브러리/네트워크 예외가 서비스 레이어까지 흘러들어옴</p>
</li>
<li><p><strong>예외 처리 일관성 붕괴</strong></p>
<p>  → 같은 실패인데 어떤 API는 500, 어떤 API는 400으로 응답하거나, 로그 메시지도 제각각</p>
</li>
<li><p><strong>장애 추적 어려움</strong></p>
<p>  → 로그에서 API 호출 실패인지, 파싱 실패인지, 클라이언트 버그인지 분간이 안 됨</p>
</li>
<li><p><strong>테스트와 시뮬레이션이 어려움</strong></p>
<p>  → 예외를 모킹할 수 없고, 실제 API 서버에 의존</p>
</li>
</ul>
<hr>
<h3 id="🛡-그래서-에러-격벽-처리란">🛡 그래서, 에러 &#39;격벽 처리&#39;란?</h3>
<p><strong>격벽 처리</strong> 는 외부 예외를 시스템 내부에서 <em>완전히 캡슐화</em>하여,</p>
<p>내부 도메인 또는 클라이언트로 전파하지 않는 설계 패턴입니다.</p>
<pre><code class="language-java">java
복사편집
// 외부 API → HTTP/IO 예외 → APIException (표준화)
// 서비스 레이어 → APIException → CustomException (도메인 에러에 매핑)
// 컨트롤러 → ErrorHandler → 사용자에게 명확한 메시지 전달
</code></pre>
<p>이렇게 되면 &quot;외부 API는 실패할 수 있다&quot;는 전제를 시스템이 안전하게 감싸는 구조가 됩니다.</p>
<hr>
<h3 id="✅-장점-요약">✅ 장점 요약</h3>
<table>
<thead>
<tr>
<th>장점</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>🧼 <strong>계층 간 책임 분리</strong></td>
<td>외부 호출에 대한 실패는 인프라 계층에서 흡수, 도메인 계층은 순수하게 유지</td>
</tr>
<tr>
<td>📖 <strong>예외 추론이 쉬움</strong></td>
<td>CustomException 기반 메시지로 어떤 이유로 실패했는지 직관적으로 파악 가능</td>
</tr>
<tr>
<td>📦 <strong>라이브러리 의존 제거</strong></td>
<td>컨트롤러나 서비스가 <code>OkHttp</code>, <code>WebClient</code>, <code>IOException</code> 등을 몰라도 됨</td>
</tr>
<tr>
<td>🧪 <strong>테스트 용이성 향상</strong></td>
<td>특정 예외를 트리거해 다양한 시나리오 테스트 가능 (Mock APIException 등)</td>
</tr>
<tr>
<td>🪵 <strong>로깅 일관성</strong></td>
<td>모든 에러 발생 시 동일한 형태로 로그 출력 (request url, code, body 등)</td>
</tr>
<tr>
<td>🔐 <strong>보안/회피 로직 통합 가능</strong></td>
<td>민감한 메시지 마스킹, 리트라이 로직 삽입, 페일오버 처리 등 삽입 가능</td>
</tr>
<tr>
<td>🚀 <strong>확장성 확보</strong></td>
<td>다수의 외부 API가 생겨도 동일한 패턴으로 격벽 처리 → 유지보수 쉬움</td>
</tr>
</tbody></table>
<hr>
<h3 id="🧱-실제-예-외부-api-처리">🧱 실제 예: 외부 API 처리</h3>
<pre><code class="language-java">java
복사편집
try {
    String res = get(url, headers);
    return objectMapper.readValue(res, Custom.class);
} catch (APIException e) {
    throw new CustomException(CustomError.getCustomError(e), e.getResMsg());
}
</code></pre>
<p>이 구조 하나로 다음을 동시에 만족시킵니다:</p>
<ul>
<li>외부 API 상태나 에러 포맷이 바뀌어도 서비스 레이어는 무관</li>
<li>내부 도메인은 <code>도메인에러</code> 같은 의미 있는 에러 코드만 보고 판단 가능</li>
<li>HTTP 408, 504, 404 등 다양한 상황을 <code>ErrorHandler</code>에서 통일된 메시지로 반환 가능</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kafka와 Redis로 초과 발급 없는 쿠폰 시스템 구축하기]]></title>
            <link>https://velog.io/@sangkyu-bae/Kafka%EC%99%80-Redis%EB%A1%9C-%EC%B4%88%EA%B3%BC-%EB%B0%9C%EA%B8%89-%EC%97%86%EB%8A%94-%EC%BF%A0%ED%8F%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sangkyu-bae/Kafka%EC%99%80-Redis%EB%A1%9C-%EC%B4%88%EA%B3%BC-%EB%B0%9C%EA%B8%89-%EC%97%86%EB%8A%94-%EC%BF%A0%ED%8F%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 25 Mar 2025 12:11:13 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하면서 선착순 쿠폰 발급을 진행시 중복발급이 발생하여 이를 해결하기 위해 여러 고민들을 하였고 <strong>Kafka + Redis</strong>를 활용한 <strong>대기열 시스템</strong>으로 개발하였습니다.</p>
<h2 id="1-기존-문제점">1. 기존 문제점</h2>
<p><img src="https://velog.velcdn.com/images/sangkyu-bae/post/2b805959-fb48-4146-9a68-8ba4ccb1760f/image.png" alt=""></p>
<blockquote>
<p><strong>case</strong>
이벤트성 상품 할인 쿠폰 이벤트 시 중복하여 쿠폰이 발생하는 경우</p>
</blockquote>
<p>이러한 문제점이 발생했습니다, 그렇담 왜 이러한 중복 발급이 발생하는걸까요?</p>
<blockquote>
<p>선착순 쿠폰 발급을 동시에 많은 사용자가 요청 시 쿠폰 수량을 줄이기전에 다른 트랜잭션이 읽을 경우 중복 발급이 발생되어집니다.    </p>
</blockquote>
<hr>
<h2 id="모색-방안">모색 방안</h2>
<p>현재 프로젝트의 경우 분산환경에서 N개의 서버를 구동중에 있습니다.</p>
<h3 id="낙관적락-비관적락">낙관적락 비관적락</h3>
<p>이러한 락을 처리하기 위해 보편적으로 많이 사용하는 낙관적락 비관적락이 있으나 현재 저의 상황에서는 맞지 않다 판단하여 적용하지 않았습니다.</p>
<h4 id="1-낙관적-락을-채택하지-않은-이유">1. 낙관적 락을 채택하지 않은 이유</h4>
<ul>
<li>낙관적 락은 version 필드를 활용하여 동시성을 제어하는데, <strong>쿠폰 발급 요청이 동시다발적으로 들어오는 상황에서는 충돌이 빈번하게 발생</strong>하여 롤백을 직접 해주어야 하기에 성능이 저하되었고, 때문에 이를 채택하지 않았습니다.</li>
</ul>
<h4 id="2-비관적-락을-채택하지-않은-이유">2. 비관적 락을 채택하지 않은 이유</h4>
<ul>
<li>비관적 락은 하나의 트랜잭션이 특정 데이터(쿠폰)를 갱신하는 동안 다른 트랜잭션의 접근을 차단하는 방식입니다. 이런 경우 데이터베이스 락이 병목이 발생하여, 성능 저하가 발생하여 이를 채택하지 않았습니다.</li>
</ul>
<p>그럼에도 불구하고 낙관적 락, 비관적 락은 많은 부하가 있지 않은 경우 훌륭한 선택지가 될 수 있다고 생각합니다.</p>
<h3 id="redisson-기반-redis-분산락-적용">Redisson 기반 Redis 분산락 적용</h3>
<p>기존의 Lettuce 방식은 스핀락 기반으로 동작하여 Redis에 지속적으로 요청을 보내는 방식이었습니다. 이는 Redis 부하를 유발하는 단점이 있었기 때문에, <strong>Redisson의 Pub/Sub</strong> 기반 분산락을 적용하여 성능을 개선하였습니다.</p>
<h4 id="redisson-적용-장점">Redisson 적용 장점:</h4>
<ul>
<li>불필요한 Redis 요청 감소: 락 대기 시 Pub/Sub 방식으로 이벤트를 수신하여, 스핀락 방식보다 효율적으로 관리 가능</li>
<li>데드락 방지: 락의 만료 시간을 설정하여 불필요한 리소스 점유 방지</li>
</ul>
<h4 id="분산-락을-적용하지-않은-이유">분산 락을 적용하지 않은 이유</h4>
<ul>
<li>부하테스트 시 높은 동시 요청이 발생하면 TPS 감소 및 서버 부하 증가 문제가 발생하여 이를 채택하지 않았습니다.</li>
</ul>
<p>여러 서버가 있는 분산 환경에서 분산락은 좋은 선택지가 될 수 있다 생각합니다.</p>
<hr>
<h2 id="kafka-대기열--redis-sorted-set-적용">Kafka 대기열 + Redis Sorted Set 적용</h2>
<p>앞선 문제들을 해결하기위해 Kafka + Redis를 활용한 대기열을 도입하였습니다.</p>
<p><strong>아키텍처</strong>
<img src="https://velog.velcdn.com/images/sangkyu-bae/post/a9a007d2-cd86-4fe4-8b91-fc62deeaab15/image.png" alt=""></p>
<h4 id="주요-방식">주요 방식</h4>
<p>** 1. Kafka를 활용한 요청 대기열 구성 **</p>
<ul>
<li>사용자가 쿠폰을 요청하면, Kafka Producer가 해당 요청을 메시지로 변환하여 대기열(Kafka 토픽)에 전송합니다.</li>
<li>이때, Kafka 브로커 개수, 파티션 수, ISR 등의 설정을 최적화하여 메시지가 빠르고 안정적으로 처리되도록 구성하며. (추후 이부분에 대한 게시글 작성하겠습니다.)</li>
<li>대기열 서버에서 순차적으로 요청을 처리하여 과부하를 방지합니다.</li>
</ul>
<p>** 2. Redis Sorted Set을 활용한 쿠폰 발급 요청 정렬 **</p>
<ul>
<li>Redis sorted Set을 활용하면 중복 요청 방지 및 순서 보장이 가능합니다.</li>
<li>Kafka에서 전달된 요청을 도착 시간 기준으로 Redis Sorted Set에 저장 합니다.</li>
</ul>
<p>** 3. 쿠폰 발급 배치를 활용한 빠른 Insert 처리 **</p>
<ul>
<li>배치 서버에서 Redis에 적재된 시간순으로 적재된 발급정보들을 읽어옵니다.(지정된 수 만큼)</li>
<li>JDBC Bulk Insert를 활용하여 DB에 적재합니다.</li>
<li>Redis에 저장된 정보를 삭제하며, 중복 발급 방지를 위한 TTL 설정이 포함된 정보를 Redis에 다시 적재하게 됩니다.</li>
</ul>
<h2 id="성능-비교">성능 비교</h2>
<h4 id="락-적용-x-기본">락 적용 X 기본</h4>
<table>
<thead>
<tr>
<th><strong>쓰레드</strong></th>
<th><strong>호출수</strong></th>
<th><strong>TPS</strong></th>
<th><strong>초과발급수</strong></th>
</tr>
</thead>
<tbody><tr>
<td>2</td>
<td>8,597</td>
<td>152.4</td>
<td>247</td>
</tr>
<tr>
<td>4</td>
<td>10,793</td>
<td>191.6</td>
<td>672</td>
</tr>
<tr>
<td>8</td>
<td>12,613</td>
<td>231.3</td>
<td>1353</td>
</tr>
<tr>
<td>10</td>
<td>13,947</td>
<td>247.3</td>
<td>1678</td>
</tr>
<tr>
<td>16</td>
<td>14,836</td>
<td>253.4</td>
<td>2850</td>
</tr>
<tr>
<td>#### 4-2-2 redis Redission (pub/sub)분산락 적용</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th><strong>쓰레드</strong></th>
<th><strong>호출수</strong></th>
<th><strong>TPS</strong></th>
<th><strong>초과발급수</strong></th>
</tr>
</thead>
<tbody><tr>
<td>2</td>
<td>4,132</td>
<td>76.1</td>
<td><strong>0</strong></td>
</tr>
<tr>
<td>4</td>
<td>4,653</td>
<td>82.7</td>
<td><strong>0</strong></td>
</tr>
<tr>
<td>8</td>
<td>4,677</td>
<td>85.1</td>
<td><strong>0</strong></td>
</tr>
<tr>
<td>10</td>
<td>4,717</td>
<td>86.8</td>
<td><strong>0</strong></td>
</tr>
<tr>
<td>16</td>
<td>6,243</td>
<td>110.9</td>
<td><strong>0</strong></td>
</tr>
<tr>
<td>#### 4-2-3 kafka + redis 대기열 시스템 적용</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th><strong>쓰레드</strong></th>
<th><strong>호출수</strong></th>
<th><strong>TPS</strong></th>
<th><strong>초과발급수</strong></th>
</tr>
</thead>
<tbody><tr>
<td>2</td>
<td>27,119</td>
<td>500.9</td>
<td><strong>0</strong></td>
</tr>
<tr>
<td>4</td>
<td>32,530</td>
<td>820.0</td>
<td><strong>0</strong></td>
</tr>
<tr>
<td>8</td>
<td>37,742</td>
<td>751.1</td>
<td><strong>0</strong></td>
</tr>
<tr>
<td>10</td>
<td>39,542</td>
<td>758.4</td>
<td><strong>0</strong></td>
</tr>
<tr>
<td>16</td>
<td>46,382</td>
<td>882.3</td>
<td><strong>0</strong></td>
</tr>
</tbody></table>
<hr>
<h2 id="효과">효과</h2>
<ul>
<li>쿠폰 초과 발급(동시성) 문제 해결 - Kafka 대기열과 Redis Sorted Set을 활용해 요청을 순차적으로 처리하면서 동시성 문제를 해결 하였습니다.</li>
<li>서버 부하 감소 - Kafka가 요청을 분산 처리하면서 서버의 부하를 효과적으로 분산할 수 있었습니다.</li>
</ul>
<h2 id="마지막으로">마지막으로</h2>
<p>선착순 쿠폰 발급 시스템의 초과 발급 문제를 해결하기 위해 <strong>Redis 기반 분산락과 Kafka 대기열 시스템을 비교해</strong> 개발을 진행해 보았습니다.</p>
<p>최종적으로 <strong>Kafka와 Redis Sorted Set을 활용한 대기열 시스템이</strong> 높은 성능을 보이며, 초과 발급을 방지하면서도 빠른 응답성을 유지할 수 있었습니다.</p>
<p>선착순 쿠폰 발급뿐만 아니라 동시 요청이 많은 서비스(티켓팅, 한정판 판매 등) 에서도 활용할 수 있다 생각하며 오늘의 글은 여기 까지 작성하도록 하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redis의 Read Through  과 Write back 패턴을 활용하여  상품 랭킹 구현]]></title>
            <link>https://velog.io/@sangkyu-bae/Redis%EC%9D%98-Read-Through-%EA%B3%BC-Write-back-%ED%8C%A8%ED%84%B4%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EC%83%81%ED%92%88-%EB%9E%AD%ED%82%B9-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@sangkyu-bae/Redis%EC%9D%98-Read-Through-%EA%B3%BC-Write-back-%ED%8C%A8%ED%84%B4%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EC%83%81%ED%92%88-%EB%9E%AD%ED%82%B9-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 03 May 2024 10:42:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>개인 프로젝트로 진행하는 쇼핑몰 프로젝트에서 상품 랭킹 서비스 구현을 Redis의 Read Through + Write back 패턴을 활용하여 구현한 내용을 정리 해보자 📒</p>
</blockquote>
<h2 id="🧐기존-랭킹-구현-상태를-살펴보자">🧐기존 랭킹 구현 상태를 살펴보자</h2>
<p>현재 프로젝트에서 사용자가 상품 조회요청을 보내면 Product-service에서 Rank-Service로 조회요청에 대한 이벤트를 발행하고 이를 Rank-service 쪽에서 Consume하여 조회수를 증가 시키고 있습니다!
<img src="https://velog.velcdn.com/images/sangkyu-bae/post/a131c289-e994-4904-b0cb-18adff21cace/image.png" alt=""></p>
<hr>
<h2 id="🤷♂️-대표적인-읽기-전략">🤷‍♂️ 대표적인 읽기 전략</h2>
<blockquote>
<p><strong>Redis의 대표적인 읽기 전략</strong></p>
</blockquote>
<ol>
<li>Look Aside 패턴</li>
<li>Read Through 패턴</li>
</ol>
<h3 id="look-aside-패턴">Look Aside 패턴</h3>
<ul>
<li>반복적인 읽기가 많은 호출에 적합</li>
<li>캐시와 DB가 분리되어 가용 원하는 데이터만 구성하여 캐시에 저장</li>
<li>캐시와 DB가 분리되어 가용되기에 캐시 장애 대비 구성이 되어있다.
Redis 다운시 DB에서 데이터를 읽어 사용</li>
<li>Redis 다운시 DB에 부하 발생</li>
</ul>
<h3 id="read-through-패턴">Read Through 패턴</h3>
<ul>
<li>캐시에서만 읽어오는 전략</li>
<li>Look Aside와 비슷하지만 데이터 동기화를 라이브러리 또는 캐시 제공자에게 위임하는 방식이라는 차이</li>
<li>데이터 조회속도가 느림</li>
<li>데이터 조회를 캐시에 의지하기에, redis 다운시 서비스에 치명적일 수 있음.</li>
<li>캐시와 DB간의 데이터 동기화가 항상 이루어져 데이터 정합성 문제에서 벗어 날 수 있음</li>
</ul>
<h2 id="🤷♂️-redis의-대표적인-쓰기-전략">🤷‍♂️ Redis의 대표적인 쓰기 전략</h2>
<blockquote>
<p>** Redis의 대표적인 쓰기 전략** </p>
</blockquote>
<ol>
<li>Write Back 패턴</li>
<li>Write Through 패턴</li>
<li>Write Around 패턴</li>
</ol>
<h3 id="write-back-패턴">Write Back 패턴</h3>
<ul>
<li>캐시와 DB 동기화를 비동기로 하기 때문에 동기화 과정이 생략</li>
<li>데이터를 저장할때 DB로 바로 쿼리하지 않으며, 일정 주기 배치 작업을 통해 DB 반영</li>
<li>캐시에 모아 놨다가 DB에 쓰기 때문에 부하를 줄일 수 있다</li>
<li>Wirte가 빈번하고 Read를 하는데 많은 양의 Resource가 소모되는 서비스에 적합</li>
<li>캐시에서 오류가 발생하면 데이터를 영구 소실함</li>
</ul>
<h3 id="write-through-패턴">Write Through 패턴</h3>
<ul>
<li>DB, Cach 동시에 데이터를 저장하는 전략</li>
<li>데이터를 저장할때 캐시에 저장한 뒤 DB에 저장 (모아두는 방식이 아님)</li>
<li>DB와 캐시 항상 동기화되어 캐시는 항상 최신의 상태로 유지</li>
<li>데이터 일관성을 유지할 수 있다</li>
<li>데이터 유실이 발생하면 안 되는 상황에 적합</li>
<li>자주 사용되지 않은 불필요한 리소스를 저장</li>
<li>매 요청 마다 Write를 두번 발생시키기에 빈번한 생성, 수정의 경우 성능 이슈가 발생</li>
</ul>
<h3 id="write-around-패턴">Write Around 패턴</h3>
<ul>
<li>Write Though 보다 빠름</li>
<li>모든 데이터는 DB에 저장 (캐시를 저장하지 않음)</li>
</ul>
<h2 id="🤷♂️-왜-랭킹-구현에-write-back--read-through-패턴이-왜-적합-할까">🤷‍♂️ 왜 랭킹 구현에 Write back + Read Through 패턴이 왜 적합 할까?</h2>
<h2 id="읽기-전략">읽기 전략</h2>
<p><strong>1. DB 접근의 최소화</strong>
현재 쇼핑몰의 메인 페이지에서, 상단 헤더 부분에 상품 랭킹에 대한 정보 들을 계속 보여줄 예정이다, 그렇다면 트래픽이 상승할 경우 랭킹 DB에 대한 접근이 많아질 것이고, 이때 부하를 일으킬 것을 가정하였다 </p>
<p><strong>2. Cluster 구성</strong>
일단 Read Through 패턴은 Redis에게 데이터 동기화를 위임하기 때문에 Redis단일로 사용하게 된다면 Redis가 장애를 일으킨다면 상당히 치명적일 수 있다. 하지만 현재 프로젝트에서 Redis Cluster를 구성하여 사용할 것이 판단하여 Redis 장애에 대한 가용성을 높일 수 있을거라 생각되었다</p>
<h2 id="쓰기-전략">쓰기 전략</h2>
<p><strong>1. DB 접근의 최소화</strong>
현재 kafka를 통해 상품 클릭시 product-service에서 이벤트를 발행하고 rank-service에서 이를 consume 하여 사용중이다 이때 많은 사용자가 있으며, 상품클릭에 대한 트래픽이 높다고 가정하고있다, 그렇기 때문에 이벤트 consume시 DB에 직접 write를 한다면 DB에 부하가 가해질 것으로 가정하였고, 이를 해결하기 위해 write-back 패턴을 도입 구체적으로 배치 작업 + jdbc bulk insert를 통해 쓰기작업을 이용하여 DB에 부하를 줄일 수 있을것으로 생각 되었다.</p>
<h2 id="최종-결정-사유">최종 결정 사유</h2>
<p>위에 나열한 이유와 랭킹서비스가 서비스 전체를 보았을때 치명적인 오류를 일으키지 않을것으로 판단하여 선택하게 되었다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redis의 Sorted set을 이용한 상품 랭킹 기능 구현]]></title>
            <link>https://velog.io/@sangkyu-bae/Redis%EC%9D%98-Sorted-set%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%83%81%ED%92%88-%EB%9E%AD%ED%82%B9-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@sangkyu-bae/Redis%EC%9D%98-Sorted-set%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%83%81%ED%92%88-%EB%9E%AD%ED%82%B9-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Mon, 22 Jan 2024 17:20:38 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>개인 프로젝트로 진행하는 쇼핑몰 프로젝트에서 상품 랭킹 서비스 구현을 Redis의 sorted set을 이용해서 구현한 내용을 정리 해보자 📒</p>
</blockquote>
<h2 id="🧐기존-랭킹-구현-상태를-살펴보자">🧐기존 랭킹 구현 상태를 살펴보자</h2>
<p>현재 프로젝트에서 사용자가 상품조회를 요청하게 되면 Product-Service 에서 Rank-Service로 조회요청에 대한 이벤트를 발행 하게 되고 이를 Rank-Service 쪽에선 Consume 하여 상품 ID을 통해 조회수를 증가시키고 있습니다!</p>
<p><img src="https://velog.velcdn.com/images/sangkyu-bae/post/a131c289-e994-4904-b0cb-18adff21cace/image.png" alt=""></p>
<hr>
<h2 id="🤷♂️-rdb를-직접-사용-하지-않고-redis를-사용하는-이유는">🤷‍♂️ RDB를 직접 사용 하지 않고 Redis를 사용하는 이유는?</h2>
<h3 id="1-속도-차이">1. 속도 차이</h3>
<p>랭킹 기능은 현재 사용중인 MySql DB에 저장된 데이터의 count 값을 증가 시켜 구현할 수 있겠으나, 만약 이 데이터 값이 많아 진다면 order by하여 값들을 계산하여 return 해주는 시간이 데이터가 늘어날수록 늘어날 것으로 판단 하였기 때문에 <strong>Redis</strong>를 선택하여 사용하였습니다.</p>
<h3 id="🤔-spring-cache가-아닌-redis를-사용한-이유는">🤔 spring cache가 아닌 Redis를 사용한 이유는?</h3>
<p>일단 저의 프로젝트 구성은 MSA구성으로 분산환경에 맞춰 개발을 진행했기 때문에 이를 공통적으로 관리 할 수 있는 환경이 필요 했고  <strong>Redis</strong>로 선택하게 되었습니다. </p>
<h3 id="2-write-back-패턴-도입">2. write-back 패턴 도입</h3>
<blockquote>
<p>상품을 지속적으로 조회수가 많아지수록 Rank DB에 대한 부하가 심해질것 이며, 이를 해결 하기 위하여 도입하기로 하였으나 이번 주제랑은 다른 관계로 다른 포스팅에서 작성 하도록 하겠습니다!</p>
</blockquote>
<hr>
<h2 id="랭킹-구현에-soted-set이-왜-적합-할까">랭킹 구현에 soted set이 왜 적합 할까?</h2>
<p><strong>로직 구현이 필요없다</strong>
sorted set은 score를 하나하나 비교하여 정렬해줄 필요가 없다 -&gt; score 값을 비교하여 정렬하기 때문이다.</p>
<hr>
<h2 id="빠르게-sorted-set-명령을-알아보자">빠르게 sorted set 명령을 알아보자</h2>
<p>*<em>(1) rank 추가 *</em></p>
<pre><code>$ ZADD &lt;key&gt; &lt;score&gt; &lt;member&gt;</code></pre><p><img src="https://velog.velcdn.com/images/sangkyu-bae/post/b284292f-2439-4429-98b7-7d1bac6ab0d3/image.png" alt=""></p>
<p><strong>(2) rank의 score 값 조회</strong></p>
<pre><code>$ ZSCORE &lt;key&gt; &lt;member&gt;</code></pre><p><img src="https://velog.velcdn.com/images/sangkyu-bae/post/59d2a9c4-3b7b-4f98-b3d8-4765dd0da792/image.png" alt="">
만약 없는 key값을 조회하려고 하면 (nil)으로 값을 반환한다.</p>
<p>일단 간단한 등록과 조회를 할 수 있는 명령어를 살펴보았습니다.</p>
<hr>
<h2 id="성능-비교를-해보자">성능 비교를 해보자</h2>
<h3 id="1-jpql을-사용한-순위-집계">1. JPQL을 사용한 순위 집계</h3>
<pre><code class="language-java"> @Query(&quot;select r from RankingEntity r order by r.clickNum desc&quot;)
 List&lt;RankingEntity&gt; findWithPagingOrderByClickNum(Pageable pageable);</code></pre>
<p>이 직접접인 DB call을 통하여 순위를 확인한 경우 </p>
<p><img src="https://velog.velcdn.com/images/sangkyu-bae/post/3c7731cf-b26e-4f10-9951-7d5978758bb2/image.png" alt="">
541S의 성능을 보여 줬습니다. 하지만 만약 데이터가 계속적으로 많아질 경우 성능이 더 늦어질 것입니다.</p>
<h3 id="2-redis-sortedset을-사용한-순위-집계">2. redis sortedSet을 사용한 순위 집계</h3>
<pre><code class="language-java">ZSetOperations zSetOps  = redisTemplate.opsForZSet();
Set&lt;String&gt; rangeRakingSet = zSetOps.reverseRange(&quot;CLICK_RANK&quot;,0,limit-1);</code></pre>
<p><img src="https://velog.velcdn.com/images/sangkyu-bae/post/b1323f10-fc41-4982-bbee-c2d8f3a8e47f/image.png" alt=""></p>
<p>위와 같이 redis sortedSet을 활용하여 순위를 집계할 경우 11s의 성능을 보여주며 RDB에 직접 접근하는것 보다 빠르다는 것을 확인할 수 있었습니다!</p>
<hr>
<h2 id="그렇담-고려할점은-없을까">그렇담 고려할점은 없을까?</h2>
<p>Redis를 사용함에 있어 트래픽이 적을 경우는 오버헤드같은 경우를 고려하지 않아도 되겠지만 대용량 데이터를 사용할 시에는 이러한 오버헤드를 잘 고려해야한다고 합니다.</p>
<p>또한 Redis가 만약 죽어버린다면 데이터를 소실할 경우가 있습니다 이때에는 Redis의 백업 정책 혹은 cluster를 활용하여 이를 방지하여야 할것입니다! </p>
<p>이러한 것들은 추후 포스팅에서 다뤄볼 예정입니다!</p>
<h2 id="그럼에도-redis를-도입한-이유는">그럼에도 Redis를 도입한 이유는?</h2>
<p>일단 redis 모든 노드등의 문제가 생겨 순위 서비스가 만약 데이터를 잃는다고 하여도, 일단 순위에 대한 서비스 자체가 현재 진행하는 프로젝트의 서비스에서 크리티컬한 문제를 일으키지 않을 거라 판단 하였습니다. 이러할 경우 Look Aside 패턴을 사용하여 이전 데이터를 RDB에서 불러오는 전략을 세워볼 수 있을거라 판단했습니다. 이러한 읽기 전략이나 쓰기 전략은 추후에 모아서 글을 작성해 해보도록 하겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effecttive Java] 6장 열거 타입과 애너테이션 - 아이템 34. int 상수 대신 열거 타입을 사용하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effecttive-Java-6%EC%9E%A5-%EC%97%B4%EA%B1%B0-%ED%83%80%EC%9E%85%EA%B3%BC-%EC%95%A0%EB%84%88%ED%85%8C%EC%9D%B4%EC%85%98-%EC%95%84%EC%9D%B4%ED%85%9C-34.-int-%EC%83%81%EC%88%98-%EB%8C%80%EC%8B%A0-%EC%97%B4%EA%B1%B0-%ED%83%80%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effecttive-Java-6%EC%9E%A5-%EC%97%B4%EA%B1%B0-%ED%83%80%EC%9E%85%EA%B3%BC-%EC%95%A0%EB%84%88%ED%85%8C%EC%9D%B4%EC%85%98-%EC%95%84%EC%9D%B4%ED%85%9C-34.-int-%EC%83%81%EC%88%98-%EB%8C%80%EC%8B%A0-%EC%97%B4%EA%B1%B0-%ED%83%80%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Mon, 11 Dec 2023 03:20:55 GMT</pubDate>
            <description><![CDATA[<h1 id="열거-타입이란">열거 타입이란?</h1>
<blockquote>
<p>일정 개수의 상수 값을 정의한 다음 그외의 값은 허용하지 않은 타입</p>
</blockquote>
<p>그렇다면 이런 열거 타입을 왜 사용할까? 열거 타입 이전에 사용 했던 정수 열거 패턴을 살펴보며 이유를 알아보자!</p>
<h2 id="정수-열거-패턴-단점">정수 열거 패턴 단점</h2>
<pre><code class="language-java">//정수 열거 패턴
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;

public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;</code></pre>
<ul>
<li>타입 안전을 보장할 방법이 없으며, 표현력 또한 좋지 않다.</li>
<li>평범한 상수를 나열이기 때문에 프로그램이 깨지기 쉽다.</li>
<li>정수 열거 그룹에 속한 모든 상수를 순회하는 방법도 마땅치 않고, 상수가 몇 개인지 알 수 없다.</li>
</ul>
<hr>
<h2 id="열거-타입">열거 타입</h2>
<p>열거 타입은 앞선 정수 열거 패턴의 단점을 말끔이 없애 준다.</p>
<pre><code class="language-java">/// 간단한 열거 타입
public enum Apple {FUJI, PIPPIN, GRANNY_SMITH}
public enum Orange {NAVEL, TEMPLE, BLOOD}</code></pre>
<h3 id="열거타입의-특징">열거타입의 특징</h3>
<ul>
<li>상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개한다.</li>
<li>열거 타입 선언으로 만들어진 인스턴스는 딱 1개만 존재한다.</li>
</ul>
<h3 id="열거타입의-장점">열거타입의 장점</h3>
<ul>
<li>타입의 안전성을 제공한다. ex) Apple 열거 타입 인수에 Orange를 넘길 수 없다.</li>
<li>같은 이름 상수 공존: 각자의 이름 공간이 있기 때문에</li>
<li>임의의 메서드나 필드를 추가할 수 있고 임의의 인터페이스를 구현하게 할 수 있다.</li>
</ul>
<h2 id="데이터와-메서드를-갖는-열거-타입">데이터와 메서드를 갖는 열거 타입</h2>
<pre><code class="language-java">public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS  (4.869e+24, 6.052e6),
    EARTH  (5.975e+24, 6.378e6),
    MARS   (6.419e+23, 3.393e6),
    JUPITER(1.899e+27, 7.149e7),
    SATURN (5.685e+26, 6.027e7),
    URANUS (8.683e+25, 2.556e7),
    NEPTUNE(1.024e+26, 2.477e7);

    private final double mass;           // 질량(단위: 킬로그램)
    private final double radius;         // 반지름(단위: 미터)
    private final double surfaceGravity; // 표면중력(단위: m / s^2)

    // 중력상수(단위: m^3 / kg s^2)
    private static final double G = 6.67300E-11;

    // 생성자
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
        surfaceGravity = G * mass / (radius * radius);
    }

    public double mass()           { return mass; }
    public double radius()         { return radius; }
    public double surfaceGravity() { return surfaceGravity; }

    public double surfaceWeight(double mass) {
        return mass * surfaceGravity;  // F = ma
    }
}</code></pre>
<p><strong>열거 타입 상수 각각을 특정 데이터와 연결지으려면 생성자에서 데이터를 받아 인스턴스 필드에 저장하면 된다.</strong></p>
<ul>
<li>열거타입은 불변이기에 모든 필드는 final이어야 한다.</li>
<li>필드를 public으로 선언해도 되지만, private으로 두고 별도의 public 접근 메서드를 두는 것이 낫다.</li>
</ul>
<h2 id="열거타입의-배열">열거타입의 배열</h2>
<pre><code class="language-java">public class WeightTable {
   public static void main(String[] args) {
      double earthWeight = Double.parseDouble(args[0]);
      double mass = earthWeight / Planet.EARTH.surfaceGravity();
      for (Planet p : Planet.values())
         System.out.printf(&quot;%s에서의 무게는 %f이다.%n&quot;,
                           p, p.surfaceWeight(mass));
   }
}</code></pre>
<p>자신 안에 정의된 상수들의 값을 배열에 담아 반환하는 정적 메서드 값들은 선언된 순서로 저장된다.</p>
<h2 id="상수별-메서드-구현">상수별 메서드 구현</h2>
<p>switch를 이용한 구현은 새로운 상수를 추가할 때마다 해당 case문도 추가해야해서 깨지기 쉽다.</p>
<p>** 상수별 메서드 구현 **
열거 타입에 추상 메서드를 선언하고, 각 상수별 클래스 몸체(constant-specific class body)를 각 상수에 맞게 재정의하는 방법</p>
<pre><code class="language-java">import java.util.*;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toMap;

public enum Operation {
    PLUS(&quot;+&quot;) {
        public double apply(double x, double y) { return x + y; }
    },
    MINUS(&quot;-&quot;) {
        public double apply(double x, double y) { return x - y; }
    },
    TIMES(&quot;*&quot;) {
        public double apply(double x, double y) { return x * y; }
    },
    DIVIDE(&quot;/&quot;) {
        public double apply(double x, double y) { return x / y; }
    };

    private final String symbol;

    Operation(String symbol) { this.symbol = symbol; }

    public abstract double apply(double x, double y);    
}</code></pre>
<h2 id="상수별-동작-혼합--전략-열거-타입-패턴">상수별 동작 혼합 : 전략 열거 타입 패턴</h2>
<p>열거 타입 상수 일부가 같은 동작을 공유한다면 전략 열거 타입 패턴을 사용하자.</p>
<pre><code class="language-java">package effectivejava.chapter6.item34;

import static effectivejava.chapter6.item34.PayrollDay.PayType.*;

enum PayrollDay {
    MONDAY(WEEKDAY), TUESDAY(WEEKDAY), WEDNESDAY(WEEKDAY),
    THURSDAY(WEEKDAY), FRIDAY(WEEKDAY),
    SATURDAY(WEEKEND), SUNDAY(WEEKEND);

    private final PayType payType;

    PayrollDay(PayType payType) { this.payType = payType; }

    int pay(int minutesWorked, int payRate) {
        return payType.pay(minutesWorked, payRate);
    }

    enum PayType {
        WEEKDAY {
            int overtimePay(int minsWorked, int payRate) {
                return minsWorked &lt;= MINS_PER_SHIFT ? 0 :
                        (minsWorked - MINS_PER_SHIFT) * payRate / 2;
            }
        },
        WEEKEND {
            int overtimePay(int minsWorked, int payRate) {
                return minsWorked * payRate / 2;
            }
        };

        abstract int overtimePay(int mins, int payRate);
        private static final int MINS_PER_SHIFT = 8 * 60;

        int pay(int minsWorked, int payRate) {
            int basePay = minsWorked * payRate;
            return basePay + overtimePay(minsWorked, payRate);
        }
    }

    public static void main(String[] args) {
        for (PayrollDay day : values())
            System.out.printf(&quot;%-10s%d%n&quot;, day, day.pay(8 * 60, 1));
    }
}</code></pre>
<p>추가하려는 메서드가 의미상 열거타입에 속하는 경우 다음과 같이 전략 열거 타입 패턴을 사용한다.</p>
<p>그렇지 않은 경우에는 switch를 적용해서 간단하게 만든다.</p>
<hr>
<h2 id="열거타입을-언제-사용할까">열거타입을 언제 사용할까?</h2>
<ul>
<li><p>필요한 원소를 컴파일 타임에 다 알 수 있는 상수 집합이라면 항상 열거 타입을 사용하자</p>
<ul>
<li>Ex) 태양계 행성, 한 주의 요일, 체스말</li>
</ul>
</li>
<li><p>열거 타입에 정의된 상수 개수가 영원히 고정 불변일 필요는 없다.</p>
<ul>
<li>Ex) 메뉴 아이템, 연산 코드, 명령줄 플래그</li>
</ul>
</li>
<li><p>열거타입의 성능은 상수와 별반 다르지 않다</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 3장 객체 생성과 파괴 - 아이템 17. 변경 가능성을 최소화하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-3%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-17.-%EB%B3%80%EA%B2%BD-%EA%B0%80%EB%8A%A5%EC%84%B1%EC%9D%84-%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-3%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-17.-%EB%B3%80%EA%B2%BD-%EA%B0%80%EB%8A%A5%EC%84%B1%EC%9D%84-%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EB%9D%BC</guid>
            <pubDate>Fri, 10 Nov 2023 01:34:23 GMT</pubDate>
            <description><![CDATA[<h1 id="불변-클래스란">불변 클래스란?</h1>
<blockquote>
<p>불변 클래스란 간단히 말해 그 인스턴스의 내부 값을 수정할 수 없는 클래스다. </p>
</blockquote>
<p><strong>특징</strong></p>
<ul>
<li><p>불변 인스턴스에 간직된 정보는 고정되어 객체가 파괴되는 순간까지 달라지지 않는다. </p>
</li>
<li><p>자바 플랫폼 라이브러리에도 String, BigInteger, BigDecimal가 여기 속한다.</p>
</li>
</ul>
<p>그렇다면 왜 불변 클래스를 사용할까?</p>
<blockquote>
<p>불변 클래스는 가변 클래스보다 설계하고 구현하고 사용하기 쉬우며, 오류의 여지도 적고 안전하다.</p>
</blockquote>
<hr>
<h2 id="불변-클래스의-규칙">불변 클래스의 규칙</h2>
<p>이제 클래스를 불변으로 만드는 <strong>다섯가지 규칙</strong>에 대해 알아보자.</p>
<ol>
<li><strong>객체의 상태를 변경하는 메서드를 제공하지 않는다.</strong></li>
<li><strong>클래스를 확장할 수 없도록 한다.</strong> (하위 클래스에서 객체의 상태를 변하게 만드는것을 방지하기 위해)</li>
<li><strong>모들 필드를 final로 선언한다.</strong> 이를 통해 설계자의 의도를 명확히 드러 낸다.</li>
<li><strong>모든 필드를 private으로 선언한다.</strong> 이를 통해 필드가 참조하는 가변 객체를 직접 수정하는 일을 방지한다.</li>
<li><strong>자신 외에는 내부 가변 컴포넌트에 접근할 수 없도록 한다.</strong></li>
</ol>
<h3 id="불변-복소수-클래스">불변 복소수 클래스</h3>
<p>이러한 불변 클래스의 규칙을 통해 만들어진 예제를 보자</p>
<pre><code class="language-java">public final class Complex {
    private final double re;
    private final double im;

    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public double realPart() {
        return re;
    }

    public double imaginaryPart() {
        return im;
    }

    public Complex plus(Complex complex) {
        return new Complex(re + complex.re, im + complex.im);
    }

    // ...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Complex complex = (Complex) o;
        return Double.compare(complex.re, re) == 0 &amp;&amp; Double.compare(complex.im, im) == 0;
    }

    @Override
    public int hashCode() {
        return Objects.hash(re, im);
    }
}</code></pre>
<ul>
<li>plus 메서드는 인스턴스 자신은 수정하지 않고 새로운 Complex 인스턴스를 만들어서 반환한다.</li>
<li>이처럼 피연산자에 함수를 적용해 그 겨과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴을 함수형 프로그래밍이라 한다.</li>
<li>절차적 혹은 명령형 프로그래밍에서는 메서드에서 피연산자인 자신을 수정해 자신의 상태가 변하게 된다.</li>
</ul>
<hr>
<h2 id="불변-클래스-장점">불변 클래스 장점</h2>
<p>그렇다면 이런 불변 클래스를 사용함으로 써 가져오는 이점은 무엇일까?</p>
<ul>
<li><strong>불변 객체는 단순하다.</strong> 생성된 시점의 상태를 파괴될 때까지 간직한다.</li>
<li><strong>불변 객체는 근본적으로 스레드 안전하여 따로 동기화할 필요 없다.</strong> </li>
<li>위와 비슷한 맥락이지만 어떤 스레도도 다른 스레드에 영향을 줄 수 없으니 <strong>불변 객체는 안심하고 공유할 수 있다.</strong> 이렇기 때문에 한번 만든 인스턴스는 재활용 하기를 권하며, 자주 쓰이는 값들은 상수로 제공 하는것이다.<pre><code class="language-java">//예제
public static final Complex ZERO = new Complex(0,0);
public static final Complex One = new Complex(1,0);
public static final Complex I = new Complex(0,1);</code></pre>
</li>
<li>자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않게 해주는 <a href="https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-1.-%EC%83%9D%EC%84%B1%EC%9E%90-%EB%8C%80%EC%8B%A0-%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%84%B0%EB%A6%AC-%EB%A9%94%EC%86%8C%EB%93%9C%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC">정적 팩터리</a>를 제공할 수 있다. 이를 통해 인스턴스를 공유하여 메모리 사용량과 가비지 컬렉션 비용이 줄어 들며, 필요에 따라 캐시 기능을 나중에 덧붙일 수 있다.</li>
<li>불변 클래스는 어파치 원본과 같기 때문에, <strong>방어적 복사가 필요 없다.</strong></li>
<li><strong>불변 객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있다.</strong></li>
<li><strong>불변 객체는 그 자체로 실패 원자성을 제공한다</strong><ul>
<li>실패 원자성이란 메서드에서 예외가 발생한 후에도 그 객체는 여전히 (메서드 호출 전과 똑같은) 유효한 상태 여야하는 성질이다.</li>
</ul>
</li>
</ul>
<h2 id="불변-클래스의-단점">불변 클래스의 단점</h2>
<p>앞서 불변 클래스의 장점을 보았을 때는 그럼 이러한 객체를 사용하지 않을 이유가 없자나! 라고 생각할 수 있다. 하지만 이러한 불변 클래스에도 단점이 존재 한다.</p>
<ul>
<li><strong>불변 클래스의 값이 다르면 반드시 독립된 객체로 만들어야 한다.</strong> 값의 가지수가 많다면 이를 만드는데 큰 비용이 들어 갈것이다.</li>
<li>이에 대처하는 방안은 두 가지이다. 첫 번째는 흔히 쓰일 다단계 연산(multistep operation)들을 예측하여 기본 기능으로 제공하는 방법이다.</li>
<li>클라이언트가 원하는 복잡한 연산들을 정확히 예측할 수 있다면 이러한 다단계 연산 속도를 높여주는 package-private인 가변 동반 클래스(companion class)만으로도 충분하다.</li>
<li>예측이 불가능하다면 가변 동반 클래스를 public으로 제공하는 게 최선이다. 대표적인 예로 StringBuilder, StringBuffer가 있다.</li>
</ul>
<hr>
<h2 id="생성자를-대신-정적-팩터리를-사용한-불변-클래스">생성자를 대신 정적 팩터리를 사용한 불변 클래스</h2>
<p>앞서 불변 클래서의 장단점을 알아 보았고, 불변 클래스를 만드는 방법을 알아볼텐데 그중 <strong>생성자를 대신 정적 팩터리를 사용한 불변 클래스</strong> 방법에 대해 알아보자 </p>
<p>그전에 불변 클래스 생성 필요한 규칙인 클래스가 불변임을 보장하려면 자신을 상속하지 못하게 해야 하는점을 생각해보자.</p>
<ul>
<li>이에 대한 가장 쉬운 방법은 final 클래스로 선언하는 것이다.</li>
<li>또는 생성자를 private로 만들고 정적 팩터리를 제공할 수도 있다(이 편이 더 유연하다).</li>
</ul>
<pre><code class="language-java">public final class Complex {
    private final double re;
    private final double im;

    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public static Complex valueOf(double re, double im) {
        return new Complex(re, im);
    }

    // ...
}</code></pre>
<ul>
<li>이 방식은 바깥에서 볼 수 없는 package-private 구현 클래스를 원하는 만큼 만들어 활용할 수 있으니 훨씬 유연하다.</li>
<li>패키지 바깥의 클라이언트에서 바라본 이 불변 객체는 사실상 final이다. public이나 protected 생성자가 없으니 다른 패키지에서 이 클래스를 확장하는 게 불가능하기 때문이다.</li>
<li>정적 팩터리 방식은 다수의 구현 클래스를 활용한 유연성을 제공하고, 이후에 캐싱 기능을 추가해 성능을 끌어올릴 수도 있다.</li>
<li>계산 비용이 큰 값의 경우, 나중에 (처음 쓰일 때) 계산하여 final이 아닌 필드에 캐시하여 사용함으로써 계산 비용을 절감할 수 있다. 불변 객체는 변하지 않기 때문에 가능하다.</li>
</ul>
<hr>
<h2 id="정리">정리</h2>
<p>이런 내용들을 정리해 보자면,</p>
<ul>
<li><p><strong>클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.</strong></p>
</li>
<li><p>단순한 값 객체는 불변으로 만들도록 하자.</p>
</li>
<li><p>성능 때문에 어쩔 수 없다면 불변 클래스와 함께 가변 동반 클래스를 public 클래스로 제공하도록 하자.</p>
</li>
<li><p>모든 클래스를 불변으로 만들 수는 없다. 하지만 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이도록 하자.</p>
<ul>
<li>꼭 변경해야 하는 필드를 제외한 나머지 모두를 final로 선언하도록 하자. 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.</li>
</ul>
</li>
<li><p>생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.</p>
<ul>
<li>확실한 이유가 없다면 생성자나 정적 팩터리 외에는 그 어떤 초기화 메서드도 public으로 제공하면 안 된다.</li>
<li>객체를 재활용할 목적으로 상태를 다시 초기화하는 메서드도 마찬가지다. 복잡성만 커지고 성능 이점은 거의 없다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DB]Mysql Replication ]]></title>
            <link>https://velog.io/@sangkyu-bae/DBMysql-Replication</link>
            <guid>https://velog.io/@sangkyu-bae/DBMysql-Replication</guid>
            <pubDate>Tue, 24 Oct 2023 05:44:28 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하면서 고가용성과 성능향상을 위해 DB를  이중화하기로 하였고, 이를 Replication을 사용하기로 결정했습니다! </p>
<h1 id="db-replication-이란">DB Replication 이란?</h1>
<p>그렇담 이 Replication은 무엇인가?!</p>
<blockquote>
<p> 데이터베이스를 복제하는 행위</p>
</blockquote>
<p>그렇담 왜 하나의 DB를 사용하면 되지만 여러개의 DB를 사용할까? </p>
<blockquote>
<p><strong>Case 1</strong>
DB의 이상으로 요청에대한 응답이 되지 않았을때
<strong>Case 2</strong>
요청 트래픽의 증가로 인한 트래픽의 부하 분산이 필요한 상황  일때
이때 스케일업을 통해 어느정도 보완이 가능하겠으나, 스케일업에는 분명 한계가 있습니다!</p>
</blockquote>
<p>이러한 상황을 타개하기 위해서 DB Replication을 사용해야 합니다.</p>
<hr>
<h1 id="db-replication의-구조">DB Replication의 구조</h1>
<p>일단 원본 데이터를 가진 데이터베이스 서버를 <strong>소스서버</strong>  복제된 DB의 데이터를 가지고 있는 DB 서버를 <strong>레플리카 서버</strong> 라고 합니다. </p>
<p>소스 서버에서 변경이 일어나면 변경된 내용은 레플리카 서버에 동일하게 반영이 됩니다. 이런 구조를 <strong>소스-레플리카 구조</strong>라고 합니다! </p>
<p>그렇기 때문에 앞선 <strong>Case 1</strong>의 경우 레플리카 서버를 소스서버로 승격시켜 사용할 수 있습니다.
<strong>Case2</strong>의 경우는 소스 서버를 쓰기 전용 DB, 레플리카 서버를 읽기 전용 DB로 사용해 부하를 해결할 수 있습니다.</p>
<hr>
<h1 id="복제는-어떻게-발생-하는가">복제는 어떻게 발생 하는가?</h1>
<p>그렇담 이러한 레플리카에 데이터 복제는 어떻게 가능하게 하는지 알아보도록 합시다.</p>
<h2 id="바이너리-로그-기반-복제">바이너리 로그 기반 복제</h2>
<h3 id="바이너리-로그란">바이너리 로그란?</h3>
<blockquote>
<p>모든 변경사항이 로그 파일에 순서대로 기록되는 것을 의미합니다.</p>
</blockquote>
<p>즉 소스서버에 변경이 발생되어 바이너리 로그 이벤트가 기록이 되면 이 이벤트는 레플리카 서버가 자신의 로컬 디스크에 저장한 이후 이벤트를 읽어 들여 레플리카DB 자기자신의 데이터 파일에 반영합니다.</p>
<p>좀더 자세히 알아볼까요?</p>
<blockquote>
<p>일단 복제 쓰레드는 총 세 가지 쓰레드가 사용됩니다.
이벤트 순서 대로</p>
</blockquote>
<ol>
<li>소스서버의 바이너리 로그에 이벤트가 발생되면 바이너리 로그 덤프 쓰레드가 이 이벤트를 읽어 레플리카 서버로 전송합니다.</li>
<li>레플리카 서버의 I/O 쓰레드는 이 변경 이벤트를 자신의 로컬파일인 릴레이 로그에 저장합니다. 
(이때 아직 자신의 DB엔 변경이 일어나지 않습니다.)</li>
<li>레플리카 서버의 DB 변경을 위해 SQL 쓰레드가 변경 내용을 데이터 파일에 저장합니다.</li>
</ol>
<h3 id="레플리카는-소스서버의-바이너리-로그-이벤트를-식별하여-반영할까">레플리카는 소스서버의 바이너리 로그 이벤트를 식별하여 반영할까?</h3>
<p>이 방식에는 두 가지 방식이 있습니다.</p>
<h4 id="1-바이너리-로그-파일-위치-기반-복제">1. 바이너리 로그 파일 위치 기반 복제</h4>
<ul>
<li>이 방식의 경우 소스서버 바이너리 로그의 로그 파일명과 오프셋을 이용해 식별합니다.</li>
<li>ex) Binary-log.00002:110 이라는 로그가 있다고 한다면 Binary-log는 로그파일명이며 콜론뒤 110은 Offset입니다. </li>
<li>이러한 방식의 단점은 소스서버에만 유용한 식별 방법 이라는 단점이 있습니다. </li>
<li>즉 소스서버에 문제가 생겨 다른 레플리카 서버가 소스서버로 승격된 경우 다른 레플리카 서버들(복제에 참여하는)은 다시 이 바이너리 파일의 위치를 찾아야 하기 때문에 복구에 시간이 소요 됩니다.</li>
</ul>
<blockquote>
<p>즉 동일한 이벤트가 레플리카 서버에서도 동일한 파일명의 
동일한 위치에 저장된다는 보장이 없습니다.</p>
</blockquote>
<h4 id="2-글로벌-트랜잭션-아이디gtid">2. 글로벌 트랜잭션 아이디(GTID)</h4>
<p>앞선 단점을 해결하기 위해 MySQL 5.6 버전 부터는 글로벌 트랜잭션 기반 복제를 기본 복제 방식으로 사용하고 있습니다.</p>
<p>그렇담 이 글로벌 트랜잭션 아이디는 무엇일까?</p>
<blockquote>
<p>복제에 참여한 모든 데이터베이스들이 고유한 식별값을 가지며 이 값들은 모두 동일하기 때문에 동일한 이벤트에 대해서 동일한 글로벌 트랜잭션만 읽기만 하면 반영이 가능하게 됩니다.</p>
</blockquote>
<hr>
<h1 id="복제-데이터-포맷">복제 데이터 포맷</h1>
<h2 id="바이너리-로그-포맷">바이너리 로그 포맷</h2>
<h3 id="1-statement-기반-방식">1. Statement 기반 방식</h3>
<pre><code class="language-sql">INSERT INTO member VALUES(1,&#39;TESTNAME&#39;)</code></pre>
<p>이러한 SQL문이 일어나면 이 SQL문이 바이너리 로그에 그대로 저장되는 방식을 Statement 기반 방식이라고 합니다.</p>
<ul>
<li>장점: 으로는 여러 개의 데이터를 수정하는 쿼리여도 바이너리 로그에 SQL문 딱 하나만 저장되기 때문에 저장 공간에 대한 부담이 감소 되며, 빠른처리가 가능하게 됩니다.</li>
<li>단점 :하지만 실행하는 쿼리마다 결과값이 바꾸니는 경우 데이터 동기화 문제가 발생하며 트랜잭션 격리수준이 <strong>REPEATABLE-READ</strong> 이상에서만 사용이 가능하다는 단점이 있습니다.</li>
</ul>
<blockquote>
<p>일관되지 않은 데이터가 저장될 위험이 있습니다!</p>
</blockquote>
<h3 id="2-row-기반-방식">2. Row 기반 방식</h3>
<p>앞선 Statement 기반의 단점을 보완하여 MySQL 5.7.7 버전 부터는 이 Row 기반 바이너리 로그 포맷을 기본으로 사용합니다. </p>
<p>이 방식은 변경 값 자체가 바이너리 로그에 그대로 저장되어 있는 형식을 말합니다.</p>
<ul>
<li>장점 : 모든 트랜잭션 격리 수준에서 사용 가능 하다.</li>
<li>단점 : 데이터를 변경하는 SQL문이 많이 발생되면 바이너리 로그 파일의 크기가 너무 커질 수 있으며, SQL쿼리를 육안으로 확인하기 어렵습니다.<blockquote>
<p>그럼에도 불구하고 <strong>데이터를 일관되게 저장 하는 가장 안전한 방식</strong> 입니다.</p>
</blockquote>
<h3 id="3-mixed-기반-방식">3. Mixed 기반 방식</h3>
이 Mixed 방식은 사용자가 커스텀하여 사용이 가능합니다.
기본적인 쿼리는 <strong>Statement 포맷</strong>으로 저장하되 <strong>비확정적 쿼리 라면 Row 포맷</strong>으로 저장하는 방식을 사용할 수 있습니다.</li>
</ul>
<hr>
<h1 id="복제-동기화-방식">복제 동기화 방식</h1>
<p>그렇다면 복제가 잘 일어났는지 어떻게 확인할까?</p>
<h2 id="1-비동기-복제">1. 비동기 복제</h2>
<p>소스 서버가 레플리카 서버에서 변경 이벤트가 정상적으로 전달 됐는지 확인하지 않는다.</p>
<p>자세히 알아보자면.</p>
<blockquote>
<ol>
<li>데이터 변경 요청이 들어온다면 바이너리 로그의 이벤트를 먼저 작성한 뒤 바로 소스 서버 스토리지 엔진에 커밋을 하게 된다.</li>
<li>그 이후 변경 이벤트를 레플리카 서버로 전송합니다.</li>
</ol>
</blockquote>
<p>이 방식은 변경 이벤트를 레플리카 서버로부터 소스 서버로 확인 이벤트를 보내지 않습니다. 그렇기 때문에 <strong>성능은 빠를 수 있으나 동기화는 보장하지 않습니다.</strong></p>
<h2 id="2-반동기-복제">2. 반동기 복제</h2>
<p>앞선 비동기 복제의 단점을 보완하여 MySQL 5.5 버전 부터는 반동기 복제 방식을 사용합니다.</p>
<p><strong>소스 서버는 레플리카 서버가 소스 서버로부터 전달 받은 변경 이벤트를 릴레이 로그에 기록 후 응답을 보내면 그때 트랜잭션을 완전히 커밋한다.</strong> </p>
<p>자세히 알아보자면</p>
<blockquote>
<ol>
<li>데이터 변경 요청이 소스 서버로 들어오면 바로 이벤트를 바이너리 로그에 기록후 레플리카 서버로 전송합니다.</li>
<li>레플리카 서버는 이 변경 이벤트를 잘 받았다는 응답을 보내게 되며, 응답이 오고 난 이후 소스 서버는 변경 내역을 스토리지 엔진에 커밋합니다. </li>
</ol>
</blockquote>
<p>이때 레플리카 서버가 보내는 응답은 <strong>변경 이벤트를 잘 받았다는 응답일뿐</strong> 이벤트가 레플리카 서버에 적용되었다는 응답을 보내는 것은 아닙니다.</p>
<hr>
<h1 id="복제-구성-형태복제-토폴리지">복제 구성 형태(복제 토폴리지)</h1>
<p>그렇다면 소스 서버와 레플리카 서버를 구성 할까요?</p>
<h3 id="1싱글-레플리카">1.싱글 레플리카</h3>
<p>이는 소스 서버와 레플리카 서버를 한대씩 두는 방식이며, 레플리카 서버는 예비 서버 및 데이터 백업용으로 활용합니다.</p>
<h3 id="2멀티-레플리카">2.멀티 레플리카</h3>
<p>싱글 레플리카 방식에서 레플리카 서버를 한대 더 둔 멀티 레플리카 방식이 있으며 이때 레플리카 두 대를 사용하는데 한대는 쿼리 부하 분산용으로, 두 번째 레플리카 서버는 백업 용으로 사용할 수 있습니다.</p>
<h3 id="3-체인-복제">3. 체인 복제</h3>
<p>소스 서버에 연결된 레플리카 서버가 많다면 소스 서버의 복제부하가 커지게 됩니다. 이때 다른 레플리카 서버를 소스 서버로 활용하여 복제 부하를 분산시키는데 사용할 수 있습니다.
혹은 기존에 사용하던 서버를 업데이트 하거나 장비 교체할 때 사용할 수 있습니다.</p>
<h3 id="4-듀얼-소스-복제">4. 듀얼 소스 복제</h3>
<p>싱글 레플리카를 두개 만들어 놓은 것이며 이는 두 서버 모두 쓰기가 가능한 형태입니다. 이방식은 트랜잭션이 충돌이 일어날 경우 복제 멈춤 현상이 일어나기 때문에 잘 사용되지 않습니다. </p>
<h3 id="5-멀티-소스-복제">5. 멀티 소스 복제</h3>
<p>하나의 레플리카 서버가 둘 이상의 소스서버를 가지며 이는 데이터를 분석 시 데이터를 모아 분석을 수행할 때 사용하게 됩니다.</p>
<hr>
<h1 id="장애복구">장애복구</h1>
<p>그렇다면 소스 서버에 장애가 난 경우엔 레플리카 서버를 활용하는 경우들은 살펴 보았습니다. 하지만 레플리카 서버에 문제가 생긴다면 어떻게 다시 동기화를 만들까?</p>
<h3 id="1-크레시-세이프">1. 크레시 세이프</h3>
<p>MySQL에선 크레시 세이프 복제 방식을 제공하고 있습니다.</p>
<blockquote>
<ol>
<li>레플리카 서버는 I/O 쓰레드와 SQL 쓰레드를 이용하여 소스 서버에 바이너리 로그 이벤트 위치를 읽을 때와 트랜잭션 실행정보를 읽을 때 어디까지 읽었는지에 대한 포지션 정보를 로컬에 저장해 둡니다.</li>
<li>레플리카 서버에 문제가 생겨 다시 재가동했을 시 그 정보를 기반으로 다시 소스서버에 동기화 과정을 거칩니다! </li>
</ol>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 3장 객체 생성과 파괴 - 아이템 11. equals를 재정의하려거든 hashCode도 재정의하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-3%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-11.-equals%EB%A5%BC-%EC%9E%AC%EC%A0%95%EC%9D%98%ED%95%98%EB%A0%A4%EA%B1%B0%EB%93%A0-hashCode%EB%8F%84-%EC%9E%AC%EC%A0%95%EC%9D%98%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-3%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-11.-equals%EB%A5%BC-%EC%9E%AC%EC%A0%95%EC%9D%98%ED%95%98%EB%A0%A4%EA%B1%B0%EB%93%A0-hashCode%EB%8F%84-%EC%9E%AC%EC%A0%95%EC%9D%98%ED%95%98%EB%9D%BC</guid>
            <pubDate>Thu, 12 Oct 2023 08:28:59 GMT</pubDate>
            <description><![CDATA[<h1 id="들어-가면서">들어 가면서</h1>
<p><strong>equals</strong>를 재정의한 클래스는 <strong>hashCode</strong>도 재정의 해야한다. 
그렇지 않는다면 hashCode 일반 규약을 어기게 되어 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제를 일으키기 때문이다.</p>
<hr>
<h2 id="hashcode-규약">HashCode 규약</h2>
<ul>
<li><strong>equals</strong>비교에 사용되는 정보가 변경되지 않다면, 객체의 hashCode 메서드는 항상 같은 값을 반환한다.</li>
<li><strong>equals</strong>가 두 객체를 같다고 판단하면, 두 객체는 같은 hashCode를 반환한다.</li>
<li><strong>equals</strong>가 두 객체를 다르다고 판단하더라고 다른 hashCode를 반환할 필요는 없으나, 다른 값을 반환해야 해시 테이블의 성능이 좋다.</li>
</ul>
<h3 id="논리적으로-같은-객체는-같은-해시코드를-반환한다">논리적으로 같은 객체는 같은 해시코드를 반환한다.</h3>
<pre><code class="language-java">Map&lt;PhoneNumber, String&gt; map = new HashMap&lt;&gt;();
map.put(new PhoneNumber(010,1111,1111), new Person(&quot;테스트&quot;));</code></pre>
<p>이 코드에 <strong>map.get(new PhoneNumber(010,1111,1111))</strong>을 실행하면 <strong>테스트</strong>값이 아닌 <strong>null</strong> 값을 반환한다. 
이유는 <strong>PhoneNumber</strong>클래스에 hashCode를 재정의 하지 않았기 때문에, 다른 해시코드를 반환하기 때문이다.
<strong>hashMap</strong>은 해시코드가 다른 엔트리 끼리는 비교를 시도조차 하지 않도록 최적화 되어 있다.</p>
<hr>
<h3 id="좋은-해시코드를-작성하는-법은">좋은 해시코드를 작성하는 법은?</h3>
<p>좋은 해시코드는 서로 다른 인스턴스 마다 다른 해시코드를 반환하는 것이 이상적인 해시 함수 이며, 32비트 정수 범위에 균일하게 분배되어야 한다.</p>
<p>1.int 변수인 result를 선언한 후 값을 c로 초기화한다.</p>
<ul>
<li>이 때, c는 해당 객체의 첫번째 핵심 필드를 단계 2.1 방식으로 계산한 해시코드이다.</li>
<li>여기서 핵심 필드는 equals 비교에 사용되는 필드를 말한다.</li>
</ul>
<ol start="2">
<li>해당 객체의 나머지 핵심 필드인 f 각각에 대해 다음 작업을 수행한다.</li>
</ol>
<ul>
<li>해당 필드의 해시코드 c 를 계산한다.<ul>
<li>기본 타입 필드라면,Type.hashCode(f)를 수행한다</li>
<li>참조 타입 필드면서, 이 클래스의 equals 메서드가 이 필드의 equals를 재귀적 호출하여 비교하면, 이 필드의 hashCode를 재귀적으로 호출한다.</li>
<li>필드가 배열이라면, 핵심 원소 각각 별도 필드처럼 다룬다. 모든 원소가 핵심 원소라면 Arrays.hashCode를 사용한다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="주의할점">주의할점</h3>
<ul>
<li>equals 비교에 사용되지 않은 필드는 <strong>반드시</strong> 제외한다.</li>
<li>성능 향상을 위해 해시코드를 계산할 때 핵심 필드를 생랴하면 안 된다.</li>
<li>참조 타입 필드가 null일 경우 0을 사용한다.</li>
<li>31을 곱하는 이유는 홀수이면서 소수 이기 때문이다.</li>
</ul>
<p><strong>전형적인 hashCode 메서드</strong></p>
<pre><code class="language-java">@Override
    public int hashCode() {
        int result = Integer.hashCode(areaCode);
        result = 31 * result + Integer.hashCode(prefix);
        result = 31 * result + Integer.hashCode(lineNum);
        return result;
    }</code></pre>
<p>PhoneNumber 핵심 필드 3개를 이용해 간단한 계산만 수행하고, 이 과정에 비결정적 요소는 전혀 없다.
-&gt; 동치 이기 때문에 PhoneNumber 인스턴스는 서로 같은 해시코드를 가질 것이 확실하다.</p>
<hr>
<h2 id="핵심">핵심</h2>
<p><strong>equals</strong>를 재정의할 때는 hashCode를 반드시 재정의해야 하며, 그렇지 않으면 프로그램이 제대로 동작하지 않을것이며, 서로 다른 인스턴스라면 되도록 해시코드가 서로 다르게 구현해야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] 영속성 컨테이너]]></title>
            <link>https://velog.io/@sangkyu-bae/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88</link>
            <guid>https://velog.io/@sangkyu-bae/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88</guid>
            <pubDate>Fri, 06 Oct 2023 07:17:34 GMT</pubDate>
            <description><![CDATA[<h1 id="영속성-컨테이너란">영속성 컨테이너란?</h1>
<p>영속성 컨텍스트는 <strong>엔티티를 영구 저장 하는 환경</strong> 이다. 영속성 컨텍스트는 애플리케이션과 DB 사이에서 객체를 보관하는 가상 DB 역할을 하며, 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.</p>
<hr>
<h2 id="엔티티-생명주기">엔티티 생명주기</h2>
<ul>
<li><p>비영속(new/transient): 영속성 컨텍스와 전혀 관계 없는 상태  </p>
<pre><code class="language-java">//객체만 생성한 비영속 상태
Member member = new Member();
member.setId(&quot;member&quot;);
member.setUsername(&quot;회원&quot;);</code></pre>
</li>
<li><p>영속 상태: 영속성 컨텍스트에 저장된 상태</p>
<pre><code class="language-java">//객체만 생성한 비영속 상태
Member member = new Member();
member.setId(&quot;member&quot;);
member.setUsername(“회원”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);</code></pre>
</li>
<li><p>준영속 상태: 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 더이상 관리하지 않으면 준영속 상태가 되며 <strong>em.datach()</strong>을 호출 하면 된다 </p>
</li>
</ul>
<pre><code class="language-java">em.persist(member);</code></pre>
<p>이런 준영속 상태의 특징 으로는 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩등 영속성 컨텍스트가 제공하는 기능이 작동하지 않는다.</p>
<ul>
<li>삭제 상태: 삭제된 상태<pre><code class="language-java">em.remove(member);</code></pre>
</li>
</ul>
<hr>
<h2 id="영속성-컨텍스트-장점">영속성 컨텍스트 장점</h2>
<h3 id="1-1차-캐시">1. 1차 캐시</h3>
<ul>
<li>영속성 내부에는 1차 캐시가 존재하며, 이는 영속 상태의 엔티티를 저장하기 때문에 엔티티 조회시 1차 캐시에 엔티티가 존재한다면 DB를 찾아보지 않는다.</li>
</ul>
<h3 id="2-영속-엔티티의-동일성-보장">2. 영속 엔티티의 동일성 보장</h3>
<pre><code class="language-java">Member a = em.find(Member.class, &quot;member&quot;);
Member b = em.find(Member.class, &quot;member&quot;);

System.out.println(a==b) // true</code></pre>
<p>1차 캐시로 반복 가능한 읽기 등급의 트랙잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공해 준다.</p>
<h3 id="3-트랜잭션을-지원하는-쓰기-지연">3. 트랜잭션을 지원하는 쓰기 지연</h3>
<pre><code class="language-java">EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작.
transaction.begin(); // [트랜잭션] 시작

em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않고 모아 둔다.

//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋</code></pre>
<p>em.persist()로 객체를 영속성 컨텍스트에 저장하여도 DB에 곧장 Insert 쿼리를 보내지 않는다. SQL 쿼리들을 모아둔 후 flush 될 때(영속성 컨텍스트의 변경내용을 DB에 반영할 시점) 모아둔 쿼리를 Insert 쿼리를 보낸다. 이를 쓰기 지연이라 한다. 이를 통하여 성능이 향상될 수 있다.</p>
<h3 id="4-변경-감지">4. 변경 감지</h3>
<pre><code class="language-java">EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작

// 영속 엔티티 조회
Member member = em.find(Member.class, &quot;member&quot;);

// 영속 엔티티 데이터 수정
member.setName(&quot;hi&quot;);
member.setAge(10);

transaction.commit(); // [트랜잭션] 커밋</code></pre>
<p>영속성 컨텍스트에서 엔티티를 조회해서 해당 엔티티를 수정한다 할 때, 조회한 엔티티를 다시 업데이트를 하지 않아도 영속성 컨텍스트내의 스냅샷과 엔티티를 비교해 변경된 엔티티가 있으면 update 쿼리를 자동 생성한다.이 Update 쿼리도 쓰기 지연이 가능하다.</p>
<h3 id="5-지연-로딩">5. 지연 로딩</h3>
<p>지연로딩은 연관 관계 매핑되어 있는 엔티티를 조회 시 프록시 객체를 우선 반환하며, 실제 필요한 데이터를 쿼리를 날려 가져오는 기능이다. </p>
<hr>
<h2 id="플러시">플러시</h2>
<p>플러시는 영속성 컨텍스트의 변경 내용을 DB에 반영한다. 영속성 컨텍스트의 엔티티를 지우는게 아니라 변경 내용을 DB에 동기화하는 것이다.</p>
<p>플러시의 흐름으로는 </p>
<ol>
<li>변경 감지가 동작하여 스냅샷과 비교해서 수정된 엔티티를 찾는다.</li>
<li>수정된 엔티티에 대해 수정 쿼리를 만들어 SQL 저장소에 등록한다.</li>
<li>쓰기 지연 SQL 저장소의 쿼리를 DB에 전송한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 2장 객체 생성과 파괴 - 아이템 9. try-finally보다는 try-with-resources를 사용하라
]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-9.-try-finally%EB%B3%B4%EB%8B%A4%EB%8A%94-try-with-resources%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-9.-try-finally%EB%B3%B4%EB%8B%A4%EB%8A%94-try-with-resources%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Tue, 26 Sep 2023 01:34:46 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가기-전에">들어가기 전에</h2>
<p>얼마전에 작성했던 try-with-resources의 대한 내용이 책에서도 나오는것을 보면 많이 사용되는 개념인것 같다. 앞서 　
<a href="https://velog.io/@sangkyu-bae/Java-try-with-resources-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC">try-with-resources 예외 처리</a>
여기서 설명했지만 각설하고 다시 정리해보도록 하자</p>
<h1 id="개요">개요</h1>
<p>자바의 <strong>InputStream, OutputStream, java.sql.Connection</strong> 등은  close 메서드를 통하여 자원을 닫아야 한다. 하지만 클라이언트가 놓치기 쉽고 이는 성능 문재로 이어진다. 안정망으로 <strong>finalizer</strong>를 사용한다만, <a href="https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-8.-finalizer%EC%99%80-cleaner-%EC%82%AC%EC%9A%A9%EC%9D%84-%ED%94%BC%ED%95%98%EB%9D%BC">아이템 8</a>의 내용처럼 그리 믿을만 하지 않다.</p>
<pre><code class="language-java">public static String firstLineOfFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        br.close();
    }
}</code></pre>
<p>위 코드는 close 메서드를 이용해 자원을 해제주는 코드로 나쁘지 않아 보이지만 자원의 사용이 많아질 경우 코드가 지저분해지고 실수를 유발할 가능성이 높아진다.</p>
<pre><code class="language-java">public class Copy {
    static void copy(String src, String dst) throws IOException {
        InputStream in = new FileInputStream(src);
        try {
            OutputStream out = new FileOutputStream(dst);
            try {
                byte[] buf = new byte[BUFFER_SIZE];
                int n;
                while ((n = in.read(buf)) &gt;= 0)
                    out.write(buf, 0, n);
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }
    }
}</code></pre>
<p>여기서 문제는 예외가 try 블록과 finally 블록 모두 발생할 수 있으나 마지막 예외가 모든 예외를 덮어 버리게 되고 이는 디버깅을 어렵게할 것이다.</p>
<hr>
<h2 id="try-with-resources-등장">try-with-resources 등장</h2>
<p>앞선 문제들은 try-with-resources등장 이후 해결되었다. </p>
<pre><code class="language-java">    static String firstLineOfFile(String path) throws IOException {
        try (BufferedReader br = new BufferedReader(
                new FileReader(path))) { 
            return br.readLine();
        }
    }</code></pre>
<p>이렇게 회수해야할 자원을 try 구문에 써주게 된다면 자원을 자동으로 닫아주고 앞선 덮여진 예외의 디버깅등 상당히 편리해 졌으며, 숨겨진 예외의 경우도 무시가 되지 않고 숨겨졌다는 메시지로 출력된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 2장 객체 생성과 파괴 - 아이템 8. finalizer와 cleaner 사용을 피하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-8.-finalizer%EC%99%80-cleaner-%EC%82%AC%EC%9A%A9%EC%9D%84-%ED%94%BC%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-8.-finalizer%EC%99%80-cleaner-%EC%82%AC%EC%9A%A9%EC%9D%84-%ED%94%BC%ED%95%98%EB%9D%BC</guid>
            <pubDate>Thu, 21 Sep 2023 06:35:20 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>자바에서 두 가지 객체 소멸자인 finalizer과 cleaner를 제공한다. 하지만 이를 사용하는 것은 지양해야한다. 자바 9 에서 finalizer은 deprecated 되었고, clenaner가 대안으로 나왔으나 여전히 예측할 수 없고, 느리며, 불필요 하다.</p>
<h2 id="예측-불가능성-문제">예측 불가능성 문제</h2>
<pre><code class="language-java">public class FinalizerTest {

    @Override
    protected void finalize() throws Throwable {
        System.out.println(&quot;Clean test&quot;);
    }

    public void test() {
        System.out.println(&quot;test&quot;);
    }
}
public class Main {

    public static void main(String[] args) throws InterruptedException {
        new Main().run();
        Thread.sleep(100);
    }

    private void run() {
        FinalizerTest finalizerTest = new FinalizerTest();
        finalizerTest.test();
    }
}</code></pre>
<p>FinalizerTest의 인스턴스에 대한 GC(가비자 컬렉터)가 수행될때 &quot;Clean test&quot;라는 문자열을 출력되도록 finalize 메서드를 오버라이드 한 후 Main 클래스를 실행 해보자.</p>
<p>예상해 본다면 run 메서드가 종료되면 FinalizerTest의 인스턴스 참조가 존재하지 않기에 GC가 실행되어 0.1초 기다리는 사이에 &quot;Clean test&quot;가 출력되어야 한다.</p>
<p>하지만 실제 finalize 메서드는 호출되지 않는다.</p>
<p>자바 언어 명세는 어떤 스레드가 finalizer나 cleaner의 수행 시점뿐 아니라 수행 여부조차 보장하지 않는다.</p>
<p>그렇기에 상태를 영구적으로 수정하는 작업에서는 더욱이 finalizer나 cleaner의 사용을 지양하여야 한다.</p>
<hr>
<h2 id="사용지양의-이유">사용지양의 이유</h2>
<ul>
<li><p>성능 문제</p>
<ul>
<li>간단한 AutoCloseable 객체를 생성하고 가비지 컬렉터가 수거시와 finalizer을 사용한 객체 생성 파괴를 살펴보면 무려 50배가 느리다. 이는 finalizer가 가비지 컬렉터의 효율을 떨러어뜨리기 때문이다.</li>
</ul>
</li>
<li><p>보안 문제</p>
<ul>
<li>finalizer은 생성자나 직렬화 과정에서 예외가 발생하면, 생성되다 만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수 있게 된다. 이는 절대 일어나설 안되는 일이다. 다른 객체 생성 방지로는 생성자에 예외를 던지면 되지만finalizer은 그것을 방해한다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="그렇담-언제-사용하는가">그렇담 언제 사용하는가?</h2>
<h3 id="1-안정망-역할">1. 안정망 역할</h3>
<p>cleaner나 finalizer가 즉시 호출되지 않을순 있지만, 클라이언트가 하지 않은 자원 회수를 늦게라도 해주는 것이 안전하다. 대표적으로 FileInputStrean, FileOupputStrea, ThreadPoolExecutor가 있다.</p>
<h3 id="2-네이티브-피어-정리">2. 네이티브 피어 정리</h3>
<p>네이티브 객체는 일반적인 객체가 아니라서 GC가 그 존재를 모른다. 따라서 네이티브 피어가 들고 있는 리소스를 Cleaner나 Finalizer를 사용해서 해당 자원을 반납할 수도 있다.</p>
<p>하지만 이는 해당 자원이 중요하지 않거나, 성능상 영향이 크지 않다면 사용해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 2장 객체 생성과 파괴 - 아이템 7. 다 쓴 객체 참조를 해제하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-7.-%EB%8B%A4-%EC%93%B4-%EA%B0%9D%EC%B2%B4-%EC%B0%B8%EC%A1%B0%EB%A5%BC-%ED%95%B4%EC%A0%9C%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-7.-%EB%8B%A4-%EC%93%B4-%EA%B0%9D%EC%B2%B4-%EC%B0%B8%EC%A1%B0%EB%A5%BC-%ED%95%B4%EC%A0%9C%ED%95%98%EB%9D%BC</guid>
            <pubDate>Wed, 20 Sep 2023 08:10:10 GMT</pubDate>
            <description><![CDATA[<h1 id="java의-메모리-관리">JAVA의 메모리 관리</h1>
<p>C,C++ 와는 다르게 자바에서는 GC(가비지 컬렉터)을 통해 메모리를 회수해 가지만. 그렇다고 <strong>메모리 관리를 하지 않아도 된다고 오해</strong>해서는 절대 안된다</p>
<h2 id="메모리-누수">메모리 누수</h2>
<pre><code class="language-java">public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    public void push() {
        ensureCapacity();
        elements[size++] = e;
    }
    public Object pop() {
        if (size == 0) throw new EmptyStackException();
        return elements[--size];
    }
    // 공간이 부족할 시 2배로 공간을 확장한다.
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}</code></pre>
<p>간단한 스택을 구현한 코드이다. 특별한 문제는 없어 보이지만, 이스택을 사용하는 프로그램을 오래 실행하다 보면 가비지 컬렉션 활동과 메모리 사용량의 증가로 성능이 저하할것 이며, 심할 경우 OutOfMemoeryError을 일으킬 것이다.</p>
<p>그 이유는 pop 메서드를 통해 특정 객체를 꺼내 사용하여 메모리를 해제하는것이 아니라 push 메서드 사용시 그 위치에 덮어 씌우는 방식으로 프로그래밍 되어 있기 때문이다.</p>
<p>가비지 컬렉터의 경우 객체 참조를 살려두면 그 객체가 참조하는 모든 객체를 회수해가지 않는다. 그렇기 때문에 배열에서 참조 해제 시켜주지 않는다면 메모리 누수가 일어날 것이다.</p>
<hr>
<h2 id="참조-해제">참조 해제</h2>
<p>그렇다면 이런 코드의 참조 해제 방법은 무엇이 있냐면</p>
<h3 id="1-다-쓴-참조에-null처리">1. 다 쓴 참조에 null처리</h3>
<pre><code class="language-java">public Object pop() {
    if (size == 0) throw new EmptyStackException();

    Object result = elements[--size];
    elements[size] = null;
    return result;
}</code></pre>
<p>위처럼 다 쓴 참조를 null 처리를 함으로 써, 메모리 누수가 없는 코드를 완성할 수 있다. 그거 의외의 이점으로, 만약 null 처리한 참조를 실수로 사용하려 하면 프로그램은 즉시 NullPointerException을 발생시켜 종료 시킬 것이다.</p>
<p>하지만 이는 코드를 필요 이상으로 더럽힐 것이다. 그렇기에 <strong>null 처리하는 일은 예외적인 경우여야 한다.</strong></p>
<h3 id="2-유효-범위-처리">2. 유효 범위 처리</h3>
<p>다 쓴 참조 해제중 가장 좋은 방법은 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>이외에도 캐시, 리스너, 콜백의 경우에도 메모리 누수를 발생시킬 것이다. 따라서 이런 메모리 사용시 꼭 사용 해제를 해주어야 한다.</p>
<p>메모리 누수는 수년간 시스템에 잠복하는 경우도 있다. 이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원하여 발견되기도 한다. 그렇기에 이런 예방법을 익혀두는 것이 중요할 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 2장 객체 생성과 파괴 - 아이템 6. 불필요한 객체 생성을 피하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-6.-%EB%B6%88%ED%95%84%EC%9A%94%ED%95%9C-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EC%9D%84-%ED%94%BC%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-6.-%EB%B6%88%ED%95%84%EC%9A%94%ED%95%9C-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EC%9D%84-%ED%94%BC%ED%95%98%EB%9D%BC</guid>
            <pubDate>Fri, 15 Sep 2023 06:52:34 GMT</pubDate>
            <description><![CDATA[<h1 id="불필요한-객체를-생성하는-경우">불필요한 객체를 생성하는 경우</h1>
<h2 id="new-string-사용하는-경우">new String() 사용하는 경우</h2>
<pre><code class="language-java">String a = new String(&quot;test&quot;);
String b = new String(&quot;test&quot;);
String c = new String(&quot;test&quot;);</code></pre>
<p>문자열 a,b,c는 결국 동일한 &quot;test&quot;라는 문자열을 가지군다. 하지만 실행될때 마다 String 인스턴스를 새로 만들어 버린다. 이렇게 되면 메모리를 할당하기 때문에 낭비가 발생되어 버린다.</p>
<p>그렇기 때문에 문자열 리터럴을 사용하여 이러한 메모리 낭비를 줄여야 한다.</p>
<pre><code class="language-java">String a = &quot;test&quot;;
String b = &quot;test&quot;;
String c = &quot;test&quot;;</code></pre>
<h2 id="stringmatches의-사용">String.matches()의 사용</h2>
<pre><code class="language-java">public static boolean isRomanNumeral(String s) {
    return s.matches(&quot;^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$&quot;);
}</code></pre>
<p>이러한 String.matches()는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에선 반복하여 사용하기 어렵다. 왜냐하면 내부에서 만드는 정규 표현식용 Pattern 인스턴스는, 한 번 쓰고 버려지기에 가비지 컬렉션 대상이 되기 때문이다. 성능 개선을 하려면 불변 인스턴스를 클래스 초기화시 캐싱해두고 메서드 호출시 인스턴스를 재사용하면 된다.</p>
<pre><code class="language-java">public class RomanNumerals {

    private static final Pattern ROMAN = Pattern.compile(
        &quot;^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$&quot;);

    public static boolean isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
    }
}</code></pre>
<p>개선시 속도도 빨라지고 static final 필드로 이름도 정하기 때문에 코드의 의미가 잘 드러난다.</p>
<h3 id="주의사항">주의사항</h3>
<p>클래스가 초기화된 후 이 메서드를 사용하지 않는 다면 ROMAN 필드는 쓸데없이 최기호가 된것이다.</p>
<p>어댑터는 뒷단 객체에 위임하고, 자신은 제2의 인터페이스 역할을 해주는 객체이다. 어댑터는 뒷단 객체만 관리하면 되므로 객체 하나당 어댑터 하나씩만 만들어 주면된다.</p>
<p>예를 들어 Map 인터페이스의 KeySet 메서드는 Map 객체 안의 키 전부를 담은 Set뷰를 반환한다. 사용자는 KeySet을 호출할 때마다 새로운 Set 인스턴스를 반환한다 생각할 수 있지만. 가변 반환된 Set 인스턴스를 반환한다.</p>
<p>즉 반환된 객체 중 하나를 수정하면 모든 객체가 바뀐다. 모두 똑같은 Map 인스턴스를 대변하기 때문이다. 이러한 이유로 KeySet이 뷰 객체를 여러 개 만들어도 상관 없지만, 그럴 필요도 없고 이득도 없다.</p>
<pre><code class="language-java">Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.put(&quot;A&quot;, 1);
map.put(&quot;B&quot;, 2);

// 두 개의 KeySet 뷰를 얻습니다.
Set&lt;String&gt; keySet1 = map.keySet();
Set&lt;String&gt; keySet2 = map.keySet();

// keySet1을 수정하면 실제 맵도 수정됩니다.
keySet1.add(&quot;C&quot;);

System.out.println(map); // 출력 결과: {A=1, B=2, C=null}</code></pre>
<p>이런것 처럼 Set뷰는 실제 맵의 키를 복제 하지 않고, Map 인스턴스를 대변하는 Set 뷰 객체가 생성되기에 Map 자체가 수정되어 버린다. 이는 동일한 맵 인스턴스를 참조하기 때문이다.</p>
<hr>
<h2 id="오토-박싱">오토 박싱</h2>
<p>오토박싱은 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해 주는 기술이다. 하지만 오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐리게 할뿐 완전히 없애주는 것이 아니다.</p>
<pre><code class="language-java">public static long sum() {
    Long sum = 0L;
    for (long i = 0; i &lt;= Integer.MAX_VALUE; i++) {
        sum += i;
    }
    return sum;
}</code></pre>
<p>이 코드는 정확한 답을 내기는 하나, 성능적으로는 상당히 비효율적인 코드이다.</p>
<p>sum 타입은 <strong>Long</strong>타입이고, i는 <strong>long</strong> 타입이다. 반복문을 돌면서 sum에 더해질때 마다 새로운 Long 인스턴스를 만들게 되며 이는 불필요한 인스턴스가 늘어난게 되며 속도가 느려지게 된다.</p>
<blockquote>
<p>박싱된 기본 타입 보다는 기본 타입을 사용하고, 의도 치 않은 오토박싱이 숨어 들지 않도록 하자.</p>
</blockquote>
<hr>
<h2 id="오해하지-말아야-한다">오해하지 말아야 한다.</h2>
<p><strong>&quot;객체 생성은 비싸니 피해야 한다&quot;</strong>라고 오해하면 안된다.
특히 요즘 JVM은 작은 객체를 생성하고 회수하는 일이 크게 부담되지 않는다. 그렇기 때문에 명확성, 간결성, 기능을 위해 객체를 추가로 생성하는 것이라면 좋은것 이라고 할 수 있다. 
그러므로, 데이터베이스 연결과 같은 생성 비용이 비싼 경우를 제외 하면, 커스텀 객체 풀을 만들지 않아야한다.</p>
<p>마지막으로 방어적 복사가 필요한 상황에서 객체를 재사용 했을때의 피해가, 필요 없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다는 것을 기억하자. 반복 생성의 부작용은 코드 형태와 성능에만 영향을 주지만, 방어적 복사가 실패하면 버그와 보안 문제로 직행한다.</p>
<pre><code class="language-java">public class ImmutableClass {
    private List&lt;String&gt; items;

    public ImmutableClass(List&lt;String&gt; items) {
        // 외부에서 전달된 리스트를 복사하여 내부에 저장합니다.
        this.items = new ArrayList&lt;&gt;(items);
    }

    public List&lt;String&gt; getItems() {
        // 내부 리스트를 그대로 반환합니다.
        return items;
    }
}

public class Client {
    public static void main(String[] args) {
        List&lt;String&gt; originalList = new ArrayList&lt;&gt;();
        originalList.add(&quot;Item 1&quot;);
        originalList.add(&quot;Item 2&quot;);

        ImmutableClass immutableObj = new ImmutableClass(originalList);

        // 클라이언트가 내부 리스트에 직접 접근하여 수정하려고 시도합니다.
        List&lt;String&gt; internalList = immutableObj.getItems();
        internalList.add(&quot;Item 3&quot;);

        // 원래 의도대로 불변 객체인 immutableObj를 수정하려는 시도이지만, 내부 리스트도 변경됩니다.
        System.out.println(immutableObj.getItems()); // 출력 결과: [Item 1, Item 2, Item 3]
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 2장 객체 생성과 파괴 - 아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-5.-%EC%9E%90%EC%9B%90%EC%9D%84-%EC%A7%81%EC%A0%91-%EB%AA%85%EC%8B%9C%ED%95%98%EC%A7%80-%EB%A7%90%EA%B3%A0-%EC%9D%98%EC%A1%B4-%EA%B0%9D%EC%B2%B4-%EC%A3%BC%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-5.-%EC%9E%90%EC%9B%90%EC%9D%84-%EC%A7%81%EC%A0%91-%EB%AA%85%EC%8B%9C%ED%95%98%EC%A7%80-%EB%A7%90%EA%B3%A0-%EC%9D%98%EC%A1%B4-%EA%B0%9D%EC%B2%B4-%EC%A3%BC%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Fri, 15 Sep 2023 05:01:54 GMT</pubDate>
            <description><![CDATA[<h1 id="의존성-주입-기법">의존성 주입 기법</h1>
<blockquote>
<p>클래스가 하나 이상의 자원에 의존하고, 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.이 자원들을 클래스가 직접 만들게 해서도 안 된다. 대신 필요한 자원을 생성자에 넘겨주자. 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선해준다.</p>
</blockquote>
<hr>
<h2 id="예제">예제</h2>
<h3 id="정적-유틸리티를-잘못-사용한-예">정적 유틸리티를 잘못 사용한 예</h3>
<pre><code class="language-java">public class SpellChecker {

    private static final Lexicon dictionary = new Lexicon();

    private SpellChecker() {
    }

    public static boolean isValid(String word) {
        ...
    }

    public static List&lt;String&gt; suggestions(String typo) {
      ....
    }
}</code></pre>
<p>해당 유틸리티 클래스는 사전을 하나만 사용한다고 가정하고 이 있다. 하지만 실제에서는 사전이 언어별 따로 있고 특수 어휘용 사전을 따로 두는 경우도 있다.</p>
<h3 id="싱글턴을-잘못-사용한-예">싱글턴을 잘못 사용한 예</h3>
<pre><code class="language-java">public class SpellChecker {

    private final Lexicon dictionary = new Lexicon();

    public static SpellChecker INSTANCE = new SpellChecker();

    private SpellChecker() {
    }

    public static boolean isValid(String word) {
        ...
    }

    public static List&lt;String&gt; suggestions(String typo) {
        ...
    }
}</code></pre>
<p>싱글턴 방식에서도 마찬가지 사전을 하나만 사용한다고 가정하기에 앞선 단점이 발생한다.</p>
<hr>
<h2 id="해결책">해결책</h2>
<h3 id="final-한정자를-제거">final 한정자를 제거</h3>
<pre><code class="language-java">public class SpellChecker {

    private Lexicon dictionary = new Lexicon();

    public static SpellChecker INSTANCE = new SpellChecker();

    private SpellChecker() {
    }

    public static void changeDictionary(Lexicon dictionary) {
        this.dictionary = dictionary;
    }

    public static boolean isValid(String word) {
        ...
    }

    public static List&lt;String&gt; suggestions(String typo) {
        ...
    }
}</code></pre>
<p>dictionary 필드에서 final 한정자를 제거하고 다른 사전으로 교체하는 메서드를 추가하였다. 하지만 이방식은 오류를 내기 쉬우며 멀티스레드 환경에서는 사용할 수 없다.</p>
<h3 id="의존-객체-주입-방식">의존 객체 주입 방식</h3>
<pre><code class="language-java">public class SpellChecker {

    private final Lexicon dictionary;

    public SpellChecker(Lexicon dictionary) {
        this.dictionary = dictionary;
    }

    public static boolean isValid(String word) {
        ...
    }

    public static List&lt;String&gt; suggestions(String typo) {
        ...
    }
}</code></pre>
<p>의존 객체 주입을 사용한 코드는 위에 예는 처럼 dictionary라는 하나의 자원만 사용하지만, 자원이 몇 개가되든 잘 작동한다. 또한 불변을 보장하여 여러 클라이언트가 의존 객체들을 공유할 수 있는 장점이 있다. 의존 객체 중비은 생성자, 정적 팩터리, 빌더 모두 똑같이 응용이 가능하다.</p>
<p>의존 객체 주입은 생성자에 자원 팩터리를 넘겨주는 방식도 종종 사용된다. 팩터리란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어 주는 객체를 말한다. 이러한 방식을 팩터리 메서드 패턴이라고 한다.자바 8에서 ** Supplier&lt; T &gt;**가 완벽한 예시이다.</p>
<pre><code class="language-java">public static List&lt;Test&gt; create(Supplier&lt;? extends Test&gt; generator) {
    ...
}</code></pre>
<p>일반적으로 한정적 와일드카드 타입을 사용하여 팩터리의 타입 매개 변수를 제한한다. 이 방식을 사용해 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있다.</p>
<p>의존 객체 주입은 유연성과 테스트 용이성을 개선해주지만, 의존성이 수천 개나 되는 큰 프로젝트에서는 코드가 상당히 방대해지고 어지러워 진다. 이런 경우 의존 객체 주입 프레임워크(Dagger, Guice, Spring)을 사용하여 이러한 문제를 해결할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 2장 객체 생성과 파괴 - 아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-4.-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%ED%99%94%EB%A5%BC-%EB%A7%89%EC%9C%BC%EB%A0%A4%EA%B1%B0%EB%93%A0-private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-4.-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%ED%99%94%EB%A5%BC-%EB%A7%89%EC%9C%BC%EB%A0%A4%EA%B1%B0%EB%93%A0-private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Thu, 14 Sep 2023 06:31:36 GMT</pubDate>
            <description><![CDATA[<h2 id="추상-클래스로-만드는-것으로는-인스턴스화를-막을-수-없다">추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다.</h2>
<p>정적 메서드와 정직 필드만을 담은 클래스를 만들고 싶을 때가 있다. 예를 들어 java.lang.Math, java.util.Arrays 처럼 기본 타입 값이나 배열 관련 메서드들을 모아 놓을 수 있다. 혹은 java.util.Collections처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드를 모아놓을 수 있다.</p>
<p>이렇나 유틸리티 클래스는 인스턴스 변수나 메서드가 없어도 사용할 수 있다. 하지만 사용자는 생성자가 자동 생성된 것인지 구분할 수 없다.</p>
<p>이를 막기 위해 추상 클래스로 만드는 경우가 있는데 추상 클래스로는 인스턴스화를 막을 수 없다. 하위 클래스를 만들어 인스턴스화 하면 그만이기 때문이다.</p>
<pre><code class="language-java">public abstract class Parents {

    public static void talk(String message) {
        System.out.println(message);
    }
}

public class Child extends Parents {

    public Student() {
        super();
    }
}</code></pre>
<p>그렇기 때문에 유틸리티 클래스의 인스턴스화를 막기 위해서는 생성자의 접근 제어자를 private으로 만들어주어야 한다.</p>
<pre><code class="language-java">public abstract class Parents {
    private Parents() {
        throw new AssertionError();
    }

    public static void talk(String message) {
        System.out.println(message);
    }
}</code></pre>
<p>명시적 생성자가 private이니 클래스 바깥에스는 접근할 수 없다. 이러한 private 생성자는 직관적이지 않기에 적절한 주석을 다는 것이 좋다. 또한 private으로 선언 하면서 하위 클래스가 상위 클래스의 생성자에 접근할 길이 막히기 때문에 상속을 불가능하게 하는 효과도 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 2장 객체 생성과 파괴 - 아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-3.-private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%82%98-%EC%97%B4%EA%B1%B0-%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C-%EC%8B%B1%EA%B8%80%ED%84%B4%EC%9E%84%EC%9D%84-%EB%B3%B4%EC%A6%9D%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-3.-private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%82%98-%EC%97%B4%EA%B1%B0-%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C-%EC%8B%B1%EA%B8%80%ED%84%B4%EC%9E%84%EC%9D%84-%EB%B3%B4%EC%A6%9D%ED%95%98%EB%9D%BC</guid>
            <pubDate>Thu, 14 Sep 2023 05:26:41 GMT</pubDate>
            <description><![CDATA[<h1 id="싱글턴">싱글턴</h1>
<h2 id="싱글턴-이란">싱글턴 이란?</h2>
<blockquote>
<p>싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. 싱글턴의 전형적인 예로는 함수와 같은 무상태 객체, 설계상 유일한 시스템 컴포넌트를 말한다. 하지만 싱글턴 클래스는 타입을 인터페이스로 정의하고 그것을 구현체로 정의한 것이 아니라면 테스트하기 어려워질 수 있다.</p>
</blockquote>
<hr>
<h2 id="싱글턴-생성-방식">싱글턴 생성 방식</h2>
<h3 id="1-public-static-final-필드-방식">1. public static final 필드 방식</h3>
<pre><code class="language-java">public class Elvis {

    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
        ...
    }

    public void leaveTheBuilding() {
        System.out.println(&quot;elvis&quot;);
    }
}</code></pre>
<p>private 생성자는 public static final 필드인 Elvis.INSTANCE를 초기화할 때 딱 한번만 호출 된다. 예외는 단 한가지, 권한이 있는 클라이언트는 리플렉션 API인 AccessibleObject.setAccessible을 사용해 private 생성자를 호출할 수 있는데, 이렇나 공격을 방어하려면 생성자를 수정하여 두번째 객체가 생성되려 할 때 예외를 던져 막을 수 있다.
<br></p>
<h4 id="장점">장점</h4>
<ul>
  <li>해당 클래스가 싱글턴임이 API에 명백히 드러남</li>
  <li>간결함</li>
</ul>

<h3 id="2-정적-팩터리-방식">2. 정적 팩터리 방식</h3>
<pre><code class="language-java">public class Elvis {

    private static final Elvis INSTANCE = new Elvis();

    private Elvis() {
        ...
    }

    public static Elvis getInstance() {
        return INSTANCE;
    }

    public void leaveTheBuilding() {
        System.out.println(&quot;elvis&quot;);
    }
}</code></pre>
<p>Elvis.getInstance는 항상 같은 객체의 참조를 반환하므로 Elvis의 유일한 인스턴스임을 보장한다.
<br></p>
<h4 id="장점-1">장점</h4>
<ul>
  <li>API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다. <br>
    - 스레드별 다른 인스턴스를 넘기게 하기
  </li>
  <li>제네릭 싱글턴 팩터리 메서드로 변경할 수 있다.</li>
  <li>정적 펙터리 메서드 참조를 공급자로 사용할 수 있다.
  <br>
      - Elvis::getInstance를 대신해 Supplier < Elvis>로 사용할 수 있다.
  </li>
</ul>
하지만 이러한 장점이 필요하지 않다면 첫 번째 방식을 사용하는 편이 좋다

<h3 id="3-열거-타입-사용법">3. 열거 타입 사용법</h3>
<pre><code class="language-java">public enum Elvis {

    INSTANCE;

    public void leaveTheBuilding() {
        System.out.println(&quot;elvis&quot;);
    }
}
</code></pre>
<p>가장 바람직한 방식은 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 것이 가장 좋은 방법이다.
public 필드 방식과 비슷하지만 간결하고, 직렬화 상황이나 리플렉션 공격에도 방어가 가능하다.</p>
<p>단, 만드려는 싱글턴이 Enum외의 클래스를 상속해야 한다면 이방법은 사용할 수 없다.</p>
<hr>
<h2 id="싱글턴-클래스를-직렬화할-때-주의할-것">싱글턴 클래스를 직렬화할 때 주의할 것</h2>
<p>첫 번째, 두 번째로 만든 싱글턴 클래스를 직렬화 하려면 단순히 Serializable을 구현하는 것 외, 인스턴스 필드를 일시적 이라고 선언하고, readResolve 메서드를 제공해야만 한다.</p>
<pre><code class="language-java">private Object readResolve throws ObjectStreamException {
    return INSTANCE;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 2장 객체 생성과 파괴 - 아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-2.-%EC%83%9D%EC%84%B1%EC%9E%90%EC%97%90-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EA%B0%80-%EB%A7%8E%EB%8B%A4%EB%A9%B4-%EB%B9%8C%EB%8D%94%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-2.-%EC%83%9D%EC%84%B1%EC%9E%90%EC%97%90-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EA%B0%80-%EB%A7%8E%EB%8B%A4%EB%A9%B4-%EB%B9%8C%EB%8D%94%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC</guid>
            <pubDate>Wed, 13 Sep 2023 08:42:45 GMT</pubDate>
            <description><![CDATA[<h2 id="점층적-생성자-패턴">점층적 생성자 패턴</h2>
<p>정적 팩터리와 생성자에는 선택적 매개변수의 수가 많을 때 대응하기가 어렵다. 이럴 때 점증적 생성자 패턴을 사용할 수 있다.</p>
<pre><code class="language-java">public class NutritionFacts {

    private final int servingSize;

    private final int servings;

    private final int calories;

    private final int fat;

    private final int sodium;

    private final int carbohydrate;

    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0, 0, 0, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, 0, 0, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this(servingSize, servings, calories, fat, 0, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }
}</code></pre>
<p>하지만 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기가 매우 어렵다.</p>
<hr>
<h2 id="자바-빈즈-패턴">자바 빈즈 패턴</h2>
<pre><code class="language-java">public class NutritionFactsWithJavaBeansPattern {

    private int servingSize = -1; // 필수. 기본 값 없음.

    private int servings = -1; // 필수. 기본 값 없음.

    private int calories;

    private int fat;

    private int sodium;

    private int carbohydrate;

    public void setServingSize(int servingSize) {
        this.servingSize = servingSize;
    }

    public void setServings(int servings) {
        this.servings = servings;
    }

    public void setCalories(int calories) {
        this.calories = calories;
    }

    public void setFat(int fat) {
        this.fat = fat;
    }

    public void setSodium(int sodium) {
        this.sodium = sodium;
    }

    public void setCarbohydrate(int carbohydrate) {
        this.carbohydrate = carbohydrate;
    }
}</code></pre>
<p>자바빈즈 패턴은 매개변수가 많더라도 인스턴스를 쉽게 만들 수 있다. 하지만 setter 메서드를 통한 반복적인 메서들 호출과, 객체가 온전히 완성되지 않았다면 일관성이 깨질 수 있다. 이는 클래스를 불변으로 만들 수 없는 문제가 생긴다.</p>
<hr>
<h2 id="빌더-패턴">빌더 패턴</h2>
<p>점층적 생성자 패턴의 안전성과 자바 빈즈 패턴의 가독성을 겸비한 빌더 패턴을 주로 사용한다. 클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수 만으로 생성자를 호출해 빌더 객체를 얻는다. 이후 빌더 객체가 제공하는 세터 메서드들로 원하는 선택 매개변수를 설정하고, 매개변수가 없는 <strong>build()</strong> 메서드를 호출해 필요한 객체를 얻는다.</p>
<pre><code class="language-java">public class NutritionFactsWithBuilderPattern {

    private final int servingSize;

    private final int servings;

    private final int calories;

    private final int fat;

    private final int sodium;

    private final int carbohydrate;

    private NutritionFactsWithBuilderPattern(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }

    public static class Builder {

        private final int servingSize;

        private final int servings;

        private int calories;

        private int fat;

        private int sodium;

        private int carbohydrate;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            calories = val;
            return this;
        }

        public NutritionFactsWithBuilderPattern build() {
            return new NutritionFactsWithBuilderPattern(this);
        }
    }
}</code></pre>
<p>NutritionFactsWithBuilderPattern 클래스는 불변하며, Builder의 Setter 메서들은 빌더 자신을 반환하기 때문에 연쇄적 호출이 가능하다. 이런 방식은 API 혹은 메서드 연쇄라고 한다.</p>
<pre><code class="language-java">NutritionFactsWithBuilderPattern nutritionFacts = 
    new NutritionFactsWithBuilderPattern.Builder(240, 8)
        .calories(100)
        .sodium(35)
        .build();</code></pre>
<p>클러이언트는 빌더 패턴을 통해 코드를 읽기,쓰기 작업들을 편하기할 수 있다.</p>
<hr>
<h2 id="빌더-패턴은-계층적으로-설계된-클래스와-함께-쓰기-좋다">빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기 좋다.</h2>
<pre><code class="language-java">public abstract class Pizza {

    public enum Topping {
        HAM, MUSHROOM, ONION, PEPPER, SAUSAGE
    }

    final Set&lt;Topping&gt; toppings;

    Pizza(Builder&lt;?&gt; builder) {
        toppings = builder.toppings.clone();
    }

    abstract static class Builder&lt;T extends Builder&lt;T&gt;&gt; {

        private EnumSet&lt;Topping&gt; toppings = EnumSet.noneOf(Topping.class);

        public T addTopping(Topping topping) {
            toppings.add(topping);
            return self();
        }

        abstract Pizza build();

        protected abstract T self();
    }
}</code></pre>
<p>Pizza.Bulder 클래스는 재귀적 타입 한정을 이용하는 제네릭 타입이다. 추상 메서드인 self를 더해 하위 클래스에서는 형변환하지 않고도 메서드를 연쇄를 지원할 수 있다. 하위 클래스에서는 이 추상 메서드의 반환값을 자기 자신을 주면 된다.
Pizza를 상속받는 뉴욕 피자와 칼초네 피자를 보며 좀더 살펴보자</p>
<pre><code class="language-java">public class NyPizza extends Pizza {

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    private final Size size; // 필수 매개변수

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }

    public static class Builder extends Pizza.Builder&lt;Builder&gt; {

        private final Size size;

        public Builder(Size size) {
            this.size = size;
        }

        @Override
        NyPizza build() {
            return new NyPizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

}</code></pre>
<pre><code class="language-java">public class CalzonePizza extends Pizza {

    private final boolean sauceInside; // 선택 매개변수

    private CalzonePizza(Builder builder) {
        super(builder);
        sauceInside = builder.sauceInside;
    }

    public static class Builder extends Pizza.Builder&lt;Builder&gt; {

        private boolean sauceInside = false;

        public Builder sauceInside() {
            sauceInside = true;
            return this;
        }

        @Override
        CalzonePizza build() {
            return new CalzonePizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }
}</code></pre>
<p>각 하위 클래스의 빌더가 정의한 <strong>build()</strong>를 통해 하위 클래스를 반환하고 있다. NYPizza.builder는 NyPizza를 반환하고, Calzone.Builder는 Calzone 피자를 반환하다는 것이다. 하위 메서드가 상위 클래스의 메서가 반환한 타입이 아닌, 그 하위 타입을 반환하는 기능을 공변 반환 타이핑이라고 한다. 이 기능 사용하면 클라이언트가 형변환을 할 필요가 없다.</p>
<pre><code class="language-java">NyPizza nyPizza = new NyPizza.Builder(Size.SMALL)
    .addTopping(Topping.SAUSAGE).addTopping(Topping.ONION).build();

CalzonePizza calzonePizza = new CalzonePizza.Builder()
    .addTopping(Topping.HAM).sauceInside().build();</code></pre>
<p>클라이언트 입장에서 Pizza의 enum과 각 하위 클래스의 enum(열거형)을 혼용할 수 있 있다. 이러한 빌더 패턴은 매우 큰 유연성을 가져다 준다.</p>
<hr>
<h2 id="빌더-패턴-단점">빌더 패턴 단점</h2>
<ol>
<li>빌더 객체의 생성 </li>
<li>매개변수가 적을시 용이하다.</li>
<li>코드의 장황함.</li>
</ol>
<hr>
<h2 id="결론">결론</h2>
<p>생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] try-with-resources 예외 처리 ]]></title>
            <link>https://velog.io/@sangkyu-bae/Java-try-with-resources-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@sangkyu-bae/Java-try-with-resources-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Mon, 11 Sep 2023 04:43:37 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>프로젝트를 진행하면서 요약하자면 파일 다운로드 하는기능을 만들다가 오류가 발생했는데 디버깅 하기가 쉽지 않았다 그저 close()에서 오류가 발생했다고만 나오고 자세히 기술되어 나오지 않았기 때문이다. 대략적인 코드이다</p>
<pre><code class="language-java">  try  {
           File file = new File( &quot;/test&quot; );
           dataByte = new byte[(int) file.length()];
           fileInputStream = new FileInputStream(file);
           fileInputStream.read(dataByte);
        } catch (Exception e) {
           throw new UnknownException(&quot;Unknown Exception&quot;, e);
        } finally {
           fileInputStream.close();
        }</code></pre>
<p>그래서 try-with-resources으로 바꾸었고 try-with-resources가 무엇인지 알아보자.</p>
<hr>
<h2 id="1-try-catch-finally란">1. try-catch-finally란?</h2>
<h3 id="java7-이전의-try-catch-finally">Java7 이전의 try-catch-finally</h3>
<p>사용 후에 반납해주어야 하는 자운들은 Closable 인터페이스를 구현하고 있으며, 사용 후에 <strong>close</strong> 메소드를 호출해 주어야 했다.
앞선 코드에서는 파일을 다운 받는 경우로 예시를 들었었다.
문제는 이렇나 과정에서 여러가지 단점을 가지고 있다.</p>
<ul>
  <li>자원 반납에 의해 코드가 복잡해진다.</li>
  <li>실수로 인한 자원반납 코드의 부재</li>
  <li>에러로 인한 자원 반납하지 못하는 경우</li>
  <li>디버깅의 어려움</li>
</ul>
이러한 문제를 해결하기 위해 try-with-resources라는 문법이 Java7부터 추가됐다.

<h3 id="java7-이후-try-with-resources">Java7 이후 try-with-resources</h3>
<p>Java는 이렇나 문제점을 해결하기 위해 자원을 자동 반납해주는 try-with-resources 문법을 추가했다. Java는 AutoCloseable 인터페이스를 구현하고 있는 자원에 대해 try-with-resources를 적용 가능하도록 만들었고, 이를 사용함으로써 코드의 유연함과 에러를 잡는 효과를 만들었다.</p>
<pre><code class="language-java">  try (FileInputStream fileInputStream = new FileInputStream(file)) {
             File file = new File( &quot;/test&quot; );
           dataByte = new byte[(int) file.length()];
           fileInputStream = new FileInputStream(file);
           fileInputStream.read(dataByte);
            }
        }</code></pre>
<h3 id="closeable의-autocloseable-상속">Closeable의 AutoCloseable 상속</h3>
<p>close()를 따라가보면 기존의 Closeable에 부모 인터페이스로 AutoClosable을 추가한것을 볼 수 있다. 이는 기존 부모 인터페이스에 AutoClosable을 새롭게 추갛나 것이다.
보편적으로 먼저 만들어진 인터페이스를 자식 구현체에서 사용하는것이 일반적이다. 하지만 먼저 만들어진 Closable 인터페이스에 부모 AutoCloesable을 추가하면서 하위 호환성을 달성 시키며 변경 작업에 대한 수고를 덜었다. 만약 반대의 경우 Closeable을 부모로 만들고 진행하였다면 기존 상속받은 클래스들 모두 AutoClosable을 구현하도록 수정이 필요했을 것이다. </p>
<hr>
<h2 id="2-try-with-resources을-권장해야-하는-이유">2. try-with-resources을 권장해야 하는 이유</h2>
<h3 id="에러-스택-트레이스의-누락">에러 스택 트레이스의 누락</h3>
<p>try-catch-finally를 이용하면 개요에서 설명한것 처럼 에러 스택 트레이스가 누락되는 경우가 발생할 수 있다. 이로인해 디버깅을 통한 오류 파악이 매우 어렵게 만든다.
예를 들어 AutoCloseable을 구현채를 만들어 보자</p>
<pre><code class="language-java">public class TestResource implements AutoCloseable {

    @Override
    public void close() throws RuntimeException{
        System.out.println(&quot;Test close&quot;);
        throw new Exception();
    }

    public void print() {
        System.out.println(&quot;print&quot;);
        throw new Exception();
    }
}</code></pre>
<p>클래스를 살펴 보자면 print와 close 호출시 Exception을 발생시킨다. 이제 try-catch-finally와 try-with-resources로 테스트를 해보자</p>
<h3 id="try-catch-finally로-구현">try-catch-finally로 구현</h3>
<p>try-catch-finally로 자원을 반납하는 코드를 구현해보자 </p>
<pre><code class="language-java">public void test() {
    TestResource testResource  = null;

    try {
        testResource = new TestResource();
        testResource.print();
    } finally {
       testResource.close();
    }
}
</code></pre>
<p>문제는 이코드를 실행하면 앞선 설명처럼 close 호출 시에 에러만 찍힌다는 것이다</p>
<h3 id="try-with-resources로-구현">try-with-resources로 구현</h3>
<p>이번에는 try-with-resource로 구현해보자.</p>
<pre><code class="language-java">public void test() {
    try (TestResource testResource = new TestResource()) {
        testResource.print();
    }
}</code></pre>
<p>이경우에는 print와 close 모두에 에러 트레이스가 남게 된다. 이런 에러 트레이스가 잘남는 다는것은 에러에대한 원인파악을 한층더 편하게 해준다. 또한 코드 자체도 finally로 close를 하지 않아도 자동으로 자원을 반납하기 때문에 close()를 실수로 호출하지 않아 생기는 문제도 미연에 방지할 수 있다.</p>
<p>정리하자면 다음과 같은 이유로 try-with-resources를 사용을 적극 권장한다.</p>
<ul>
 <li>코드의 간결함</li>
  <li>번거로운 자원 반납 작업의 간소화</li>
  <li>모든 에러에 대한 스택 트레이스를 남김으로써 디버깅의 간소화</li>
</ul>

<p>이러한 이유들로 앞으로는 자원을 반납하는 경우를 만나면 <strong>try-with-resources</strong>를 적극 사용하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] 2장 객체 생성과 파괴 - 아이템 1. 생성자 대신 정적 팩터리 메소드를 고려하라]]></title>
            <link>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-1.-%EC%83%9D%EC%84%B1%EC%9E%90-%EB%8C%80%EC%8B%A0-%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%84%B0%EB%A6%AC-%EB%A9%94%EC%86%8C%EB%93%9C%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@sangkyu-bae/Effective-Java-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4-%EC%95%84%EC%9D%B4%ED%85%9C-1.-%EC%83%9D%EC%84%B1%EC%9E%90-%EB%8C%80%EC%8B%A0-%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%84%B0%EB%A6%AC-%EB%A9%94%EC%86%8C%EB%93%9C%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sun, 10 Sep 2023 08:02:58 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>일반적으로 클래스의 인스턴스를 얻는 방법으로는 public 생성자다.</p>
<pre><code class="language-java">public class Product {
    private String name;

    private int price;

    private String description;

    private ProductStatus productStatus;

    public Product(String name, int price, String description, ProductStatus productStatus){
        this.name = name;
        this.price = price;
        this.description = description;
        this.productStatus = productStatus;
    }


}

public enum ProductStatus{
    WAITING,
    RELEASE,
    DISCONTINUE
}</code></pre>
<p>일반적인 클래스에선 public 생성자만응로도 충분히 생성이 가능하지만, 생성자 외에 정적 팩터리 메서드를 제공하면 사용자 입장에서 의도한 대로 인스턴스를 만들기 쉬워지는 경우가 종종 있다.</p>
<p>정적 팩터리 메서드의 대표적인 예시는 <strong>Boolean</strong>의 <strong>valueOf()</strong> 메서드가 있다.</p>
<pre><code class="language-java">public static Boolean valueOf(boolean b){
    return b ? Boolean.TRUE : Boolean.FALSE;
}</code></pre>
<p>메서드는 기본 타입인 boolean값을 받아와 Boolean 객체로 만들어 반환하고 있다.</p>
<hr>
<h2 id="정적-팩터리-메서드의-장점">정적 팩터리 메서드의 장점</h2>
<h3 id="이름을-가질-수-있다">이름을 가질 수 있다.</h3>
<p>책 에서는 BinInteger(int, int, Random)과 정적 팩터리 메서드인 BigInteger.probablePrime중 어느 쪽이 &#39;값이 소수인 BigInteger를 반환하다&#39;는 의미인지 생각해 보라고 한다. 앞서 작성한 public 생성자 코드를 예로 예제를 만들어 보자면</p>
<p>만약 Product클래스의 메인 생성자인 (name, price, description, productStatus)만 보고 어떤 특성을 가진 Product인지 파악하기 어렵다. </p>
<p>또한, 하나의 시그니처로는 하나의 생성자를 만들 수 있는데, 정적 팩터리 메서드는 이름을 가질 수 있으므로 하나의 시그니처로 여러 개의 정적 팩터리 메서드를 만들어 인스턴스를 반환할 수 있다.</p>
<pre><code class="language-java">public class Product {
    private String name;

    private int price;

    private String description;

    private ProductStatus productStatus;

    public Product(String name, int price, String description, ProductStatus productStatus){
        this.name = name;
        this.price = price;
        this.description = description;
        this.productStatus = productStatus;
    }

    public static Product waitingMember(String name, int price, String description) {
        return new Product(name, price, description, ProductStatus.WAITING);
    }

    public static Product releaseMember(String name, int price, String description) {
        return new Product(name, price, description, ProductStatus.RELEASE);
    }

    public static Product discontinueMember(String name, int price, String description) {
        return new Product(name, price, description, ProductStatus.DISCONTINUE);
    }

}

public enum ProductStatus{
    WAITING,
    RELEASE,
    DISCONTINUE
}</code></pre>
<p>위와 같은 생성자로 ProductStatus를 구분하는 것보단 같은 행위를 가진 여러개의 정적 팩터리 메서드를 만들면, 사용자 입장에선 혼동의 여지 없이 특정 상태의 Product 인스턴스를 생성할 수 있게 된다.</p>
<hr>
<h3 id="호출될-때마다-인스턴스를-새로-생성하지-않아도-된다">호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.</h3>
<pre><code class="language-java">public static Boolean valueOf(boolean b){
    return b ? Boolean.TRUE : Boolean.FALSE;
}</code></pre>
<p>Boolean의 valueOf() 메소드는 인스턴스를 미리 캐싱해뒀다가 반환해 주는 것을 알 수 있다. 이러한 생성 비용이 큰 객체가 자주 요청되는 상황이라면 성능을 상당히 높여줄 수 있고, <strong>플라이웨이트 패턴</strong>도 이와 비슷한 기법으로 볼 수 있다.</p>
<hr>
<h3 id="반환-타입의-하위-타입-객체를-반환할-수-있는-능력이-있다">반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.</h3>
<pre><code class="language-java">  String[] strs = {&quot;alpha&quot;, &quot;beta&quot;, &quot;charlie&quot;};
  List&lt;String&gt; lst = Arrays.asList(strs);</code></pre>
<p>해당 코드는 간단하게 문자열 배열을 list로 변환하는 코드이 이다 이런 Arrays유틸 클래스의 asList() 메서드를 사용해본 적이 있다면 해당 장점을 이해할 수 있을 것이다.</p>
<pre><code class="language-java">public static &lt;T&gt; List&lt;T&gt; asList(T... a) {
   return new ArrayList&lt;&gt;(a);
}</code></pre>
<p>실제 asList 구현 코드를 확인하면 List의 하위 구현체인 ArrayList로 값을 래핑하여 반환하는데, 이때 사용자는 이러한 구현체까지 알 필요가 없다. 즉 반환 객체의 클래스를 자유롭게 선택할 수 있다는 유연성은 개발자가 구현체를 공개하지 않고 구현체를 반환할 수 있기에 API를 작게 유지할 수 있다.</p>
<hr>
<h3 id="입력-매개변수에-따라-매번-다른-클래스의-객체를-반환할-수-있다">입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.</h3>
<p>반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관 없다. 이점은 파라미터의 값에 따랄 다른 하위 타입을 반환할 수 있도록 한다.
만약 어떤 값에 따라 ProductStatus를 반환하고 싶다면, 정적 팩터리를 만들고 그 안에 비교 로직을 세우면 될 것이다.</p>
<pre><code class="language-java">public enum ProductStatus{
    WAITING(0,30),
    RELEASE(31,60),
    DISCONTINUE(61,100);

    private final int min;
    private final int max;

    ProductStatus(int min,int max){
        this.min = min;
        this.max = max;
    }

    public static ProductStatus of(int num){
        return Arrays.stream(values())
                .filter(checkProductStatus(num))
                .findFirst()
                .orElseThrow(()-&gt; new NoSuchElementException(&quot;해당 ProductStatus를 찾을 수 없습니다.&quot;))
    }

    private static Predicate&lt;ProductStatus&gt; checkProductStatus(int num){
        return ele -&gt; ele.min &lt;= num &amp;&amp; ele.max &gt;= num;
    }
}</code></pre>
<hr>
<h2 id="정적-팩토리-메서드-단점">정적 팩토리 메서드 단점</h2>
<h3 id="상속을-하려면-public이나-protected-생성자가-필요하니-정적-팩터리-메서드만-제공하면-하위-클래스를-만들-수-없다">상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.</h3>
<p>하지만 이 제약은 상속보다 컴포지션을 사용하도록 유도하고 불변 타입으로 만드려면 이 제약을 지켜야 하기에 오히려 장점으로 받아들일 수 도있다.</p>
<hr>
<h3 id="정적-팩터리-메서드는-프로그래머가-찾기-어렵다">정적 팩터리 메서드는 프로그래머가 찾기 어렵다.</h3>
<p>생성자 처럼 API 설명에 명확히 드러나지 않기에 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 한다. 그렇기 때문에 API 문서를 잘 써놓고 메서드 이름도 널리 알려진 규약을 사용하여 문제를 완화하여야 한다.</p>
<h3 id="정적-팩터리-메서드-명명-방식">정적 팩터리 메서드 명명 방식</h3>
<ol>
  <li>
      form : 매개변수를 하나 받아서 인스턴스 반환하는 형변환 메서드
    ex) Date d = Date.from(instant) 
  </li>  
   <li>
      of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환 집계 메서드
    ex) Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  </li>  
   <li>
      valueOf : from과 of의 더 자세한 버전
    ex) BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  </li>  
   <li>
      instance 혹은 getInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
    ex) StackWalker luke = StackWalker.getInstance(options);
  </li>   
  <li>
      create 혹은 newInstance : 매개변수를 하나 받아서 인스턴스 반환하는 형변환 메서드
    ex) Object newArray = Array.newInstance(classObject, arraylen);
  </li>  
   <li>
      getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 쓴다.
    ex) FileStore fs = Files.getFileStore(path); 
  </li>  
   <li>
      newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 쓴다.
    ex) BufferedReader br = Files.newBufferedReader(path);
  </li>
     <li>
      type :getType과 newType의 간결한 버전
    ex) List<Complaint> litany = Collections.list(legacyLitany); 
  </li>
</ol>
]]></description>
        </item>
    </channel>
</rss>