Framework & Library/SpringDataAccess

QueryDSL ) Spring에서 queryDsl 사용해보기.

ruu++ 2024. 5. 20. 23:51

1. Spring-data-jpa QueryDsl 사용하는 방법

  • BoardRepository
    public interface BoardRepository extends JpaRepository<Board, Long>, BoardRepositoryCustoms{  

    @Query()  
    Optional<Board> findByUrl(final String url);  
}
  • BoardRepositoryCustoms
package org.ruu.developerkorea.domain.board.repository;  

import com.querydsl.core.Tuple;  
import org.springframework.data.domain.Page;  
import org.springframework.data.domain.Pageable;  

public interface BoardRepositoryCustoms {  

    public Page<Tuple> findBoardByUrl(  
            String url,  
            String category,  
            Pageable pageable);  
}
  • BoardRepositoryImpl

import com.querydsl.core.Tuple;  
import com.querydsl.core.types.dsl.BooleanExpression;  
import org.ruu.developerkorea.domain.board.domain.board.Board;  
import org.ruu.developerkorea.domain.board.domain.board.QBoard;  
import org.ruu.developerkorea.domain.board.domain.board.QCategory;  
import org.ruu.developerkorea.domain.board.domain.post.QPost;  
import org.ruu.developerkorea.domain.board.repository.support.Querydsl5RepositorySupport;  
import org.springframework.data.domain.Page;  
import org.springframework.data.domain.Pageable;  
import org.springframework.stereotype.Repository;  

import static org.springframework.util.StringUtils.hasText;  

@Repository  
public class BoardRepositoryImpl extends Querydsl5RepositorySupport implements BoardRepositoryCustoms {  
    public BoardRepositoryImpl() {  
        super(Board.class);  
    }  

}

 

  • Querydsl5RepositorySupport
package org.ruu.developerkorea.domain.board.repository.support;

import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.Querydsl;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.List;
import java.util.function.Function;

@Component
public abstract class Querydsl5RepositorySupport {

    private final Class domainClass;
    private Querydsl querydsl;
    private EntityManager entityManager;
    private JPAQueryFactory queryFactory;

    public Querydsl5RepositorySupport(Class<?> domainClass) {
        Assert.notNull(domainClass, "Domain class must not be null!");
        this.domainClass = domainClass;
    }

    @Autowired
    public void setEntityManager(EntityManager entityManager) {
        Assert.notNull(entityManager, "EntityManager must not be null!");
        JpaEntityInformation entityInformation =
                JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager);
        SimpleEntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
        EntityPath path = resolver.createPath(entityInformation.getJavaType());
        this.entityManager = entityManager;
        this.querydsl = new Querydsl(entityManager, new
                PathBuilder<>(path.getType(), path.getMetadata()));
        this.queryFactory = new JPAQueryFactory(entityManager);
    }


    @PostConstruct
    public void validate() {
        Assert.notNull(entityManager, "EntityManager must not be null!");
        Assert.notNull(querydsl, "Querydsl must not be null!");
        Assert.notNull(queryFactory, "QueryFactory must not be null!");
    }

    protected JPAQueryFactory getQueryFactory() {
        return queryFactory;
    }
    protected Querydsl getQuerydsl() {
        return querydsl;
    }
    protected EntityManager getEntityManager() {
        return entityManager;
    }
    protected <T> JPAQuery<T> select(Expression<T> expr) {
        return getQueryFactory().select(expr);
    }
    protected <T> JPAQuery<T> selectFrom(EntityPath<T> from) {
        return getQueryFactory().selectFrom(from);
    }
    protected <T> Page<T> applyPagination(Pageable pageable,
                                          Function<JPAQueryFactory, JPAQuery> contentQuery) {
        JPAQuery jpaQuery = contentQuery.apply(getQueryFactory());
        List<T> content = getQuerydsl().applyPagination(pageable,
                jpaQuery).fetch();
        return PageableExecutionUtils.getPage(content, pageable,
                jpaQuery::fetchCount);
    }
    protected <T> Page<T> applyPagination(Pageable pageable,
                                          Function<JPAQueryFactory, JPAQuery> contentQuery, Function<JPAQueryFactory,
            JPAQuery> countQuery) {
        JPAQuery jpaContentQuery = contentQuery.apply(getQueryFactory());
        List<T> content = getQuerydsl().applyPagination(pageable,
                jpaContentQuery).fetch();
        JPAQuery countResult = countQuery.apply(getQueryFactory());
        return PageableExecutionUtils.getPage(content, pageable,
                countResult::fetchCount);
    }
}

 

