1. Spring-data-jpa QueryDsl 사용하는 방법
public interface BoardRepository extends JpaRepository<Board, Long>, BoardRepositoryCustoms{
@Query()
Optional<Board> findByUrl(final String url);
}
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);
}
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;
}