0804 QueryDSL - 서브쿼리와 동적 쿼리 활용
✅ 주요 개념 요약
- QueryDSL을 활용해 복잡한 SQL을 Java 코드로 타입 안정성과 가독성을 유지하며 작성할 수 있음
JPAExpressions를 통해 서브쿼리를 작성 가능
BooleanBuilder를 활용한 동적 쿼리 구성 가능
OrderSpecifier를 사용하여 동적 정렬 처리 가능
◼ 서브쿼리 실습
1. 특정 그룹 평균 나이보다 많은 아이돌 조회
List<Idol> idolList = factory.selectFrom(idol)
.where(idol.age.gt(
JPAExpressions.select(idol.age.avg())
.from(idol)
.where(idol.group.groupName.eq("르세라핌"))
))
.fetch();
2. 그룹별 가장 최근 발매 앨범 조회
List<Tuple> result = factory.select(group.groupName, albumA.albumName, albumA.releaseYear)
.from(group)
.innerJoin(group.albums, albumA)
.where(albumA.id.in(
JPAExpressions.select(albumS.id)
.from(albumS)
.where(albumS.group.id.eq(albumA.group.id)
.and(albumS.releaseYear.eq(
JPAExpressions.select(albumS.releaseYear.max())
.from(albumS)
.where(albumS.group.id.eq(albumA.group.id))
))
)
))
.fetch();
3. 특정 연도에 앨범이 2개 이상 발매된 그룹 조회
JPQLQuery<Long> subQuery = JPAExpressions
.select(subAlbum.group.id)
.from(subAlbum)
.where(subAlbum.releaseYear.eq(2022))
.groupBy(subAlbum.group.id)
.having(subAlbum.count().goe(2L));
List<Group> result = factory.selectFrom(group)
.where(group.id.in(subQuery))
.fetch();
4. 그룹이 존재하지 않는 아이돌 조회
JPQLQuery<Long> subQuery = JPAExpressions.select(group.id)
.from(group)
.where(group.id.eq(idol.group.id));
List<Idol> result = factory.selectFrom(idol)
.where(subQuery.notExists())
.fetch();
5. 아이브 평균 나이보다 많은 여자 아이돌 조회
JPQLQuery<Double> subQuery = JPAExpressions.select(idol.age.avg())
.from(idol)
.where(idol.group.groupName.eq("아이브"));
List<Idol> result = factory.selectFrom(idol)
.where(idol.age.gt(subQuery).and(idol.gender.eq("여")))
.fetch();
6. 특정 연도에 앨범 없는 그룹 조회
JPQLQuery<Long> subQuery = JPAExpressions
.select(album.group.id)
.from(album)
.where(album.releaseYear.eq(2023).and(album.group.id.eq(group.id)));
List<Group> result = factory.selectFrom(group)
.where(subQuery.notExists())
.fetch();
◼ 동적 쿼리 구성
1. 조건에 따라 검색조건을 조합하는 동적 쿼리
BooleanBuilder booleanBuilder = new BooleanBuilder();
if (name != null) booleanBuilder.and(idol.idolName.eq(name));
if (gender != null) booleanBuilder.and(idol.gender.eq(gender));
if (minAge != null) booleanBuilder.and(idol.age.goe(minAge));
if (maxAge != null) booleanBuilder.and(idol.age.loe(maxAge));
List<Idol> result = factory.selectFrom(idol).where(booleanBuilder).fetch();
2. 동적 정렬 처리
OrderSpecifier<?> specifier = switch (sortBy) {
case "age" -> ascending ? idol.age.asc() : idol.age.desc();
case "idolName" -> ascending ? idol.idolName.asc() : idol.idolName.desc();
case "groupName" -> ascending ? idol.group.groupName.asc() : idol.group.groupName.desc();
default -> null;
};
List<Idol> result = factory.selectFrom(idol).orderBy(specifier).fetch();
◼ 그룹별 평균 나이 API
Repository (Custom 구현)
public List<GroupAverageAge> groupAverage() {
return factory.select(
Projections.constructor(GroupAverageAge.class, idol.group.groupName, idol.age.avg())
).from(idol).groupBy(idol.group).fetch();
}
Service
public List<GroupAverageAge> average() {
return groupRepository.groupAverage();
}
Controller
@GetMapping("/avg")
public ResponseEntity<?> avg() {
return ResponseEntity.ok().body(idolService.average());
}
✅ 요약 정리
JPAExpressions는 QueryDSL에서 서브쿼리를 지원하는 방식
- 복잡한 조건의
where, group by, having도 QueryDSL로 구성 가능
BooleanBuilder, OrderSpecifier로 유연한 동적 쿼리와 정렬 구현
- API 응답 DTO 설계 후
Projections.constructor()로 매핑하여 조회 결과 전달