[Spring] Querydsl 을 통해 동적 쿼리 해결하기

728x90

 

Querydsl 소개

- 동적 쿼리 문제를 완벽하게 해결한다.

- 쿼리 문장에 오타가 있어도 컴파일 시점에 오류를 막을 수 있다.

- 메서드 추출을 통해 코드를 재사용할 수 있다.

 

초기 설정

build.gradle 파일에 다음 설정을 추가해 준다.

implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:$ {dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api
clean {
   file('src/main/generated')
}

자동 생성된 Q 클래스를 gradle clean 으로 제거한다.

 

Q 타입 생성 확인 방법

build 를 Gradle 로 한 경우 > Gradle 에서 clean 버튼을 누른다. (or `./gradlew clean compileJava`)

그러면 `build/generated/sources/annotationProcessor/java/main` 하위에 `QItem` 생성

 

build 를 IntelliJ IDEA 로 한 경우 > Build 에서 `Build Project` or `Rebuild` or `main() 실행`

그러면 `src/main/generated` 하위에 `QItem` 생성 

 

Querydsl (+ JPA) 사용 예시

@Repository
@Transactional
public class JpaItemRepositoryV3 implements ItemRepository {

  private final EntityManager em;
  private final JPAQueryFactory query;
 
  public JpaItemRepository(EntityManager em) {
    this.em = em;
    this.query = new JPAQueryFactory(em);
  }
 
  @Override
  public Item save(Item item) {
    em.persist(item);
    return item;
  }
 
  @Override
  public void update(Long itemId, ItemUpdateDto updateParam) {
    Item findItem = findById(itemId).orElseThrow();
    findItem.setItemName(updateParam.getItemName());
    findItem.setPrice(updateParam.getPrice());
    findItem.setQuantity(updateParam.getQuantity());
  }
 
  @Override
  public Optional<Item> findById(Long id) {
    Item item = em.find(Item.class, id);
    return Optional.ofNullable(item);
  }
 
  @Override
  public List<Item> findAll(ItemSearchCond cond) {
    String itemName = cond.getItemName();
    Integer maxPrice = cond.getMaxPrice();
    List<Item> result = query.select(QItem.item)
                             .from(QItem.item)
                             .where(likeItemName(itemName), maxPrice(maxPrice))
                             .fetch();
    return result;
  }
  
  private BooleanExpression likeItemName(String itemName) {
    if (StringUtils.hasText(itemName)) {
      return QItem.item.itemName.like("%" + itemName + "%");
    }
    return null;
  }
  
  private BooleanExpression maxPrice(Integer maxPrice) {
    if (maxPrice != null) {
      return QItem.item.price.loe(maxPrice);
    }
    return null;
  }
}

- JPAQueryFactory 가 필요한데, 이는 JPA 쿼리인 JPQL 을 만들기 때문에 EntityManager 가 필요하다.

- likeItemName 과 maxPrice 같은 메서드로 사용하기 때문에 다른 쿼리를 작성할 때 재사용할 수 있다.

 

동적 쿼리

List<Item> result = query
  .select(item)
  .from(item)
  .where(likeItemName(itemName), maxPrice(maxPrice))
  .fetch();

- where 에 다양한 조건을 직접 넣을 수 있다.

 

Querydsl 예외 추상화

스프링 예외 추상화를 지원하지 않는다.

따라서 스프링이 @Repository가 붙은 클래스에 대해 JPA 예외를 스프링 예외 추상화인 DataAccessException 으로 변환해 준다.

 

1. 스프링과 JPA 를 함께 사용하는 경우 스프링이 JPA 예외 변환기인 PersistenceExceptionTranslator 인터페이스를 자동으로 등록한다.

2. 또한 @Repository 가 붙은 클래스에 AOP 프록시를 생성하고, 예외 변환 어드바이저를 등록하는 PersistenceExceptionTranslationPostProcesser 빈 후처리기를 자동으로 등록한다.

  (따라서 단순히 @Repository 만 붙여도 예외 변환이 적용된다.)

3. 예외가 발생하면, AOP 프록시가 해당 예외를 감지해 스프링 예외 추상화로 변환한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 | 스프링 DB 2(김영한) - 인프런

728x90