<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>soobin_n_n_i.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 19 May 2025 15:06:53 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>soobin_n_n_i.log</title>
            <url>https://velog.velcdn.com/images/soobin_n_n_i/profile/b14f75df-3ccd-4c3a-b968-4bc7d32cf158/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. soobin_n_n_i.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/soobin_n_n_i" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Java instanceof 연산자의 성능과 설계 원칙]]></title>
            <link>https://velog.io/@soobin_n_n_i/Java-instanceof-%EC%97%B0%EC%82%B0%EC%9E%90%EC%9D%98-%EC%84%B1%EB%8A%A5%EA%B3%BC-%EC%84%A4%EA%B3%84-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@soobin_n_n_i/Java-instanceof-%EC%97%B0%EC%82%B0%EC%9E%90%EC%9D%98-%EC%84%B1%EB%8A%A5%EA%B3%BC-%EC%84%A4%EA%B3%84-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Mon, 19 May 2025 15:06:53 GMT</pubDate>
            <description><![CDATA[<h2 id="-궁금증">? 궁금증</h2>
<p>오늘 JAVA 기초 스터디 발표를 듣던 중, instanceof 연산자가 피연산자가 어떤 클래스의 인스턴스인지, 하위 클래스인지, 또는 인터페이스 구현체인지 여부를 확인한다는 것을 배웠습니다. 이 과정에서 클래스 계층 구조를 확인해야 한다는 점이 &#39;무거운 연산이 아닐까?&#39;라는 궁금증을 갖게 되었습니다.
왜냐하면 특히 깊은 상속 구조를 가진 클래스들이 존재할 것이고, 그것에 대해 빈번한 타입 체크를 하게 된다면 성능에 영향을 줄 수 있을 것 같았기 때문입니다. </p>
<h2 id="instanceof-연산자란">instanceof 연산자란?</h2>
<p>instanceof 연산자는 런타임 시 객체가 특정 클래스의 인스턴스인지, 하위 클래스인지, 또는 특정 인터페이스의 구현체인지 여부를 true/false로 반환합니다.</p>
<h2 id="성능-관점에서의-instanceof">성능 관점에서의 instanceof</h2>
<p>instanceof의 성능에 대해 결론은 &quot;비교적 무거운 편이지만, 매우 무거운 연산은 아니다&quot;입니다.</p>
<h3 id="동작-원리">동작 원리</h3>
<ul>
<li>instanceof는 런타임 시 객체 헤더에 포함된 클래스 포인터를 통해 클래스 메타데이터(메서드 영역)에 접근하여 상속 관계를 확인합니다.</li>
<li>단순한 포인터 비교만으로는 상속 관계를 완전히 확인할 수 없으므로, 클래스 메타데이터 내의 슈퍼클래스 정보를 참조하여 체인을 따라 검사합니다.</li>
</ul>
<h3 id="성능-특성">성능 특성</h3>
<ol>
<li><strong>직접적인 클래스 비교</strong>: 객체가 정확히 해당 클래스의 인스턴스인 경우 거의 포인터 비교 수준으로 빠릅니다.</li>
<li><strong>상속 계층 탐색</strong>: 상속 계층 구조를 확인할 때는 상속 깊이에 비례해 비용이 증가합니다. 상속 계층이 깊을수록 더 많은 비교 연산이 필요합니다.</li>
<li><strong>JVM 최적화</strong>: JVM은 클래스 계층 구조 정보를 캐싱하는 최적화를 적용하므로, 반복적인 instanceof 검사는 첫 번째 검사보다 빠를 수 있습니다.</li>
</ol>
<h3 id="실제-영향">실제 영향</h3>
<ul>
<li>일반적인 애플리케이션에서 instanceof는 성능 병목으로 작용하기 어렵습니다.</li>
<li>타입 체크가 매우 빈번하게 발생하는 핫 루프(hot loop)에서는 성능에 영향을 줄 수 있습니다.</li>
</ul>
<h2 id="객체지향-설계-관점에서의-instanceof">객체지향 설계 관점에서의 instanceof</h2>
<p>instanceof는 성능보다 설계 관점에서 더 큰 문제를 가질 수 있다는 것을 추가적인 공부에서 알 수 있었습니다.</p>
<ul>
<li>instanceof가 객체지향 설계 원칙을 위반하기 쉬운 문법이라 지양해야 한다고 합니다.</li>
<li>자세한 내용은 다음 글을 참고할 수 있습니다:<ul>
<li><a href="https://ksh-coding.tistory.com/84">instanceof 사용을 지양하기 - Why? Solution?</a></li>
<li><a href="https://tecoble.techcourse.co.kr/post/2021-04-26-instanceof/">instanceof의 사용을 지양하자</a></li>
</ul>
</li>
</ul>
<h2 id="결론">결론</h2>
<p>instanceof는 성능 측면에서는 &#39;무거운&#39; 연산은 아니지만, 단순 포인터 비교나 기본 산술 연산보다는 비용이 더 큽니다. 성능보다는 객체지향 설계 원칙 측면에서 더 신중하게 사용을 고려해야 합니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EntityGraph에서 QueryDSL 프로젝션으로]]></title>
            <link>https://velog.io/@soobin_n_n_i/Entity-Graph%EC%97%90%EC%84%9C-QueryDSL%EB%A1%9C-%EC%A3%BC%EB%AC%B8-%EC%83%81%EC%84%B8-%EC%A1%B0%ED%9A%8C-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@soobin_n_n_i/Entity-Graph%EC%97%90%EC%84%9C-QueryDSL%EB%A1%9C-%EC%A3%BC%EB%AC%B8-%EC%83%81%EC%84%B8-%EC%A1%B0%ED%9A%8C-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 18 Feb 2025 14:59:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>✏️</code> &nbsp;&nbsp; 주문 상세 조회 API를 개발하면서 N+1 문제를 해결하기 위한 두 가지 접근법, EntityGraph와 QueryDSL 프로젝션을 비교 검토했다. 초기에는 JPA의 EntityGraph를 적용했으나 실제 쿼리 로그를 분석하며 몇 가지 개선이 필요한 부분을 발견했다.
&nbsp;&nbsp;　EntityGraph는 코드가 간결하고 직관적인 장점이 있었지만, 모든 컬럼을 조회하는 비효율성과 복잡한 JOIN 구조로 인한 성능 문제가 우려되었다. 이를 개선하기 위해 QueryDSL 프로젝션 방식으로 전환하고 그 과정에서 얻은 인사이트를 정리하여 공유하고자 한다.</p>
</blockquote>
<hr>
<br/>

<h2 id="✏️-내용">✏️ 내용</h2>
<p>　주문 상세 조회 기능을 구현할 때 처음에는 EntityGraph로 N+1 문제를 해결하려 했으나, 필요 이상의 데이터를 조회하는 문제가 있었다. 이를 QueryDSL 프로젝션으로 전환하여 필요한 데이터만 효율적으로 조회하는 방식으로 개선했다.</p>
<h3 id="entitygraph-방식"><em>EntityGraph 방식</em></h3>
<p>　처음에는 다음과 같이 JPA의 EntityGraph를 활용했다.</p>
<pre><code class="language-java">@EntityGraph(attributePaths = {&quot;user&quot;, &quot;store&quot;, &quot;orderItems&quot;, &quot;orderItems.product&quot;})
Optional&lt;Order&gt; findById(UUID orderId);</code></pre>
<p>　이 방식은 간결하지만 실제 로그를 분석해보니 모든 테이블의 모든 컬럼을 조회하는 것을 확인했다.</p>
<pre><code class="language-sql">SELECT o1_0.id, o1_0.cancel_reason, o1_0.created_at, /* ... 모든 컬럼 ... */
FROM p_order o1_0 
LEFT JOIN p_order_item oi1_0 ON o1_0.id=oi1_0.order_id 
LEFT JOIN p_product p1_0 ON p1_0.id=oi1_0.product_id 
JOIN p_store s2_0 ON s2_0.id=o1_0.store_id 
JOIN p_user u2_0 ON u2_0.id=o1_0.customer_id 
WHERE o1_0.id=?</code></pre>
<h3 id="querydsl-프로젝션-방식"><em>QueryDSL 프로젝션 방식</em></h3>
<p>　필요한 데이터만 선택적으로 조회하기 위해 QueryDSL 프로젝션으로 전환했다. 이 방식은 두 개의 쿼리로 나누어 실행한다.</p>
<pre><code class="language-java">// 주문 기본 정보 조회
private Tuple fetchOrderMainData(UUID orderId) {
  return queryFactory
      .select(
          new QOrderDetailResponse_OrderResponse(
              order.id, order.orderType, order.status,
              order.totalPrice, order.cancelReason, order.request),
          new QOrderDetailResponse_UserResponse(user.id, user.username),
          new QOrderDetailResponse_StoreResponse(store.id, store.name))
      .from(order)
      .join(order.user, user)
      .join(order.store, store)
      .where(order.id.eq(orderId))
      .fetchOne();
}

// 주문 상품 정보 조회
private List&lt;OrderItemResponse&gt; fetchOrderItems(UUID orderId) {
  return queryFactory
      .select(new QOrderDetailResponse_OrderItemResponse(
          orderItem.id, product.name, orderItem.amount))
      .from(orderItem)
      .join(orderItem.product, product)
      .where(orderItem.order.id.eq(orderId))
      .fetch();
}</code></pre>
<p>　이 쿼리들은 실제로 필요한 컬럼만 선택적으로 조회한다.</p>
<pre><code class="language-sql">/* 기본 정보 조회 */
SELECT o1_0.id, o1_0.order_type, o1_0.status, o1_0.total_price,
       o1_0.cancel_reason, o1_0.request, u1_0.id, u1_0.username,
       s1_0.id, s1_0.name 
FROM p_order o1_0 
JOIN p_user u1_0 ON u1_0.id=o1_0.customer_id 
JOIN p_store s1_0 ON s1_0.id=o1_0.store_id 
WHERE o1_0.id=?

/* 주문 상품 조회 */
SELECT oi1_0.id, p1_0.name, oi1_0.amount 
FROM p_order_item oi1_0 
JOIN p_product p1_0 ON p1_0.id=oi1_0.product_id 
WHERE oi1_0.order_id=?</code></pre>
<br/>


<h3 id="두-방식의-장단점-비교"><em>두 방식의 장단점 비교</em></h3>
<p>　EntityGraph와 QueryDSL 프로젝션 방식의 주요 차이점은 다음과 같다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>EntityGraph</th>
<th>QueryDSL 프로젝션</th>
</tr>
</thead>
<tbody><tr>
<td>쿼리 수</td>
<td>1개</td>
<td>2개</td>
</tr>
<tr>
<td>조회 컬럼</td>
<td>모든 컬럼</td>
<td>필요한 컬럼만</td>
</tr>
<tr>
<td>코드 복잡성</td>
<td>낮음</td>
<td>중간</td>
</tr>
<tr>
<td>데이터 전송량</td>
<td>많음</td>
<td>적음</td>
</tr>
<tr>
<td>메모리 사용</td>
<td>많음</td>
<td>적음</td>
</tr>
<tr>
<td>유연성</td>
<td>낮음</td>
<td>높음</td>
</tr>
</tbody></table>
<br/>


<h3 id="성능-최적화-고려사항"><em>성능 최적화 고려사항</em></h3>
<ol>
<li><strong>데이터 전송량</strong>: 필요한 컬럼만 조회하여 네트워크 트래픽 감소</li>
<li><strong>메모리 사용</strong>: 필요한 데이터만 로딩하여 메모리 효율성 증가</li>
<li><strong>쿼리 최적화</strong>: 복잡한 대형 쿼리보다 목적에 맞는 간결한 쿼리 사용</li>
<li><strong>컬렉션 조회</strong>: 컬렉션은 별도 쿼리로 조회하여 데이터 중복 방지</li>
</ol>
<br/>

<h3 id="결론적-선택"><em>결론적 선택</em></h3>
<p>　모든 최적화에는 트레이드오프가 있지만, 이번 케이스에서는 다음 이유로 QueryDSL 프로젝션 방식을 선택했다.</p>
<ol>
<li>필요한 데이터만 정확히 조회하여 성능 향상</li>
<li>쿼리가 더 간결하고 이해하기 쉬움</li>
<li>메모리와 네트워크 리소스 효율적 사용</li>
<li>DTO 직접 매핑으로 변환 오버헤드 감소</li>
</ol>
<br/>

<hr>
<blockquote>
<p><code>✏️</code>&nbsp;&nbsp;　이번 개발을 통해 단순히 코드의 간결함보다 실제 성능과 효율성을 고려한 의사결정의 중요성을 체험했다. 특히 쿼리 로그를 분석하여 실제 동작을 이해하는 과정이 매우 중요했다. 앞으로도 다양한 ORM 기능들을 사용할 때는 편의성과 함께 실제 성능 특성을 항상 검토해야 한다는 점을 배웠다. 또한 QueryDSL의 유연성을 활용하면 JPA의 편리함을 유지하면서도 세밀한 성능 최적화가 가능하다는 점도 중요한 인사이트였다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[인증 모듈과 도메인 개발, 어떻게 분리할 수 있을까?]]></title>
            <link>https://velog.io/@soobin_n_n_i/%EC%9D%B8%EC%A6%9D-%EB%AA%A8%EB%93%88%EA%B3%BC-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B0%9C%EB%B0%9C-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B6%84%EB%A6%AC%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@soobin_n_n_i/%EC%9D%B8%EC%A6%9D-%EB%AA%A8%EB%93%88%EA%B3%BC-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B0%9C%EB%B0%9C-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B6%84%EB%A6%AC%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Thu, 13 Feb 2025 14:56:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>✏️</code> &nbsp;&nbsp; 주문 상세 조회 API를 개발하면서 N+1 문제를 해결하기 위한 두 가지 접근법, EntityGraph와 QueryDSL 프로젝션을 비교 검토했다. 초기에는 JPA의 EntityGraph를 적용했으나 실제 쿼리 로그를 분석하며 몇 가지 개선이 필요한 부분을 발견했다.
&nbsp;&nbsp;　EntityGraph는 코드가 간결하고 직관적인 장점이 있었지만, 모든 컬럼을 조회하는 비효율성과 복잡한 JOIN 구조로 인한 성능 문제가 우려되었다. 이를 개선하기 위해 QueryDSL 프로젝션 방식으로 전환하고 그 과정에서 얻은 인사이트를 정리하여 공유하고자 한다.</p>
</blockquote>
<hr>
<br/>

<h2 id="✏️-내용">✏️ 내용</h2>
<p>　주문 상세 조회 기능을 구현할 때 처음에는 EntityGraph로 N+1 문제를 해결하려 했으나, 필요 이상의 데이터를 조회하는 문제가 있었다. 이를 QueryDSL 프로젝션으로 전환하여 필요한 데이터만 효율적으로 조회하는 방식으로 개선했다.</p>
<h3 id="entitygraph-방식"><em>EntityGraph 방식</em></h3>
<p>　처음에는 다음과 같이 JPA의 EntityGraph를 활용했다.</p>
<pre><code class="language-java">@EntityGraph(attributePaths = {&quot;user&quot;, &quot;store&quot;, &quot;orderItems&quot;, &quot;orderItems.product&quot;})
Optional&lt;Order&gt; findById(UUID orderId);</code></pre>
<p>　이 방식은 간결하지만 실제 로그를 분석해보니 모든 테이블의 모든 컬럼을 조회하는 것을 확인했다.</p>
<pre><code class="language-sql">SELECT o1_0.id, o1_0.cancel_reason, o1_0.created_at, /* ... 모든 컬럼 ... */
FROM p_order o1_0 
LEFT JOIN p_order_item oi1_0 ON o1_0.id=oi1_0.order_id 
LEFT JOIN p_product p1_0 ON p1_0.id=oi1_0.product_id 
JOIN p_store s2_0 ON s2_0.id=o1_0.store_id 
JOIN p_user u2_0 ON u2_0.id=o1_0.customer_id 
WHERE o1_0.id=?</code></pre>
<h3 id="querydsl-프로젝션-방식"><em>QueryDSL 프로젝션 방식</em></h3>
<p>　필요한 데이터만 선택적으로 조회하기 위해 QueryDSL 프로젝션으로 전환했다. 이 방식은 두 개의 쿼리로 나누어 실행한다.</p>
<pre><code class="language-java">// 주문 기본 정보 조회
private Tuple fetchOrderMainData(UUID orderId) {
  return queryFactory
      .select(
          new QOrderDetailResponse_OrderResponse(
              order.id, order.orderType, order.status,
              order.totalPrice, order.cancelReason, order.request),
          new QOrderDetailResponse_UserResponse(user.id, user.username),
          new QOrderDetailResponse_StoreResponse(store.id, store.name))
      .from(order)
      .join(order.user, user)
      .join(order.store, store)
      .where(order.id.eq(orderId))
      .fetchOne();
}

// 주문 상품 정보 조회
private List&lt;OrderItemResponse&gt; fetchOrderItems(UUID orderId) {
  return queryFactory
      .select(new QOrderDetailResponse_OrderItemResponse(
          orderItem.id, product.name, orderItem.amount))
      .from(orderItem)
      .join(orderItem.product, product)
      .where(orderItem.order.id.eq(orderId))
      .fetch();
}</code></pre>
<p>　이 쿼리들은 실제로 필요한 컬럼만 선택적으로 조회한다.</p>
<pre><code class="language-sql">/* 기본 정보 조회 */
SELECT o1_0.id, o1_0.order_type, o1_0.status, o1_0.total_price,
       o1_0.cancel_reason, o1_0.request, u1_0.id, u1_0.username,
       s1_0.id, s1_0.name 
FROM p_order o1_0 
JOIN p_user u1_0 ON u1_0.id=o1_0.customer_id 
JOIN p_store s1_0 ON s1_0.id=o1_0.store_id 
WHERE o1_0.id=?

/* 주문 상품 조회 */
SELECT oi1_0.id, p1_0.name, oi1_0.amount 
FROM p_order_item oi1_0 
JOIN p_product p1_0 ON p1_0.id=oi1_0.product_id 
WHERE oi1_0.order_id=?</code></pre>
<br/>


<h3 id="두-방식의-장단점-비교"><em>두 방식의 장단점 비교</em></h3>
<p>　EntityGraph와 QueryDSL 프로젝션 방식의 주요 차이점은 다음과 같다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>EntityGraph</th>
<th>QueryDSL 프로젝션</th>
</tr>
</thead>
<tbody><tr>
<td>쿼리 수</td>
<td>1개</td>
<td>2개</td>
</tr>
<tr>
<td>조회 컬럼</td>
<td>모든 컬럼</td>
<td>필요한 컬럼만</td>
</tr>
<tr>
<td>코드 복잡성</td>
<td>낮음</td>
<td>중간</td>
</tr>
<tr>
<td>데이터 전송량</td>
<td>많음</td>
<td>적음</td>
</tr>
<tr>
<td>메모리 사용</td>
<td>많음</td>
<td>적음</td>
</tr>
<tr>
<td>유연성</td>
<td>낮음</td>
<td>높음</td>
</tr>
</tbody></table>
<br/>


<h3 id="성능-최적화-고려사항"><em>성능 최적화 고려사항</em></h3>
<ol>
<li><strong>데이터 전송량</strong>: 필요한 컬럼만 조회하여 네트워크 트래픽 감소</li>
<li><strong>메모리 사용</strong>: 필요한 데이터만 로딩하여 메모리 효율성 증가</li>
<li><strong>쿼리 최적화</strong>: 복잡한 대형 쿼리보다 목적에 맞는 간결한 쿼리 사용</li>
<li><strong>컬렉션 조회</strong>: 컬렉션은 별도 쿼리로 조회하여 데이터 중복 방지</li>
</ol>
<br/>

<h3 id="결론적-선택"><em>결론적 선택</em></h3>
<p>　모든 최적화에는 트레이드오프가 있지만, 이번 케이스에서는 다음 이유로 QueryDSL 프로젝션 방식을 선택했다.</p>
<ol>
<li>필요한 데이터만 정확히 조회하여 성능 향상</li>
<li>쿼리가 더 간결하고 이해하기 쉬움</li>
<li>메모리와 네트워크 리소스 효율적 사용</li>
<li>DTO 직접 매핑으로 변환 오버헤드 감소</li>
</ol>
<br/>

<hr>
<blockquote>
<p><code>✏️</code>&nbsp;&nbsp;　이번 개발을 통해 단순히 코드의 간결함보다 실제 성능과 효율성을 고려한 의사결정의 중요성을 체험했다. 특히 쿼리 로그를 분석하여 실제 동작을 이해하는 과정이 매우 중요했다. 앞으로도 다양한 ORM 기능들을 사용할 때는 편의성과 함께 실제 성능 특성을 항상 검토해야 한다는 점을 배웠다. 또한 QueryDSL의 유연성을 활용하면 JPA의 편리함을 유지하면서도 세밀한 성능 최적화가 가능하다는 점도 중요한 인사이트였다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[DIP(Dependency Inversion Principle)]]></title>
            <link>https://velog.io/@soobin_n_n_i/DIPDependency-Inversion-Principle</link>
            <guid>https://velog.io/@soobin_n_n_i/DIPDependency-Inversion-Principle</guid>
            <pubDate>Wed, 12 Feb 2025 14:56:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p> <code>✏️</code> &nbsp;&nbsp; &quot;&quot;추상화에 의존해야지, 구체화에 의존하면 안 된다.&quot;
 &nbsp;&nbsp; 최근, &quot;도메인 주도 개발 시작하기&quot;를 통해 DIP에 대한 개념을 접했고, 이에 대한 내용을 정리하고자 블로그 글을 작성한다.</p>
