스프링(Spring), 스프링부트(SpringBoot)/스프링부트(SpringBoot) 기초

스프링 부트 핵심 가이드(장정우 지음) - Spring Data JPA 활용 3(QueryDSL 적용하기2)

개발학생 2025. 8. 28. 18:17
반응형

*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성  

 

[Java] 차근차근 Java 설치하기 (JDK17, Window 11)

자바 개발 도구 설치 방법에 대해서 알아보겠습니다. Java17은 LTS(Long Term Support : 장기 지원) 릴리즈로 1년 후까지 기술 지원 및 버그를 개선한 서비스를 제공받을 수 있습니다. 업데이트 버전을 꾸

yungenie.tistory.com

 

[Windows] Spring Tool Suite 4(STS 4) 다운로드 및 설치

STS란?Spring Tool Suite(STS)는 스프링 프로젝트를 생성하고, 개발할 수 있게 해주는 도구입니다. STS 설치 과정에 대해 설명드리겠습니다. 설치 파일 다운로드STS 공식 사이트에서 설치 파일을 다운로

priming.tistory.com

 

MySQL :: Download MySQL Community Server

Select Version: 9.3.0 Innovation 8.4.5 LTS 8.0.42 Select Operating System: Select Operating System… Microsoft Windows Ubuntu Linux Debian Linux SUSE Linux Enterprise Server Red Hat Enterprise Linux / Oracle Linux Fedora Linux - Generic Oracle Solaris mac

dev.mysql.com

  • Gradle

**STS에서 Gradle 프로젝트 생성한 과정 

*** 함께 보면 좋은 글

2025.07.10 - [스프링(Spring), 스프링부트(SpringBoot)/스프링부트(SpringBoot) 기초] - 스프링 부트 핵심 가이드(장정우 지음) - 스프링 부트 개요

 

스프링 부트 핵심 가이드(장정우 지음) - 스프링 부트 개요

1. 스프링 프레임워크자바(Java) 기반 애플리케이션 프레임워크로, 엔터프라이즈급(기업 환경 대상 개발) 애플리케이션을 위한 다양한 기능 제공-> 오픈소스 경량급 애플리케이션 프레임워크로

keep-programming-study.tistory.com

2025.07.11 - [스프링(Spring), 스프링부트(SpringBoot)/스프링부트(SpringBoot) 기초] - 스프링 부트 핵심 가이드(장정우 지음) - 개발에 앞서 알면 좋은 기초 지식

 

스프링 부트 핵심 가이드(장정우 지음) - 개발에 앞서 알면 좋은 기초 지식

