JPA 스프링부트 - Query DSL 고급

박지명·2026년 5월 11일

스프링부트

목록 보기
7/10

1. WHERE 절 조건들 (m33)

public List<Item> m33(ItemDto dto) {
    return factory
            .selectFrom(item)
            .where(item.color.eq("white")          // 동등
                    .and(item.price.gt(100000))     // AND + 크다
                    .and(item.qty.isNotNull()))     // AND + null 체크
            .fetch();
}

WHERE 조건 패턴

// 동등
.where(item.color.eq("white"))           // =
.where(item.color.ne("white"))           // !=

// 비교
.where(item.price.gt(100000))            // >
.where(item.price.goe(100000))           // >=
.where(item.price.lt(100000))            // <
.where(item.price.loe(100000))           // <=
.where(item.price.between(50000, 100000)) // between

// NULL
.where(item.description.isNull())        // is null
.where(item.description.isNotNull())     // is not null

// IN
.where(item.color.in("red", "yellow", "blue"))   // in
.where(item.color.notIn("red", "yellow", "blue")) // not in

// 문자열
.where(item.description.startsWith("최신"))  // like '최신%'
.where(item.description.endsWith("최신"))    // like '%최신'
.where(item.description.contains("최신"))    // like '%최신%'
.where(item.description.like("%최신%"))      // like

// 논리 연산
.where(condition1.and(condition2))       // AND
.where(condition1.or(condition2))        // OR
.where(condition.not())                  // NOT

2. 정렬 (m34)

public List<Item> m34() {
    return factory
            .selectFrom(item)
            .orderBy(item.qty.asc().nullsFirst())  // asc + null을 먼저
            .fetch();
}

정렬 패턴

.orderBy(item.color.asc())                    // 오름차순
.orderBy(item.color.desc())                   // 내림차순
.orderBy(item.color.asc(), item.price.desc()) // 다중 정렬
.orderBy(item.qty.asc().nullsFirst())         // null을 처음에
.orderBy(item.qty.desc().nullsLast())         // null을 마지막에

3. 페이징 (m35)

public List<Item> m35(int offset, int limit) {
    return factory
            .selectFrom(item)
            .offset(offset)   // 시작 위치 (0부터)
            .limit(limit)     // 가져올 개수
            .fetch();
}

컨트롤러에서 사용

@GetMapping("/list")
public String list(@RequestParam(defaultValue = "1") Integer page) {
    int limit = 10;
    int offset = (page - 1) * limit;  // 0, 10, 20...
    
    List<Item> list = repository.m35(offset, limit);
}

4. 집계함수 (m36)

public Object m36() {
    return factory
            .select(item.qty.min())  // 최솟값
            .from(item)
            .fetchOne();
}

집계함수 패턴

.select(item.count())      // COUNT(*)
.select(item.qty.sum())    // SUM(qty)
.select(item.qty.avg())    // AVG(qty)
.select(item.qty.max())    // MAX(qty)
.select(item.qty.min())    // MIN(qty)

5. GROUP BY + HAVING (m37)

public List<Tuple> m37() {
    return factory
            .select(item.color, item.count(), item.price.avg())
            .from(item)
            .groupBy(item.color)           // 색상별로 그룹핑
            .having(item.count().gt(5))    // 개수가 5개 초과인 것만
            .fetch();
}

// SQL: select color, count(*), avg(price) from tblItem 
//      group by color having count(*) > 5

6. 서브쿼리 - WHERE절 (m38)

public List<Item> m38() {
    QItem item2 = new QItem("item2");  // 별칭 필요
    
    return factory
            .selectFrom(item)
            .where(item.price.goe(
                JPAExpressions.select(item2.price.avg())  // 서브쿼리
                    .from(item2)
            ))
            .fetch();
}

// SQL: select * from tblItem a 
//      where price >= (select avg(price) from tblItem b)

7. 서브쿼리 - SELECT절 (m39)

public List<Tuple> m39() {
    QItem item2 = new QItem("item2");
    
    return factory
            .select(
                item.name, item.price, item.color,
                JPAExpressions
                    .select(item2.price.avg())
                    .from(item2)
                    .where(item2.color.eq(item.color))  // 상관 서브쿼리
            )
            .from(item)
            .fetch();
}