</blockquote>
<hr>
<h3 id="1-핵심-개념">1. <em>핵심 개념</em></h3>
<h4 id="dip">DIP</h4>
<ul>
<li><strong>DIP</strong>(Dependency Inversion Principle)는 객체 지향 프로그래밍에서 SOLID 원칙 중 하나로, 고수준 모듈이 저수준 모듈에 의존하는 대신, 추상화된 인터페이스에 의존하도록 만들어 시스템의 유연성과 확장성을 높이는 것을 목표로 한다.</li>
<li>이 원칙은 구체적인 구현이 아닌 추상화(인터페이스나 추상 클래스를 통해)에 의존하도록 하여, 코드의 유연성을 극대화하고, 변경에 강한 구조를 제공한다. 이를 통해, 구현 세부 사항이 변경되더라도 시스템 전체에 미치는 영향을 최소화할 수 있다.</li>
</ul>
<h4 id="dip의-핵심-규칙">DIP의 핵심 규칙:</h4>
<ul>
<li>고수준 모듈은 저수준 모듈에 의존하지 않는다.</li>
<li>고수준과 저수준 모듈 모두 추상화(인터페이스)에 의존해야 한다.</li>
</ul>
<h4 id="고수준-모듈">고수준 모듈</h4>
<ul>
<li>고수준 모듈은 실제 비즈니스 로직을 담당하는 모듈로, 예를 들어 <code>CustomerService</code>와 같이 비즈니스 로직을 처리한다. 이 모듈은 구체적인 데이터 접근 구현체에 의존하지 않고, 추상화된 인터페이스를 통해 작업을 처리한다.</li>
</ul>
<h4 id="저수준-모듈">저수준 모듈</h4>
<ul>
<li>저수준 모듈은 데이터 접근과 같은 하위 기능을 실제로 구현한 모듈로, 예를 들어 <code>CustomerRepository</code>와 같은 RDBMS를 사용하는 데이터 접근 구현체를 의미한다. 고수준 모듈과의 의존성을 끊기 위해 인터페이스를 통해 연결된다.</li>
</ul>
<br/>

<h3 id="2-dip의-적용과-유연성">2. <em>DIP의 적용과 유연성</em></h3>
<ul>
<li>고수준 모듈이 저수준 모듈을 직접 사용하게 되면, 구현 교체가 어려워진다. 예를 들어, 데이터베이스를 MySQL에서 PostgreSQL로 변경하려면 고수준 모듈도 함께 수정해야 할 수 있다.</li>
<li>DIP는 이를 해결한다. 고수준 모듈은 인터페이스에 의존하고, 저수준 모듈은 이 인터페이스를 구현한다. 이를 통해 구체적인 구현을 교체하더라도 고수준 모듈에는 영향을 주지 않으며, 시스템을 유연하게 유지할 수 있다.</li>
</ul>
<br/>

<h3 id="3-실제-구현-예시">3. <em>실제 구현 예시</em></h3>
<pre><code class="language-java">// 1. 인터페이스 정의
public interface CustomerRepository {
    Customer findById(Long id);
    void save(Customer customer);
}

// 2. 저수준 모듈 구현 (JpaRepository)
import org.springframework.data.jpa.repository.JpaRepository;

public interface JpaCustomerRepository extends JpaRepository&lt;Customer, Long&gt; {
    // JpaRepository에서 기본적인 CRUD 메서드가 제공된다.
}

// 3. CustomerRepository 구현 (고수준 모듈이 의존하는 부분)
public class CustomerRepositoryImpl implements CustomerRepository {
    private final JpaCustomerRepository jpaCustomerRepository;

    public CustomerRepositoryImpl(JpaCustomerRepository jpaCustomerRepository) {
        this.jpaCustomerRepository = jpaCustomerRepository;
    }

    @Override
    public Customer findById(Long id) {
        return jpaCustomerRepository.findById(id)
                .orElseThrow(() -&gt; new ApiBusinessException(CustomerError.NOT_EXIST));
    }

    @Override
    public void save(Customer customer) {
        jpaCustomerRepository.save(customer);
    }
}

// 4. 고수준 모듈 구현 (서비스 클래스)
public class CustomerService {
    private final CustomerRepository customerRepository;

    public CustomerService(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    public void registerCustomer(Customer customer) {
        customerRepository.save(customer);
    }

    public Customer getCustomer(Long id) {
        return customerRepository.findById(id);
    }
}

// 5. 사용 예시
public class Main {
    public static void main(String[] args) {
        JpaCustomerRepository jpaCustomerRepository = new JpaCustomerRepository(); // 실제 구현체는 DI를 통해 주입받는다.
        CustomerRepository customerRepository = new CustomerRepositoryImpl(jpaCustomerRepository);
        CustomerService customerService = new CustomerService(customerRepository);

        Customer newCustomer = new Customer(&quot;John Doe&quot;);
        customerService.registerCustomer(newCustomer);

        Customer retrievedCustomer = customerService.getCustomer(newCustomer.getId());
        System.out.println(&quot;고객 이름: &quot; + retrievedCustomer.getName());
    }
}</code></pre>
<h4 id="설명">설명:</h4>
<ul>
<li><strong>CustomerRepository</strong> 인터페이스는 여전히 고수준 모듈인 <code>CustomerService</code>에 의해 의존되고 있으며, 실제 구현체인 <code>CustomerRepositoryImpl</code>에서는 <code>JpaCustomerRepository</code>를 통해 데이터베이스를 처리한다.</li>
<li><code>CustomerRepositoryImpl</code>은 데이터 접근에 필요한 예외 처리나 추가적인 로직을 처리할 수 있다.</li>
<li>고수준 모듈인 <code>CustomerService</code>는 <strong>CustomerRepository</strong> 인터페이스에만 의존하므로, 구현체 변경 시 <code>CustomerService</code>는 수정하지 않고도 다른 데이터 접근 구현체로 쉽게 교체할 수 있다. 예를 들어, <code>JpaCustomerRepository</code>를 <code>MongoCustomerRepository</code>로 변경해도 <code>CustomerService</code>는 영향을 받지 않는다.</li>
</ul>
<br/>

<h3 id="4-테스트-용이성">4. <em>테스트 용이성</em></h3>
<ul>
<li>DIP를 적용하면, 실제 구현체 없이 테스트를 수행할 수 있어 단위 테스트가 용이해진다.</li>
<li>Mock 객체나 테스트용 구현체를 사용해 빠르고 간단한 테스트가 가능하다. 예를 들어, 아래와 같이 테스트용 구현체를 만들어 단위 테스트를 할 수 있다.</li>
</ul>
<pre><code class="language-java">// 테스트용 Mock 구현체
public class MockCustomerRepository implements CustomerRepository {
    private Map&lt;Long, Customer&gt; database = new HashMap&lt;&gt;();