1. 서버 간 통신마이크로서비스 아키텍처에서 한 서버가 다른 서버에 통신을 요청하는 것을 의미-> 한 대는 서버/다른 한 대는 클라이언트가 됨 가장 많이 사용되는 방식은 HTTP/HTTPS 방식(TCP/IP, SOA

keep-programming-study.tistory.com

2025.08.15 - [스프링(Spring), 스프링부트(SpringBoot)/스프링부트(SpringBoot) 기초] - 스프링 부트 핵심 가이드(장정우 지음) - Spring Data JPA 활용 1(JPQL, 쿼리 메서드 살펴보기, 정렬과 페이징 처리)

 

스프링 부트 핵심 가이드(장정우 지음) - Spring Data JPA 활용 1(JPQL, 쿼리 메서드 살펴보기, 정렬과

*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성 Windows11(윈도우 11) 환경자바 JDK 17 버전 설치 https://yungenie.tistory.com/11 [Java] 차근차근 Java 설치하기 (JDK17, Window 11)자바 개발 도구 설치 방법

keep-programming-study.tistory.com

2025.08.19 - [스프링(Spring), 스프링부트(SpringBoot)/스프링부트(SpringBoot) 기초] - 스프링 부트 핵심 가이드(장정우 지음) - Spring Data JPA 활용 2(@Query 어노테이션 사용하기, QueryDSL 적용하기)

 

스프링 부트 핵심 가이드(장정우 지음) - Spring Data JPA 활용 2(@Query 어노테이션 사용하기, QueryDSL 적

*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성 Windows11(윈도우 11) 환경자바 JDK 17 버전 설치 https://yungenie.tistory.com/11 [Java] 차근차근 Java 설치하기 (JDK17, Window 11)자바 개발 도구 설치 방법

keep-programming-study.tistory.com

 

1. src/test/java/com.example.demo/ProductRepositoryTest.java 우클릭 후 Run As -> JUnit Test했을 때 문제 해결하기 

SLF4J(W): Class path contains multiple SLF4J providers.

SLF4J(W): Found provider [org.slf4j.simple.SimpleServiceProvider@815b41f]

SLF4J(W): Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider@5542c4ed]

SLF4J(W): See https://www.slf4j.org/codes.html#multiple_bindings for an explanation.

SLF4J(I): Actual provider is of type [org.slf4j.simple.SimpleServiceProvider@815b41f]

[main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.example.demo.ProductRepositoryTest]: ProductRepositoryTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.

[main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration com.example.demo.StudyApplication for test class com.example.demo.ProductRepositoryTest

1) build.gradle코드에서 slf4j-simple 제거하고 logback만 남기기 & ProductRepositoryTest.java에서 @DataJpaTest를 @SpringBootTest로 변경

  • 저장 후 반드시 우클릭 -> Gradle -> Refresh Gradle Project!! 
dependencies {
    ... 생략 ...
    implementation 'org.slf4j:slf4j-api:2.0.12'
    // 제거: implementation 'org.slf4j:slf4j-simple:2.0.12'
}
  • 다른 오류 발생...
    -> Hibernate가 데이터베이스 Dialect를 결정할 없어서 entityManagerFactory Bean을 생성하지 못한

2025-08-28 16:28:05.170 [main] ERROR o.s.boot.SpringApplication - Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)

at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)

at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:970)

at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627)

at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)

at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)

at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)

at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:144)

at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)

at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)

at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1461)

2) 1)에서 발생한 문제 해결을 위해 ProductRepositoryTest.java의 @SpringBootTest 아래에 설정 추가

// 테스트 클래스에 명시적으로 src/test/resources의 application.properties 설정 추가
@TestPropertySource(locations = "classpath:application.properties")
  • 또 다른 문제가 발생...
    -> QProduct 클래스가 이미 존재하는데, 다시 생성하려고 해서 충돌이 난 것이라고 함 

ests: com.example.demo.ProductRepositoryTest

> Task :initQuerydslSourcesDir

> Task :compileQuerydsl

 

> Task :compileJava

Attempt to recreate a file for type com.example.demo.jpa.data.entity.QProduct

error: Attempt to recreate a file for type com.example.demo.jpa.data.entity.QProduct

1 error

 

> Task :compileJava FAILED

 

[Incubating] Problems report is available at: file:///C:/sts/workspace/study/build/reports/problems/problems-report.html

 

FAILURE: Build failed with an exception.

 

* What went wrong:

Execution failed for task ':compileJava'.

> Compilation failed; see the compiler output below.

error: Attempt to recreate a file for type com.example.demo.jpa.data.entity.QProduct

1 error

3) 2)에서 발생한 문제 해결을 위해 build/generated/querydsl의 QProduct.java를 삭제한 후, cmd에서 프로젝트 루트로 이동해서 gradlew clean compileQuerydsl 입력 & @TestPropertySource 삭제 후 @SpringBootTest에 properties 추가
=> 드디어 JUnit Test성공!! application.properties를 src/test/resources에 추가하면 인식을 못하는구나.. 

// 명시적으로 src/test/resources의 application.properties 설정 추가
@SpringBootTest(properties = {
		  "spring.datasource.url=jdbc:h2:mem:testdb",
		  "spring.datasource.driver-class-name=org.h2.Driver",
		  "spring.datasource.username=sa",
		  "spring.datasource.password=",
		  "spring.jpa.hibernate.ddl-auto=create",
		  "spring.jpa.database-platform=org.hibernate.dialect.H2Dialect"
		})

 2. src/test/java/com.example.demo/ProductRepositoryTest.java에 테스트 메서드 추가하고 JUnit Test 또 시도

