QueryDSL 사용하기

By | 2022년 7월 23일
Table of Content

QueryDSL 사용하기

QueryDSL 5.0.0 으로 테스트 되었습니다.

테이블 생성

CREATE DATABASE db_test;

USE db_test;

SHOW TABLES;

DROP TABLE tbl_user;
DROP TABLE tbl_team;

CREATE TABLE tbl_user (
    id int NOT NULL AUTO_INCREMENT,
    team varchar(32) NULL,
    username varchar(32),
    email varchar(32),
    PRIMARY KEY (id)
)
COLLATE='utf8_general_ci'
ENGINE=INNODB;

CREATE TABLE tbl_team (
    id varchar(32) NULL,
    teamname varchar(32),
    PRIMARY KEY (id)
)
COLLATE='utf8_general_ci'
ENGINE=INNODB;

INSERT INTO tbl_team(id, teamname)
VALUES('001', '개발팀');

INSERT INTO tbl_team(id, teamname)
VALUES('002', '운영팀');

INSERT INTO tbl_user(team, username, email)
VALUES('001', 'Lee', 'skyer9@gmail.com');

INSERT INTO tbl_user(team, username, email)
VALUES('005', 'Lee2', 'skyer9@gmail.com');

QClass 명

Entity 명과 필드명이 일치하는 경우,
QClass 명이 team1 과 같이 숫자가 붙는다.

@Getter
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "tbl_team", catalog = "db_test")
public class Team {

    @Id
    private String team;

    private String teamname;
}

위에서 클래스명과 필드명이 동일하므로,
QClass 는 team1 이 된다.

import static com.example.demo.domain.QTeam.team1;

join, leftJoin, eq

    public List<User> join() {

        return jpaQueryFactory
                .selectFrom(user)
                .join(team)
                    .on(user.team.eq(team.id))
                .where(team.teamname.eq("개발팀"))
                .fetch();
    }

    public List<User> leftJoin() {

        return jpaQueryFactory
                .selectFrom(user)
                .leftJoin(team)
                    .on(user.team.eq(team.id))
                .where(team.teamname.eq("개발팀"))
                .fetch();
    }

join subquery

참조

JPQL 이 join subquery 를 지원하지 않기에,
QueryDsl 도 서브쿼리를 사용할 수 없다.

아래의 방법으로 hibernate 레벨에서 제공하는 서브 쿼리를 이용할 수 있다.
(native query 를 사용하므로 DB 교체시 고려해야 한다.)

@Entity
@Getter
@Setter
@Subselect("select * from tbl_user where username in ('Lee', 'Lee2')") // native query
public class SubUser {

    @Id
    private Integer id;

    private String team;

    private String username;

    private String email;
}
import static com.example.demo.domain.QTeam.team;
import static com.example.demo.domain.QUser.user;
import static com.example.demo.domain.QSubUser.subUser;

// ......

    public List<User> joinSubQuery() {

        return jpaQueryFactory
                .selectFrom(user)
                .join(subUser)
                    .on(user.id.eq(subUser.id))
                .where(subUser.username.eq("Lee"))
                .fetch();
    }

>, >=, <, <=, between

gt == greater then, goe == greater or equal
lt == lower then, loe == lower or equal

        return jpaQueryFactory
                .selectFrom(user)
//                .where(user.id.gt(1))
//                .where(user.id.goe(1))
//                .where(user.id.lt(1))
//                .where(user.id.loe(1))
                .where(user.id.between(1, 2))
                .fetch();

or

    public List<User> or() {

        return jpaQueryFactory
                .selectFrom(user)
                .join(team)
                    .on(user.id.eq(user.id))
                .where(
                        team.teamname.eq("개발팀")
                        .or
                        (team.teamname.eq("운영팀"))
                )
                .where(user.username.eq("Lee"))
                .fetch();
    }

not

ne == not equal

        return jpaQueryFactory
                .selectFrom(user)
                .join(team)
                    .on(user.team.eq(team.id))
                .where(team.teamname.ne("운영팀"))
                .where(user.username.eq("Lee"))
                .fetch();

is null

        return jpaQueryFactory
                .selectFrom(user)
                .leftJoin(team)
                    .on(user.team.eq(team.id))
                .where(team.teamname.isNull())
                .where(user.username.eq("Lee2"))
                .fetch();

is not null

        return jpaQueryFactory
                .selectFrom(user)
                .leftJoin(team)
                    .on(user.team.eq(team.id))
                .where(team.teamname.isNotNull())
                .where(user.username.eq("Lee"))
                .fetch();

ifnull(isnull)

대체 필드도 가능하고, 대체 변수도 가능하다.
필드 또는 변수는 여러개도 가능하다.
여러개인 경우 null 이 아닌 첫번째 아이템이 반환된다.
모두 null 이면 null 이 반환된다.

    public List<User> isnull() {
        return jpaQueryFactory
                .selectFrom(user)
                .where(
                        //user.username.coalesce(user.email).eq("Lee")
                        user.username.coalesce("Lee").eq("Lee")
                )
                .fetch();
    }