    @Override
    public Customer findById(Long id) {
        return database.get(id);
    }

    @Override
    public void save(Customer customer) {
        database.put(customer.getId(), customer);
    }
}

// 단위 테스트
public class CustomerServiceTest {
    @Test
    public void testRegisterCustomer() {
        CustomerRepository mockRepository = new MockCustomerRepository();
        CustomerService service = new CustomerService(mockRepository);

        Customer newCustomer = new Customer(&quot;Jane Doe&quot;);
        service.registerCustomer(newCustomer);

        Customer retrievedCustomer = service.getCustomer(newCustomer.getId());
        assertEquals(&quot;Jane Doe&quot;, retrievedCustomer.getName());
    }
}</code></pre>
<ul>
<li>위 예시에서는 <code>MockCustomerRepository</code>라는 테스트용 구현체를 사용하여 <code>CustomerService</code>의 기능을 테스트한다. 실제 데이터베이스 연결 없이 테스트를 수행할 수 있으므로, 단위 테스트가 훨씬 간단하고 빠르게 이루어진다.</li>
</ul>
<br/>

<h3 id="5-아키텍처-관점의-dip">5. <em>아키텍처 관점의 DIP</em></h3>
<ul>
<li>DIP는 인프라스트럭처, 응용, 도메인 영역 간의 의존 관계에서도 유용하다. 예를 들어, 이메일 알림 기능(<code>EmailNotifier</code>)이나 주문 처리 기능(<code>OrderService</code>)에서 각 영역을 인터페이스를 통해 분리하는 것이 좋은 예이다.</li>
<li>이러한 아키텍처에서는 기능 변경이나 확장 시 다른 영역에 미치는 영향을 최소화할 수 있다.</li>
</ul>
<br/>

<h3 id="6-주의사항">6. <em>주의사항</em></h3>
<ul>
<li>DIP는 항상 최선의 선택이 아닐 수 있다. 시스템의 크기나 복잡도에 따라 적절히 적용해야 한다. 작은 프로젝트나 단순한 시스템에서는 DIP 적용이 오히려 복잡도를 증가시킬 수 있다.</li>
<li>과도한 추상화는 오히려 시스템을 복잡하게 만들 수 있으므로, 상황에 맞는 판단이 필요하다.</li>
</ul>
<hr>
<blockquote>
<p> <code>✏️</code> &nbsp;&nbsp; DIP의 적용을 통해 코드의 유연성과 테스트 용이성을 높일 수 있으며, 시스템의 결합도를 낮추고 응집도를 높이는 데 기여할 수 있다. 이 원칙을 잘 이해하고 활용하면, 더 견고하고 유지보수가 용이한 소프트웨어를 개발할 수 있을 것이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Cascade (영속성 전이)]]></title>
            <link>https://velog.io/@soobin_n_n_i/Cascade-%EC%98%81%EC%86%8D%EC%84%B1-%EC%A0%84%EC%9D%B4</link>
            <guid>https://velog.io/@soobin_n_n_i/Cascade-%EC%98%81%EC%86%8D%EC%84%B1-%EC%A0%84%EC%9D%B4</guid>
            <pubDate>Tue, 11 Feb 2025 08:38:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p> <code>✏️</code> &nbsp;&nbsp; 영속성 전이(Cascade)와 고아 객체 제거(orphanRemoval) 두 옵션에 대해 공부하면서 이 두 옵션이 실제로 어떻게 차이가 나는지 구체적으로 알게 되었다. 이에 대한 이해를 바탕으로 더 깊이 알아보고자 Cascade의 사용 위치, 조건, 옵션 종류, 그리고 orphanRemoval과의 차이점을 예시 코드와 함께 정리해보고자 한다.</p>
</blockquote>
<hr>
<h2 id="✏️-내용">✏️ 내용</h2>
<h3 id="1-영속성-전이cascade">1. <em>영속성 전이(Cascade)</em></h3>
<p>영속성 전이는 객체 간의 관계에서 부모 엔티티의 상태 변화를 자식 엔티티에 전이하는 메커니즘이다. 이는 JPA에서 제공하는 기능으로, 특정 엔티티에 대한 작업이 연관된 다른 엔티티에도 자동으로 적용되도록 한다.</p>
<br/>



<h3 id="2-cascade-옵션">2. <em>Cascade 옵션</em></h3>
<h4 id="사용-위치">사용 위치</h4>
<ul>
<li>영속성 전이는 연관관계의 주인 반대편, 즉 부모 엔티티(다대일 관계에서 일 쪽)에서 설정하게 된다. </li>
<li>예컨대 <code>주문</code>(Order)과 <code>주문 항목</code>(OrderItem)의 관계에서는 <code>주문</code> 쪽에 설정할 수 있다. </li>
<li>이는 비즈니스 로직의 일관성을 유지하고 데이터의 정합성을 보장하는데 중요한 역할을 한다.</li>
</ul>
<h4 id="사용-조건">사용 조건</h4>
<ul>
<li>영속성 전이를 사용할 때는 양쪽 엔티티의 라이프사이클이 동일하거나 비슷해야 한다. </li>
<li>예를 들어, 주문이 삭제되면 해당 주문 항목도 함께 삭제되어야 한다. </li>
<li>또한, 대상 엔티티로의 영속성 전이는 현재 엔티티에서만 전이되어야 하며, 다른 곳에서 전이를 설정하면 안 된다. </li>
<li>이는 데이터의 일관성과 무결성을 유지하는데 매우 중요하다.</li>
</ul>
<h4 id="옵션-종류">옵션 종류</h4>
<p>영속성 전이의 옵션 종류는 다음과 같다:</p>
<pre><code class="language-java">public enum CascadeType {
    ALL,      // Cascade all operations
    PERSIST,  // Cascade persist operation
    MERGE,    // Cascade merge operation
    REMOVE,   // Cascade remove operation
    REFRESH,  // Cascade refresh operation
    DETACH    // Cascade detach operation
}</code></pre>
<ul>
<li><strong>ALL</strong>: 모든 상태 변화를 전이한다. 부모 엔티티의 모든 상태 변화가 자식 엔티티에도 적용된다.</li>
<li><strong>PERSIST</strong>: 저장 상태만 전이한다. 부모 엔티티가 저장될 때 자식 엔티티도 함께 저장된다.</li>
<li><strong>REMOVE</strong>: 삭제 상태만 전이한다. 부모 엔티티가 삭제될 때 자식 엔티티도 함께 삭제된다.</li>
<li><strong>MERGE</strong>: 업데이트 상태만 전이한다. 부모 엔티티가 병합될 때 자식 엔티티도 함께 병합된다.</li>
<li><strong>REFRESH</strong>: 갱신 상태만 전이한다. 부모 엔티티가 새로고침될 때 자식 엔티티도 함께 새로고침된다.</li>
<li><strong>DETACH</strong>: 영속성 컨텍스트에서 분리될 때 자식 엔티티도 함께 분리된다.</li>
</ul>
<br/>


<h3 id="3-orphanremoval-고아-객체-제거-옵션">3. <em>orphanRemoval (고아 객체 제거) 옵션</em></h3>
<ul>
<li><p>고아 객체 제거는 부모 엔티티에서 사용되며, 일반적으로 <code>@OneToMany</code> 또는 <code>@OneToOne</code> 관계에서 사용된다. </p>
</li>
<li><p>이는 Cascade.REMOVE와 유사하게 삭제를 전파하는 기능이다. </p>
</li>
<li><p>부모 객체에서 리스트 요소를 삭제하면 해당 자식 객체는 매핑 정보가 없어지므로 자동으로 삭제된다.</p>
</li>
<li><p>고아 객체 제거는 참조가 제거된 엔티티를 자동으로 삭제하므로, 해당 엔티티가 다른 곳에서 참조되지 않는 것이 보장되어야 한다.</p>
<pre><code class="language-java">@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = &quot;order&quot;, cascade = {CascadeType.ALL}, orphanRemoval = true)
    private List&lt;OrderItem&gt; items = new ArrayList&lt;&gt;();
    // getters, setters, etc.
}

@Entity
public class OrderItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = &quot;order_id&quot;)
    private Order order;
    // getters, setters, etc.
}</code></pre>
<pre><code class="language-java">// 영속성 전이와 고아 객체 제거 예시
Order order = new Order();
OrderItem item1 = new OrderItem();
OrderItem item2 = new OrderItem();

order.getItems().add(item1);
order.getItems().add(item2);

// 주문 저장 (Cascade.PERSIST)
entityManager.persist(order);

// 주문 항목 삭제
order.getItems().remove(item1); // item1이 삭제됨 (orphanRemoval=true)</code></pre>
</li>
</ul>
<h4 id="옵션">옵션</h4>
<ul>
<li><strong>true</strong>: 고아 객체 제거 활성화</li>
<li><strong>false</strong>: 고아 객체 제거 비활성화 (기본값)</li>
</ul>
<br/>

<h3 id="4-cascaderemove와-orphanremoval의-차이점">4. <em>Cascade.REMOVE와 orphanRemoval의 차이점</em></h3>
<p><code>Cascade.REMOVE</code>는 부모 엔티티를 em.remove로 직접 삭제할 때 자식 엔티티가 삭제되는 반면, <code>orphanRemoval</code>은 부모 엔티티의 컬렉션에서 자식 엔티티를 제거하거나 참조를 null로 설정할 때도 자식 엔티티가 자동으로 삭제되는 더 강력한 기능을 제공한다. 이러한 차이는 도메인 모델의 일관성을 유지하는데 매우 중요한 역할을 한다.</p>
<br/>

<h3 id="5-orphanremovaltrue--cascadeall-조합">5. orphanRemoval=true + Cascade.ALL 조합</h3>
<p>위와 같은 옵션의 특징을 고려해봤을 때 부모-자식 관계에서 <code>orphanRemoval=true + Cascade.ALL</code> 조합의 사용이 권장된다고 할 수 있다. 이는 다음과 같은 명확한 이점들을 제공하기 때문이다:</p>
<ol>
<li><p><strong>완전한 생명주기 통합</strong></p>
<ul>
<li>자식 엔티티의 라이프사이클이 부모 엔티티와 완전히 동기화된다</li>
<li>별도의 자식 엔티티 Repository 없이도 모든 생명주기 관리가 가능하다</li>
</ul>
</li>
<li><p><strong>코드 품질 향상</strong></p>
<ul>
<li>코드의 간결성이 향상된다</li>
<li>유지보수가 용이해진다</li>
<li>비즈니스 로직이 더 명확해진다</li>
</ul>
</li>
<li><p><strong>데이터 일관성 보장</strong></p>
<ul>
<li>부모 엔티티를 통한 자식 엔티티의 통합 관리로 데이터 정합성이 향상된다</li>
<li>고아 객체 발생을 방지하여 데이터 정합성을 유지한다</li>
</ul>
</li>
<li><p><strong>성능 최적화</strong></p>
<ul>
<li>연관 관계의 영속성 전이가 자동으로 처리되어 불필요한 쿼리를 줄일 수 있다</li>
<li>부모 엔티티 조작만으로 연관된 자식 엔티티들의 상태 변화를 한 번에 처리할 수 있다</li>
</ul>
</li>
</ol>
<hr>
<blockquote>
<p> <code>✏️</code> &nbsp;&nbsp; <strong>결론</strong>을 내자면, 부모-자식 엔티티 관계에서 <code>orphanRemoval=true + Cascade.ALL</code> 조합은 매우 강력하고 유용한 도구가 된다. </p>
<p> &nbsp;&nbsp;다만 이 조합을 사용할 때는 두 가지를 고려해야 한다. 첫째, 자식 엔티티가 다른 엔티티와 공유되지 않아야 하며, 둘째, 부모 엔티티의 생명주기에 자식 엔티티가 종속되어도 괜찮은 경우여야 한다. </p>
<p>&nbsp;&nbsp;이러한 조건이 충족된다면, 이 조합은 코드의 간결성, 데이터 정합성, 그리고 성능 최적화까지 모두 달성할 수 있는 효과적인 방법이 될 것이다. 특히 복잡한 도메인 로직에서 엔티티 간의 관계를 명확하게 표현하고 관리해야 할 때, 이 조합의 사용을 적극적으로 고려해볼 만할 것이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA에서 데이터 무결성을 보장하는 필드 제약 조건 이해하기]]></title>
            <link>https://velog.io/@soobin_n_n_i/JPA%EC%97%90%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AC%B4%EA%B2%B0%EC%84%B1%EC%9D%84-%EB%B3%B4%EC%9E%A5%ED%95%98%EB%8A%94-%ED%95%84%EB%93%9C-%EC%A0%9C%EC%95%BD-%EC%A1%B0%EA%B1%B4-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@soobin_n_n_i/JPA%EC%97%90%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AC%B4%EA%B2%B0%EC%84%B1%EC%9D%84-%EB%B3%B4%EC%9E%A5%ED%95%98%EB%8A%94-%ED%95%84%EB%93%9C-%EC%A0%9C%EC%95%BD-%EC%A1%B0%EA%B1%B4-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 13 Jan 2025 02:01:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p> <code>✏️</code> &nbsp;&nbsp; JPA는 데이터베이스와 객체 간 매핑을 처리하는 중요한 프레임워크로, 다양한 필드 제약 조건을 제공한다. 그러나 각각의 속성은 적용되는 계층과 목적이 다르기 때문에 이를 혼동하지 않고 올바르게 사용해야 한다. 이 포스팅에서는 <code>@NotNull</code>, <code>nullable = false</code>, <code>optional = false</code>의 차이와 사용 사례를 정리하여 JPA를 활용할 때의 혼란을 줄이고자 한다.</p>
