Framework/프로젝트로 스프링 이해하기

[이커머스 프로젝트] QueryDSL를 통해 상품 검색하기

simDev1234 2022. 12. 13. 21:28

|  개요

1. Build.Gradle에 QueryDSL과 Jakarta를 함께 주입한다.

2. Config 클래스를 만들어 EntityManager를 생성하고 이를 JpaQueryFactory에 넣어준다.

3. ProductRepositoryCustom과 ProductRepositoryImpl을 만들고, ProductRepostory에서 Custom 인터페이스를 상속한다.

 

|  상세 

 

1. Build.Gradle에 QueryDSL를 추가한다.

* Jakarta를 추가하는 이유는, NoClassDefFoundError를 방지하기 위해서이다.

* NoClassDefFoundError는 컴파일 시에는 있었는데 실행 시에는 클래스를 찾을 수 없을 때 발생한다고 한다..

  QueryDSL를 사용할 때 자주 나타나는 오류인 것 같다.

// queryDSL
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jpa"
// Jakarta
// java.lang.NoClassDefFoundError(javax.annotation.Entity) 발생 대응
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
// java.lang.NoClassDefFoundError(javax.annotation.Generated) 발생 대응
annotationProcessor "jakarta.annotation:jakarta.annotation-api"

 

2. Config 클래스

- EntityManager를 생성하고, 이를 JPAQueryFactory에 넣어준다.

@Configuration
public class QueryDslConfig {

  @PersistenceContext
  private EntityManager entityManager;

  @Bean
  public JPAQueryFactory jpaQueryFactory(){
    return new JPAQueryFactory(entityManager);
  }

}

 

3. 서비스 클래스

- 상품명을 통해 조회하는 메소드를 작성한다.

@Service
class XXXService{

	/**
     * 상품명을 통해 유사 상품 목록 조회
     */
    public List<Product> searchByName(String name) {
        return productRepository.searchByName(name);
    }
}

 

4. JPQL를 적용할 커스텀 Repository 만들기

   (1) Custom interface와 (2) Impl 구현체 클래스

  * 이때, 이름은 ProductRepository에 Custom, Impl을 붙여 만들어 주어야 JPA가 인식할 수 있다.

출처:https://elsboo.tistory.com/35

(1) ProductRepositoryCustom

public interface ProductRepositoryCustom {

    List<Product> searchByName(String name);

}

 

(2) ProductRepositoryImpl

1) JPAQueryFactory를 주입한다.

2) QProduct를 생성한다.

   (또는 이것을 private final로 뺄 수도 있다. --> 양식은 QMember, QProduct와 같이 이름을 맞추어야 한다.)

3) 1)의 JpaQueryFactory를 통해 커스텀 쿼리를 작성한다.

@Repository
@RequiredArgsConstructor
public class ProductRepositoryImpl implements ProductRepositoryCustom{

    private final JPAQueryFactory queryFactory;

    @Override
    public List<Product> searchByName(String name){

        String search = "%" + name + "%";

        QProduct product = QProduct.product;

        return queryFactory.selectFrom(product)
                .where(product.name.like(search))
                .fetch();

    }

}

 

(3) ProductRepository에서 (1)에서 만든 interfacte를 extends한다.

* 참고로에 아래에 적은 @EntityGraph는 지연로딩 때문에 Proxy로 가져올 데이터들을 전부 로딩하기 위해 작업했던 것이었다. 따로 정리할 시간이 없을 것 같아 일단 노트해둔다..

public interface ProductRepository extends JpaRepository<Product, Long>, ProductRepositoryCustom {

    @EntityGraph(attributePaths = {"productItems"}, type = EntityGraph.EntityGraphType.LOAD)
    Optional<Product> findBySellerIdAndId(Long sellerId, Long id);

    @EntityGraph(attributePaths = {"productItems"}, type = EntityGraph.EntityGraphType.LOAD)
    Optional<Product> findWithProductItemsById(Long id);

}

 

5. 컨트롤러를 작성하고 테스트를 해본다.

@RestController
@RequestMapping("/search/product")
@RequiredArgsConstructor
public class SearchController {

    private final ProductSearchService productSearchService;

    @GetMapping
    public ResponseEntity<List<ProductDto>> searchByName(@RequestParam String name) {

        return ResponseEntity.ok(productSearchService.searchByName(name).stream()
            .map(ProductDto::withoutItemsFrom).collect(Collectors.toList()));

    }

}

 

 

[참고]

부트캠프 수업 내용 정리

https://elsboo.tistory.com/35

@EntityGraph https://itmoon.tistory.com/77