<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>IT 개발자들의 울타리</title>
    <link>https://jddng.tistory.com/</link>
    <description>서로 도와가며 발전해가는 개발자들의 울타리</description>
    <language>ko</language>
    <pubDate>Tue, 12 May 2026 06:14:05 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jddng</managingEditor>
    <item>
      <title>Java - 람다식</title>
      <link>https://jddng.tistory.com/349</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;람다식 사용법&lt;/li&gt;
&lt;li&gt;함수형 인터페이스&lt;/li&gt;
&lt;li&gt;Variable Capture&lt;/li&gt;
&lt;li&gt;메서드, 생성자 레퍼런스&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;람다식이란?&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;람다식은 JDK 1.8부터 추가된 것으로 메서드를 하나의 '식(expression)'으로 표현한 것이다. 람다식을 사용함으로써 코드가 간결해지고 또한 가독성이 증가되는 여러 이점등이 있다. 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로 람다식을 익명함수(anonymous function)라고도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;람다식의 특징&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;익명&lt;br /&gt;- 보통의 메서드와 달리 이름이 없으므로 익명이라 표현한다.&lt;br /&gt;- 구현해야 할 코드에 대한 걱정거리가 줄어듬&lt;/li&gt;
&lt;li&gt;함수&lt;br /&gt;- 람다는 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 불린다.&lt;br /&gt;- 메서드처럼 파라미터 리스트, 바디, 반환형식, 가능한 예외 리스트를 포함한다.&lt;/li&gt;
&lt;li&gt;전달&lt;br /&gt;- 람다 표현식을 메서드 인수로 전달하거나 변수로 저장 할 수 있다.&lt;/li&gt;
&lt;li&gt;간결성&lt;br /&gt;- 익명 클래스처럼 많은 자질구레한 코드를 구현할 필요가 없다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649588813664&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//람다식을 적용하기 전 코드
Comparator&amp;lt;String&amp;gt; s = new Comparator&amp;lt;String&amp;gt;() {
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
}

//람다식을 적용
Comparator&amp;lt;String&amp;gt; s = (String s1, String s2) -&amp;gt; s1.compareTo(s2));&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;람다식 사용법&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1649589059756&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 메서드
반환타입 메서드이름(매개변수 선언){
	문장들
}
// ex
int max(int a, int b){
	return a&amp;gt; b ? a: b;
}