</blockquote>
<hr>
<h3 id="1-notnull">1. @NotNull</h3>
<ul>
<li><strong>적용 범위:</strong> 애플리케이션 레벨 (Java Bean Validation)</li>
<li><strong>역할:</strong> 필드가 <code>null</code>이 아님을 검증하며, 유효성 검증 라이브러리를 통해 애플리케이션 수준에서 제약 조건을 강제한다.</li>
<li><strong>검증 시점:</strong> 런타임 시점에서 유효성 검사를 수행한다.</li>
</ul>
<h4 id="특징">특징:</h4>
<ul>
<li><code>@NotNull</code>은 애플리케이션 레벨에서 사용자 입력 데이터 또는 비즈니스 로직 검증에 사용된다.</li>
<li>데이터베이스 스키마에는 영향을 미치지 않으며, Hibernate는 두 가지를 모두 적용할 수 있다.</li>
</ul>
<h4 id="예시">예시:</h4>
<pre><code class="language-java">@Entity
public class User {
    @NotNull
    private String name;
}</code></pre>
<ul>
<li>위 코드에서 <code>name</code> 필드는 반드시 값이 설정되어야 하며, 사용자가 이름을 입력하지 않을 경우 유효성 검증에서 오류 메시지가 발생한다.</li>
</ul>
<hr>
<h3 id="2-nullable--false">2. nullable = false</h3>
<ul>
<li><strong>적용 범위:</strong> 데이터베이스 레벨 (DDL 제약 조건)</li>
<li><strong>역할:</strong> 데이터베이스 테이블 생성 시, 해당 컬럼에 <code>NOT NULL</code> 제약 조건을 추가한다.</li>
<li><strong>검증 시점:</strong> 데이터가 데이터베이스에 저장될 때 검증이 이루어진다.</li>
</ul>
<h4 id="특징-1">특징:</h4>
<ul>
<li>데이터베이스 컬럼에서 <code>null</code> 값을 허용하지 않으며, JPA가 데이터베이스 테이블을 생성할 때만 영향을 미친다.</li>
</ul>
<h4 id="예시-1">예시:</h4>
<pre><code class="language-java">@Entity
public class User {
    @Column(nullable = false)
    private String name;
}</code></pre>
<ul>
<li>위 코드에서 <code>name</code> 컬럼은 <code>NOT NULL</code>로 설정되어, <code>null</code> 값을 저장하려고 하면 SQL 예외가 발생한다. 이때 예외 처리 방법을 추가하여 유용성을 높일 수 있다.</li>
</ul>
<hr>
<h3 id="3-optional--false">3. optional = false</h3>
<ul>
<li><strong>적용 범위:</strong> JPA 객체 모델 (영속성 컨텍스트)</li>
<li><strong>역할:</strong> 연관 관계에서 대상 엔티티가 반드시 존재해야 함을 강제한다.</li>
<li><strong>검증 시점:</strong> 객체가 영속 상태로 전환될 때 검증이 이루어진다.</li>
</ul>
<h4 id="특징-2">특징:</h4>
<ul>
<li><code>optional = false</code>는 객체 모델에서 연관된 엔티티의 필수 여부를 결정하며, 데이터베이스 스키마에는 영향을 미치지 않는다.</li>
</ul>
<h4 id="예시-2">예시:</h4>
<pre><code class="language-java">@Entity
public class Employee {
    @ManyToOne(optional = false)
    private Department department;
}</code></pre>
<ul>
<li>위 코드에서 <code>department</code> 필드는 반드시 값이 설정되어야 하며, <code>null</code>일 경우 JPA에서 예외가 발생한다. 이 경우에도 예외 처리 방법을 추가하여 유용성을 높일 수 있다.</li>
</ul>
<hr>
<h3 id="4-nullable--false와-optional--false의-차이">4. nullable = false와 optional = false의 차이</h3>
<table>
<thead>
<tr>
<th>속성</th>
<th>nullable = false</th>
<th>optional = false</th>
</tr>
</thead>
<tbody><tr>
<td><strong>적용 범위</strong></td>
<td>데이터베이스 테이블 스키마</td>
<td>JPA 객체 모델</td>
</tr>
<tr>
<td><strong>검증 위치</strong></td>
<td>데이터베이스 저장/업데이트 시점</td>
<td>영속성 컨텍스트에서 엔티티 상태 전환 시</td>
</tr>
<tr>
<td><strong>주요 역할</strong></td>
<td>컬럼 값이 <code>null</code>인지 검사</td>
<td>연관된 엔티티의 필수 여부를 검사</td>
</tr>
<tr>
<td><strong>스키마 반영 여부</strong></td>
<td><code>NOT NULL</code>로 설정</td>
<td>스키마에는 영향 없음</td>
</tr>
</tbody></table>
<h4 id="함께-사용하는-경우">함께 사용하는 경우:</h4>
<pre><code class="language-java">@Entity
public class Employee {
    @ManyToOne(optional = false)
    @JoinColumn(name = &quot;department_id&quot;, nullable = false)
    private Department department;
}</code></pre>
<ul>
<li><code>optional = false</code>: <code>department</code> 필드는 반드시 객체 모델에서 설정되어야 한다.</li>
<li><code>nullable = false</code>: 데이터베이스 <code>department_id</code> 컬럼은 반드시 <code>NOT NULL</code>이어야 한다.</li>
</ul>
<hr>
<h3 id="5-notnull과-nullable--false의-차이">5. @NotNull과 nullable = false의 차이</h3>
<table>
<thead>
<tr>
<th>속성</th>
<th>@NotNull</th>
<th>nullable = false</th>
</tr>
</thead>
<tbody><tr>
<td><strong>적용 범위</strong></td>
<td>애플리케이션 레벨 (Bean Validation)</td>
<td>데이터베이스 테이블 스키마</td>
</tr>
<tr>
<td><strong>검증 위치</strong></td>
<td>런타임 시점 (유효성 검증 라이브러리 사용)</td>
<td>데이터 저장/업데이트 시점</td>
</tr>
<tr>
<td><strong>주요 역할</strong></td>
<td>필드가 <code>null</code>이 아님을 검증</td>
<td>컬럼에 <code>NOT NULL</code> 제약 조건 추가</td>
</tr>
<tr>
<td><strong>스키마 반영 여부</strong></td>
<td>스키마에는 영향 없음</td>
<td>데이터베이스 컬럼에만 반영</td>
</tr>
</tbody></table>
<h4 id="함께-사용하는-경우-1">함께 사용하는 경우:</h4>
<pre><code class="language-java">@Entity
public class User {
    @NotNull
    @Column(nullable = false)
    private String name;
}</code></pre>
<ul>
<li><code>@NotNull</code>: 애플리케이션 레벨에서 필드 유효성 검사를 수행한다.</li>
<li><code>nullable = false</code>: 데이터베이스에서 해당 컬럼이 <code>NOT NULL</code>로 설정된다.</li>
</ul>
<hr>
<blockquote>
<p> <code>✏️</code> &nbsp;&nbsp; <code>@NotNull</code>, <code>nullable = false</code>, <code>optional = false</code>는 각각 다른 계층에서 필드 제약 조건을 관리한다. 따라서 JPA를 사용할 때는 각 속성의 차이를 명확히 이해하고 올바르게 조합하여 객체 모델과 데이터베이스 간의 일관성을 유지하는 것이 중요하다. </p>
</blockquote>
<ul>
<li><strong>추천 패턴:</strong> 서로 보완적인 관계이므로, 애플리케이션과 데이터베이스 모두에서 무결성을 보장하려면 <code>@NotNull</code>과 <code>nullable = false</code>, <code>optional = false</code>를 함께 사용하는 것이 좋다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ORM과 JPA를 사용하는 이유: 객체-관계 패러다임의 불일치 해결]]></title>
            <link>https://velog.io/@soobin_n_n_i/ORM%EA%B3%BC-JPA%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EA%B0%9D%EC%B2%B4-%EA%B4%80%EA%B3%84-%ED%8C%A8%EB%9F%AC%EB%8B%A4%EC%9E%84%EC%9D%98-%EB%B6%88%EC%9D%BC%EC%B9%98-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@soobin_n_n_i/ORM%EA%B3%BC-JPA%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EA%B0%9D%EC%B2%B4-%EA%B4%80%EA%B3%84-%ED%8C%A8%EB%9F%AC%EB%8B%A4%EC%9E%84%EC%9D%98-%EB%B6%88%EC%9D%BC%EC%B9%98-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Wed, 18 Dec 2024 21:29:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p> <code>✏️</code> &nbsp;&nbsp;최근 JPA 관련 서적을 읽으며 <code>ORM</code>(Object-Relational Mapping)의 필요성과 그 이점에 대해 깊이 이해하게 되었다. 따라서 글에서 ORM을 사용하는 이유와 그로 인해 개발자들이 얻는 이점을 정리하고자 한다. </p>
</blockquote>
<hr>
<br/>

<h2 id="✏️-내용">✏️ 내용</h2>
<p>　개발자가 애플리케이션을 객체지향 언어로 개발하며 객체를 메모리가 아닌 <code>영구적</code>으로 보관해야 할 때, 현실적인 대안으로 객체를 데이터 형식으로 변환하여 관계형 데이터베이스에 저장하는 방법이 있다. 그런데 이 방식에는 몇 가지 <code>문제점</code>이 존재한다.</p>
<h3 id="패러다임의-불일치"><em>패러다임의 불일치</em></h3>
<p>　객체는 비즈니스 요구사항을 모델링한 반면, 관계형 데이터베이스는 데이터 중심의 구조를 가지고 있다. 이 근본적인 차이는 다음과 같은 패러다임의 불일치로 이어지며 이에 개발자는 객체를 데이터베이스에 저장하기 위해 많은 매핑 코드와 SQL을 작성해야 하는 어려움에 직면하게 된다.</p>
<table>
<thead>
<tr>
<th></th>
<th><strong>객체</strong></th>
<th><strong>관계형 데이터베이스 테이블</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>상속관계</strong></td>
<td>O</td>
<td>X</td>
</tr>
<tr>
<td><strong>연관관계</strong></td>
<td><strong>참조를 통한 단방향 관계</strong></td>
<td><strong>외래 키를 통한 양방향 관계</strong></td>
</tr>
<tr>
<td><strong>그래프 탐색</strong></td>
<td>객체는 객체 그래프를 자유롭게 탐색(참조를 통해서).</td>
<td>SQL은 처음 실행 시 조인을 통해서 탐색 범위가 결정.</td>
</tr>
<tr>
<td><strong>비교</strong></td>
<td><strong>동일성</strong>(identity, ==, 주소값)과 <strong>동등성</strong>(equality, .equals(), 객체 내부값)으로 각 객체 구분</td>
<td>기본 키 값으로 각 로우 구분</td>
</tr>
</tbody></table>
<br/>

<h3 id="그래서-orm을-활용하면"><em>그래서 ORM을 활용하면..</em></h3>
<p>　JPA와 같은 ORM을 활용하게 되면, 이러한 문제들을 극복할 수 있다. JPA는 객체와 데이터베이스 간의 매핑을 자동으로 처리하여 개발자가 반복적으로 SQL을 작성할 필요가 없도록 해주기 때문이다. 예를 들어 엔티티와 매핑 정보를 바탕으로 자동으로 SQL을 생성하고, 연관된 객체의 데이터를 필요할 때만 조회할 수 있게 하여 개발자가 비즈니스 로직에 집중할 수 있도록 한다.</p>
<p><code>예시</code> <strong>회원과 팀 간의 연관관계를 설정하고 저장할 때</strong></p>
<pre><code class="language-java">  // JPA 사용
  member.setTeam(team); // 회원과 팀 연관 관계 설정
  jpa.persist(member); // 회원과 연관관계가 함께 저장됨</code></pre>
<p>  &nbsp;&nbsp;객체는 참조를 통해, 테이블은 외래키를 통해 연관을 판별한다. 따라서 엔티티에 row 데이터를 담을 때는 new 연산자로 객체를 생성하고 필드를 설정하는 과정이 필요하다. 반대로 엔티티의 필드 값을 데이터베이스 테이블에 저장하려고 할 때는 객체의 외래 키 값을 추출하는 과정이 필요하다. 
  &nbsp;&nbsp;하지만 위의 코드에서 알 수 있듯이, member.setTeam(team)을 통해 회원과 팀 간의 연관관계를 설정한 후, jpa.persist(member)를 호출하면 JPA가 자동으로 회원 객체와 팀 객체의 관계를 외래키로 변환하여 적절한 INSERT SQL을 생성하고 실행한다.</p>
