효습
QueryDSL 사용하기 본문
프로젝트 중에 스마트 워크 센터를 검색을 구현하는 api를 담당했다.
처음에 검색 요소가 이름 , 태그 2개인 줄 알고
검색어가 없는 경우, 1개가 들어오는 경우 , 2개가 들어오는 경우 생각해서 메서드를 4개를 만드려고 했다.
이렇게 코드 짤 때가 너무 비효율적인 것 같다고 생각했는데 알고보니 지역구까지 해서 검색 요소가 3개인 것이다!
3개면 만들어야하는 메서드가 6개다.
물론 만들수는 있지
그런데 더 좋은 방법이 있는데 그걸 선택하지 않을 이유는 없다
예전에 살짝 공부한 적이 있는 QueryDSL이 떠올라 한번 적용해보기로 했다
Dependencies 추가
//QueryDsl
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
spring boot 버전 : 3.3.3
QClass 빌드
QClass 란?
엔티티 클래스의 메타 정보를 담고 있는 클래스로, Querydsl은 이를 이용하여 타입 안정성(Type safe)을 보장하면서 쿼리를 작성할 수 있게 된다.
- QClass는 엔티티 클래스와 대응되며 엔티티의 속성을 나타내고 있다.
- QClass를 사용하여 쿼리를 작성하면 엔티티 속성을 직접 참조하고 조합하여 쿼리를 구성할 수 있다.
- QClass를 사용하면 컴파일 시점에 오류를 확인할 수 있고, IDE의 자동완성 기능을 활용하여 쿼리 작성을 보다 편리하게 할 수 있다.
이러한 이유로 QClass를 만들어 사용했다
인텔리제이 우측에 Gradle에서 Task -> Build -> Clean 눌러주고
Task -> other -> compileJava 를 눌러주면 된다
QClass가 빌드된 걸 확인할 수 있음
QueryDSL 사용하기 위해 JPAQueryFactory를 Bean에 등록
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QueryDslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
JPAQueryFactory는 QueryDSL에서 제공하는 클래스 중 하나로 , QueryDSL에서 JPQL 쿼리를 생성하고 실행할 때 사용한다
CustomRepository 에서 QueryDSL 사용하기
public interface CenterRepository extends JpaRepository<Center , Long>{
}
보통 이렇게 Spring Data JPA를 상속 받은 Repository에서 쿼리를 만들어서 사용하는데 동적 쿼리 작성과 같은 복잡한 로직은 가지고 있는 경우 사용하기에 적합하지 않다.
그래서 기존의 CenterRepository 말고 CustomCenterRepository 인터페이스를 만들어준다
public interface CustomCenterRepository {
List<Center> searchCenters(String name, List<String> tags, String district);
}
난 센터 이름 , 태그 , 지역구로 검색 하는 기능 구현을 위해 CustomCenterRepository를 만들고 그 안에 메서드를 정의했다.
CustomRepository 인터페이스를 만들었으니까 이제 구현 클래스를 만들어야한다.
@Repository
@AllArgsConstructor
public class CustomCenterRepositoryImpl implements CustomCenterRepository{
private final JPAQueryFactory jpaQueryFactory;
@Override // 상위 인터페이스인 CustomCenterRepository에 있는 searchCenter를 재정의하고 있기 때문에 @Override를 붙인다.
public List<Center> searchCenters(String name, List<String> tags, String district) {
QCenter center = QCenter.center;
QCenterTag centerTag = QCenterTag.centerTag;
QTag tag = QTag.tag;
return jpaQueryFactory
.selectFrom(center)
.leftJoin(center.centerTagList, centerTag)
.leftJoin(centerTag.tag, tag)
.where(
name != null ? center.name.containsIgnoreCase(name) : null,
district != null ? center.addressDetail1.containsIgnoreCase(district) : null,
tags != null && !tags.isEmpty() ? tag.tagName.in(tags) : null
)
.fetch();
}
}
Center와 Tag가 다대다 관계여서 다대일 + 일대다 관계로 CenterTag라는 중간 테이블을 만들었다
그래서 Center를 기준으로 CenterTag와 left join , CenterTag와 Tag를 left join 해서 데이터를 다 합친 다음
각각의 이름 , 지역구 , 태그 검색이 존재하면 결과를 반환하도록 하였다.
근데 지금 생각해보면 데이터가 중복되어 나올 수 있으니까 distinct() 붙이는 게 나았을듯 ...ㅎㅎ
QCenter center = QCenter.center;
QCenterTag centerTag = QCenterTag.centerTag;
QTag tag = QTag.tag;
jpaQueryFactory로 동적 쿼리를 만들기 전에 QCenter와 같이 QueryDSL에서 자동으로 생성되는 메타모델 객체를 쿼리에서 사용한 걸 볼 수 있다.
저런 메타모델 객체를 사용하는 이유는 엔티티 클래스와 필드를 직접 참조하면서도 안전하게 쿼리를 작성할 수 있기 때문이다.
그리고 마지막으로
public interface CenterRepository extends JpaRepository<Center , Long> , CustomCenterRepository{
}
이렇게 CenterRepository에 CustomCenterRepository 상속 받아주기
'프로젝트' 카테고리의 다른 글
redis 배포하기 (0) | 2025.01.02 |
---|---|
Java에서 equals() 와 == 의 차이 (0) | 2024.08.16 |
서버 배포 과정 이해하기 (1) | 2024.06.19 |