포스트

Querydsl 동적 페이지네이션 쿼리

Spring에서 Querydsl로 동적 커서 기반 페이지네이션 쿼리를 구성하는 방법에 대해 알아보겠습니다.

개요

동적 쿼리란?

동적 쿼리는 상황에 알맞는 쿼리문을 생성해서 데이터베이스에 날려주는것 입니다.

예시 1

users 테이블에서 커서 기반 페이지네이션으로 회원을 조회하고 싶으면 다음과 같이 코드를 구현해야 됩니다.

JpaRepository
1
2
3
public interface UsersRepository extends JpaRepository<Long, Users> {
    Page<Users> findAll(Pageable pageable);
}
Querydsl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserCustomRepository {
    private final JPAQueryFactory queryFactory;
    
    @Override
    public Page<Users> findByPage(long pageFrom, int pageSize) {
        Long count = queryFactory.select(user.count())
                .from(user)
                .where(
                        user.isSeekingTeam.isTrue()
                ).fetchOne();

        Pageable pageable = Pageable.ofSize(pageSize);

        if (count == null || count == 0)
            return new PageImpl<>(List.of(), pageable, 0);

        List<User> users = queryFactory.selectFrom(user)
                .where(
                        user.id.lt(pageFrom),
                        user.isSeekingTeam.isTrue()
                ).orderBy(user.createdAt.desc())
                .limit(pageSize)
                .fetch();

        return new PageImpl<>(users, pageable, count);
    }
}
SQL
1
SELECT * FROM users WHERE user_id<100 ORDER BY created_at DESC LIMIT 10;

예시 2

동일한 조건에서 positionBACKEND인 회원으로 조건을 추가하고 싶으면 다음과 같이 코드를 구현해야 됩니다.

JpaRepository
1
2
3
public interface UsersRepository extends JpaRepository<Long, Users> {
    Page<Users> findAllByPosition(Pageable pageable, Position position);
}

Querydsl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserCustomRepository {
    private final JPAQueryFactory queryFactory;
    
    @Override
    public Page<Users> findByPage(Position position, long pageFrom, int pageSize) {
        Long count = queryFactory.select(user.count())
                .from(user)
                .where(
                        user.position.eq(position)
                ).fetchOne();

        Pageable pageable = Pageable.ofSize(pageSize);

        if (count == null || count == 0)
            return new PageImpl<>(List.of(), pageable, 0);

        List<User> users = queryFactory.selectFrom(user)
                .where(
                        user.id.lt(pageFrom),
                        user.position.eq(position)
                ).orderBy(user.createdAt.desc())
                .limit(pageSize)
                .fetch();

        return new PageImpl<>(users, pageable, count);
    }
}
SQL
1
SELECT * FROM users WHERE user_id<100 AND position='BACKEND' ORDER BY created_at DESC LIMIT 10;

동적 쿼리 구현

예시1도 필요하고 예시2도 필요한 쿼리라고 가정을 하겠습니다. 정적 쿼리만 활용한다면 예시1과 예시2의 코드를 둘 다 작성해야 됩니다.

하지만 동적 쿼리를 사용하면 두개의 코드를 하나로 묶어서 효율적이고 간결하게 코드를 구성할 수 있습니다.

동적 쿼리를 활용하기 위해서는 Jpa, Jdbc 또는 Querydsl 등을 활용할 수 있는데, JpaRepository interface로는 활용할 수 없습니다. 이 중 컴파일 과정에서 에러를 발견할 수 있는 Querydsl로 동적 쿼리를 작성해 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserCustomRepository {
    private final JPAQueryFactory queryFactory;
    
    @Override
    public Page<User> findPage(Position position, long pageFrom, int pageSize) {
        Long count = queryFactory.select(user.count())
                .from(user)
                .where(
                        positionEq(position),
                ).fetchOne();

        Pageable pageable = Pageable.ofSize(pageSize);

        if (count == null || count == 0)
            return new PageImpl<>(List.of(), pageable, 0);

        List<User> users = queryFactory.selectFrom(user)
                .where(
                        user.id.lt(pageFrom),
                        positionEq(position),
                ).orderBy(user.createdAt.desc())
                .limit(pageSize)
                .fetch();

        return new PageImpl<>(users, pageable, count);
    }

    private Predicate positionEq(Position position) {
        return position != Position.NONE ? user.position.eq(position) : null;
    }
}

위의 코드를 보면 positionEq()메서드에서 만약 Position.NONE일 경우에 where문에 null을 반환하고, 반대의 경우에는 user.position.eq(position)을 반환하게 되어있습니다. Querydsl은 where문에 null이 들어오면 무시를 하고 쿼리문을 구성할 수 있기에 가능한 것입니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.