2. QueryDSL WHERE, JOIN 식 관련

1. WHERE

  • where 절을 통해서 데이터 조건 절을 처리할 수 있습니다.
  • eq , or, isNotNull 등등
public void tests(){
    String boardName = "1";
    queryFactory.selectFrom(board)
        .where(board.name.eq(boardName))
        .fetch()
}

DefaultQueryMetadata.class

  • 조건을 동적으로 처리할 때, 사용하는 데이터 클래스입니다.
  • where(Predicate... o)를 쉼표를 통해서 사용 가능하도록 만들어주는 구현 클래스입니다.
  • .where(board.name.eq(boardName) , board.name.isNotNull()) 쉼표를 사용하면 And 연산을 한다는 것을 알 수 있습니다.
public void addHaving(Predicate e) {  
    if (e != null) {  
        e = (Predicate)ExpressionUtils.extract(e);  
        if (e != null) {  
            this.having = and(this.having, e);  
        }  

    }  
}

2. JOIN

  • queryDsl에서는 관계가 있는 처리 방법과 On절이 사용 가능합니다.
  • Board와 Post는 1: N 관계입니다.

Join

public void tests(){
    Qboard board = Qboard.board;
    Qpost post = Qpost.post;
    String boardName = "1";
    queryFactory.select(board, post)
        .from(board)
        .join(board.posts, post)
        .where(board.name.eq(boardName))
        .fetch();

    queryFactory.select(board, post)
        .from(board)
        .leftJoin(board.posts, post)
        .where(board.name.eq(boardName))
        .fetch();

    queryFactory.select(board, post);
        .from(board)
        .rightJoin(board.posts, post)
        .where(board.name.eq(boardName))
        .fetch();
}

On절을 포함한 조인

public void tests(){
    Qboard board = Qboard.board;
    Qpost post = Qpost.post;
    String boardName = "1";
    queryFactory.select(board, post)
        .from(board)
        .leftJoin(board.posts, post)
        .on(board.name.eq("커뮤니티"))
        .fetch();
}

Fetch Join

  • QueryDsl도 Jpa 기반으로 만들어진 라이브러리입니다. 그래서 JPA의 N + 1 문제로 자유롭지 않아 FetchJoin이 존재합니다.
public void tests(){
    Qboard board = Qboard.board;
    Qpost post = Qpost.post;
    String boardName = "1";
    queryFactory.select(board, post)
        .from(board)
        //조인문 다음에 fetchjoin()을 사용합니다.
        .leftJoin(board.posts, post).fetchjoin()
        .on(board.name.eq("커뮤니티"))
        .fetch();
}

3. QueryDsl 동적 쿼리문 처리방법

1. BooleanBuilder를 사용한 처리

  • booleanBuilder를 통해서 And 및 or 연산에 대한 동적 처리가 가능합니다.
  • hasText() StringUtils에서 제공하는 메서드로 공백인지 null 인지 체크하는 메서드입니다.

    private final QBoard board = QBoard.board;  
    private final QPost post = QPost.post;  
    private final QCategory qCategory = QCategory.category;  

    public Page<Board> findBoardByUrl(  
            String url,  
            String category,  
            Pageable pageable) {  

        BooleanBuilder booleanBuilder = new BooleanBuilder();  

        if(hasText(url)) {  
            booleanBuilder.and(board.url.eq(url));  
        }  

        if(hasText(category)) {  
            booleanBuilder.and(qCategory.name.eq(category));  
        }  

        return applyPagination(pageable, query -> query.  
                select(board)  
                .from(board)  
                .leftJoin(board.posts, post)  
                .leftJoin(board.categories, qCategory)  
                .where(booleanBuilder)  
        );  
    }

2. BooleanExpression 를 사용한 처리

  • 다른 쿼리에서도 재 사용이 가능한 방법입니다.
  • 복잡하지 않은 연산 처리는 BooleanExpression 방법을 사용하는 것이 좋습니다.
private final QBoard board = QBoard.board;  
private final QPost post = QPost.post;  
private final QCategory qCategory = QCategory.category;  

public Page<Board> findBoardByUrl(  
        String url,  
        String category,  
        Pageable pageable) {  


    return applyPagination(pageable, query -> query.  
            select(board)  
            .from(board)  
            .leftJoin(board.posts, post)  
            .where(  
                    eqUrl(url),  
                    eqCategory(category))  
    );  
}  

public BooleanExpression eqCategory(String category) {  
    return hasText(category) ? qCategory.name.eq(category) : null;  
}  

public BooleanExpression eqUrl(String url) {  
    return hasText(url) ? board.url.eq(url) : null;  
}