본문 바로가기

SPRING

스프링 DB 2편 - 데이터 접근 활용 기술 (7)

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2

 

스프링 DB 2편 - 데이터 접근 활용 기술 - 인프런 | 강의

백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 백엔드 개발자

www.inflearn.com

 

 

데이터 접근 기술 - Querydsl

 

Querydsl 설정

 

build.gradle

//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt:$
{dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

 

//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
clean {
delete file('src/main/generated')
}

 

Q 타입 생성 확인 방법

 

1. Gradle : Gradle을 통해서 빌드한다.

2. IntelliJ IDEA : IntelliJ가 직접 자바를 실행해서 빌드한다.

 

build -> generated -> sources -> annotationProcessor -> java/main 하위에 

hello.itemservice.domain.QItem 생성 확인

 

Q타입은 컴파일 시점에 자동 생성되므로 버전관리에 포함되지 않는 것이 좋다.

gradle 옵션을 선택하면 Q타입은 gradle build 폴더 아래에 생성되기 때문에 대부분 gradle build 폴더를 git에 포함하지 않기 때문에 이 부분은 자연스럽게 해결된다.

 

 

IntelliJ IDEA 옵션

 

IntelliJ IDEA 옵션을 선택하면 Q타입은 src/main/generated 폴더 아래에 생성되기 때문에 여기를 포함하지 않는 것이 좋다.

 

 

//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
clean {
delete file('src/main/generated')
}

 

gradle에 해당 스크립트를 추가하면 gradle clean 명령어를 실행할 때 src/main/generated 파일도 함께 삭제해준다.

 

Querydsl 적용

package hello.itemservice.repository.jpa;

import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import hello.itemservice.domain.Item;
import hello.itemservice.domain.QItem;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

import static hello.itemservice.domain.QItem.item;

@Repository
@Transactional
public class JpaItemRepositoryV3 implements ItemRepository {

    private final EntityManager em;
    private final JPAQueryFactory query;

    public JpaItemRepositoryV3(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> findAllOld(ItemSearchCond cond) {

        String itemName = cond.getItemName();
        Integer maxPrice = cond.getMaxPrice();

        QItem item = new QItem("i");

        BooleanBuilder builder = new BooleanBuilder();
        if (StringUtils.hasText(itemName)) {
            builder.and(item.itemName.like("%" + itemName + "%"));
        }
        if (maxPrice != null) {
            builder.and(item.price.loe(maxPrice));
        }


        List<Item> result = query.select(item)
                .from(item)
                .where(builder)
                .fetch();


        return result;
    }

    @Override
    public List<Item> findAll(ItemSearchCond cond) {

        String itemName = cond.getItemName();
        Integer maxPrice = cond.getMaxPrice();

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


        return result;
    }

    private BooleanExpression likeItemName(String itemName) {
        if (StringUtils.hasText(itemName)) {
            return item.itemName.like("%" + itemName + "%");
        }

        return null;
    }

    private BooleanExpression maxPrice(Integer maxPrice) {
        if (maxPrice != null) {
            return item.price.loe(maxPrice);
        }

        return null;
    }

}

 

Querydsl을 사용하려면 JPAQueryFactory가 필요하다. JPAQueryFactory는 JPA 쿼리인 JPQL을 만들기 때문에 EntityManager가 필요하다. 

 

findAllOld 메소드

Querydsl을 사용해서 동적 쿼리 문제를 해결했다.

BooleanBuilder 를 사용해서 원하는 where 조건들을 넣어주면 된다.

 

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

 

Querydsl 에서 where(A,B) 에 다양한 조건들을 직접 넣을 수 있는데, 이렇게 넣으면 AND 조건으로 처리 된다.

where에 null을 입력하면 해당 조건은 무시한다.

이 코드의 장점은 likeItemName(), maxPrice()를 다른 쿼리를 작성할 때 재사용 할 수 있다는 점이다.

 

package hello.itemservice.config;

import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.jpa.JpaItemRepositoryV3;
import hello.itemservice.service.ItemService;
import hello.itemservice.service.ItemServiceV1;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;

@Configuration
@RequiredArgsConstructor
public class QuerydslConfig {

    private final EntityManager em;
    @Bean
    public ItemService itemService() {
        return new ItemServiceV1(itemRepository());
    }
    @Bean
    public ItemRepository itemRepository() {
        return new JpaItemRepositoryV3(em);
    }


}

 

 

테스트를 실행했을때 정상적으로 동작하는것을 볼수있다.

 

 

Querydsl 장점

Querydsl 덕분에 동적 쿼리를 깔끔하게 사용할 수 있다.

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

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