728x90

Querydsl

Querydsl은 JPA, Hibernate 등 다양한 ORM 프레임워크와 호환되며, 개체지향 쿼리 언어를 사용하여 타입 세이프한 쿼리를 작성할 수 있는 라이브러리이다.

 

프로젝션(Projection)

프로젝션은 select절에서 사용되며 쿼리 결과를 원하는 개체나 값으로 변환해주는 기능을 제공한다.

 

Queyrdsl에서 프로젝션을 사용하는 방법

프로젝션을 사용해서 DTO를 조회하는 방법은 크게 4가지 방법이 있다.

  • @QueryProjection
  • Projections.bean()
  • Projections.constructor()
  • Projections.fields()

 

@QueryProjection

@QueryProjection은 생성자에 생성자 방식으로 프로젝션을 수행한다. 이 방식은 컴파일 시에 생성자를 만들어주기 때문에 런타임 오류를 방지할 수 있고, 코드의 가독성을 높여준다.

방법은 간단하다. DTO의 생성자에 @QueryProjection을 붙여주면 된다. 이후 빌드를 하면 해당 DTO의 Q파일이 생성된다.

public class TagDto {

    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public static class Response {
        String name;
        Long count;

        @Builder
        @QueryProjection
        public Response(String name, Long count) {
            this.name = name;
        }
    }
}

생성된 Q타입으로 일반 객체를 생성하듯이 Querydsl에서 사용할 수 있다.

@Repository
@RequiredArgsConstructor
public class TagCustomRepositoryImpl implements TagCustomRepository {

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public List<TagDto.Response> findTagsStartingWith(String tagName, int size) {
        return jpaQueryFactory.select(
                        new QTagDto_Response(tag.name))
                .from(tag)
                .where(tag.name.startsWith(tagName))
                .limit(size)
                .orderBy()
                .fetch();
    }
}

@QueryProjection을 사용하면 컴파일 시에 타입 체크를 할 수 있어서 가장 안전하고 추천되는 방법이다. 하지만 DTO Q클래스를 생성하고 유지보수해야 하는 불편함이 있다.

 

Projections.bean()

프로퍼티로 접근하는 방법이다. Setter를 사용하기 때문에 Setter와 기본 생성자가 있어야 한다. Reflection을 사용하기 때문에 속도가 느릴 수 있다.

@Repository
@RequiredArgsConstructor
public class TagCustomRepositoryImpl implements TagCustomRepository {

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public List<TagDto.Response> findTagsStartingWith(String tagName, int size) {
        return jpaQueryFactory.select(
                        Projections.bean(TagDto.Response.class, tag.name))
                .from(tag)
                .where(tag.name.startsWith(tagName))
                .limit(size)
                .orderBy()
                .fetch();
    }
}

 

Projections.fields()

필드를 통해 접근하는 방법이다. 필드와 기본 생성자가 필수적으로 있어야 한다. DTO 클래스와 엔티티 클래스의 필드명이 같아야 하기 때문에 유연성이 떨어진다. 하지만 가장 빠른 속도를 보장한다.

@Repository
@RequiredArgsConstructor
public class TagCustomRepositoryImpl implements TagCustomRepository {

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public List<TagDto.Response> findTagsStartingWith(String tagName, int size) {
        return jpaQueryFactory.select(
                        Projections.fields(TagDto.Response.class, tag.name))
                .from(tag)
                .where(tag.name.startsWith(tagName))
                .limit(size)
                .orderBy()
                .fetch();
    }
}

 

Projections.constructor()

생성자를 통해 접근하는 방법이다. 명시적으로 DTO 클래스의 생성자를 지정할 수 있기 때문에 유연성이 높다. 하지만 DTO 클래스의 생성자가 변경될 때마다 쿼리도 변경해줘야하는 번거로움이 있다.

@Repository
@RequiredArgsConstructor
public class TagCustomRepositoryImpl implements TagCustomRepository {

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public List<TagDto.Response> findTagsStartingWith(String tagName, int size) {
        return jpaQueryFactory.select(
                        Projections.constructor(TagDto.Response.class, tag.name))
                .from(tag)
                .where(tag.name.startsWith(tagName))
                .limit(size)
                .orderBy()
                .fetch();
    }
}
728x90
Cold Bean