// SQL: select name, price, color, 
//           (select avg(price) from tblItem b where a.color = b.color)
//      from tblItem a

8. JOIN (m40, m41)

INNER JOIN (m40)

public User m40() {
    return factory
            .selectFrom(user)
            .join(user.userInfo, userInfo)  // User와 UserInfo를 조인
            .where(user.id.eq("hong"))
            .fetchOne();
}

// SQL: select u.* from tblUser u 
//      inner join tblUserInfo ui on u.id = ui.id
//      where u.id = 'hong'

LEFT JOIN (m41)

public List<User> m41() {
    return factory
            .selectFrom(user)
            .leftJoin(user.userInfo, userInfo)  // left outer join
            .fetch();
}

// SQL: select u.* from tblUser u 
//      left join tblUserInfo ui on u.id = ui.id

JOIN 패턴

.join(user.userInfo, userInfo)           // inner join
.innerJoin(user.userInfo, userInfo)      // inner join (명시적)
.leftJoin(user.userInfo, userInfo)       // left outer join
.rightJoin(user.userInfo, userInfo)      // right outer join

9. FetchType (m42~m44)

FetchType란?

1개의 엔티티를 조회할 때, 관련 엔티티를 언제 가져올 것인가?

LAZY (지연 로딩):

- 부모 엔티티만 먼저 로드
- 자식 엔티티는 필요할 때 가져옴
- 쿼리 여러 번 실행

EAGER (즉시 로딩):

- 부모 엔티티와 자식 엔티티를 한 번에 로드
- JOIN으로 실행
- 쿼리 1번 실행

기본값 (JPA 기본 설정)

@OneToOne      // EAGER (기본값)
@ManyToOne     // EAGER (기본값)
@OneToMany     // LAZY (기본값)
@ManyToMany    // LAZY (기본값)

변경 방법

@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<Board> board;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id")
private User user;

사용 예시

// m42: 1:N 관계
User user = userRepository.findById("hong").get();

// 1. 부모 테이블 접근
System.out.println(user.getName());

// 2. 자식 테이블 접근 (LAZY면 여기서 쿼리 실행)
for(Board board : user.getBoard()) {
    System.out.println(board.getSubject());
}

// m43, m44: 다른 관계에서도 FetchType 동작

10. Projections (DTO로 직접 매핑) (m32)

public List<ItemDto> m32() {
    // select 결과를 바로 DTO로 매핑
    // 전제: ItemDto 생성자에 (name, color, qty) 매개변수 필요
    
    return factory
        .select(Projections.constructor(
            ItemDto.class, 
            item.name, 
            item.color, 
            item.qty
        ))
        .from(item)
        .fetch();
}

다른 Projections 방식

// 1. Constructor (생성자)
Projections.constructor(ItemDto.class, item.name, item.color)

// 2. Bean (Setter)
Projections.bean(ItemDto.class, item.name, item.color)

// 3. Fields (리플렉션으로 필드 설정)
Projections.fields(ItemDto.class, item.name, item.color)

11. Query DSL 요약표

기능표현SQL
기본 조회selectFrom(item).fetch()select *
특정 컬럼select(item.name).from(item)select name
조건where(item.color.eq("white"))where color = ?
AND.and(item.price.gt(100000))and price > ?
OR.or(item.qty.isNull())or qty is null
정렬orderBy(item.color.asc())order by color asc
LIMIToffset(10).limit(5)offset 10 limit 5
집계select(item.count()).from(item)select count(*)
그룹groupBy(item.color)group by color
서브쿼리JPAExpressions.select(...)select (...)
JOINjoin(user.userInfo, userInfo)join ... on
LEFT JOINleftJoin(user.userInfo, userInfo)left join

12. 핵심 정리

m33: WHERE절 조건들 (eq, gt, in, contains 등)
m34: 정렬 (orderBy)
m35: 페이징 (offset, limit)
m36: 집계함수 (count, sum, avg, max, min)
m37: GROUP BY + HAVING
m38: 서브쿼리 (WHERE절)
m39: 서브쿼리 (SELECT절) - 상관 서브쿼리
m40: INNER JOIN
m41: LEFT/RIGHT JOIN
m42~m44: FetchType (LAZY, EAGER)

0개의 댓글