1) src/test/java/com.example.demo/ProductRepositoryTest.java에 테스트 메서드 2개 추가

// src/test/java/com.example.demo/ProductRepositoryTest.java
package com.example.demo;

//Java의 List 클래스 import
import java.util.List;

//JUnit 5의 테스트 어노테이션
import org.junit.jupiter.api.Test;

//Spring Boot의 JPA 테스트용 어노테이션
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;

import com.querydsl.core.Tuple;
//QueryDSL의 JPAQuery 클래스 import
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
//테스트 대상 엔티티 및 리포지토리 import
import com.example.demo.jpa.data.entity.Product;
import com.example.demo.jpa.data.entity.QProduct;

//JPA의 EntityManager 관련 클래스 import
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;


// 명시적으로 src/test/resources의 application.properties 설정 추가
@SpringBootTest(properties = {
		  "spring.datasource.url=jdbc:h2:mem:testdb",
		  "spring.datasource.driver-class-name=org.h2.Driver",
		  "spring.datasource.username=sa",
		  "spring.datasource.password=",
		  "spring.jpa.hibernate.ddl-auto=create",
		  "spring.jpa.database-platform=org.hibernate.dialect.H2Dialect"
		})
public class ProductRepositoryTest {

	... 생략 ...
    
	// QueryDSL을 활용한 테스트 메서드2
	@Test
	void queryDslTest2() {
		// EntityManager를 기반으로 JPAQueryFactory 생성 (QueryDSL 쿼리 작성에 사용)
		JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
		
		// QProduct는 Product 엔티티에 대한 QueryDSL 메타 모델 클래스
		QProduct qProduct = QProduct.product;

		// QueryDSL을 사용하여 name이 "펜"인 Product를 가격 오름차순으로 조회
		List<Product> productList = jpaQueryFactory
		    .selectFrom(qProduct)               // 조회 대상 테이블 지정
		    .where(qProduct.name.eq("펜"))      // 조건: name이 "펜"인 경우
		    .orderBy(qProduct.price.asc())      // 가격 오름차순 정렬
		    .fetch();                           // 결과 리스트로 반환
		
		// 조회된 결과 출력
		for (Product product : productList) {
	        System.out.println("--------------");
	        System.out.println();
	        System.out.println("Product Number: " + product.getNumber()); // 제품 번호
	        System.out.println("Product Name: " + product.getName());     // 제품 이름
	        System.out.println("Product Price: " + product.getPrice());   // 제품 가격
	        System.out.println("Product Stock: " + product.getStock());   // 제품 재고
	        System.out.println();
	        System.out.println("--------------");
		}
	}
	
	// QueryDSL을 활용한 테스트 메서드3
	@Test
	void queryDslTest3() {
		// EntityManager를 기반으로 JPAQueryFactory 생성 (QueryDSL 쿼리 작성에 사용)
		JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
		
		// QProduct는 Product 엔티티에 대한 QueryDSL 메타 모델 클래스
		QProduct qProduct = QProduct.product;
		
	    // name 필드만 조회하는 쿼리: name이 "펜"인 제품들을 가격 오름차순으로 정렬
	    List<String> productList = jpaQueryFactory
	        .select(qProduct.name)                 // name 필드만 선택
	        .from(qProduct)                        // 조회 대상 테이블 지정
	        .where(qProduct.name.eq("펜"))         // 조건: name이 "펜"인 경우
	        .orderBy(qProduct.price.asc())         // 가격 오름차순 정렬
	        .fetch();                              // 결과 리스트로 반환

	    // 조회된 제품 이름만 출력
	    for (String product : productList) {
	        System.out.println("--------------");
	        System.out.println("Product Name: " + product);     // 제품 이름 출력
	        System.out.println("--------------");
	    }

	    // name과 price 두 필드를 함께 조회하는 쿼리: Tuple로 결과 반환
	    List<Tuple> tupleList = jpaQueryFactory
	        .select(qProduct.name, qProduct.price) // name과 price 필드 선택
	        .from(qProduct)                        // 조회 대상 테이블 지정
	        .where(qProduct.name.eq("펜"))         // 조건: name이 "펜"인 경우
	        .orderBy(qProduct.price.asc())         // 가격 오름차순 정렬
	        .fetch();                              // 결과 리스트로 반환

	    // 조회된 제품 이름과 가격을 출력
	    for (Tuple product : tupleList) {
	        System.out.println("--------------");
	        // Tuple에서 각각의 필드를 꺼내서 출력해야 함
	        System.out.println("Product Name: " + product.get(qProduct.name));   // 제품 이름
	        System.out.println("Product Price: " + product.get(qProduct.price)); // 제품 가격
	        System.out.println("--------------");
	    }
	}
}

 2) 우클릭 후 Run As -> JUnit Test 클릭: 3개 메서드 모두 테스트 성공함! 