<p>  <br/><br/><br/></p>
<hr>
<blockquote>
<p> <code>✏️</code>&nbsp;&nbsp;　JPA가 자바 표준 ORM 기술이라는 점에서 이를 단순히 활용하였을 뿐, 이를 배제한다면 개발자가 객체와 데이터베이스 간의 데이터를 매핑하는 과정에서 논리적으로 DAO와 엔티티가 SQL에 강한 의존성을 가지게 되어 코드의 비효율성이 발생할 수 있다는 점을 깨닫고 정리할 수 있었다!
&nbsp;&nbsp;하지만 복잡한 조인이나 서브쿼리가 필요한 경우, 대용량 데이터 배치 처리, 혹은 통계 데이터 집계와 같은 특수한 상황에서는 여전히 Native SQL를 활용해야할 것이다. 이로 인해, 엔티티의 무결성을 보장하면서 비즈니스 로직과 데이터 접근 계층을 효과적으로 분리할 수 있는 아키텍처 설계 방법에 대해 새롭게 공부하고 싶어졌다😀.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[payload]]></title>
            <link>https://velog.io/@soobin_n_n_i/payload</link>
            <guid>https://velog.io/@soobin_n_n_i/payload</guid>
            <pubDate>Thu, 21 Nov 2024 11:15:56 GMT</pubDate>
            <description><![CDATA[<p><strong><code>payload</code></strong> STOMP 메시징에서 payload(페이로드)는 실제로 주고받는 메시지의 내용물이다.</p>
<blockquote>
<ul>
<li>서버로부터 실제로 받게 될 메시지 데이터</li>
<li>어떤 형태로 받을지 지정하고(getPayloadType)</li>
<li>받은 후 어떻게 처리할지 정의(handleFrame)하는 데 사용된다.</li>
</ul>
</blockquote>
<p>구독 자체에는 payload가 필요 없지만, 구독 후 메시지를 받았을 때 그 메시지(payload)를 어떻게 처리할지 정의하는 것이 필요하다.</p>
<ol>
<li>메시지 타입 처리를 위해:<pre><code class="language-java">public Type getPayloadType(StompHeaders headers) {
 return String.class; // 어떤 타입으로 메시지를 받을지 지정
}</code></pre>
</li>
</ol>
<ul>
<li>JSON 문자열로 받을지</li>
<li>바이트 배열로 받을지</li>
<li>커스텀 객체로 받을지 등을 지정한다.</li>
</ul>
<br>

<ol start="2">
<li>실제 메시지 수신을 위해:<pre><code class="language-java">public void handleFrame(StompHeaders headers, Object payload) {
 String message = (String) payload;  // 실제 받은 메시지 내용
 // 여기서 메시지를 처리
}</code></pre>
</li>
</ol>
<p>예를 들어 채팅 애플리케이션이라면:</p>
<pre><code class="language-java">// 채팅 메시지 클래스
class ChatMessage {
    private String sender;
    private String content;
    // getters, setters...
}

// 구독 설정
session.subscribe(&quot;/topic/chat&quot;, new StompFrameHandler() {
    @Override
    public Type getPayloadType(StompHeaders headers) {
        return ChatMessage.class;  // ChatMessage 타입으로 받겠다
    }

    @Override
    public void handleFrame(StompHeaders headers, Object payload) {
        ChatMessage chatMessage = (ChatMessage) payload;
        System.out.println(chatMessage.getSender() + &quot;: &quot; + chatMessage.getContent());
    }
});</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[MockMvc 직접 설정 vs 어노테이션 사용]]></title>
            <link>https://velog.io/@soobin_n_n_i/MockMvc-%EC%A7%81%EC%A0%91-%EC%84%A4%EC%A0%95-vs-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@soobin_n_n_i/MockMvc-%EC%A7%81%EC%A0%91-%EC%84%A4%EC%A0%95-vs-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Thu, 21 Nov 2024 09:09:29 GMT</pubDate>
            <description><![CDATA[<h3 id="mockmvc를-직접-설정하는-경우와-그-문제점"><code>MockMvc</code>를 직접 설정하는 경우와 그 문제점</h3>
<p>직접 설정하는 경우, <code>MockMvc</code> 객체를 수동으로 생성하고, 컨트롤러를 수동으로 주입하거나, 의존성들을 수동으로 모킹해야 한다. 이를 통해 테스트 환경을 구성하는 데 많은 코드가 필요하고, 번거로운 설정 작업이 동반된다.</p>
<h4 id="1-mockmvc-수동-설정-예시">1. <code>MockMvc</code> 수동 설정 예시</h4>
<pre><code class="language-java">import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

public class ManualMockMvcTest {

    private MockMvc mockMvc;

    @Mock
    private ExampleService exampleService;

    @InjectMocks
    private ExampleController exampleController;

    @BeforeEach
    void setup() {
        MockitoAnnotations.openMocks(this); // Mock 객체 초기화
        mockMvc = MockMvcBuilders.standaloneSetup(exampleController).build(); // MockMvc 수동 생성
    }

    @Test
    void testExampleEndpoint() throws Exception {
        mockMvc.perform(get(&quot;/example&quot;))
               .andExpect(status().isOk())
               .andExpect(content().string(&quot;Hello World&quot;));
    }
}</code></pre>
<p><strong>문제점</strong>:</p>
<ol>
<li><code>MockMvc</code> 객체를 매번 수동으로 초기화해야 한다.</li>
<li><code>ExampleService</code>와 같은 의존성을 직접 모킹하고 주입해야 한다.</li>
<li>컨트롤러 외의 Spring MVC 기능(예: 필터, 인터셉터, 컨트롤러 어드바이스 등)은 제대로 테스트되지 않는다.</li>
<li>의존성이 많아질수록 테스트 설정 코드가 복잡해진다.</li>
</ol>
<hr>
<h3 id="autoconfiguremockmvc를-사용하는-경우"><code>@AutoConfigureMockMvc</code>를 사용하는 경우</h3>
<p>Spring Boot에서 <code>@AutoConfigureMockMvc</code>를 사용하면 이러한 설정을 자동화해준다. <code>MockMvc</code> 객체를 테스트 컨텍스트에 자동으로 등록하고 필요한 모든 의존성을 자동으로 관리하므로, 테스트 코드는 컨트롤러의 동작에만 집중할 수 있다.</p>
<h4 id="2-autoconfiguremockmvc-사용-예시">2. <code>@AutoConfigureMockMvc</code> 사용 예시</h4>
<pre><code class="language-java">import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
public class AutoConfiguredMockMvcTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testExampleEndpoint() throws Exception {
        mockMvc.perform(get(&quot;/example&quot;))
               .andExpect(status().isOk())
               .andExpect(content().string(&quot;Hello World&quot;));
    }
}</code></pre>
<p><strong>편리한 점</strong>:</p>
<ol>
<li><code>MockMvc</code> 객체를 자동으로 생성하고 주입할 수 있다.</li>
<li>컨트롤러 및 서비스, 리포지토리 등 모든 의존성을 자동으로 관리한다.</li>
<li>Spring MVC 환경을 완전하게 지원한다. (필터, 인터셉터, 컨트롤러 어드바이스 등이 실제 환경과 동일하게 동작)</li>
<li>설정 코드 없이 테스트 로직에만 집중할 수 있다.</li>
</ol>
<hr>
<h3 id="두-접근법의-비교">두 접근법의 비교</h3>
<table>
<thead>
<tr>
<th><strong>특징</strong></th>
<th><strong>직접 설정</strong></th>
<th><strong><code>@AutoConfigureMockMvc</code></strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>MockMvc 생성</strong></td>
<td><code>MockMvcBuilders</code>를 사용해 수동 생성</td>
<td>자동 생성 및 주입 (<code>@Autowired</code>)</td>
</tr>
<tr>
<td><strong>Spring MVC 지원</strong></td>
<td>제한적 (필터, 인터셉터 등이 테스트되지 않음)</td>
<td>필터, 인터셉터, 어드바이스 모두 포함</td>
</tr>
<tr>
<td><strong>의존성 관리</strong></td>
<td>의존성을 수동 모킹 및 주입</td>
<td>자동 관리 (Spring Context에 의해 관리)</td>
</tr>
<tr>
<td><strong>테스트 준비 시간</strong></td>
<td>더 많은 설정 코드 필요</td>
<td>설정 없이 바로 테스트 가능</td>
</tr>
<tr>
<td><strong>유지보수성</strong></td>
<td>코드가 복잡하고 추가 의존성마다 수정 필요</td>
<td>테스트 로직만 작성하면 된다</td>
</tr>
</tbody></table>
<hr>
<h3 id="왜-편리한가">왜 편리한가?</h3>
<ol>
<li><strong>시간 절약</strong>: 수동으로 객체를 생성하거나 의존성을 모킹할 필요가 없다.</li>
<li><strong>통합적인 테스트 환경</strong>: Spring의 다양한 컴포넌트(Support 클래스, 필터 등)가 실제 환경과 동일하게 작동하므로 테스트의 신뢰성이 높아진다.</li>
<li><strong>유지보수성 향상</strong>: 코드가 간결하고, 의존성 변화에도 추가 설정이 필요 없으므로 유지보수하기 쉽다.</li>
</ol>
<p>따라서, <code>@AutoConfigureMockMvc</code>를 사용하면 테스트의 설정 부담을 줄이고, 애플리케이션의 실제 동작에 가깝게 테스트를 수행할 수 있어 효율적이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AutoConfigureMockMvc]]></title>
            <link>https://velog.io/@soobin_n_n_i/AutoConfigureMockMvc</link>
            <guid>https://velog.io/@soobin_n_n_i/AutoConfigureMockMvc</guid>
            <pubDate>Thu, 21 Nov 2024 08:56:14 GMT</pubDate>
            <description><![CDATA[<p><code>@AutoConfigureMockMvc</code>는 Spring Boot에서 테스트를 쉽게 하기 위해 제공하는 어노테이션이다. 주로 <strong>Spring MVC의 컨트롤러 테스트</strong>를 할 때 사용되며, MockMvc를 자동으로 설정하고 주입해준다. 이를 통해 컨트롤러 계층을 별도로 실행하지 않고도 요청과 응답을 모킹하여 테스트할 수 있다.</p>
<blockquote>
<h4 id="mockmvc를-직접-설정하는-경우와-그-문제점"><a href="https://velog.io/@soobin_n_n_i/MockMvc-%EC%A7%81%EC%A0%91-%EC%84%A4%EC%A0%95-vs-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%82%AC%EC%9A%A9"><code>MockMvc</code>를 직접 설정하는 경우와 그 문제점</a></h4>
<p><code>MockMvc</code> 객체를 수동으로 생성하고, 컨트롤러를 수동으로 주입하거나, 의존성들을 수동으로 모킹(mocking)해야 하므로 이를 통해 테스트 환경을 구성하는 데 많은 코드가 필요하고, 번거로운 설정 작업이 동반된다.</p>
</blockquote>
<hr>
<h3 id="기본-사용-방법">기본 사용 방법</h3>
<pre><code class="language-java">@SpringBootTest
@AutoConfigureMockMvc
public class ExampleControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testExampleEndpoint() throws Exception {
        mockMvc.perform(get(&quot;/example&quot;)) // 요청
               .andExpect(status().isOk()) // 응답 상태 확인
               .andExpect(content().string(&quot;Hello World&quot;)); // 응답 내용 확인
    }
}</code></pre>
<hr>
<h3 id="함께-사용되는-어노테이션">함께 사용되는 어노테이션</h3>
<ul>
<li><p><strong><code>@SpringBootTest</code></strong>:
전체 컨텍스트를 로드하여 통합 테스트 환경에서 MockMvc를 사용할 수 있다.</p>
</li>
<li><p><strong><code>@WebMvcTest</code></strong>:
컨트롤러 계층만 로드하고, 서비스나 리포지토리는 Mock으로 대체하므로 빠른 테스트에 적합.</p>
</li>
</ul>
<pre><code class="language-java">@WebMvcTest(controllers = ExampleController.class)
@AutoConfigureMockMvc
public class ExampleControllerSliceTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testExampleEndpoint() throws Exception {
        mockMvc.perform(get(&quot;/example&quot;))
               .andExpect(status().isOk())
               .andExpect(content().string(&quot;Hello World&quot;));
    }
}</code></pre>
<hr>
<h3 id="주요-옵션">주요 옵션</h3>
<p><code>@AutoConfigureMockMvc</code>에는 몇 가지 유용한 옵션이 있다.</p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>secure</code></td>
<td>보안이 활성화된 상태로 MockMvc를 설정. 기본값은 <code>true</code></td>
</tr>
<tr>
<td><code>addFilters</code></td>
<td>Spring Security와 같은 필터를 추가할지 여부를 결정, 기본값은 <code>true</code>.</td>
</tr>
<tr>
<td><code>print</code></td>
<td>테스트 실패 시 요청/응답을 출력, 기본값은 <code>false</code>.</td>
</tr>
</tbody></table>
<pre><code class="language-java">@AutoConfigureMockMvc(secure = false, addFilters = false)
public class ExampleControllerTest {
    // 보안 및 필터가 비활성화된 MockMvc를 테스트에 사용
}</code></pre>
<hr>
<h3 id="언제-사용할까">언제 사용할까?</h3>
<ol>
<li><strong>통합 테스트</strong>를 통해 요청/응답 전체를 검증하고 싶을 때.</li>
<li>컨트롤러 계층만 <strong>슬라이스 테스트</strong>하고 싶을 때.</li>
<li>MockMvc 객체를 직접 생성하거나 설정하는 번거로움을 피하고 싶을 때.</li>
</ol>
<hr>
<h3 id="주의점">주의점</h3>
<ul>
<li><strong>DB와 연관된 서비스 로직을 테스트하려면 <code>@DataJpaTest</code>와 같은 다른 어노테이션이 필요</strong>할 수 있다.</li>
<li>전체 컨텍스트를 로드하는 경우 테스트 속도가 느려질 수 있다. 이를 최소화하려면 <code>@WebMvcTest</code>를 선택할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[함수형 인터페이스]]></title>
            <link>https://velog.io/@soobin_n_n_i/%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@soobin_n_n_i/%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Sat, 06 Apr 2024 17:10:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>함수형 인터페이스(Functional Interface)는 자바에서 람다식을 지원하기 위해 도입된 인터페이스의 종류다.</p>
