TIL - 20250804

juni·2025년 8월 4일

TIL

목록 보기
84/317

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()로 매핑하여 조회 결과 전달

0개의 댓글