3. QueryDSL을 실제 비즈니스 로직에서 활용할 수 있게 설정하기

1) src/test/java/com.example.demo.config/QueryDSLConfiguration.java 클래스 생성

// QueryDSL 설정 클래스: Spring이 이 클래스를 설정으로 인식하도록 지정
package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.querydsl.jpa.impl.JPAQueryFactory;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

// Spring 설정 클래스임을 나타내는 어노테이션
@Configuration
public class QueryDSLConfiguration {

    // JPA의 EntityManager를 주입받기 위한 어노테이션
    // EntityManager는 JPA의 핵심 객체로, 영속성 컨텍스트를 관리함
    @PersistenceContext
    EntityManager entityManager;

    // JPAQueryFactory를 빈으로 등록
    // 다른 클래스에서 @Autowired 또는 생성자 주입으로 사용 가능
    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        // EntityManager를 기반으로 JPAQueryFactory 생성
        return new JPAQueryFactory(entityManager);
    }
}

2) 이제 ProductRepositoryTest.java 클래스 내에 테스트 코드를 추가할 때, 다음과 같이 입력하면 됨

  • 매번 JPAQueryFactory를 초기화하지 않고 스프링 컨테이너에서 가져다 쓸 수 있음(중요)
@Autowired
JPAQueryFactory jpaQueryFactory;

@Test
void queryDslTest4() {
	QProduct qProduct = QProduct.product;
    
    List<String> productList = jpaQueryFactory
    	.select(qProduct.name)
        .from(qProduct)
        .where(qProduct.name.eq("펜"))
        .orderBy(qProduct.price.asc())
        .fetch();
        
    for (String product : productList) {
    	System.out.println("--------------");
	    System.out.println("Product Name: " + product);   // 제품 이름
	    System.out.println("--------------");
    }
}

4. QuerydslPredicateExecutor, QuerydslRepositorySupport 활용

  • 활용을 위해,src/test/java/com.example.demo에 QProductRepositoryTest.java 클래스 생성 및 테스트 

1) QuerydslPredicateExecutor 인터페이스

  • JpaRepository와 함께, 리포지토리에서 QueryDSL을 사용할 수 있게 인터페이스 제공
  • 주요 메서드
Optional<T> findOne(Predicate predicate) 조건에 맞는 단일 엔티티를 조회
결과가 없으면 Optional.empty() 반환
Iterable<T> findAll(Predicate predicate) 조건에 맞는 모든 엔티티를 조회
결과가 없으면 빈 Iterable 반환
Iterable<T> findAll(Predicate predicate, Sort sort) 조건에 맞는 엔티티들을 정렬해서 조회
(Spring의 Sort 객체 사용)
Iterable<T> findAll(OrderSpecifier<?>... orders) 렬 조건만으로 전체 엔티티 조회
(QueryDSL의 OrderSpecifier 사용)
Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) 조건 + 정렬을 함께 적용해서 조회
Page<T> findAll(Predicate predicate, Pageable pageable) 조건에 맞는 엔티티들을 페이징 처리해서 조회
long count(Predicate predicate) 조건에 맞는 엔티티 개수를 반환
boolean exists(Predicate predicate) 조건에 맞는 엔티티가 존재하는지 여부를 반환