</blockquote>
<h2 id="특징">특징</h2>
<p><strong>① 단 하나의 추상 메서드를 가짐</strong><br>단 하나의 추상 메서드만을 가지는 인터페이스인데 이는 단 하나만 가지고 있어야 람다식과 인터페이스의 메서드가 1:1로 연결될 수 있기 때문이다(default, static, private은 가능, Object클래스의 메서드 호출도 가능).</p>
<p><strong>② 조건</strong><br>함수형 인터페이스의 추상메서드와 람다식의 매개변수의 타입과 개수, 반환값이 일치해야 함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있다.</p>
<p><strong>③ @FunctionalInterface</strong><br><code>@FunctionalInterface</code> 어노테이션을 클래스 레벨에 선언함으로써, 컴파일러가 함수형 인터페이스를 올바르게 작성했는지 컴파일 시점에 확인할 수 있다.</p>
<p><strong>④ Runnable, Comparator, Predicate</strong><br>함수형 인터페이스의 대표적인 예시다.</p>
<hr>
<h2 id="함수형-인터페이스의-활용">함수형 인터페이스의 활용</h2>
<p><strong>① <a href="https://velog.io/@soobin_n_n_i/%EB%9E%8C%EB%8B%A4%EC%8B%9D">람다 표현식 지원</a></strong></p>
<p><strong>② <a href="https://velog.io/@soobin_n_n_i/%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%B0%B8%EC%A1%B0">메서드 참조</a></strong></p>
<p><strong>③ <a href="https://velog.io/@soobin_n_n_i/Stream-API%EC%99%80-%EB%9E%8C%EB%8B%A4%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%B3%91%EB%A0%AC-%EC%B2%98%EB%A6%AC">Stream API와의 통합</a></strong></p>
<p><strong>④ 병렬 프로그래밍</strong><br>병렬 처리 및 비동기 프로그래밍에서 사용</p>
<p><strong>⑤ 코드 재사용</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Stream API와 람다를 통한 병렬 처리]]></title>
            <link>https://velog.io/@soobin_n_n_i/Stream-API%EC%99%80-%EB%9E%8C%EB%8B%A4%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%B3%91%EB%A0%AC-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@soobin_n_n_i/Stream-API%EC%99%80-%EB%9E%8C%EB%8B%A4%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%B3%91%EB%A0%AC-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Sat, 06 Apr 2024 17:10:02 GMT</pubDate>
            <description><![CDATA[<h2 id="java의-stream-api">Java의 Stream API</h2>
<blockquote>
<p>Java 8에 도입된 기능으로 데이터의 흐름을 다루기 위한 선언형 API다.<br>스트림을 사용하여 필터링, 매핑, 정렬 등 다양한 데이터 처리 작업을 적용할 수 있으며 최종 결과를 배열이나 컬렉션으로 변환할 수 있다.<br>스트림은 데이터 처리 작업을 연속적인 파이프라인으로 나타낼 수 있어 가독성이 높고 병렬 처리를 쉽게 구현할 수 있다.</p>
</blockquote>
<h3 id="stream의-연산">Stream의 연산</h3>
<p><img src="https://velog.velcdn.com/images/soobin_n_n_i/post/1c2f14c6-48dc-42b2-a2c9-2e0416ade83a/image.png" alt=""></p>
<ul>
<li>Java의 Stream은 중간 연산과 최종 연산을 가진다. <code>중간 연산</code>은 스트림을 처리하고 _<strong>다른 스트림을 반환</strong>_하는 반면 <code>최종 연산</code>은 스트림을 처리하고 _<strong>결과를 반환</strong>_한다.</li>
</ul>
<hr>
<h3 id="배열을-스트림으로-변환">배열을 스트림으로 변환</h3>
<p>배열을 스트림으로 변환한다는 것은 배열의 원소들을 스트림 형태로 변환하여 처리할 수 있게 하는 것이다. 스트림은 원본 데이터를 변경하지 않고 필요한 데이터 처리 작업을 적용한 결과를 생성하기 때문에 인덱스를 통한 직접 접근은 제공하지 않는다.</p>
<ul>
<li><p><code>stream()</code> 배열을 스트림으로 변환 </p>
<pre><code class="language-java">      int[] numbers = {1,2,3,4,5};
      IntStream stream = Arrays.stream(numbers);</code></pre>
</li>
<li><p><code>filter()</code> 스트림을 통해 필터링 작업 및 데이터 처리 작업</p>
<pre><code class="language-java">      IntStream evenStream = stream.filter(n-&gt;n%2==0); // 중간 연산, 람다식 사용
      int evenSum = evenStream.sum();                    // 최종 연산
      // ==&gt; int evenSum = stream.filter(n-&gt;n%2==0).sum();

      System.out.println(evenSum);</code></pre>
</li>
<li><p><code>toArray()</code> 스트림을 배열로 변환</p>
<pre><code class="language-java">      int[] numbers = {1,2,3,4,5};
      IntStream stream = Arrays.stream(numbers);

      IntStream evenStream = stream.filter(n-&gt;n%2==0);
      // [2, 4]
      System.out.println(Arrays.toString(evenStream.toArray()));</code></pre>
</li>
</ul>
<h4 id="다른-예시">다른 예시</h4>
<ul>
<li><p>짝수 요소를 제곱하고 그 합을 구함</p>
<pre><code class="language-java">interface FilterEven{
    boolean isEven(int n);
}

public class ArrayStreamEx {
    public static boolean isEvenStatic(int n){
        return n % 2 == 0;
    }
    public static void main(String[] args) {
        List&lt;Integer&gt; numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

        // 객체 n의 조건자 boolean값을 반환하는 함수형 인터페이스
        Predicate&lt;Integer&gt; isEven = n -&gt; n%2 == 0;
        FilterEven fe = n -&gt; n%2 == 0;

        int sumOfsquares = numbers.stream()
                .filter(isEven) // 짝수 스트림 필터
//              .filter(fe::isEven) 같은 표현
//              .filter(ArrayStreamEx::isEvenStatic) 같은 표현
                .sorted()       // 스트림 정렬
                .map(n-&gt;n*n)    // 모든 원소를 제곱함
                .reduce(0, Integer::sum); // 초기값 0에 원소들을 순차적으로 더함(누적합)
        System.out.println(&quot;짝수 제곱의 합: &quot;+sumOfsquares);
    }
}</code></pre>
</li>
<li><p>대문자로 변환</p>
<pre><code class="language-java">List&lt;String&gt; words = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;, &quot;orange&quot;);

List&lt;String&gt; uppercaseWords = words.stream()
        .map(n -&gt; n.toUpperCase())
        .collect(Collectors.toList());

// 대문자로 변환한 리스트: [APPLE, BANANA, CHERRY, ORANGE]
System.out.println(&quot;대문자로 변환한 리스트: &quot;+uppercaseWords.toString());</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Stream]]></title>
            <link>https://velog.io/@soobin_n_n_i/Stream</link>
            <guid>https://velog.io/@soobin_n_n_i/Stream</guid>
            <pubDate>Sat, 06 Apr 2024 15:58:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>연속적인 데이터의 흐름 또는 데이터를 전송하는 소프트웨어 모듈을 가리키며, 연속된 데이터의 단방향 흐름을 추상화한 것이다.<br>스트림은 자료의 입출력을 도와주는 중간 매개 역할을 하며 메모리의 원시 데이터를 프로그래머가 사용할 수 있는 데이터로 바꾸어주는 역할도 수행한다.</p>
</blockquote>
<p>어느 한 쪽에서 다른 쪽으로 데이터를 전달하려면, 두 대상을 연결하고 데이터를 전송할 수 있게 하는 <strong>무언가</strong>가 필요한데 그 역할을 하는 것이 스트림이다.</p>
<p>예를 들어, 표준 입출력(System.out, System.in)은 키보드와 모니터를 대상으로 데이터를 읽고 쓰는 가장 기본적인 스트림이다. 프로그램과 외부 장치 및 파일 사이의 데이터 흐름도 스트림이다.</p>
<p>Java의 스트림은 <code>바이트 스트림</code>과 <code>문자 스트림</code>으로 구분된다.</p>
<hr>
<h3 id="스트림-사용">스트림 사용</h3>
<p><img src="https://velog.velcdn.com/images/soobin_n_n_i/post/f093c937-3e55-4d2b-8f9d-148ec63ebd74/image.png" alt=""></p>
<ul>
<li>데이터의 출발지와 연결하거나 데이터의 목적지와 연결할 수 있다.</li>
<li>스트림은 사용 전에 열고, 사용 후에 제한된 자원(일회성)이므로 닫기가 필수다.</li>
<li>스트림은 한 번에 열 수 있는 개수가 제한되어 있고 동일한 파일은 둘 이상의 스트림으로 열 수 없다.</li>
<li>효과적으로 스트림을 활용하는 방법은 특정 스트림이 제공하는 기능을 모두 활용하기 위해 다수의 스트림을 연결해 파이프라인을 구성하는 것이다.</li>
</ul>
<hr>
<h3 id="스트림-특징">스트림 특징</h3>
<p>① <code>선입선출</code></p>
<p>　순차적으로 흘러가고 순차적으로 접근하기 때문에 스트림에 포함된 데이터의 순서는 변하지 않는다.</p>
<p>② <code>단방향</code></p>
<p>　따라서 하나의 스트림으로 읽기와 쓰기가 동시 수행할 수 없다.</p>
<p>③ <code>메서드 제공</code></p>
<p>　객체이므로 여러 유용한 메서드들이 제공된다.</p>
<p>④ <code>연결</code></p>
<pre><code class="language-java">    // 키보드로부터 문자를 입력받기 위해 표준 입력 스트림인 System.in과 InputStreamReader를 연결
    InputStreamReader rd = new InputStreamReader(System.in);</code></pre>