in

        List<String> usernames = Arrays.asList("Lee", "Lee2");
        return jpaQueryFactory
                .selectFrom(user)
                .where(user.username.in(usernames))
                .fetch();

in subquery

서브쿼리는 언제나 성능이 떨어지니 다른 방법을 찾아보자.

        return jpaQueryFactory
                .selectFrom(user)
                .where(user.username.in(
                        JPAExpressions
                                .select(user.username)
                                .from(user)
                                .where(user.username.in("Lee", "Lee2"))))
                .fetch();

like

like() 는 % 를 넣어주어야 한다.

        return jpaQueryFactory
                .selectFrom(user)
                // .where(user.username.startsWith("Lee"))
                .where(user.username.contains("Lee"))
                .fetch();

        return jpaQueryFactory
                .selectFrom(user)
                .where(user.username.like("Lee%"))
                .fetch();

case when

    public List<String> caseWhen() {

        return jpaQueryFactory
                .select(
                        user.username
                                .when("Lee").then("A")
                                .when("Lee2").then("B")
                                .otherwise("C")
                                .as("username")
                )
                .from(user)
                .where(user.id.between(1, 2))
                .fetch();
    }

dynamic query

간단한 조건인 경우

    public List<User> dynamicQuery(String username, String teamname) {

        return jpaQueryFactory
                .selectFrom(user)
                .join(team)
                    .on(user.team.eq(team.id))
                .where(
                        (username != null) ? user.username.eq(username) : null,
                        (teamname != null) ? team.teamname.eq(teamname) : null
                )
                .fetch();
    }

QueryDelegate

QClass 를 재컴파일해야 인식한다.
where 절이 아주 깔끔해진다.

@QueryEntity
public class UserExpression {

    @QueryDelegate(User.class)
    public static BooleanExpression isMe(QUser user) {

        return user.username.eq("Lee");
    }

    @QueryDelegate(User.class)
    public static BooleanExpression isYou(QUser user, String username) {

        if (username == null) {
            return null;
        }

        return user.username.eq(username);
    }
}
    @Override
    public List<User> querydelegate() {
        return jpaQueryFactory
                .selectFrom(user)
                .where(
                        user.isMe(),
                        user.isYou("Lee")
                )
                .fetch();
    }

order by

    public List<User> orderBy() {

        return jpaQueryFactory
                .selectFrom(user)
                .where(user.username.contains("Lee"))
                .orderBy(user.username.asc(), user.id.asc())
                // .orderBy(user.username.desc())
                .fetch();
    }

group by

Projections 의 객체 생성방법 3가지 에 대해 먼저 읽으시면 좋습니다.

아래 샘플코드는 Setter 를 이용한 방식입니다.

@Getter
@Setter
@NoArgsConstructor
public class UserGroupByDto {

    private String username;
    private Long count;

    private UserGroupByDto(String username, Long count) {
        // Projections 을 이용해 객체 생성할 것
        throw new RuntimeException();
    }
}
    public List<UserGroupByDto> groupBy() {

        return jpaQueryFactory
                .select(
                        Projections.bean(
                                UserGroupByDto.class
                                , user.username
                                , user.count().as("count")
                        )
                )
                .from(user)
                .groupBy(user.username)
                .orderBy(user.username.asc())
                .fetch();
    }
    @Test
    void groupBy() {
        // given

        // when
        List<UserGroupByDto> list = repository.groupBy();

        // then
        assertTrue(list.size() > 0);

        UserGroupByDto item = list.get(0);
        Assertions.assertEquals(item.getUsername(), "Lee");
        Assertions.assertEquals(item.getCount(), 3);
    }

union, union all

JPA 에서는 union 을 지원하지 않는다.

"com.querydsl:querydsl-sql:5.0.0" 을 이용해 Native SQL 로 작성하거나,
hibernate 레벨에서 native query 를 작성해야 한다.
(어느 경우든 native query 를 사용하므로 DB 교체시 고려해야 한다.)

paging

PageableExecutionUtils 를 사용하면,
count 쿼리가 필요없을 때 쿼리를 생략한다.

    public Page<User> paging(Pageable pageable) {

        // 'fetchResults()' is deprecated

        List<User> list = jpaQueryFactory
                .selectFrom(user)
                .where(user.username.like("Lee%"))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();

        JPAQuery<User> count = jpaQueryFactory
                .selectFrom(user)
                .where(user.username.like("Lee%"));

        return PageableExecutionUtils.getPage(list, pageable, () -> count.fetch().size());
    }
    @Test
    void paging() {
        // given
        Pageable pageable = PageRequest.of(0, 2);

        // when
        Page<User> page = repository.paging(pageable);

        // then
        List<User> list = page.toList();

        assertEquals(page.getTotalElements(), 3);
        assertEquals(list.size(), 2);
    }

답글 남기기