[Spring Data] JPA - Querydsl

Seonghun Kim·2022년 9월 17일
0

Spring!

목록 보기
8/8
post-thumbnail

📌 Querydsl

소스코드로 query를 생성하는 방식

  • 고정된 SQL문이 아닌 조건에 맞게 동적으로 쿼리를 생성
  • 유사한 query 재사용이 가능하며, 가독성을 향상시킬 수 있음
  • 문자열이 아닌 소스코드로 작성하기 때문에 컴파일 시점에서 바로 오류를 발견할 수 있음

✔ Setting

1. pom.xml에 알맞는 버전의 dependency 추가

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>5.0.0</version>
</dependency>

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>5.0.0</version>
</dependency>

...
...

<plugin>
    <groupId>com.mysema.maven</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.1.3</version>
    <executions>
        <execution>
            <goals>
                <goal>process</goal>
            </goals>
            <configuration>
                <outputDirectory>target/generated-sources/java</outputDirectory>
                <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
        </execution>
    </executions>
</plugin>

2. maven dependency 추가

  • reload all maven projects 클릭

3. Q class 생성

  • maven compile

  • Q class 확인

  • Q Domain을 import할 수 있도록 설정 (File > Project Structure > Modules)
    • generated-sources 폴더를 Excluded에서 Sources로 변경 후 Apply

✔ Test

  • repository에 따로 구현없이 사용 가능
@Autowired
ItemRepository itemRepository;

@PersistenceContext
EntityManager em;

JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QItem qItem = QItem.item;
JPAQuery<Item> query = queryFactory.selectFrom(qItem)
	.where(qItem.itemSellStatus.eq(ItemSellStatus.SELL))
    .where(qItem.itemDetail.like("%테스트 상품 상세 설명%"))
    .orderBy(qItem.price.desc());

List<Item> itemList = query.fetch();
  • 영속성 컨텍스트를 사용하기 위해 @PersistenceContextEntityManager 빈을 주입
  • JPAQueryFactory를 이용하여 query를 동적으로 생성
  • Querydsl을 통해 쿼리를 생성하기 위해 Q class 객체 이용
  • fetch 메소드를 이용하여 query 결과를 리스트로 반환

✔ Querydsl 조회 결과 메서드

methoddescription
List<T> fetch()조회 대상들을 리스트로 반환
QueryResults<T> fetchResults()조회 대상 리스트 및 개수를 포함한 결과 QueryResults 반환
T fetchOne()조회 대상이 1개이면 반환, 1개가 아니면 에러 발생
T fetchFirst()조회 대상이 1개 이상이면 첫번째 대상 반환
Long fetchCount()조회 대상들의 전체 개수 반환

📌 Querydsl with JPA

✔ 사용자 정의 인터페이스 작성

public interface ItemRepositoryCustom {

    Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable);

    Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable);

}
  • 상품 조회 조건을 담고 있는 itemSearchDto 객체와 페이징 정보를 담고 있는 pageable 객체를 인자로 받음
  • 원하는 조회 결과 형태를 Page 객체로 반환

✔ 사용자 정의 인터페이스 구현

public class ItemRepositoryCustomImpl implements ItemRepositoryCustom {

    private JPAQueryFactory queryFactory;

    public ItemRepositoryCustomImpl(EntityManager em) {
        this.queryFactory = new JPAQueryFactory(em);
    }

    ...

}
  • 사용자 정의 인터페이스를 구현하며, 클래스명 끝에 "Impl"를 붙여야 정상적으로 작동
  • 쿼리를 동적으로 생성하기 위해서 JPAQueryFactory 클래스를 사용하고, 생성자로 EntityManager 객체를 전달
public class ItemRepositoryCustomImpl implements ItemRepositoryCustom {

    ...
    
    private BooleanExpression regDtsAfter(String searchDateType) {

        LocalDateTime dateTime = LocalDateTime.now();

        if (StringUtils.equals("all", searchDateType) || searchDateType == null) {
            return null;
        } else if (StringUtils.equals("1d", searchDateType)) {
            dateTime = dateTime.minusDays(1);
        } else if (StringUtils.equals("1w", searchDateType)) {
            dateTime = dateTime.minusWeeks(1);
        } else if (StringUtils.equals("1m", searchDateType)) {
            dateTime = dateTime.minusMonths(1);
        } else if (StringUtils.equals("6m", searchDateType)) {
            dateTime = dateTime.minusMonths(6);
        }

        return QItem.item.regTime.after(dateTime);
    }

    private BooleanExpression searchByLike(String searchBy, String searchQuery) {

        if (StringUtils.equals("itemNm", searchBy)) {
            return QItem.item.itemNm.like("%" + searchQuery + "%");
        } else if (StringUtils.equals("createdBy", searchBy)) {
            return QItem.item.createdBy.like("%" + searchQuery + "%");
        }

        return null;
    }

    private BooleanExpression itemNmLike(String searchQuery) {
        return StringUtils.isEmpty(searchQuery) ? null : QItem.item.itemNm.like("%" + searchQuery + "%");
    }

    @Override
    public Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {

        QueryResults<Item> results = queryFactory
                .selectFrom(QItem.item)
                .where(searchSellStatusEq(itemSearchDto.getSearchSellStatus()),
                        regDtsAfter(itemSearchDto.getSearchDateType()),
                        searchByLike(itemSearchDto.getSearchBy(), itemSearchDto.getSearchQuery()))
                .orderBy(QItem.item.id.desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults();

        List<Item> content = results.getResults();
        long total = results.getTotal();
        return new PageImpl<>(content, pageable, total);
    }

    @Override
    public Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {

        QItem item = QItem.item;
        QItemImg itemImg = QItemImg.itemImg;

        QueryResults<MainItemDto> results = queryFactory
                .select(new QMainItemDto(item.id, item.itemNm, item.itemDetail, itemImg.imgUrl, item.price))
                .from(itemImg)
                .join(itemImg.item, item)
                .where(itemImg.repImgYn.eq("Y"))
                .where(itemNmLike(itemSearchDto.getSearchQuery()))
                .orderBy(item.id.desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults();

        List<MainItemDto> content = results.getResults();
        long total = results.getTotal();
        return new PageImpl<>(content, pageable, total);
    }

}
  • Q class를 이용하여 조회 조건 함수들을 구현
  • 인터페이스의 메서드를 오버라이딩하여 쿼리를 생성하고, 쿼리 결과 관련 값을 반환

✔ JPA repository에서 사용자 정의 인터페이스 상속

public interface ItemRepository extends JpaRepository<Item, Long>, QuerydslPredicateExecutor<Item>, ItemRepositoryCustom {

   ...

}

✔ Service에서 사용

@Service
@Transactional
@RequiredArgsConstructor
public class ItemService {

    private final ItemRepository itemRepository;
    
    @Transactional(readOnly = true)
    public Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable){
        return itemRepository.getAdminItemPage(itemSearchDto, pageable);
    }
    
    @Transactional(readOnly = true)
    public Page<MainItemDto> getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {
        return itemRepository.getMainItemPage(itemSearchDto, pageable);
    }

}

0개의 댓글