빙응의 공부 블로그
[UniP]QueryDSL로 조회 최적화하기 본문
이번 게시글은 조회 최적화와 QueryDSL을 다루겠습니다.
📝배경
이번에 졸업 작품 프로젝트에서 메인서버를 맡아 개발하게 되었습니다.
파티를 만들어서 그안에 코스를 보고 대학생들이 자연스럽게 약속하는 앱을 만드는 과정인데
해당 과정에서 중요하게 본 점은 조회 쿼리에 대한 효율입니다.
그래서 김영한의 스프링에서 학습한 내용을 기반으로 QueryDSL을 사용하여 조회 최적화를 시도해 보았습니다 .
QueryDSL을 사용하여 조건에 따른 동적 쿼리를 유연하게 처리할 수 있었습니다.
해당 슬라이드에서 설명하는 것처럼 파티는 식사, 음주, 종합으로 나누어 조회합니다.
이 과정을 쉽게 하기 위해 동적 쿼리 추가가 쉬운 QueryDSL을 사용했습니다.
QueryDSL에 대해 자세히 알고 싶으면 전에 공부했던 포스팅을 보시면 됩니다.
[Spring]QueryDSL에 대해 알아보자!
📝 QueryDSL은 무엇일까요?DSL은 Domain Specific Language의 약자로, 특정 영역에 특화된 언어를 의미한다.즉 QueryDSL은 쿼리생성에 특화된 프레임워크를 의미한다. 그렇다면 QueryDSL이 필요한 이유가 무엇
quddnd.tistory.com
📝QueryDSL 설정하기
QueryDSL을 프로젝트에 적용하기 위해서는 의존성 설정과 다른 설정들이 필요합니다.
의존성 설정하기
// 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'
Q타입 만들기
Q타입클래스는 QueryDSL 플러그인으로 컴파일하면 지정된 위치에 생성되며
QueryDSL이 JPQL을 생성할 수 있도록 Entity 정보를 담습니다.
개발자는 Q타입 객체를 생성하여 JPQL 생성에 필요한 데이터를 QueryDSL로 넘길 수 있습니다.
Q 타입은 generated에 만들어지며 build.clean에서 만들 수 있습니다.
만약 엔티티를 만들고 해당 과정을 거치지않으면 빌드 시 오류가 생기니 참고해주세요
📝QueryDSL 적용하기
이제 QueryDSL로 동적 쿼리를 만드는 일부터 해봅시다.
파티 타입은 아래 3개의 타입 중 하나를 가집니다.
이것을 가지고 조회를 할 겁니다. 파티 타입이 null로 들어오면 모든 파티를 조회할 수 있도록 설정했습니다.
@Getter
public enum PartyType {
RESTAURANT("식사"),
BAR("술집"),
COMPREHENSIVE("종합");
private final String description;
PartyType(String description) {
this.description = description;
}
}
일단 QueryDSL를 작성하기 전에 Repository 부분과 합쳐줘야 합니다. 그래서 PartyRepository에
QueryDSL을 구현할 인터페이스를 extends 해줍니다.
@Repository
public interface PartyRepository extends JpaRepository<Party, Long>, PartyRepositoryCustom {
Optional<Party> findById(Long id);
}
PartyRepositoryCustom 인터페이스에서 기존에는 Repository 방식이 아닌 직접 DTO로 데이터를 받아와 쿼리 최적화를 진행하였습니다. 이를 통해 필요한 정보만을 선택적으로 조회할 수 있게 되어, 성능을 높이겠습니다.
public interface PartyRepositoryCustom {
List<PartyResponseDto> getMainPartyPage(PartyType partyType);
Optional<PartyDetailDto> findPartyDetailById(Long id);
List<PartyMyDto> getMyParty(Long id);
}
이 방식은 불필요한 엔티티 조회를 줄이고, 원하는 정보만을 DTO로 받아와 조회 효율성을 높이는 데 유리합니다.
@Override
public List<PartyResponseDto> getMainPartyPage(PartyType partyType) {
BooleanBuilder conditions = createMainPartyConditions(partyType);
return queryFactory
.select(Projections.constructor(PartyResponseDto.class,
party.id,
member.name,
member.profile_image,
party.title,
party.partyType,
party.partyLimit,
party.peopleCount,
party.startTime,
party.endTime
))
.from(party)
.join(party.member, member)
.where(conditions)
.fetch();
}
private BooleanBuilder createMainPartyConditions(PartyType partyType) {
BooleanBuilder builder = new BooleanBuilder();
builder.and(party.endTime.goe(LocalDateTime.now())); // 현재 시간 이후의 파티만 조회
builder.and(party.isClosed.isFalse()); // 종료되지 않은 파티만 조회
if (partyType != null) {
builder.and(party.partyType.eq(partyType)); // 파티 타입 조건 추가
}
return builder;
}
일단 위와 같이 동적 쿼리를 만들어 주었습니다. 만약 타입이 들어오면 해당 파티 타입을 추가하는 방식으로 만들었습니다.
나머지 메소드도 구현해주었습니다.
@Override
public Optional<PartyDetailDto> findPartyDetailById(Long id) {
Party result = queryFactory
.selectFrom(party)
.where(party.id.eq(id))
.fetchOne();
if (result == null) {
return Optional.empty();
}
List<CourseDto> courses = queryFactory
.select(Projections.constructor(CourseDto.class,
course.address,
course.name,
course.content
))
.from(course)
.where(course.party.id.eq(result.getId()))
.fetch();
PartyDetailDto partyDetailDto = PartyDetailDto.builder()
.id(result.getId())
.title(result.getTitle())
.content(result.getContent())
.limit(result.getPartyLimit())
.peopleCount(result.getPeopleCount())
.startTime(result.getStartTime())
.endTime(result.getEndTime())
.courses(courses)
.build();
return Optional.of(partyDetailDto);
}
@Override
public List<PartyMyDto> getMyParty(Long id) {
return queryFactory
.select(Projections.constructor(PartyMyDto.class,
party.id,
party.title,
party.partyLimit,
party.peopleCount
))
.from(party)
.where(party.member.id.eq(id))
.fetch();
}
이번 포스팅에서는 QueryDSL을 활용하여 파티 조회 최적화와 동적 쿼리 처리를 진행했습니다. QueryDSL을 통해 복잡한 조건을 유연하게 처리할 수 있었고, 불필요한 엔티티 조회를 줄여 성능을 최적화할 수 있었습니다. 특히, 파티 타입에 따른 동적 쿼리 생성과 DTO로 필요한 정보만을 조회하는 방식으로 데이터베이스 부담을 줄였으며, 이를 통해 효율적인 데이터 처리가 가능해졌습니다. QueryDSL을 사용하여 동적 쿼리를 작성하는 방식은 조건에 따라 쿼리를 쉽게 조정할 수 있어 향후 다양한 요구사항에 대응하기 좋을 것 같습니다.
'Project > UniP' 카테고리의 다른 글
[UniP] Real MySQL로 배우는 무한 페이징 & 복합 인덱스 설계 전략 (0) | 2025.01.01 |
---|---|
[UniP]무한 페이징 기능 구현 및 성능 개선 (0) | 2024.11.16 |
[UniP] 인증 메일 발송 비동기 처리하기 (1) | 2024.11.11 |
[UniP]Redis를 통한 RefreshToken 관리하기 (0) | 2024.11.06 |
[UniP]JWT 검증 필터 예외처리는 어떻게 처리할까? (3) | 2024.10.15 |