(1) src/main/java/com.example.demo.jpa.data.repository/ProductRepository.java 코드 수정

package com.example.demo.jpa.data.repository;

// Spring Data JPA와 QueryDSL 관련 클래스 import
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;

import com.example.demo.jpa.data.entity.Product;

// Product 엔티티에 대한 리포지토리 인터페이스 정의
public interface ProductRepository extends JpaRepository<Product, Long>, QuerydslPredicateExecutor<Product> {
    
    // JpaRepository: 기본적인 CRUD 메서드 제공 (save, findById, delete 등)
    // QuerydslPredicateExecutor: QueryDSL을 사용한 동적 조건 검색 기능 제공
    
    // 이 인터페이스를 통해 Product 엔티티에 대한 기본 + 동적 쿼리 모두 사용 가능
}

(2) QProductRepositoryTest.java 클래스에 코드 작성하고 저장 후, 우클릭 -> Run As -> JUnit Test 바로 성공! 

package com.example.demo;

import java.util.Optional;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.example.demo.jpa.data.entity.Product; // Product 엔티티 클래스
import com.example.demo.jpa.data.entity.QProduct; // QueryDSL이 생성한 QProduct 클래스
import com.example.demo.jpa.data.repository.ProductRepository; // Product 엔티티용 리포지토리
import com.querydsl.core.types.Predicate; // QueryDSL의 Predicate 타입

// 테스트 환경에서 사용할 데이터베이스 설정을 명시적으로 지정
@SpringBootTest(properties = {
    "spring.datasource.url=jdbc:h2:mem:testdb", // 인메모리 H2 데이터베이스 사용
    "spring.datasource.driver-class-name=org.h2.Driver",
    "spring.datasource.username=sa",
    "spring.datasource.password=",
    "spring.jpa.hibernate.ddl-auto=create", // 테스트 시작 시 DB 테이블 자동 생성
    "spring.jpa.database-platform=org.hibernate.dialect.H2Dialect"
})
public class QProductRepositoryTest {

    @Autowired
    ProductRepository productRepository; // ProductRepository를 주입받음

    @Test
    public void queryDSLTest1() {
        // QueryDSL을 사용해 동적 조건 생성:
        // 이름에 "펜"이 포함되고, 가격이 1000~2500 사이인 상품을 찾음
        // Predicate는 BooleanExpression의 상위 인터페이스 
    	// -> .and(), .or() 같은 메서드 체이닝이 가능하고, QuerydslPredicateExecutor를 사용하는 리포지토리 메서드에 그대로 넘겨줄 수 있음
        Predicate predicate = QProduct.product.name.containsIgnoreCase("펜")
            .and(QProduct.product.price.between(1000, 2500));

        // 조건에 맞는 상품을 하나 조회 (Optional로 반환됨)
        Optional<Product> foundProduct = productRepository.findOne(predicate);

        // 조회된 상품이 존재하면 정보 출력
        if (foundProduct.isPresent()) {
            Product product = foundProduct.get();
            System.out.println(product.getNumber()); // 상품 번호 출력
            System.out.println(product.getName());   // 상품 이름 출력
            System.out.println(product.getPrice());  // 상품 가격 출력
            System.out.println(product.getStock());  // 상품 재고 출력
        }
    }
}

2) QuerydslRepositorySupport 추상 클래스 사용하기

  • Spring Data JPA에서 QueryDSL을 더 쉽게 사용할 수 있도록 도와주는 추상 클래스
  • 복잡한 동적 쿼리를 작성할 때 JPAQueryFactory 없이도 from(), applyPagination() 같은 유틸 메서드를 활용할 수 있게 해줌
  • 주요 기능
    • from(QEntity)로 시작하는 쿼리 작성 가능
    • applyPagination()으로 페이징 처리 간편하게 적용
    • EntityManager 자동 주입
    • Querydsl 객체를 통한 정렬 처리 가능 (단, Sort는 제한적)