// 람다식
(매개변수 선언) -&amp;gt; {
	문장들
}
// ex
(int a, int b) -&amp;gt; { return a &amp;gt; b ? a: b; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;람다식 표현 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;반환값이 있는 메서드의 경우, return문 대식 '식(expression)'으로 대신 할 수 있다.&lt;/b&gt; 식의 연산결과가 자동적으로 반환 값이 되며 '문장(statement)'이 아닌 식이므로 끝에 세미콜론(;)을 붙이지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1649589303971&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(int a, int b) -&amp;gt; { return a &amp;gt; b ? a : b; } // return문
(int a, int b) -&amp;gt; a &amp;gt; b ? a : b             // 식(expression)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 람다식에 선언된 &lt;b&gt;매개변수의 타입은 추론이 가능한 경우에 생략이 가능&lt;/b&gt;하다. 대부분의 경우 생략이 가능하며 람다식에 람다식에 반환타입이 없는 이유도 항상 추론이 가능하기 때문이다. 단, 두 매개변수 중 어느 하나의 타입만 생략하는 것은 허용되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1649589426724&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(int a, int b) -&amp;gt; a &amp;gt; b ? a : b
(a, b) -&amp;gt; a &amp;gt; b ? a : b   // 매개변수의 타입 생략&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 선언된 매개변수가 하나뿐인 경우에는 괄호()를 생략할 수 있다. 단, 매개변수의 타입이 있으면 생략할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1649589519770&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a -&amp;gt; a * a      // Ok.
int a -&amp;gt; a * a  // error.
(int a) -&amp;gt; a * a // Ok.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 괄호{} 안의 문장이 하나일 때는 역시 괄호{}를 생략할 수 있다. 이 때 문장의 끝에 세미콜론(;)를 붙이지 않아야 한다&lt;/p&gt;
&lt;pre id=&quot;code_1649589581540&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(String name, int i) -&amp;gt; {
    System.out.println(name + &quot; = &quot; + i);
}

(String name, int i) -&amp;gt;
    System.out.println(name + &quot; = &quot; + i)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 괄호{} 안의 문장이 return문일 경우 괄호{}를 생략할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1649589630419&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(int a, int b) -&amp;gt; { return a &amp;gt; b ? a : b; } // return문 Ok.
(int a, int b) -&amp;gt;   return a &amp;gt; b ? a : b    // return문 error.
(int a, int b) -&amp;gt;   a &amp;gt; b ? a : b           // 식(expression)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;함수형 인터페이스&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;함수형 인터페이스란 오직 하나의 추상 메서드만 가지는 인터페이스를 말한다. 람다식을 처음 접하면 메서드와 동등한 것으로 생각하게 되지만 사실 람다식은 익명 클래스의 객체와 동등하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;람다식으로 정의된 익명 객체의 메서드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 함수형 인터페이스 구현&lt;/p&gt;
&lt;pre id=&quot;code_1649591095661&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface MyFunction{
	public abstract int max(int a, int b);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 위에서 구현한 인터페이스를 익명 클래스의 객체로 생성&lt;/p&gt;
&lt;pre id=&quot;code_1649591253538&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MyFunction f = new MyFunction(){
	public int max(int a, int b){
		return a &amp;gt; b ? a: b;
	}
}

int big = f.max(5, 3);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 익명 객체를 람다식으로 대체&lt;/p&gt;
&lt;pre id=&quot;code_1649591298414&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MyFunction f = (int a, int b) -&amp;gt; a &amp;gt; b ? a : b;
int big = f.max(5, 3);


// 다양한 익명 객체의 메서드 사용 방법
MyFunction add = (int a, int b) -&amp;gt; {return a + b; };
MyFunction add1 = (int a, int b) -&amp;gt; a + b;
MyFunction add2 = Integer::sum;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이처럼 MyFunction 인터페이스를 구현한 익명 객체를 람다식으로 대체 가능한 이유는 람다식도 실제로는 익명 객체이고 MyFunction 인터페이스를 구현한 익명 객체의 메서드 max()와 람다식의 매개변수의 타입과 개수 그리고 반환값이 일치하기 때문에 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하나의 메서드가 선언된 인터페이스를 정의하여 람다식을 다루면 기존의 자바 규칙을 어기지 않으면서 자연스럽게 다룰 수 있다. 그래서 인터페이스를 통해 람다식을 다루기로 결정했고, 람다식을 다루기 위한 인터페이스를 &amp;lsquo;함수형 인터페이스(functional interface)&amp;rsquo;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참고&lt;br /&gt;&lt;/span&gt;@FunctionalInterface&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;람다식과 인터페이스의 메서드가 1:1로 연결될 수 있기 떄문에 &lt;span&gt;함수형 인터페이스는 오직 하나의 추상 메서드만 정의되어 있어야 한다는 제약이 있다.&lt;span&gt; 이러한 제약조건을 잘 지켰는지 컴파일러가 확인하도록 @Functionalinterface를 사용하는 것이 좋다. (static 메서드와 default 메서드의 개수는 제약이 없다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;Variable Capture&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;람다식에서 외부 지역변수를 참조하는 행위를 Lambda Capturing이라고 한다. 람다에서 접근가능한 변수는 아래와 같이 세가지 종류가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;지역 변수&lt;/li&gt;
&lt;li&gt;static 변수&lt;/li&gt;
&lt;li&gt;인스턴스 변수&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;람다식에서 외부 지역변수를 참조할 때 위에서 지역변수만 변경이 불가능하고 나머지 변수들은 읽기 및 쓰기가 가능하다. 람다는 지역 변수가 존재하는 스택에 직접 접근하지 않고, 지역 변수를 자신의 스택에 복사한다. 따라서 각각의 쓰레드마다 고유한 스택을 갖고 있어서 지역 변수가 존재하는 쓰레드가 사라져도 람다는 복사된 값을 참조하면서 에러가 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하지만 멀티 쓰레드 환경에서는 문제가 발생한다. 여러 개의 쓰레드에서 람다식을 사용하면서 람다 캡처링이 계속 발생하는데 이 때 &lt;b&gt;외부 변수 값의 불변성을 보장하지 못하면서 동기화(sync) 문제가 발생&lt;/b&gt;한다. 이러한 문제로 인해 지역변수는 final, Effectively Final 제약조건을 갖게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;인스턴스 변수나 static 변수는 스택 영역이 아닌 힙 영역에 위치하고, 힙 영역은 모든 쓰레드가 공유하는 메모리 영역이기 때문에 값의 쓰기가 발생하여도 별 문제가 없게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bglyi0/btryY9QlPcj/77PKkCL7iEopq9GKmaqKU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bglyi0/btryY9QlPcj/77PKkCL7iEopq9GKmaqKU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bglyi0/btryY9QlPcj/77PKkCL7iEopq9GKmaqKU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbglyi0%2FbtryY9QlPcj%2F77PKkCL7iEopq9GKmaqKU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;310&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;메서드, 생성자 레퍼런스&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;메서드 레퍼런스&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;람다식으로 메서드를 간결하게 표현할 수 있는데, 놀랍게도 람다식을 더욱 간결하게 표현할 수 있는 방법이 있다. 람다식이 하나의 메서드만 호출하는 경우에 &amp;lsquo;&lt;b&gt;메서드 참조(method reference)&lt;/b&gt;&amp;rsquo;라는 방법으로 간략히 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 메서드 참조하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스태틱 메소드 참조 &amp;rarr; 타입::스태틱 메소드&lt;/li&gt;
&lt;li&gt;특정 객체의 인스턴스 메소드 참조 &amp;rarr; 객체 래퍼런스::인스턴스 메소드&lt;/li&gt;
&lt;li&gt;임의 객체의 인스턴스 메소드 참조 &amp;rarr; 타입::인스턴스 메소드&lt;/li&gt;
&lt;li&gt;생성자 참조 &amp;rarr; 타입::new&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649593162654&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BiFunction&amp;lt;String, String, Boolean&amp;gt; f = (s1, s2) -&amp;gt; s1.equals(s2);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위 람다식을 메서드 참조로 변경하면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1649593212784&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BiFunction&amp;lt;String, String, Boolean&amp;gt; f = String::equals;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;s1과 s2를 생략하고나면 equals만 남는데, 두 개의 String을 받아서 Boolean으로 반환하는 equals라는 이름의 메서드는 다른 클래스에도 존재할 수 있기 때문에 equals 앞에 클래스 이름이 반드시 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;static 메서드 참조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;메서드 참조는 static 메서드를 직접적으로 가리킬 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1649593356488&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;클래스이름::메서드이름
(매개변수) -&amp;gt; Class.staticMethod(매개변수)

String::valueOf
str -&amp;gt; String.valueOf(str)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인스턴스 메서드 참조&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1649593397702&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;클래스이름::메서드이름
(obj, 매개변수) -&amp;gt; obj.instanceMethod(매개변수)

String::length
(value) -&amp;gt; value.length();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특정 객체 인스턴스 메서드 참조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;특정 인스턴스의 메서드를 참조, 클래스 이름이 아닌 인스턴스 명을 넣는다&lt;/p&gt;
&lt;pre id=&quot;code_1649593489748&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;obj::instanceMethod
(매개변수) -&amp;gt; obj.instanceMethod(매개변수)

object::toString
() -&amp;gt; object.toString()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;생성자의 메서드 참조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;생성자를 호출하는 람다식도 메서드 참조로 변환할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1649593610366&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Supplier&amp;lt;MyClass&amp;gt; s = () -&amp;gt; new MyClass();
Supplier&amp;lt;MyClass&amp;gt; s = MyClass::new;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;매개변수가 필요한 생성자라면, 매개변수의 개수에 따라 알맞은 함수형 인터페이스를 사용하면 된다. 필요에 따라 함수형 인터페이스를 새로 정의해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1649593675514&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Function&amp;lt;Integer, MyClass&amp;gt; f = (i) -&amp;gt; new MyClass(i);
Function&amp;lt;Integer, MyClass&amp;gt; f2 = MyClass::new;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/java study</category>
      <author>jddng</author>
      <guid isPermaLink="true">https://jddng.tistory.com/349</guid>
      <comments>https://jddng.tistory.com/349#entry349comment</comments>
      <pubDate>Sun, 10 Apr 2022 21:28:24 +0900</pubDate>
    </item>
    <item>
      <title>Java - 제네릭(Generics)</title>
      <link>https://jddng.tistory.com/347</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;제네릭(Generics)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제네릭 사용법&lt;/li&gt;
&lt;li&gt;제네릭 - 바운디드 타입&lt;/li&gt;
&lt;li&gt;제네릭 - 와일드카드&lt;/li&gt;
&lt;li&gt;제네릭 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;제네릭(Generics)란?&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;지네릭스는 다양한 타입의 객체를 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다. 객체의 타입을 컴파일 시에 체크하기 때문에 &lt;b&gt;&lt;u&gt;객체의 타입 안정성을 높이고 형 변환의 번거로움을 줄일 수 있다.&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 타입 안정성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형 변환되어 발생할 수 있는 오류를 줄여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 제네릭을 사용하는 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제네릭 타입을 사용함으로써 &lt;b&gt;잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거&lt;/b&gt;할 수 있기 때문&lt;/li&gt;
&lt;li&gt;자바 컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기 위해&lt;b&gt; 제네릭 코드에 대해 강한 타입 체크&lt;/b&gt;를 한다.&lt;/li&gt;
&lt;li&gt;실행 시 타입 에러가 나는 것보다&lt;b&gt; 컴파일 시에 미리 타입을 강하게 체크&lt;/b&gt;하여 에러를 사전에 방지할 수 있다.&lt;/li&gt;
&lt;li&gt;제네릭 코드를 사용하면 타입을 국한하기 때문에 요소를 찾아올 때 타입 변환을 할 필요가 없어 &lt;b&gt;프로그램 성능이 향상&lt;/b&gt;되는 효과를 얻을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;제네릭 사용법&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&amp;nbsp;- 제네릭 클래스 선언&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648989840915&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Box&amp;lt;T&amp;gt; {

    private T type;
    
    public void set(T type) { this.type = type }
    public T get() { return type; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T를 타입 변수(Type variable) 이라 한다.&lt;/li&gt;
&lt;li&gt;타입 변수는 상황에 맞는 의미 있는 문자를 선택하여 사용할 수 있다.&lt;br /&gt;&amp;nbsp; &amp;nbsp;- T : Type, E : Element, K : Key, V : Value&lt;/li&gt;
&lt;li&gt;과거에는 Object로 다양한 종류의 타입을 다룰 때 사용했지만 타입 체크 시 문제가 발생했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 제네릭 클래스 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648990208513&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Box&amp;lt;String&amp;gt; box = new Box();
// 타입 변수에 String을 전달하였으므로 아래와 같다.
public class Box {

    private String type;
    
    public void set(String type) { this.type = type }
    public String get() { return type; }
}

Box&amp;lt;int&amp;gt; box = new box();
// 타입 변수에 int를 전달하였으므로 아래와 같다.
public class Box {

    private int type;
    
    public void set(int type) { this.type = type }
    public int get() { return type; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 지네릭스의 제한&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 객체에 대해 동일하게 동작해야 하는 static멤버에 타입 변수 T를 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;지네릭 타입의 배열을 생성할 수 없다.(컴파일러가 컴파일 시점에 타입 T 가 무엇인지 알아야 하기 때문)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;제네릭 주요 개념 - 바운디드 타입(Bounded Type)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바운디드 타입은 특정 타입의 서브 타입으로 제한한다.&lt;/li&gt;
&lt;li&gt;클래스나 인터페이스 설계할 때 가장 흔하게 사용할 정도로 많이 볼 수 있는 개념이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648991048033&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Box&amp;lt;T extends S&amp;gt; {

	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입 변수 T는 S의 서브 타입만 허용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;제네릭 주요 개념 - 와일드카드(WildCard)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- Unbounded WildCard&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;? 와 같은 형태로 물음표만 가지고 정의한다.( ex. List&amp;lt;?&amp;gt; )&lt;/li&gt;
&lt;li&gt;내부적으로 Object로 정의되어 있기 때문에 모든 타입의 인자를 받을 수 있다.&lt;/li&gt;
&lt;li&gt;타입 파라미터에 의존하지 않는 메서드만을 사용하거나 Object 메서드에서 제공하는 기능으로&lt;br /&gt;충분한 경우 사용한다.&lt;/li&gt;
&lt;li&gt;Object 클래스에서 제공되는 기능을 이용하여 구현하는 메서드에서 사용&lt;/li&gt;
&lt;li&gt;타입 파라미터에 의존적이지 않는 일반 클래스의 메서드를 사용하는 경우에 사용&lt;br /&gt;ex) List, clear, List, size 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- Upper Bounded Wildcard&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 클래스의 자식 클래스만을 인자로 받을 때 사용한다.&lt;/li&gt;
&lt;li&gt;사용할 수 있는 기능은 특정 클래스에서 정의된 기능만 사용할 수 있다.&lt;br /&gt;ex) List&amp;lt;? extends Skill&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- Lower Bounded Wildcard&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 클래스의 부모 클래스만을 인자로 받을 수 있다.&lt;br /&gt;ex) List&amp;lt;? supper Skill&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;지네릭 메서드&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메서드 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다.&lt;/li&gt;
&lt;li&gt;static 멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에는 사용이 가능하다.&lt;/li&gt;
&lt;li&gt;지네릭 타입의 선언 위치는 반환 타입 바로 앞이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1648991945006&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static &amp;lt;T&amp;gt; void sort(List&amp;lt;T&amp;gt; list, Comparator&amp;lt;? super T&amp;gt; c)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제네릭 클래스에 정의된 타입 매개변수와 제네릭 메서드에 정의된 타입 매개변수는&lt;br /&gt;서로 다른 타입 변수다.(같은 타입 문자 T를 사용해도 같은 타입 변수가 아니다)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1648992037005&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class FruitBox&amp;lt;T&amp;gt;{
	...
	static &amp;lt;T&amp;gt; void sort(List&amp;lt;T&amp;gt; list, Compatrator&amp;lt;? super T&amp;gt; c){
		...
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648992215715&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void printAll(
			ArrayList&amp;lt;? extends Product&amp;gt; list,
			ArrayList&amp;lt;? extends Product&amp;gt; list2){
    
	for(Unit u : list){
		System.out.println(u);
	}
}


// 지네릭 메서드로 변경

public static void printAll(
			ArrayList&amp;lt;? extends Product&amp;gt; list,
			ArrayList&amp;lt;? extends Product&amp;gt; list2){
            
	for(Unit u : list){
		System.out.println(u);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/java study</category>
      <category>generics</category>
      <category>제네릭</category>
      <author>jddng</author>
      <guid isPermaLink="true">https://jddng.tistory.com/347</guid>
      <comments>https://jddng.tistory.com/347#entry347comment</comments>
      <pubDate>Sun, 3 Apr 2022 22:26:15 +0900</pubDate>
    </item>
    <item>
      <title>Java - Input과 Output(I/O)</title>
      <link>https://jddng.tistory.com/346</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Input과&amp;nbsp;Output&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스트림(Stream) / 버퍼(Buffer) / 채널(Channel) 기반의 I/O&lt;/li&gt;
&lt;li&gt;InputStream과 OutputStream&lt;/li&gt;
&lt;li&gt;Byte와 Character 스트림&lt;/li&gt;
&lt;li&gt;표준 스트림(System.in, System.out, System.err)&lt;/li&gt;
&lt;li&gt;파일 읽고 쓰기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;I/O(Input/Output)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;I/O 란?&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;I/O란 Input과 Output의 약자로 입력과 출력, 간단히 입출력이라 한다. I/O를 이용하여 시스템은 컴퓨터 내부 또는 외부 장치와 &lt;b&gt;프로그램간의 데이터를 주고 받는 것을 말한다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5IX5p/btrxu9kjjUz/k4kMf8kknv18CBkBtCYRD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5IX5p/btrxu9kjjUz/k4kMf8kknv18CBkBtCYRD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5IX5p/btrxu9kjjUz/k4kMf8kknv18CBkBtCYRD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5IX5p%2Fbtrxu9kjjUz%2Fk4kMf8kknv18CBkBtCYRD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;387&quot; height=&quot;215&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;스트림(stream)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스트림이란 데이터를 운반하는데 사용되는 연결 통로이다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;자바에서 데이터를 전달하고자 할 때, 두 대상을 연결하고 데이터를 전송할 수 있는 &lt;br /&gt;통로가 필요한데 이것을 &lt;b&gt;스트림(stream)이라&lt;/b&gt; 한다.&lt;/li&gt;
&lt;li&gt;람다와 스트림에서 얘기하는 스트림과 같은 용어를 사용하지만, 다른 개념이다.&lt;/li&gt;
&lt;li&gt;스트림은 단방향통신만 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하나의 스트림으로 입력과 출력을 동시에 처리할 수 없다.&lt;br /&gt;&lt;/b&gt;(입력을 위한 입력 스트림, 출력을 위한 출력 스트림 2개가 필요)&lt;/li&gt;
&lt;li&gt;Queue와 같은 FIFO 구조로 되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;버퍼(Buffer)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 임시 저장하는 공간을 의미&lt;/li&gt;
&lt;li&gt;바이트 배열을 사용하여 일정량의 데이터 크기만큼 모아서 전송&lt;/li&gt;
&lt;li&gt;시스템 콜의 횟수가 줄어들어 성능상 이점이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;채널(Channel)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;채널은 스트림과 달리 양방향으로 입력과 출력이 가능하다.&lt;/li&gt;
&lt;li&gt;비동기적으로 입출력이 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;InputStream과 OutputStream&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바이트 기반 스트림의 최상위 클래스인 추상 클래스&lt;/li&gt;
&lt;li&gt;모든 byteStrean의 조상&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;표준 입출력&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;System.in : 표준 입력 스트림, InputStream&lt;/li&gt;
&lt;li&gt;System.out : 표준 출력 스트림, PrintStream&lt;/li&gt;
&lt;li&gt;System.err : 표준 오류 스트림, PrintStream&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 사이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.notion.so/I-O-af9b3036338c43a8bf9fa6a521cda242&quot;&gt;I/O (notion.so)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648390404429&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;I/O&quot; data-og-description=&quot;WhiteShip Java Study 시즌 1&quot; data-og-host=&quot;www.notion.so&quot; data-og-source-url=&quot;https://www.notion.so/I-O-af9b3036338c43a8bf9fa6a521cda242&quot; data-og-url=&quot;https://www.notion.so/af9b3036338c43a8bf9fa6a521cda242&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/h6Khr/hyNPDP3kUl/Uy2IK1rkcRAAklqj7aNz5k/img.jpg?width=1797&amp;amp;height=604&amp;amp;face=0_0_1797_604,https://scrap.kakaocdn.net/dn/ckyUUq/hyNQ6pv8Ti/37bOtkbWN9Mmc8cXhlEkTk/img.jpg?width=1797&amp;amp;height=604&amp;amp;face=0_0_1797_604&quot;&gt;&lt;a href=&quot;https://www.notion.so/I-O-af9b3036338c43a8bf9fa6a521cda242&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.notion.so/I-O-af9b3036338c43a8bf9fa6a521cda242&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/h6Khr/hyNPDP3kUl/Uy2IK1rkcRAAklqj7aNz5k/img.jpg?width=1797&amp;amp;height=604&amp;amp;face=0_0_1797_604,https://scrap.kakaocdn.net/dn/ckyUUq/hyNQ6pv8Ti/37bOtkbWN9Mmc8cXhlEkTk/img.jpg?width=1797&amp;amp;height=604&amp;amp;face=0_0_1797_604');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;I/O&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;WhiteShip Java Study 시즌 1&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.notion.so&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/java study</category>
      <author>jddng</author>
      <guid isPermaLink="true">https://jddng.tistory.com/346</guid>
      <comments>https://jddng.tistory.com/346#entry346comment</comments>
      <pubDate>Sun, 27 Mar 2022 23:13:29 +0900</pubDate>
    </item>
    <item>
      <title>Querydsl - Spring Data JPA에서 제공하는 페이징 활용</title>
      <link>https://jddng.tistory.com/345</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Spring&amp;nbsp;Data&amp;nbsp;JPA에서&amp;nbsp;제공하는&amp;nbsp;페이징&amp;nbsp;활용&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;QueryDSl에서 페이징 사용&lt;/li&gt;
&lt;li&gt;Count 쿼리 최적화&lt;/li&gt;
&lt;li&gt;Controller 개발&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;QueryDSL에서 페이징 사용&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 커스텀 인터페이스에 메서드 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648299149799&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface MemberRepositoryCustom {
    List&amp;lt;MemberTeamDto&amp;gt; search(MemberSearchCondition condition);
    Page&amp;lt;MemberTeamDto&amp;gt; searchPage(MemberSearchCondition condition, Pageable pageable);	// (1)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : Pageable 페이징에 필요한 정보 전달(offset, limit)&lt;/li&gt;
&lt;/ul&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 커스텀 인터페이스 구현체에서 메서드 구현&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648299239521&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public Page&amp;lt;MemberTeamDto&amp;gt; searchPage(MemberSearchCondition condition, Pageable pageable) {
    List&amp;lt;MemberTeamDto&amp;gt; content = queryFactory	// (1)
            .select(new QMemberTeamDto(
                    member.id,
                    member.username,
                    member.age,
                    team.id,
                    team.name))
            .from(member)
            .leftJoin(member.team, team)
            .where(
                    usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe())
            )
            .offset(pageable.getOffset())   // (2) 페이지 번호
            .limit(pageable.getPageSize())  // (3) 페이지 사이즈
            .fetch();

    Long count = queryFactory		// (4) 
            .select(member.count())
            .from(member)
//                .leftJoin(member.team, team)		(5) 검색조건 최적화
            .where(
                    usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe())
            )
            .fetchOne();

    return new PageImpl&amp;lt;&amp;gt;(content, pageable, count);	// (6) PageImpl 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : 페이징을 사용한 데이터 조회&lt;/li&gt;
&lt;li&gt;(2) : 페이지 offset(0부터 시작)&lt;/li&gt;
&lt;li&gt;(3) : 페이지 limit(페이지 사이즈)&lt;/li&gt;
&lt;li&gt;(4) : count 조회&lt;/li&gt;
&lt;li&gt;(5) : 검색조건 최적화(성능 최적화), count에 필요없는 leftJoin 제거 후 count&lt;/li&gt;
&lt;li&gt;(6) : 페이징과 관련된 정보 반환&lt;/li&gt;
&lt;/ul&gt;

&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 코드 최적화&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648299770527&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public Page&amp;lt;MemberTeamDto&amp;gt; searchPageComplex(MemberSearchCondition condition, Pageable pageable) {
    List&amp;lt;MemberTeamDto&amp;gt; content = getMemberTeamDtos(condition, pageable);

    Long count = getCount(condition);

    return new PageImpl&amp;lt;&amp;gt;(content, pageable, count);
}

private Long getCount(MemberSearchCondition condition) {
    Long count = queryFactory
            .select(member.count())
            .from(member)
//                .leftJoin(member.team, team)
            .where(
                    usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe())
            )
            .fetchOne();
    return count;
}

private List&amp;lt;MemberTeamDto&amp;gt; getMemberTeamDtos(MemberSearchCondition condition, Pageable pageable) {
    List&amp;lt;MemberTeamDto&amp;gt; content = queryFactory
            .select(new QMemberTeamDto(
                    member.id,
                    member.username,
                    member.age,
                    team.id,
                    team.name))
            .from(member)
            .leftJoin(member.team, team)
            .where(
                    usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe())
            )
            .offset(pageable.getOffset())   // 페이지 번호
            .limit(pageable.getPageSize())  // 페이지 사이즈
            .fetch();
    return content;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;참고로 fetchResult를 사용하면 페이징 처리한 데이터와 count를 같이 조회할 수 있다. 하지만 fetchResult를 사용하면 count 쿼리 부분에서 문제가 된다. count에 상관없는 테이블을 조회하는 등의 문제로 성능 이슈가 발생한다. 따라서 현재 Querydsl에서는 fetchResult를 사용하지 않도록 deprecate 된 상태이므로 위에서 작성한 예시 코드처럼 페이징으로 데이터 조회하는 쿼리와 count를 구하는 쿼리를 따로 작성하는 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. 테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648300087151&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;Page 테스트&quot;)
public void searchWithPage2() {
    Team teamA = new Team(&quot;teamA&quot;);
    Team teamB = new Team(&quot;teamB&quot;);

    em.persist(teamA);
    em.persist(teamB);

    Member member1 = new Member(&quot;member1&quot;, 10, teamA);
    Member member2 = new Member(&quot;member2&quot;, 20, teamA);
    Member member3 = new Member(&quot;member3&quot;, 30, teamB);
    Member member4 = new Member(&quot;member4&quot;, 40, teamB);

    em.persist(member1);
    em.persist(member2);
    em.persist(member3);
    em.persist(member4);

    MemberSearchCondition condition = new MemberSearchCondition();
    PageRequest pageRequest = PageRequest.of(0, 3);		// (1)


    Page&amp;lt;MemberTeamDto&amp;gt; results = memberRepository.searchPageComplex(condition, pageRequest);	// (2)

    assertThat(results.getSize()).isEqualTo(3);
    assertThat(results.getContent()).extracting(&quot;username&quot;).containsExactly(&quot;member1&quot;, &quot;member2&quot;, &quot;member3&quot;);

}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : 페이징에 사용할 page, size 데이터를 갖는 PageRequest 생성&lt;/li&gt;
&lt;li&gt;(2) : 페이징 결과 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;Count 쿼리 최적화&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PageableExecutionUtils를 사용하면 Count 쿼리 최적화할 수 있다.&lt;/li&gt;
&lt;li&gt;내부적으로 Count 쿼리가 필요없으면 조회해오지 않는다&lt;/li&gt;
&lt;li&gt;조건 : 페이지 시작이면서 컨텐츠 사이즈가 페이지 사이즈보다 작을 때&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 마지막 페이지 일 때(offset + 컨텐츠 사이즈를 더해서 전체 사이즈를 구함)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648303976038&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public Page&amp;lt;MemberTeamDto&amp;gt; searchPageComplex(MemberSearchCondition condition, Pageable pageable) {
    List&amp;lt;MemberTeamDto&amp;gt; content = getMemberTeamDtos(condition, pageable);

    JPAQuery&amp;lt;Long&amp;gt; countQuery = getCount(condition);

    return PageableExecutionUtils.getPage(content, pageable, () -&amp;gt; countQuery.fetchOne()); // (1)
//        return new PageImpl&amp;lt;&amp;gt;(content, pageable, count);
}

private JPAQuery&amp;lt;Long&amp;gt; getCount(MemberSearchCondition condition) {
    JPAQuery&amp;lt;Long&amp;gt; countQuery = queryFactory
            .select(member.count())
            .from(member)
//                .leftJoin(member.team, team)
            .where(
                    usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe())
            );
    return countQuery;	// (2)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : PageableExecutionUtils가 내부적으로 count 쿼리가 필요없으면 조회해오지 않는다.&lt;/li&gt;
&lt;li&gt;(2) : JPQL 쿼리 리턴&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;

&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;Controller 개발&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 페이징처리를 위한 컨트롤러 구현&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648304161886&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/v2/members&quot;)
public Page&amp;lt;MemberTeamDto&amp;gt; searchMemberV2(MemberSearchCondition condition, Pageable pageable) {
    return memberRepository.searchPageComplex(condition, pageable); // 바인딩 된다 page, size, sort
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. Postman 요청&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;참고로 파라미터 없이 요청하면 기본 설정으로 Pageable에 page와 size가 바인딩된다.(page=0, size=20)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #eeeeee; color: #212121;&quot;&gt;http://localhost:8080/v2/members&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648304486075&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    /* select
        member1.id,
        member1.username,
        member1.age,
        team.id,
        team.name 
    from
        Member member1   
    left join
        member1.team as team */ select
            member0_.member_id as col_0_0_,
            member0_.username as col_1_0_,
            member0_.age as col_2_0_,
            team1_.team_id as col_3_0_,
            team1_.name as col_4_0_ 
        from
            member member0_ 
        left outer join
            team team1_ 
                on member0_.team_id=team1_.team_id fetch first ? rows only

...

    /* select
        count(member1) 
    from
        Member member1 */ select
            count(member0_.member_id) as col_0_0_ 
        from
            member member0_&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #eeeeee; color: #212121;&quot;&gt;http://localhost:8080/v2/members?page=1&amp;amp;size=5&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648304570094&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    /* select
        member1.id,
        member1.username,
        member1.age,
        team.id,
        team.name 
    from
        Member member1   
    left join
        member1.team as team */ select
            member0_.member_id as col_0_0_,
            member0_.username as col_1_0_,
            member0_.age as col_2_0_,
            team1_.team_id as col_3_0_,
            team1_.name as col_4_0_ 
        from
            member member0_ 
        left outer join
            team team1_ 
                on member0_.team_id=team1_.team_id offset ? rows fetch next ? rows only
    
...
    
    
    /* select
        count(member1) 
    from
        Member member1 */ select
            count(member0_.member_id) as col_0_0_ 
        from
            member member0_&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #eeeeee; color: #212121;&quot;&gt;http://localhost:8080/v2/members?page=0&amp;amp;size=200&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648304675107&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    /* select
        member1.id,
        member1.username,
        member1.age,
        team.id,
        team.name 
    from
        Member member1   
    left join
        member1.team as team */ select
            member0_.member_id as col_0_0_,
            member0_.username as col_1_0_,
            member0_.age as col_2_0_,
            team1_.team_id as col_3_0_,
            team1_.name as col_4_0_ 
        from
            member member0_ 
        left outer join
            team team1_ 
                on member0_.team_id=team1_.team_id fetch first ? rows only&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;count 쿼리로 조회해오지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Querydsl</category>
      <category>QueryDSL 페이징</category>
      <author>jddng</author>
      <guid isPermaLink="true">https://jddng.tistory.com/345</guid>
      <comments>https://jddng.tistory.com/345#entry345comment</comments>
      <pubDate>Sat, 26 Mar 2022 22:10:33 +0900</pubDate>
    </item>
    <item>
      <title>Querydsl - Spring Data JPA와 QueryDSL</title>
      <link>https://jddng.tistory.com/344</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Spring&amp;nbsp;Data&amp;nbsp;JPA와&amp;nbsp;QueryDSL&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Spring Data JPA를 사용할 때 QueryDSL을 사용하기 위해선 사용자 정의 리포지토리와 구현체를 만들어 상속받아야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wmNfc/btrxjHJnaQg/c7HHo4248T4av8Dzf9vkXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wmNfc/btrxjHJnaQg/c7HHo4248T4av8Dzf9vkXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wmNfc/btrxjHJnaQg/c7HHo4248T4av8Dzf9vkXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwmNfc%2FbtrxjHJnaQg%2Fc7HHo4248T4av8Dzf9vkXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;335&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;QueryDSL을 사용하기 위한 순서&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;JpaRepository를 상속받는 인터페이스 생성&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Data JPA에서 제공하는 JpaRepository 인터페이스를 상속받는 인터페이스 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648296068339&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface MemberRepository extends JpaRepository&amp;lt;Member, Long&amp;gt; {

}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;사용자 정의 인터페이스 생성&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;QueryDSL을 사용하기 위해 필요한 인터페이스 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648296150251&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface MemberRepositoryCustom {
    List&amp;lt;MemberTeamDto&amp;gt; search(MemberSearchCondition condition);	// (1)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : QueryDSL로 구현할 메서드 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;사용자 정의 인터페이스 구현체 생성&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 정의 인터페이스의 메서드를 오버라이딩하여 QueryDSL 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648296253868&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom{

    private final JPAQueryFactory queryFactory;	// (1)

    @Override
    public List&amp;lt;MemberTeamDto&amp;gt; search(MemberSearchCondition condition) {	// (2)
        return queryFactory
                .select(new QMemberTeamDto(
                        member.id,
                        member.username,
                        member.age,
                        team.id,
                        team.name))
                .from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                )
                .fetch();
    }

    private BooleanExpression usernameEq(String username) {
        return StringUtils.hasText(username) ? member.username.eq(username) : null;
    }

    private BooleanExpression teamNameEq(String teamName) {
        return StringUtils.hasText(teamName) ? team.name.eq(teamName) : null;
    }

    private BooleanExpression ageGoe(Integer ageGoe) {
        return ageGoe != null ? member.age.goe(ageGoe) : null;
    }

    private BooleanExpression ageLoe(Integer ageLoe) {
        return ageLoe != null ? member.age.loe(ageLoe) : null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : QueryDSL을 사용하기위한 팩토리 클래스 주입&lt;/li&gt;
&lt;li&gt;(2) : 인터페이스 메서드를 오버라이드하여 동적 쿼리 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;Spring Data Repository에 사용자 정의 인터페이스 상속&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648296419624&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface MemberRepository extends JpaRepository&amp;lt;Member, Long&amp;gt;, MemberRepositoryCustom {

}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;사용자 정의 리포지토리 동작 테스트&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648296526426&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Autowired
EntityManager em;

@Autowired
MemberRepository memberRepository;

@Test
@DisplayName(&quot;사용자 정의 리포지토리 테스트&quot;)
public void searchTest2() {
    Team teamA = new Team(&quot;teamA&quot;);
    Team teamB = new Team(&quot;teamB&quot;);

    em.persist(teamA);
    em.persist(teamB);

    Member member1 = new Member(&quot;member1&quot;, 10, teamA);
    Member member2 = new Member(&quot;member2&quot;, 20, teamA);
    Member member3 = new Member(&quot;member3&quot;, 30, teamB);
    Member member4 = new Member(&quot;member4&quot;, 40, teamB);

    em.persist(member1);
    em.persist(member2);
    em.persist(member3);
    em.persist(member4);

    MemberSearchCondition condition = new MemberSearchCondition();
    condition.setAgeGoe(35);
    condition.setAgeLoe(40);
    condition.setTeamName(&quot;teamB&quot;);

    List&amp;lt;MemberTeamDto&amp;gt; results = memberRepository.search(condition);	// (1)

    assertThat(results).extracting(&quot;username&quot;).containsExactly(&quot;member4&quot;);

}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;사용자 정의 리포지토리 메서드 사용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Querydsl</category>
      <category>Custom Repository</category>
      <category>사용자 정의 레포지토리</category>
      <author>jddng</author>
      <guid isPermaLink="true">https://jddng.tistory.com/344</guid>
      <comments>https://jddng.tistory.com/344#entry344comment</comments>
      <pubDate>Sat, 26 Mar 2022 21:10:09 +0900</pubDate>
    </item>
    <item>
      <title>Querydsl - where절을 이용한 동적 쿼리와 성능 최적화</title>
      <link>https://jddng.tistory.com/343</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;where절을&amp;nbsp;이용한&amp;nbsp;동적&amp;nbsp;쿼리와&amp;nbsp;성능&amp;nbsp;최적화&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Member와 Team을 동적 쿼리를 이용하여 해당 조건에 맞는 데이터를 가져와 보자&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 성능 최적화를 위해 DB에서 필요한 데이터만 가져올 수 있는 DTO를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1648282678947&quot; class=&quot;kotlin&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class MemberTeamDto {

    private Long memberId;
    private String username;
    private int age;
    private Long teamId;
    private String teamName;

    public MemberTeamDto() {
    }

    @QueryProjection	// (1)
    public MemberTeamDto(Long memberId, String username, int age, Long teamId, String teamName) {
        this.memberId = memberId;
        this.username = username;
        this.age = age;
        this.teamId = teamId;
        this.teamName = teamName;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : Q-Type을 만들기위한 어노테이션&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 동적 쿼리에 사용할 검색 조건 클래스 생성&lt;/p&gt;
&lt;pre id=&quot;code_1648282678948&quot; class=&quot;lasso&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class MemberSearchCondition {

    // 회원명, 팀명, 나이(ageGoe, ageLoe)

    private String username;
    private String teamName;
    private Integer ageGoe;
    private Integer ageLoe;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Repository에 Where절을 이용한 동적 쿼리 메서드 구현&lt;/p&gt;
&lt;pre id=&quot;code_1648283922702&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;MemberTeamDto&amp;gt; searchByWhere(MemberSearchCondition condition) {
    return queryFactory
            .select(new QMemberTeamDto(
                    member.id,
                    member.username,
                    member.age,
                    team.id,
                    team.name))
            .from(member)
            .leftJoin(member.team, team)
            .where(
                    usernameEq(condition.getUsername()),	// (1)
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe())
            )
            .fetch();
}
//  BooleanExpression은 and, or 을 조합해서 새로운 BooleanExpression을 만들 수 있다.
//  또한, 결과 값이 null일 경우 무시하기 때문에 npe를 방지할 수 있다
private BooleanExpression usernameEq(String username) {	// (2)
    return StringUtils.hasText(username) ? member.username.eq(username) : null;
}

private BooleanExpression teamNameEq(String teamName) {	// (3)
    return StringUtils.hasText(teamName) ? team.name.eq(teamName) : null;
}

private BooleanExpression ageGoe(Integer ageGoe) {	// (4)
    return ageGoe != null ? member.age.goe(ageGoe) : null;
}

private BooleanExpression ageLoe(Integer ageLoe) {	// (5)
    return ageLoe != null ? member.age.loe(ageLoe) : null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : where절의 특징으로 콤마(,)를 사용하면 and 조건으로 처리 된다. &lt;b&gt;만약 null이면 해당 조건은 제외 된다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;(2) : username의 값이 존재하면 조건 추가, null or 빈문자(&quot;&quot;)일 경우 null 반환&lt;/li&gt;
&lt;li&gt;(3) : teamName의 값이 존재하면 조건 추가, null or 빈문자(&quot;&quot;)일 경우 null 반환&lt;/li&gt;
&lt;li&gt;(4) : ageGoe의 값이 존재하면 조건 추가, null 일 경우 null 반환&lt;/li&gt;
&lt;li&gt;(5) : ageLoe의 값이 존재하면 조건 추가, null 일 경우 null 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2), (3), (4), (5)을 보면 반환 타입을 BooleanExpression을 사용한 것을 볼 수 있는데 Predicate를 사용하지 않은 이유는 BooleanExpression은 and, or을 조합하여 새로운 BooleanExpression을 만들 때 결과 값이 null일 경우 해당 조건은 제외시켜 NullPointrException을 방지할 수 있기 때문에 BooleanExpression을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 조회 테스트&lt;/p&gt;
&lt;pre id=&quot;code_1648286145231&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void searchTest2() {
    Team teamA = new Team(&quot;teamA&quot;);
    Team teamB = new Team(&quot;teamB&quot;);

    em.persist(teamA);
    em.persist(teamB);

    Member member1 = new Member(&quot;member1&quot;, 10, teamA);
    Member member2 = new Member(&quot;member2&quot;, 20, teamA);
    Member member3 = new Member(&quot;member3&quot;, 30, teamB);
    Member member4 = new Member(&quot;member4&quot;, 40, teamB);

    em.persist(member1);
    em.persist(member2);
    em.persist(member3);
    em.persist(member4);

    MemberSearchCondition condition = new MemberSearchCondition();
    condition.setAgeGoe(35);
    condition.setAgeLoe(40);
    condition.setTeamName(&quot;teamB&quot;);

    List&amp;lt;MemberTeamDto&amp;gt; results = memberQuerydslRepository.searchByWhere(condition);


}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Querydsl 쿼리와 JPQL 쿼리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648286204964&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    /* select
        member1.id,
        member1.username,
        member1.age,
        team.id,
        team.name 
    from
        Member member1   
    left join
        member1.team as team 
    where
        team.name = ?1 
        and member1.age &amp;gt;= ?2 
        and member1.age &amp;lt;= ?3 */ select
            member0_.member_id as col_0_0_,
            member0_.username as col_1_0_,
            member0_.age as col_2_0_,
            team1_.team_id as col_3_0_,
            team1_.name as col_4_0_ 
        from
            member member0_ 
        left outer join
            team team1_ 
                on member0_.team_id=team1_.team_id 
        where
            team1_.name=? 
            and member0_.age&amp;gt;=? 
            and member0_.age&amp;lt;=?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위 쿼리를 보면 MemberSearchCondition의 username값이 null이므로 검색조건에 제외된 것을 볼 수 있다. 이렇듯 null&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이면 검색조건에 제외, null이 아닌 데이터 값이 있을 경우 검색 조건에 추가되는 동적 쿼리를 사용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;조회 API 컨트롤러 예시&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648288045715&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberQuerydslRepository memberQuerydslRepository;
    
    @GetMapping(&quot;/v1/members&quot;)
    public List&amp;lt;MemberTeamDto&amp;gt; searchMemberV1(MemberSearchCondition condition){
        return memberJpaRepository.search(condition);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 예시&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;localhost:8080/v1/members?&lt;b&gt;teamName=team&amp;amp;ageGoe=31&amp;amp;ageLoe=35&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 검색조건을 넘겨주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참고&lt;br /&gt;&lt;/span&gt;BooleanBuilder와 BooleanExpression&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/394&quot;&gt;[Querydsl] 다이나믹 쿼리 사용하기 (tistory.com)&lt;/a&gt;​&lt;/p&gt;</description>
      <category>Spring/Querydsl</category>
      <category>where절을 이용한 동적 쿼리</category>
      <author>jddng</author>
      <guid isPermaLink="true">https://jddng.tistory.com/343</guid>
      <comments>https://jddng.tistory.com/343#entry343comment</comments>
      <pubDate>Sat, 26 Mar 2022 18:19:05 +0900</pubDate>
    </item>
    <item>
      <title>Querydsl - BooleanBuilder를 사용한 동적 쿼리와 성능 최적화</title>
      <link>https://jddng.tistory.com/342</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;BooleanBuilder를 사용한 동적 쿼리와 성능 최적화&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Member와 Team을 동적 쿼리를 이용하여 해당 조건에 맞는 데이터를 가져와 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 성능 최적화를 위해 DB에서 필요한 데이터만 가져올 수 있는 DTO를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1648281696344&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class MemberTeamDto {

    private Long memberId;
    private String username;
    private int age;
    private Long teamId;
    private String teamName;

    public MemberTeamDto() {
    }

    @QueryProjection	// (1)
    public MemberTeamDto(Long memberId, String username, int age, Long teamId, String teamName) {
        this.memberId = memberId;
        this.username = username;
        this.age = age;
        this.teamId = teamId;
        this.teamName = teamName;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : Q-Type을 만들기위한 어노테이션&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 동적 쿼리에 사용할 검색 조건 클래스 생성&lt;/p&gt;
&lt;pre id=&quot;code_1648281780432&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class MemberSearchCondition {

    // 회원명, 팀명, 나이(ageGoe, ageLoe)

    private String username;
    private String teamName;
    private Integer ageGoe;
    private Integer ageLoe;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Repository에 Builder를 이용한 동적 쿼리 메서드 구현&lt;/p&gt;
&lt;pre id=&quot;code_1648281848487&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;MemberTeamDto&amp;gt; searchByBuilder(MemberSearchCondition condition) {

    BooleanBuilder builder = new BooleanBuilder();
    if (StringUtils.hasText(condition.getUsername())) {	// (1)
        builder.and(member.username.eq(condition.getUsername()));
    }
    if (StringUtils.hasText(condition.getTeamName())) {	// (2)
        builder.and(team.name.eq(condition.getTeamName()));
    }
    if (condition.getAgeGoe() != null) {	// (3)
        builder.and(member.age.goe(condition.getAgeGoe()));
    }
    if (condition.getAgeLoe() != null) {	// (4)
        builder.and(member.age.loe(condition.getAgeLoe()));
    }


    return queryFactory
            .select(new QMemberTeamDto(
                    member.id.as(&quot;memberId&quot;),
                    member.username,
                    member.age,
                    team.id.as(&quot;teamID&quot;),
                    team.name.as(&quot;teamName&quot;)))
            .from(member)
            .leftJoin(member.team, team)
            .where(builder)		//(5)
            .fetch();
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : StringUtils.hasText를 이용하여 null과 빈문자(&quot;&quot;) 체크 (null or &quot;&quot; 이면 false)&lt;/li&gt;
&lt;li&gt;(2) : StringUtils.hasText를 이용하여 null과 빈문자(&quot;&quot;) 체크&amp;nbsp;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;(null or &quot;&quot; 이면 false)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;(3) : ageGoe이 null인지 체크&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;(4) : ageLoe이 null 인지 체크&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;(1), (2), (3), (4) : true 일 경우 builder에 and조건 추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;(5) : where문에 builder 적용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 조회 테스트&lt;/p&gt;
&lt;pre id=&quot;code_1648282222630&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void searchTest() {
    Team teamA = new Team(&quot;teamA&quot;);
    Team teamB = new Team(&quot;teamB&quot;);

    em.persist(teamA);
    em.persist(teamB);

    Member member1 = new Member(&quot;member1&quot;, 10, teamA);
    Member member2 = new Member(&quot;member2&quot;, 20, teamA);
    Member member3 = new Member(&quot;member3&quot;, 30, teamB);
    Member member4 = new Member(&quot;member4&quot;, 40, teamB);

    em.persist(member1);
    em.persist(member2);
    em.persist(member3);
    em.persist(member4);

    MemberSearchCondition condition = new MemberSearchCondition();
    condition.setAgeGoe(35);
    condition.setAgeLoe(40);
    condition.setTeamName(&quot;teamB&quot;);

    List&amp;lt;MemberTeamDto&amp;gt; results = memberQuerydslRepository.searchByBuilder(condition);

    assertThat(results).extracting(&quot;username&quot;).containsExactly(&quot;member4&quot;);

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Querydsl 쿼리와 JPQL 쿼리&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648282279825&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    /* select
        member1.id as memberId,
        member1.username,
        member1.age,
        team.id as teamID,
        team.name as teamName 
    from
        Member member1   
    left join
        member1.team as team 
    where
        team.name = ?1 
        and member1.age &amp;gt;= ?2 
        and member1.age &amp;lt;= ?3 */ select
            member0_.member_id as col_0_0_,
            member0_.username as col_1_0_,
            member0_.age as col_2_0_,
            team1_.team_id as col_3_0_,
            team1_.name as col_4_0_ 
        from
            member member0_ 
        left outer join
            team team1_ 
                on member0_.team_id=team1_.team_id 
        where
            team1_.name=? 
            and member0_.age&amp;gt;=? 
            and member0_.age&amp;lt;=?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위 쿼리를 보면 MemberSearchCondition의 username값이 null이므로 검색조건에 제외된 것을 볼 수 있다. 이렇듯 null&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이면 검색조건에 제외, null이 아닌 데이터 값이 있을 경우 검색 조건에 추가되는 동적 쿼리를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;만약 MemberSearchCondition의 모든 필드가 null일 경우 해당 Querydsl의 쿼리와 JPQL 쿼리는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1648282507373&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    /* select
        member1.id as memberId,
        member1.username,
        member1.age,
        team.id as teamID,
        team.name as teamName 
    from
        Member member1   
    left join
        member1.team as team */ select
            member0_.member_id as col_0_0_,
            member0_.username as col_1_0_,
            member0_.age as col_2_0_,
            team1_.team_id as col_3_0_,
            team1_.name as col_4_0_ 
        from
            member member0_ 
        left outer join
            team team1_ 
                on member0_.team_id=team1_.team_id&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위 쿼리를 보면 알수있듯이 MemberSearchCondition의 모든 필드가 null이므로 검색 조건이 모두 제외된 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Querydsl</category>
      <category>BooleanBuilder를 이용한 동적 쿼리</category>
      <author>jddng</author>
      <guid isPermaLink="true">https://jddng.tistory.com/342</guid>
      <comments>https://jddng.tistory.com/342#entry342comment</comments>
      <pubDate>Sat, 26 Mar 2022 17:16:13 +0900</pubDate>
    </item>
    <item>
      <title>Querydsl - Repository에서 Querydsl 사용하기</title>
      <link>https://jddng.tistory.com/341</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Repository에서&amp;nbsp;Querydsl&amp;nbsp;사용하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Querydsl을 사용하기 위해서는 JPAQueryFactory가 필요한데 Qeurydsl을 사용하는 Repository에서 주입받을려면 불편하다. 따라서 Config를 생성하여 JPAQueryFactory를 bean으로 등록하고 사용할 Repository에서 생성자 주입받는 것을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648278294229&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class JpaConfig {

    @Bean
    JPAQueryFactory jpaQueryFactory(EntityManager em) {
        return new JPAQueryFactory(em);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;참고로 동시성 문제는 문제가 없다. 스프링이 주입해주는 EntityManager는 실제 동작 시점에 진짜 EntityManager를 찾아주는 proxy용 EntityManager이다. 실제 사용 시점에서 트랜잭션 단위에 있는 EntityManager를 주입받아 영속성 컨텍스트를 사용하기 때문에 동시성 문제는 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;Querydsl 사용하기&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648279035632&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class MemberQuerydslRepository {

    private final JPAQueryFactory queryFactory;	// (1)

    public List&amp;lt;Member&amp;gt; findAll() {
        return queryFactory
                .selectFrom(member)
                .fetch();
    }

    public List&amp;lt;Member&amp;gt; findByUsername(String username) {
        return queryFactory
                .selectFrom(member)
                .where(member.username.eq(username))
                .fetch();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) : Qeurydsl을 사용하기 위해 필요한 JPAQueryFactory 생성자 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;순수 JPA Repository와 비교&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순수 JPA Repository findAll 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648279146361&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;Member&amp;gt; findAll() {
        return em.createQuery(&quot;select m from Member m&quot;, Member.class)
                .getResultList();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Querydsl Repository findAll 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648279191805&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;Member&amp;gt; findAll() {
    return queryFactory
            .selectFrom(member)
            .fetch();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순수 JPA Repository findByUsername 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648279217632&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;Member&amp;gt; findByUsername(String username) {
        return em.createQuery(&quot;select m from Member m where m.username=:username&quot;, Member.class)
                .setParameter(&quot;username&quot;, username)
                .getResultList();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Querydsl Repository findByUsername 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648279243497&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;Member&amp;gt; findByUsername(String username) {
    return queryFactory
            .selectFrom(member)
            .where(member.username.eq(username))
            .fetch();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Querydsl</category>
      <category>Querydsl 사용</category>
      <author>jddng</author>
      <guid isPermaLink="true">https://jddng.tistory.com/341</guid>
      <comments>https://jddng.tistory.com/341#entry341comment</comments>
      <pubDate>Sat, 26 Mar 2022 16:21:14 +0900</pubDate>
    </item>
    <item>
      <title>Querydsl - 수정, 삭제 벌크 연산</title>
      <link>https://jddng.tistory.com/340</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;수정,&amp;nbsp;삭제&amp;nbsp;벌크&amp;nbsp;연산&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;여러개 수정, 삭제를 하면 데이터 수만큼 쿼리가 나가지만 벌크 연산을 사용하면 쿼리가 1번만 나간다. 따라서 여러개 수정, 삭제를 할 때는 벌크 연산을 사용하면 성능이 최적화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648188233056&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;벌크 연산&quot;)
public void bulkUpdate() {
    long count = queryFactory
            .update(member)
            .set(member.username, &quot;비회원&quot;)
            .where(member.age.lt(28))
            .execute();

    assertThat(count).isEqualTo(2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;count : 업데이트가 수행된 수&lt;/li&gt;
&lt;li&gt;execute() : 벌크 연산 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1648188255304&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    /* update
        Member member1 
    set
        member1.username = ?1 
    where
        member1.age &amp;lt; ?2 */ update
            member 
        set
            username=? 
        where
            age&amp;lt;?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;참고로 해당 쿼리가 실행된 이후에는 해당 쿼리로 DB에는 값이 변경되지만 영속성 컨텍스트에는 값이 변경되지 않는다. 따라서 벌크 연산이 수행된 이후 같은 트랙잭션에서 변경된 값을 DB에서 가져오면 이미 영속성 컨텍스트에 해당 데이터가 이미 있기 때문에 영속성 컨텍스트 안에 변경되지 않은 데이터 값을 가져오는 문제가 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;따라서 벌크 연산 이후 같은 트랜잭션에서 다시 조회할 경우에는 em.flush()와 em.clear()를 해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;em.flush()와 em.clear()를 해줘야 하는 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;벌크 연산 수행 전&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberDto = MemberDto(username=member1, age=10)&lt;br /&gt;memberDto = MemberDto(username=member2, age=20)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberDto = MemberDto(username=member3, age=30)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberDto = MemberDto(username=member4, age=40)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;벌크 연산 수행 후(영속성 컨텍스트)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberDto = MemberDto(username=&lt;span style=&quot;color: #ee2323;&quot;&gt;member1&lt;/span&gt;, age=10)&lt;br /&gt;memberDto = MemberDto(username=&lt;span style=&quot;color: #ee2323;&quot;&gt;member2&lt;/span&gt;, age=20)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberDto = MemberDto(username=member3, age=30)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberDto = MemberDto(username=member4, age=40)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;벌크 연산 수행 후(DB)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberDto = MemberDto(username=&lt;span style=&quot;color: #ee2323;&quot;&gt;비회원&lt;/span&gt;, age=10)&lt;br /&gt;memberDto = MemberDto(username=&lt;span style=&quot;color: #ee2323;&quot;&gt;비회원&lt;/span&gt;, age=20)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberDto = MemberDto(username=member3, age=30)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberDto = MemberDto(username=member4, age=40)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Member PK 1,2 조회&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memberDto = MemberDto(username=&lt;span style=&quot;color: #ee2323;&quot;&gt;member1&lt;/span&gt;, age=10)&amp;nbsp;&lt;br /&gt;memberDto = MemberDto(username=&lt;span style=&quot;color: #ee2323;&quot;&gt;member2&lt;/span&gt;, age=20)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; DB에 가져오기 이전에 이미 영속성 컨텍스트에 해당 member가 있기 때문에 업데이트 된&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;member 값이 아닌 영속성 컨텍스트에 있는 업데이트가 실행 안된 값을 가져오게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Querydsl</category>
      <category>벌크 연산</category>
      <author>jddng</author>
      <guid isPermaLink="true">https://jddng.tistory.com/340</guid>
      <comments>https://jddng.tistory.com/340#entry340comment</comments>
      <pubDate>Fri, 25 Mar 2022 15:25:38 +0900</pubDate>
    </item>
    <item>
      <title>Querydsl - where절을 이용한 동적 쿼리</title>
      <link>https://jddng.tistory.com/339</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;where절을&amp;nbsp;이용한&amp;nbsp;동적&amp;nbsp;쿼리&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;where절을 이용하여 동적 쿼리를 처리할 수 있다. where 조건에서 null 값이 들어오면 값은 무시되고 쿼리가 생성된다. 이러한 특성 때문에 다른 쿼리에서도 재활용이 가능한 장점이 있고, 또한 쿼리 자체의 가독성이 높아진다.&amp;nbsp; BooleanBuilder보다 사용하기 편하므로 이 방법을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648186073835&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void dynamicQuery_WhereParam() {
    String usernameParam = &quot;member1&quot;;
    Integer ageParam = 10;

    List&amp;lt;Member&amp;gt; result = searchMember2(usernameParam, ageParam);
    assertThat(result.size()).isEqualTo(1);
}

private List&amp;lt;Member&amp;gt; searchMember2(String usernameCond, Integer ageCond) {
    return queryFactory
            .selectFrom(member)
            .where(usernameEq(usernameCond), ageEq(ageCond))
            .fetch();
}

private Predicate usernameEq(String usernameCond) {
    return usernameCond != null ? member.username.eq(usernameCond) : null;
}

private Predicate ageEq(Integer ageCond) {
    return ageCond != null ? member.age.eq(ageCond) : null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648186121701&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    /* select
        member1 
    from
        Member member1 
    where
        member1.username = ?1 
        and member1.age = ?2 */ select
            member0_.member_id as member_id1_1_,
            member0_.age as age2_1_,
            member0_.team_id as team_id4_1_,
            member0_.username as username3_1_ 
        from
            member member0_ 
        where
            member0_.username=? 
            and member0_.age=?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위에서 코드를 보면 usernameEq과 ageEq 메서드를 이용하였다. 이 메서드들은 충분히 다른 쿼리에서도 충분히 쓸 수 있기 때문에 재사용성이 높아진다. 또한 생성한 메서드를 이용한 메서드를 만들 수 있다. 다음 예시를 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648186345934&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private BooleanExpression allEq(String usernameCond, Integer ageCond) {
    return usernameEq(usernameCond).and(ageEq(ageCond));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이처럼 usernameEq 메서드와 ageEq 메서드를 다음과 같이 재사용하여 하나의 다른 메서드를 생성할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참고&lt;br /&gt;&lt;/span&gt;위에 allEq 메서드는 null 체크를 하지 않았지만&lt;br /&gt;필수적으로 null 체크를 해줘야 한다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648186893698&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@DataJpaTest
public class DynamicQueryTest {

    JPAQueryFactory queryFactory;
    @Autowired
    EntityManager em;

    @BeforeEach
    void init() {
        queryFactory = new JPAQueryFactory(em);

        em.persist(new Member(&quot;userA&quot;, 10, &quot;ROLE_MASTER&quot;));
        em.persist(new Member(&quot;userB&quot;, 20, &quot;ROLE_ADMIN&quot;));
        em.persist(new Member(&quot;userC&quot;, 30, &quot;ROLE_USER&quot;));
    }

    @Test
    void dynamicQuery() {

//        Integer age = 10;
//        String role = &quot;ROLE_MASTER&quot;;
        Integer age = null;
        String role = null;

        List&amp;lt;Member&amp;gt; result = queryFactory
                .selectFrom(member)
                .where(ageAndRoleEq(age, role))
                .fetch();

        System.out.println(&quot;result = &quot; + result);

    }

    private BooleanBuilder ageAndRoleEq(Integer age, String role) {
        return ageEq(age).and(roleEq(role));
    }

    private BooleanBuilder ageEq(Integer age) {
        if (age == null) {
            return new BooleanBuilder();
        } else {
            return new BooleanBuilder(member.age.eq(age));
        }
    }

    private BooleanBuilder roleEq(String roleName) {
        if (roleName == null) {
            return new BooleanBuilder();
        }
        return new BooleanBuilder(member.roleName.eq(roleName));
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자바 8을 이용하면 코드를 줄여줄 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1648186920944&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private BooleanBuilder ageEq(Integer age) {
    return nullSafeBuilder(() -&amp;gt; member.age.eq(age));
}

private BooleanBuilder roleEq(String roleName) {
    return nullSafeBuilder(() -&amp;gt; member.roleName.eq(roleName));
}

public static BooleanBuilder nullSafeBuilder(Supplier&amp;lt;BooleanExpression&amp;gt; f) {
    try {
        return new BooleanBuilder(f.get());
    } catch (IllegalArgumentException e) {
        return new BooleanBuilder();
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring/Querydsl</category>
      <category>where절을 이용한 동적쿼리</category>
      <author>jddng</author>
      <guid isPermaLink="true">https://jddng.tistory.com/339</guid>
      <comments>https://jddng.tistory.com/339#entry339comment</comments>
      <pubDate>Fri, 25 Mar 2022 14:36:45 +0900</pubDate>
    </item>
  </channel>
</rss>