Spring DATA JPA Querydsl

JinKyung·2023년 3월 27일
0

SpringBoot 쇼핑몰

목록 보기
6/12
post-thumbnail

업로드중..

@Query 어노테이션에도 단점이 있다.
@Query 어노테이션 안에 JPQL 문법으로 문자열을 입력하기 때문에 잘못 입력하면 컴파일 시점에 에러를 발견할 수 없다...
이를 보완하기 위한 방법이 Querydsl

Querydsl

정적 타입을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해주는 프레임워크
--> 쿼리 생성을 자동화하여 자바 코드로 작성할 수 있다!

  • SQL문을 문자열이 아닌 코드로 작성하기 때문에 컴파일러의 도움을 받을 수 있음.
  • 컴파일시에 오류를 찾을 수 있음.
  • 고정된 SQL문이 아닌 조건에 맞게 동적으로 쿼리 생성 가능
  • 비슷한 쿼리 재사용 가능. 제약 조건 조립 및 가독성 향상 가능.

Querydsl을 사용하기 위해서는 추가 설정을 해주어야한다.
pom.xml 파일에 의존성을 추가해주자.

# QueryDSL JPA 라이브러리
<dependency>
	<groupId>com.querydsl</groupId>
	<artifactId>querydsl-jpa</artifactId>
	<version>5.0.0</version>
</dependency>

# 쿼리타입(Q)를 생성할 때 사용하는 라이브러리
<dependency>
	<groupId>com.querydsl</groupId>
	<artifactId>querydsl-apt</artifactId>
	<version>5.0.0</version>
</dependency>

Qdomain이라는 자바 코드를 생성해주는 플러그인도 추가해주자.

Qdomain

엔티티를 기반으로 접두사로 Q가 붙는 클래스들을 자동으로 생성해주는 플러그인.
예를 들어, Item 테이블의 경우, QItem.java 클래스가 자동으로 생성된다.

Item.java @Entity를 가지고, QItem.java라는 Querydsl 전용 객체를 만든 것!

<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>

추가한 의존성을 받아오기 위해 인텔리제이 오른쪽에 [Maven]클릭 후 새로고침 버튼(?)을 클릭해줍니다. 앞으로도 pom.xml에 dependency를 추가하면 이 버튼을 눌러 해당 라이브러리를 다운로드 해주면 된다.

[maven compile]을 눌러주면 빌드가 되고, 빌드가 완료되면 target/generated-sources 폴더에 QItem 클래스가 생성된 것을 확인할 수 있다


인텔리제이에서 생성한 QDomain이 임포트 안 될 때가 있는데, 이때는 [File]-[Project Structure]-[Modules]에서 target폴더 아래의 generated-sources 폴더를 클릭하고 버튼을 클릭해 소스코드로 인식할 수 있게 처리한다.


Querydsl을 사용하기 위한 세팅은 완료.

JPQL에서 문자열로 작성하던 쿼리를 자바 소스를 이용해 동적으로 생성해보자.

JPAQueryFactory를 이용한 상품 조회 예제

ItemRepository.java

@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
class ItemRepositoryTest {

    @Autowired
    ItemRepository itemRepository;
	
  	// 영속성 컨텍스트를 사용하기 위해 EntityManager 빈을 주입
    @PersistenceContext
    EntityManager em;
  
    @Test
      @DisplayName("Querydsl 조회 테스트1")
      public void queryDslTest() {
          this.createItemTest();
		  // 쿼리 동적 생성. 생성자의 파라미터로는 EntityManager 객체를 넣어준다.
          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();

          for (Item item : itemList) {
              System.out.println(item.toString());
          }
      }
  }

JPAQuery 데이터 반환 메소드

메소드기능
List<T> fetch()조회 결과 리스트 반환
T fetchOne조회 대상이 1건인 경우 제네릭으로 지정한 타입 반환
T fetchFirst()조회 대상 중 1건만 반환
Long fetchCount()조회한 대상 개수 반환
QueryResult<T> fetchResults()조회한 리스트 전체 개수를 포함한 QueryResults 반환

QuerydslPredicateExecutor를 이용한 상품 조회 예제

Predicate: 이 조건이 맞다! 고 판단하는 근거를 함수로 제공하는 것

  • Repository에 Predicate를 파라미터로 전달하기 위해 QueryDslPredicateExecutor 인터페이스를 상속받는다.

ItemRepository.java

// QuerydslPredicateExecutor 인터페이스 상속 추가
public interface ItemRepository extends JpaRepository<Item, Long>, QuerydslPredicateExecutor<Item> {
  ....
}
메소드기능
long count(Predicate)조건에 맞는 데이터의 총 개수 반환
boolean exitsts(Predicate)조건에 맞는 데이터 존재 여부 반환
Iterable findAll(Predicate)조건에 맞는 모든 데이터 반환
Page<T> findAll(Predicate, Pageable)조건에 맞는 페이지 데이터 반환
Iterable findAll(Predicate, Sort)조건에 맞는 정렬된 데이터 반환
T findOne(Predicate)조건에 맞는 데이터 1개 반환

ItemRepository.java
    public void createItemList2(){
        for(int i=1;i<=5;i++){
            Item item = new Item();
            item.setItemNm("테스트 상품" + i);
            item.setPrice(10000 + i);
            item.setItemDetail("테스트 상품 상세 설명" + i);
            item.setItemSellStatus(ItemSellStatus.SELL);
            item.setStockNumber(100);
            item.setRegTime(LocalDateTime.now());
            item.setUpdateTime(LocalDateTime.now());
            itemRepository.save(item);
        }

        for(int i=6;i<=10;i++){
            Item item = new Item();
            item.setItemNm("테스트 상품" + i);
            item.setPrice(10000 + i);
            item.setItemDetail("테스트 상품 상세 설명" + i);
            item.setItemSellStatus(ItemSellStatus.SOLD_OUT);
            item.setStockNumber(0);
            item.setRegTime(LocalDateTime.now());
            item.setUpdateTime(LocalDateTime.now());
            itemRepository.save(item);
        }
    }

    @Test
    @DisplayName("상품 Querydsl 조회 테스트 2")
    public void queryDslTest2(){

		this.createItemList2();
        // 쿼리에 들어갈 조건을 만들어 주는 빌더
        BooleanBuilder booleanBuilder = new BooleanBuilder();
        QItem item = QItem.item;
        String itemDetail = "테스트 상품 상세 설명";
        int price = 10003;
        String itemSellStat = "SELL";

        booleanBuilder.and(item.itemDetail.like("%" + itemDetail + "%"));
        booleanBuilder.and(item.price.gt(price));
        System.out.println(ItemSellStatus.SELL);
        // 상품 판매 상태가 SELL일 때민 booleanBuilder에 판매 상태 조건을 동적으로 추가
        if(StringUtils.equals(itemSellStat, ItemSellStatus.SELL)){
            booleanBuilder.and(item.itemSellStatus.eq(ItemSellStatus.SELL));
        }
        // 페이지네이션 PageRequest.of(조회할 페이지 번호, 한 페이지당 조회할 데이터 개수)
        Pageable pageable = PageRequest.of(0, 5);
        Page<Item> itemPagingResult = itemRepository.findAll(booleanBuilder, pageable);
        System.out.println("total elements : " + itemPagingResult. getTotalElements ());

        List<Item> resultItemList = itemPagingResult.getContent();
        for(Item resultItem: resultItemList){
            System.out.println(resultItem.toString());
        }
    }

테스트 코드를 실행하면 자바 코드에서 지정한 조건문이 정상적으로 추가되어있다!

0개의 댓글