(1) src/main/java/com.example.demo.jpa.data.repository에 
ProductRepositoryCustom 인터페이스, ProductRepositoryCustomImpl 클래스 생성 

package com.example.demo.jpa.data.repository;

import java.util.List;

import com.example.demo.jpa.data.entity.Product;

//Product 엔티티를 조회하기 위한 커스텀 리포지토리 인터페이스
//QueryDSL을 활용한 사용자 정의 쿼리 메서드를 선언하는 인터페이스
public interface ProductRepositoryCustom {
    // 제품 이름(name)을 기준으로 Product 리스트를 조회하는 메서드
    // 실제 구현은 ProductRepositoryCustomImpl 클래스에서 수행됨
	List<Product> findByName(String name);
}
package com.example.demo.jpa.data.repository;

import java.util.List;

import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;

import com.example.demo.jpa.data.entity.Product;
import com.example.demo.jpa.data.entity.QProduct;

// ProductRepositoryCustom 인터페이스를 구현한 클래스
// QuerydslRepositorySupport를 상속받아 QueryDSL 기능을 사용할 수 있음
public class ProductRepositoryCustomImpl extends QuerydslRepositorySupport implements ProductRepositoryCustom {

    // 생성자에서 부모 클래스(QuerydslRepositorySupport)에 엔티티 클래스(Product)를 전달
    public ProductRepositoryCustomImpl() {
        super(Product.class);
    }

    // Product 이름으로 조회하는 커스텀 메서드 구현
    @Override
    public List<Product> findByName(String name) {
        // QProduct는 QueryDSL이 생성한 Product 엔티티의 메타 모델 클래스
        QProduct product = QProduct.product;

        // QueryDSL을 사용하여 name이 일치하는 Product 리스트를 조회
        List<Product> productList = from(product) // 쿼리 시작
                .where(product.name.eq(name))     // 조건: name이 일치하는 경우
                .select(product)                  // 조회할 대상: product 엔티티
                .fetch();                         // 결과를 리스트로 반환

        return productList;
    }
}

(2) src/main/java/com.example.demo.jpa.data.repository의 ProductRepository.java 다시 수정

package com.example.demo.jpa.data.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;

import com.example.demo.jpa.data.entity.Product;

//이 인터페이스는 Product 엔티티에 대한 리포지토리 역할을 수행
@Repository("productRepositorySupport") // Bean 이름을 명시적으로 지정 (선택 사항)
public interface ProductRepository extends 
     JpaRepository<Product, Long>,                // 기본 CRUD 기능 제공
     QuerydslPredicateExecutor<Product>,          // QueryDSL 기반 동적 조건 검색 지원
     ProductRepositoryCustom {                    // 사용자 정의 쿼리 메서드 포함

	 // 이 인터페이스를 통해 다음 기능들을 사용할 수 있음:
	 // - save(), findById(), delete() 등 기본적인 JPA 메서드
	 // - findAll(Predicate predicate) 형태의 QueryDSL 조건 검색
	 // - findByName(String name) 등 커스텀 구현한 메서드 호출 가능
}

(3) QProductRepositoryTest.java 클래스에 메서드 추가하고 저장 후, 우클릭 -> Run As -> JUnit Test 바로 성공!

// JUnit 테스트 메서드임을 나타내는 어노테이션
@Test
void findByNameTest() {
    
    // "펜"이라는 이름을 가진 Product들을 조회
    List<Product> productList = productRepository.findByName("펜");
    
    // 조회된 Product 리스트를 반복하면서 각 필드 값을 출력
    for(Product product : productList) {
        System.out.println(product.getNumber()); // 제품 번호 출력
        System.out.println(product.getName());   // 제품 이름 출력
        System.out.println(product.getPrice());  // 제품 가격 출력
        System.out.println(product.getStock());  // 제품 재고 수량 출력
    }
}

반응형