<p>(ex) 출력 스트림의 출력을 입력 스트림의 입력으로 연결하여 파이프라인을 구성할 수 있다. 파이프라인으로 구성되면 스트림에 포함된 데이터를 다양한 방식으로 처리해 최종 스트림에 공급할 수 있다.</p>
<p>⑤ <code>지연가능성</code></p>
<p>　(ex) 프로그램에 연결한 출력스트림에 데이터가 가득 차면 빈 공간이 생길 때까지 출력을 중단하고 지연된다, 데이터소스에 연결한 스트림이 가득 차면 프로그램이 데이터를 처리해서 빈 공간이 생길 때까지 지연된다.</p>
<hr>
<h2 id="java의-stream">Java의 Stream</h2>
<p>　Java의 스트림은 크게 바이트 스트림과 문자 스트림으로 나뉜다. 결론적으로 두 유형의 스트림은 데이터를 가공하는 방식만 다를 뿐 자료의 입출력을 byte단위로 처리하며 데이터 전송에 있어 중간 매개 역할을 수행한다.</p>
<h3 id="바이트-스트림">바이트 스트림</h3>
<blockquote>
<p>바이트 단위로 데이터를 다루는 스트림.</p>
</blockquote>
<p>　스트림에 들어오고 나가는 정보를 단순 바이너리로 다루기 때문에 문자, 오디오, 이미지에 상관 없이 흘려 보낼 수 있다.</p>
<h3 id="문자-스트림">문자 스트림</h3>
<blockquote>
<p>2바이트의 유니코드 문자 단위로 입출력하는 스트림</p>
</blockquote>
<p>　문자는 메모리에 byte단위로 저장되므로, 문자 입출력 시 byte 변환이 필요하다. 따라서 메모리 상의 바이트를 유니코드 문자로 변환해주는 역할을 문자 스트림이 수행한다.</p>
<p> 문자 스트림은 바이트들을 전달받고 이 바이트들을 문자 집합에 있는 문자인지 비교한 뒤 문자로 변환한다.</p>
<hr>
<h2 id="버퍼buffer">버퍼(Buffer)</h2>
<blockquote>
<p>데이터를 일시적으로 저장하는 임시 메모리 공간으로 주로 입출력 스트림 또는 네트워크 통신에서 데이터를 읽거나 쓸 때 사용된다.<br>크게 입력 버퍼와 출력 버퍼로 나뉜다.</p>
</blockquote>
<ul>
<li><code>BufferedReader</code>, <code>BufferedWriter</code></li>
<li><code>BufferedInputStream</code>, <code>BufferedOutputStream</code> </li>
</ul>
<p>　입출력 장치는 주 기억장치와는 다르게 매우 느리기 때문에 입출력 장치가 자주 동작할수록 효율이 떨어지고, 입출력 실행 속도가 떨어지게 된다(지연). 따라서 좀 더 효율적으로 수행을 위해 버퍼를 사용한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[람다식과 Collections.sort() 메서드
]]></title>
            <link>https://velog.io/@soobin_n_n_i/%EB%9E%8C%EB%8B%A4%EC%8B%9D%EA%B3%BC-Collections.sort-%EB%A9%94%EC%84%9C%EB%93%9C</link>
            <guid>https://velog.io/@soobin_n_n_i/%EB%9E%8C%EB%8B%A4%EC%8B%9D%EA%B3%BC-Collections.sort-%EB%A9%94%EC%84%9C%EB%93%9C</guid>
            <pubDate>Sat, 06 Apr 2024 15:26:23 GMT</pubDate>
            <description><![CDATA[<h3 id="comparator">Comparator</h3>
<blockquote>
<p>함수형 인터페이스로, 두 개의 객체를 인자로 받아 그들의 순서를 비교하는 메서드인 <code>compare</code>을 제공하여 객체의 정렬을 가능하게 한다.</p>
</blockquote>
<h4 id="compare">.compare()</h4>
<pre><code class="language-java">@FunctionalInterface
public interface Comparator&lt;T&gt; {
    int compare(T o1, T o2);
}</code></pre>
<p><code>구현</code> 반환 값이 Comparator인 메서드를 할당한다.</p>
<pre><code class="language-java">List&lt;String&gt; names = Arrays.asList(&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;, &quot;David&quot;);

Comparator&lt;String&gt; lengthComparator = (str1, str2) -&gt; Integer.compare(str1.length(), str2.length());
Comparator&lt;String&gt; desc = (str1, str2) -&gt; str1.compareTo(str2);</code></pre>
<br>

<hr>
<h3 id="collectionssort">Collections.sort()</h3>
<p><code>Collections.sort(List&lt;T&gt; list, Comparator&lt;? super T&gt; c)</code></p>
<p><a href="https://velog.io/@soobin_n_n_i/%EB%9E%8C%EB%8B%A4%EC%8B%9D#%EB%9E%8C%EB%8B%A4%EC%8B%9D%EC%9D%B8%EC%9E%90">람다식을 인자로 받는 메서드</a>로, <code>Comparator</code>에 의해 유도된 순서에 따라 <code>List</code>를 정렬한다.</p>
<pre><code class="language-java">import java.util.*;

public class LambdaSortExample {
    public static void main(String[] args) {
        List&lt;String&gt; names = Arrays.asList(&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;, &quot;David&quot;);

        // 문자열을 길이에 따라 정렬하는 Comparator 람다식
        Comparator&lt;String&gt; lengthComparator = 
                                (str1, str2) -&gt; Integer.compare(str1.length(), str2.length());

        // 리스트를 정렬
        Collections.sort(names, lengthComparator);

        /* 
            정렬된 리스트:
            Alice
            Bob
            Charlie
            David            */
        System.out.println(&quot;정렬된 리스트:&quot;);
        for (String name : names) {
            System.out.println(name);
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[함수형 인터페이스 메서드 참조]]></title>
            <link>https://velog.io/@soobin_n_n_i/%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%B0%B8%EC%A1%B0</link>
            <guid>https://velog.io/@soobin_n_n_i/%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%B0%B8%EC%A1%B0</guid>
            <pubDate>Sat, 06 Apr 2024 14:54:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이미 정의된 메서드를 직접 참조하여 람다 표현식을 더 간결하게 작성할 수 있다.</p>
</blockquote>
<h3 id="유형">유형</h3>
<p><strong>① 정적 메서드 참조</strong> <code>클래스명::메서드명</code></p>
<pre><code class="language-java">  class IntegerUtils{
      public static int stringToInt(String s){
          return Integer.parseInt(s);
      }
  }

  interface Converter&lt;T, U&gt;{
      U converter(T s);
  }

  public class FunctionalMethod {
      public static void main(String[] args){
        // 정적 메소드 참조
        Converter&lt;String, Integer&gt; intConverter = IntegerUtils::stringToInt;
        Integer intResult = intConverter.converter(&quot;123&quot;);
        System.out.println(intResult);
      }
  }</code></pre>
<p><strong>② 인스턴스 메서드 참조</strong> <code>객체참조::메서드명</code></p>
<pre><code class="language-java">  class StringUtils{
      public String reverse(String s){
          return new StringBuilder(s).reverse().toString();
      }
  }

  interface Converter&lt;T, U&gt;{
      U converter(T s);
  }

  public class FunctionalMethod {
      public static void main(String[] args){
        // 인스턴스메서드 참조
        StringUtils stringUtils = new StringUtils();
        Converter&lt;String, String&gt; strConverter = stringUtils::reverse;
        String strResult = strConverter.converter(&quot;가나다라마바사&quot;);
        System.out.println(strResult);
      }
  }</code></pre>
<p><strong>③ 특정 객체의 인스턴스 메서드 참조</strong> <code>클래스명::메서드명</code></p>
<pre><code class="language-java">  public class FunctionalMethod {
      public static void main(String[] args){
        // 특정 객체의 인스턴스 메서드 참조(String클래스의 compareTo 메서드 참조)
        List&lt;String&gt; names= Arrays.asList(&quot;John&quot;, &quot;Jane&quot;, &quot;Doe&quot;);
        Collections.sort(names, String::compareTo);
        System.out.println(names);
      }
  }</code></pre>
<p><strong>④ 생성자 참조</strong> <code>클래스명::new</code></p>
<pre><code class="language-java">  class Person{
      private String name;
      private int age;

      public Person(String name, int age){
          this.name = name;
          this.age = age;
      }
  }

  interface PersonFactory{
      Person create(String name, int age);
  }

  public class FunctionalMethod {
      public static void main(String[] args){
        // 생성자 참조
        PersonFactory pf = Person::new;
        Person p = pf.create(&quot;Kim&quot;, 27);

        // 람다식 사용
        PersonFactory pfLambda = (name, age) -&gt; new Person(name, age);
        Person pLambda = pfLambda.create(&quot;Kim&quot;, 27);
      }
  }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[람다식]]></title>
            <link>https://velog.io/@soobin_n_n_i/%EB%9E%8C%EB%8B%A4%EC%8B%9D</link>
            <guid>https://velog.io/@soobin_n_n_i/%EB%9E%8C%EB%8B%A4%EC%8B%9D</guid>
            <pubDate>Sat, 06 Apr 2024 14:22:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>프로그래밍 언어에서 함수(메서드)를 간단한 식(expression)으로 표현하는 방식.</p>
<p>Java에서 람다식은 익명함수(anonymous function)의 한 형태로, 메서드에 대한 구현을 간결하게 표현하는 방법이다.</p>
</blockquote>
<h3 id="함수와-메서드">함수와 메서드</h3>
<ul>
<li>함수와 메서드는 근본적으로 동일하지만, 함수는 일반적인 용어이고, 메서드는 객체지향 개념에서 사용되는 용어다.</li>
<li>함수는 클래스에 독립적이지만, 메서드는 클래스에 종속적이다.</li>
</ul>
<br>

<h3 id="함수형-인터페이스와-람다식">함수형 인터페이스와 람다식</h3>
<p>Java에서 모든 메서드는 클래스 내에 포함되어야 한다. </p>
<p>람다식은 메서드를 하나의 식(expression)으로 표현하여 함수를 간략하면서도 명확하게 표현하는 문법이다. 그런데 Java에서 메서드는 클래스에 속하는 것이므로, 람다식은 사실 메서드와 동등한 것이 아니라 함수형 인터페이스를 구현한 익명 클래스의 객체와 같다고 할 수 있다.</p>
<blockquote>
<p><strong><a href="https://github.com/Soobinnni/study/assets/111328823/68ddaa29-31ad-4b5a-b377-3cf2b5907634">익명클래스</a></strong> 클래스의 선언과 생성을 동시에 하는 내부 클래스</p>
</blockquote>
<pre><code class="language-java">    @FunctionalInterface 
    interface Ex{
        public abstract int max(int a, int b);
    }
    public class LambdaEx {
        public static void main(String[] args) {
            // 같은 표현
            Ex ex = new Ex(){
                @Override
                public int max(int a, int b){
                    return a &gt; b ? a : b;
                }
            };

            // 같은 표현
            Ex ex2 = (a, b) -&gt; a &gt; b ? a : b;
        }
    }</code></pre>
<ul>
<li><p>이처럼 Java는 인터페이스를 통해 람다식을 다루며, 람다식을 다루기 위한 인터페이스를 <code>함수형 인터페이스(functional interface)</code>라고 한다. 즉 람다식(익명 객체)을 다루기 위한 참조 변수의 타입은 함수형 인터페이스다.</p>
</li>
<li><p><code>java.util.function</code> 패키지에 함수형 인터페이스가 다수 정의되어 있다.</p>
</li>
</ul>
<br>

<h3 id="람다식-특징">람다식 특징</h3>
<ul>
<li><p>메서드를 하나의 식(expression)으로 표현하므로 함수를 간략하면서도 명확하게 표현할 수 있게 된다.</p>
<ul>
<li>모든 메서드는 클래스에 포함되어야 하고 메서드 호출 시 (일부 제외) 인스턴스를 생성해야 하지만, 람다식을 이용하면 그 일련의 과정을 하지 않고도 람다식 그 자체만으로 메서드의 역할을 대신할 수 있다.</li>
</ul>
</li>
<li><p>메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로 람다식을 &#39;익명 함수&#39;라고도 한다.</p>
</li>
<li><p>람다식을 메서드의 매개변수로 전달하는 것도 가능하며, 메서드의 결과로 반환될 수 있다.</p>
</li>
<li><p>람다식으로 인해 메서드를 변수처럼 다룰 수 있게 된다.</p>
</li>
</ul>
<br>

<hr>
<h3 id="람다식-작성하기">람다식 작성하기</h3>
<p><strong>람다식의 구조</strong></p>
<pre><code class="language-plain">(parameters) -&gt; {expression}</code></pre>
<ul>
<li><p>메서드에서 이름과 반환 타입을 제거하고 매개변수 선언부와 몸통 <code>{}</code> 사이에 <code>-&gt;</code>를 추가한다.
<img src="https://velog.velcdn.com/images/soobin_n_n_i/post/89381aab-e04c-455e-a005-e5979d5e7fb9/image.png" alt=""></p>
</li>
<li><p><a href="https://velog.io/@soobin_n_n_i/%EB%9E%8C%EB%8B%A4%EC%8B%9D-%EC%9E%91%EC%84%B1-%EC%98%88%EC%8B%9C">예시</a></p>
<table>
<thead>
<tr>
<th>경우</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td></td>
<td>반환 값이 있는 메서드의 경우, 문장이 아닌 식으로 표현할 수 있다.</td>
</tr>
<tr>
<td></td>
<td>식의 연산 결과가 자동적으로 반환값이 되기 때문이다.</td>
</tr>
<tr>
<td></td>
<td>문장이 아니므로 세미콜론을 식 끝에 붙이지 않는다.</td>
</tr>
<tr>
<td><strong>중괄호를 생략 가능한 경우</strong></td>
<td>중괄호 안의 문장이 <code>return</code>문이 아니면서(return문 대신 식으로 표현하면서),</td>
</tr>
<tr>
<td></td>
<td>메서드의 구현부가 단일 문장일 경우.</td>
</tr>
<tr>
<td></td>
<td>세미콜론 X</td>
</tr>
<tr>
<td><strong>()괄호를 생략 가능한 경우</strong></td>
<td>매개변수의 타입을 추론할 수 있어 매개변수의 타입이 없고(생략했으며), 매개변수가 하나일 경우</td>
</tr>
</tbody></table>
</li>
</ul>
<br>

<hr>
<h3 id="람다-표현식을-메서드에서-활용하기">람다 표현식을 메서드에서 활용하기</h3>
<h4 id="람다식이-인자로-활용되는-경우">람다식이 인자로 활용되는 경우</h4>
<p><code>예시</code> <a href="https://velog.io/@soobin_n_n_i/%EB%9E%8C%EB%8B%A4%EC%8B%9D%EA%B3%BC-Collections.sort-%EB%A9%94%EC%84%9C%EB%93%9C">Collections.sort 메서드</a></p>
<pre><code class="language-java">  @FunctionalInterface
  interface StringOperation{
      String apply(String s);
  }
  public class LambdaApply {
      public static void main(String[] args) {
          StringOperation toUpperCase = s -&gt; s.toUpperCase();
          StringOperation toLowerCase = s -&gt; s.toLowerCase();

          String input = &quot;Lambda Expressions&quot;;
          System.out.println(processString(input, toUpperCase));
          System.out.println(processString(input, toLowerCase));
      }

      public static String processString(String input, StringOperation operation){
          return operation.apply(input);
      }
  }</code></pre>
<h4 id="메서드의-반환-타입이-람다식익명-객체일-경우">메서드의 반환 타입이 람다식(익명 객체)일 경우</h4>
<pre><code class="language-java">@FunctionalInterface
interface StringOperation{
    String apply(String s);
}
public class LambdaApply {
    public static void main(String[] args) {
        StringOperation toUpperCase = getStringOperateToUpperCase();

        String input = &quot;Lambda Expressions&quot;;
        System.out.println(processString(input, toUpperCase));
    }

    public static StringOperation getStringOperateToUpperCase(){
        StringOperation toUpperCase = s -&gt; s.toUpperCase();
        return toUpperCase;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[람다식 작성 예시]]></title>
            <link>https://velog.io/@soobin_n_n_i/%EB%9E%8C%EB%8B%A4%EC%8B%9D-%EC%9E%91%EC%84%B1-%EC%98%88%EC%8B%9C</link>
            <guid>https://velog.io/@soobin_n_n_i/%EB%9E%8C%EB%8B%A4%EC%8B%9D-%EC%9E%91%EC%84%B1-%EC%98%88%EC%8B%9C</guid>
            <pubDate>Sat, 06 Apr 2024 10:53:18 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">int max(int a, int b){
   return a &gt; b ? a : b;
}

//람다식 표현
(int a, int b) -&gt; {return  a &gt; b ? a : b;} </code></pre>
<ul>
<li><p>반환값이 있는 메서드의 경우, <code>return</code>문 대신 식(expression)으로 대신할 수 있다. 식의 연산 결과가 자동으로 반환값이 되기 때문이다.</p>
<ul>
<li>이때 문장이 아닌 &#39;식&#39;이므로 끝에 <code>;</code>을 붙이지 않는다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">(int a, int b) -&gt; {return  a &gt; b ? a : b;}   /*--&gt;*/ (int a, int b) -&gt;  a &gt; b ? a : b</code></pre>
<hr>
<ul>
<li>람다식에 선언된 매개변수의 타입은 추론이 가능한 경우 생략할 수 있다.</li>
</ul>
<pre><code class="language-java">(int a, int b) -&gt; return  a &gt; b ? a : b /*--&gt;*/ (a, b) -&gt;  a &gt; b ? a : b</code></pre>
<hr>
<ul>
<li>선언된 매개변수가 하나일 경우 괄호 <code>()</code>를 생략할 수 있다. 단, 매개변수의 타입이 있으면 괄호 <code>()</code>를 생략할 수 없다.</li>
</ul>
<pre><code class="language-java">(a) -&gt; a * a         /*--&gt;*/   a -&gt; a * a   
(int a) -&gt; a * a     /*--&gt;  int a -&gt; a * a   오류. 매개변수의 타입이 존재*/</code></pre>
<hr>
<ul>
<li><p><code>{}</code>괄호 안의 문장이 하나일 때 괄호 <code>{}</code>를 생략할 수 있다. 이때 문장의 끝에 <code>;</code>을 붙이지 않도록 주의.</p>
<ul>
<li>주의: 괄호 <code>{}</code> 안의 문장이 <code>return</code>일 경우 <code>{}</code>를 생략할 수 없다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">(String name, int i) -&gt; {System.out.println(name+&quot;=&quot;+i);}
//람다식 표현
(String name, int i) -&gt; System.out.println(name+&quot;=&quot;+i)
//주의
(int a, int b) -&gt; {return a &gt; b ? a : b;}
// (int a, int b) -&gt; return a &gt; b ? a : b 는 에러</code></pre>
<hr>
<h2 id="람다식-작성-예시">람다식 작성 예시</h2>
<p><img src="https://velog.velcdn.com/images/soobin_n_n_i/post/6229fc5f-4d42-4d4a-8ea7-5364df11d11d/image.png" alt=""></p>
<hr>
<p><img src="https://velog.velcdn.com/images/soobin_n_n_i/post/0a696dd2-9189-412d-8865-26272ddd02e8/image.png" alt=""></p>
<pre><code class="language-java">@FunctionalInterface
interface MyFunction {
    void run(); // public abstract void run();
}

public class Test {
    static void execute(MyFunction f) { // 매개변수의 타입이 MyFunction인 메서드
        f.run();
    }

    static MyFunction getMyFunction() { // 반환 타입이 MyFunction인 메서드
        MyFunction f = () -&gt; System.out.println(&quot;f3.run()&quot;);
        return f;
    }

    public static void main(String[] args) {
        // 람다식으로 MyFunction의 run()을 구현
        MyFunction f1 = () -&gt; System.out.println(&quot;f1.run()&quot;);

        MyFunction f2 = new MyFunction() { // 람다식이 아닌 익명클래스로 run()을 구현
            public void run() { // public을 반드시 붙여야 함(오버라이딩 규칙)
                System.out.println(&quot;f2.run()&quot;);
            }
        };

        MyFunction f3 = getMyFunction();

        f1.run();
        f2.run();
        f3.run();

        execute(f1);
        execute(() -&gt; System.out.println(&quot;run()&quot;));
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭의 제약과 제한]]></title>
            <link>https://velog.io/@soobin_n_n_i/%EC%A0%9C%EB%84%A4%EB%A6%AD%EC%9D%98-%EC%A0%9C%EC%95%BD%EA%B3%BC-%EC%A0%9C%ED%95%9C</link>
            <guid>https://velog.io/@soobin_n_n_i/%EC%A0%9C%EB%84%A4%EB%A6%AD%EC%9D%98-%EC%A0%9C%EC%95%BD%EA%B3%BC-%EC%A0%9C%ED%95%9C</guid>
            <pubDate>Sat, 06 Apr 2024 08:33:22 GMT</pubDate>
            <description><![CDATA[<h2 id="제약">제약</h2>
<p><strong><code>요약</code></strong></p>
<p><strong>①</strong> 제네릭 클래스의 <code>static</code> 멤버에 타입변수를 활용할 수 없다(∵정적 바인딩).<br><strong>②</strong> 제네릭 타입을 선언한 메서드는 <code>static</code> 멤버가 될 수 있다.<br><em>클래스가 로드될 때 정적 제네릭 메서드도 메모리에 로드되지만, 타입 매개변수는 구체적 타입으로 결정되지 않다가, 호출될 때 전달된 인수를 기반으로 타입이 결정되기 때문이다.</em>  </p>
<pre><code class="language-java">public static void example(T generic) { /*...*/ } // error
public static &lt;T&gt; T getID(T id) { return (T)id; } // correct</code></pre>
<p><strong>③</strong> 객체 / 배열 생성 시 타입변수를 사용할 수 없다<em>(<code>선언 O</code> | <code>생성 즉 new 연산자 뒤 X</code>)</em>.</p>
<pre><code class="language-java">public class GenericArray&lt;T&gt; {
    public GenericArray(int capacity) {
        elements = (T[])(new Object[capacity]);
        // elements = new T[capacity]; X!!
    }
}</code></pre>
<p><strong>④</strong> 타입변수는 <code>instanceof</code> 연산자의 피연산자가 될 수 없다.</p>
<p><strong>⑤</strong> 제네릭 클래스의 타입 매개변수 <code>T</code>와 제네릭 클래스 내부에 선언된 제네릭 메서드의 타입 매개변수 <code>T</code>가 있다고 가정하였을 때, 이 둘은 타입 문자만 같은 것일 뿐, 사실상 서로 다른 별개의 타입 매개변수다.</p>
<p><strong>⑥</strong> 타입변수에 대입된 제네릭 타입과 생성자에 대입된 매개변수화된 타입은 일치해야 하는데, 이는 제네릭 타입 간의 다형성이 허용되지 않기 때문이다.</p>
<pre><code class="language-java">GenericArray&lt;Object&gt; obj = new GenericArray&lt;String&gt;(); // error</code></pre>
<hr>
<h2 id="제한">제한</h2>
<pre><code class="language-java">    // 제네릭 타입 변수의 구체적 타입이 Products의 자손타입으로 제한된다.
    class ProductsList&lt;T extends Products&gt;{}
    class Calculate&lt;T extends Number&gt;{}
    public &lt;T extends Animal&gt; T animal(T animal){}</code></pre>
<ul>
<li>제네릭 타입을 좁은 범위로 특정하는 방법은 <code>extends</code> 키워드를 이용하는 것이다.</li>
<li>이를 통해 제네릭 클래스나 메서드에서 사용할 수 있는 타입을 제한할 수 있다.</li>
<li><strong>주의</strong>: 인터페이스 구현 제약이 필요해도 키워드는 <code>extends</code>를 사용한다.</li>
<li>A의 자손이면서 B인터페이스를 구현하는 제네릭 타입일 경우, <code>&lt;T extends A &amp; B&gt;</code>처럼 <code>&amp;</code>기호로 연결한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 제네릭스와 제네릭]]></title>
            <link>https://velog.io/@soobin_n_n_i/%EC%9E%90%EB%B0%94-%EC%A0%9C%EB%84%A4%EB%A6%AD%EC%8A%A4%EC%99%80-%EC%A0%9C%EB%84%A4%EB%A6%AD</link>
            <guid>https://velog.io/@soobin_n_n_i/%EC%9E%90%EB%B0%94-%EC%A0%9C%EB%84%A4%EB%A6%AD%EC%8A%A4%EC%99%80-%EC%A0%9C%EB%84%A4%EB%A6%AD</guid>
            <pubDate>Sat, 06 Apr 2024 07:39:35 GMT</pubDate>
            <description><![CDATA[<h1 id="제네릭스">제네릭스</h1>
<blockquote>
<p>다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시 타입을 체크해주는 기능</p>
</blockquote>
<h2 id="도입-배경">도입 배경</h2>
<p>기존의 컬렉션 프레임워크는 <code>Object</code> 타입을 사용하여 모든 종류의 객체를 하나의 자료구조에 저장할 수 있었다. 이로 인해 컬렉션의 요소를 활용하기 위해 다운캐스팅을 수행하는 경우가 많았는데, 이는 런타임 시에 타입 불일치 오류(<code>ClassCastException</code>)를 발생시킬 수 있었다. 이에 제네릭스를 도입함으로써 컴파일러가 타입을 체크하여 잘못된 타입 사용을 사전에 방지할 수 있게 되었고(런타임 오류 사전 방지), 코드의 가독성도 높아졌다.</p>
<ul>
<li>타입 안정성 확보(type safety, 실행 오류를 컴파일 오류로 끌어옴)</li>
<li>형 변환이 생략되므로 코드가 간결해지고 가독성이 높아짐</li>
</ul>
<hr>
<h1 id="제네릭">제네릭</h1>
<blockquote>
<p>Java에서 데이터 타입을 일반화하는 방법으로, 클래스나 메소드 사용할 데이터 타입을 특정(Specific)타입으로 미리 정하는 게 아니라, 필요에 의해 지정할 수 있도록 일반(Generic)타입으로 지정하는 것이다.<br>즉 제네릭은 하나의 코드를 다양한 타입의 객체에 재사용할 수 있는 객체지향기법이다.</p>
</blockquote>
<p>데이터타입을 내부에서 지정하는 것이 아닌 외부에서 지정함으로써 사용하는 데이터 타입을 <strong>런타임</strong> 시에 결정할 수 있다(실제 데이터 타입이 사용하는 시점에 결정).</p>
<h3 id="제네릭-타입">제네릭 타입</h3>
<table>
<thead>
<tr>
<th>제네릭 변수</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>E</td>
<td>컬렉션의 요소(element)를 나타내는 제네릭 변수. 주로 <code>List</code>, <code>Set</code>, <code>Queue</code> 등의 컬렉션에서 사용된다.</td>
</tr>
<tr>
<td>K</td>
<td>맵(Map)에서 키(key)를 나타내는 제네릭 변수.</td>
</tr>
<tr>
<td>V</td>
<td>맵(Map)에서 값(value)을 나타내는 제네릭 변수.</td>
</tr>
<tr>
<td>T</td>
<td>제네릭 프로그래밍에서 사용되는 일반 타입 변수. 주로 클래스, 메서드의 반환 타입 또는 매개변수로 사용된다. 두 개 이상 일반 타입이 필요할 땐 S, U 등을 사용한다.</td>
</tr>
<tr>
<td>N</td>
<td>제네릭 프로그래밍에서 숫자(number)를 나타내는 제네릭 변수. 주로 숫자형 데이터 타입(<code>int</code>, <code>double</code>, <code>float</code> 등)을 다루는 경우에 사용된다. 일반적으로는 T와 같은 의미로 사용될 수 있다.</td>
</tr>
</tbody></table>
<h3 id="제네릭-클래스">제네릭 클래스</h3>
<blockquote>
<p>하나 이상의 타입 매개변수를 사용하여 클래스를 정의한 클래스.</p>
</blockquote>
<pre><code class="language-java">public class GenericArray&lt;T&gt; {
    private final static int DEFULAT_CAPACITY = 5;
    private T[] elements;
    private int size = 0;

    public GenericArray() {
        this(DEFULAT_CAPACITY);
    }

    public GenericArray(int capacity) {
        this.elements = (T[])(new Object[capacity]);
    } 

    public void add(T element) {
        if (size == elements.length) {
            enhanceCapacity();
        }
        elements[size++] = element;
    }

    public T get(int index) {
        if (index &lt; 0 || index &gt;= elements.length) {
            throw new IndexOutOfBoundsException(&quot;범위 초과&quot;);
        }
        return elements[index];
    }

    public int getSize() {
        return this.size;
    }

    private void enhanceCapacity() {
        int newCapacity = size + 1;
        elements = Arrays.copyOf(elements, newCapacity);
    }
}

public class Main {
    public static void main(String[] args) {
        GenericArray&lt;String&gt; ga = new GenericArray&lt;&gt;();
        ga.add(&quot;Hello&quot;);
    }
}</code></pre>
<p>제네릭 클래스의 인스턴스가 생성될 때 <code>generic</code> 타입의 <code>specific</code> 타입을 받아 그 데이터 타입을 클래스의 변수로 사용한다. 이렇게 구현함으로써 클래스의 멤버 변수, 메서드, 생성자 등에서 사용될 타입을 동적으로 지정할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/soobin_n_n_i/post/cb557c0d-8451-4f21-8891-5e64ce02fe9d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/soobin_n_n_i/post/ef640590-9f68-4290-882f-f61e8584d844/image.png" alt=""></p>
<ul>
<li><p>타입(매개)변수에 타입을 지정하는 것을 제네릭 타입 호출이라고 한다.</p>
</li>
<li><p>선언된 제네릭 클래스를 생성할 때 타입 매개변수는 구체적인 타입으로 대체한다.</p>
<ul>
<li>&#39;String&#39;과 같이 지정된 타입(구체화된 타입)을 &#39;매개변수화된 타입&#39;이라고 한다.</li>
<li>지정할 타입은 참조 타입만 가능하다.</li>
<li>실제 타입을 명시하면 내부적으로 정의된 타입 변수가 명시된 실제 타입으로 변환되어 처리된다.</li>
<li>제네릭 클래스의 생성자 부분에 있는 적용할 타입은 컴파일러가 문맥에서 타입을 추론할 수 있는 경우 생략할 수 있는데, 적용할 타입이 생략된〈〉를 다이아몬드 연산자라고 한다(Java SE 7부터 생략 가능).</li>
</ul>
</li>
</ul>
<h3 id="제네릭-메소드">제네릭 메소드</h3>
<blockquote>
<p>하나 이상의 타입 매개변수를 사용하여 메소드를 정의한 메소드.</p>
</blockquote>
<pre><code class="language-java">public class Utils {
    public static &lt;T&gt; T getLastElement(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        return array[array.length - 1];
    }
}

public class Main {
    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        String[] strArray = {&quot;One&quot;, &quot;Two&quot;, &quot;Three&quot;};

        Integer lastInt = Utils.getLastElement(intArray);
        System.out.println(&quot;Last Integer: &quot; + lastInt); // Output: Last Integer: 5

        String lastStr = Utils.getLastElement(strArray);
        System.out.println(&quot;Last String: &quot; + lastStr);  // Output: Last String: Three
    }
}</code></pre>
<p>제네릭 메소드 선언부에 지정한 제네릭 타입은 호출 시에 정해지며, 정해진 구체적인 타입으로 리턴타입이나 파라미터의 타입이 정해진다.</p>
<ul>
<li>제네릭 클래스가 아닌 일반 클래스에서도 제네릭 메소드를 정의할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Map 인터페이스]]></title>
            <link>https://velog.io/@soobin_n_n_i/Map-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@soobin_n_n_i/Map-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Sat, 06 Apr 2024 05:50:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>키를 값에 매핑하는 객체로, Map에는 중복 키가 포함될 수 없고 각 키는 최대 하나의 값에 매핑될 수 있다. 따라서 키를 이용하여 값을 검색하거나 저장할 수 있는 구조의 인터페이스다.<br>키의 순서나 값의 순서를 보장하지 않고 키와 값 모두 null이 될 수 있다.</p>
</blockquote>
<h4 id="구현-클래스">구현 클래스</h4>
<ul>
<li><strong>HashMap</strong><br>해시 테이블을 사용하여 요소를 저장하며 순서를 보장하지 않는다. </li>
<li><strong>TreeMap</strong><br>키의 순서를 정렬하여 저장하며, 이진 검색 트리를 사용한다.</li>
<li><strong>LinkedHashMap</strong><br>해시 테이블과 연결 리스트를 사용하여 요소를 저장하며 삽입 순서를 보장한다.</li>
</ul>
<br>

<h4 id="docs">Docs</h4>
<ul>
<li><a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html">Map</a></li>
<li><a href="https://velog.io/@soobin_n_n_i/java.util.Map">정리 link</a></li>
</ul>
<br>

<h4 id="주요-메서드-예시">주요 메서드 예시</h4>
<pre><code class="language-java">Map&lt;String, Integer&gt; studentScores = new HashMap&lt;&gt;();

// 추가
studentScores.put(&quot;Kim&quot;, 95);
studentScores.put(&quot;Lee&quot;, 85);
studentScores.put(&quot;Park&quot;, 90);
studentScores.put(&quot;Choi&quot;, 80);

// 조회 &gt; 95, 85
System.out.println(studentScores.get(&quot;Kim&quot;));
System.out.println(studentScores.get(&quot;Lee&quot;));

// 수정
studentScores.put(&quot;Park&quot;, 100);


/* 포함 &gt; Choi가 삭제되었는가?: null
             Choi가 삭제되었는가?: false
             100점을 맞은 학생이 있는가? true */
studentScores.remove(&quot;Choi&quot;); 
System.out.println(&quot;Choi가 삭제되었는가?: &quot;+studentScores.get(&quot;Choi&quot;));
System.out.println(&quot;Choi가 삭제되었는가?: &quot;+studentScores.containsKey(&quot;Choi&quot;));
System.out.println(&quot;100점을 맞은 학생이 있는가?: &quot;+studentScores.containsValue(100));


/* 출력 &gt; student: Lee
             score: 85
             student: Kim
             score: 95
             student: Park
             score: 100 */
for(Map.Entry&lt;String, Integer&gt; entry: studentScores.entrySet()) {
    System.out.println(&quot;student: &quot; + entry.getKey() + &quot;\nscore: &quot; + entry.getValue());
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>