implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta"
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
def generated = 'src/main/generated'
// querydsl QClass 파일 생성 위치 지정
tasks.withType(JavaCompile).configureEach {
options.getGeneratedSourceOutputDirectory().set(file(generated))
}
// gradle clean 시에 QClass 디렉토리 삭제
clean {
delete file(generated)
}
### Querydsl ###
/src/main/generated/
@Configuration
public class QueryDslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
public interface CustomStoreRepository extends JpaRepository<Story, Long>{
List<PopularStoreDto> findPopularStoreThan(final long id);
public List<Store> findStoreByStoreCategoryByPaging(final StoreCategory storeCategory, final Long offset, finet Long size);
}
@RequiredArgsConstructor
public class CustomStoreRepositoryImpl implements CustomStoreRepository {
private final JPAQueryFactory jpaQueryFactory;
@Override
List<PopularStoreDto> findPopularStoreThan(final long id) {
return /* querydsl 코드 */
}
@Override
public List<Store> findStoreByStoreCategoryByPaging(final StoreCategory storeCategory, final Long offset, finet Long size) {
return /* querydsl 코드 */
}
}
public interface CustomStoreRepository extends JpaRepository<Story, Long>, CustomStoreRepository {
List<PopularStoreDto> findPopularStoreThan(final long id);
public List<Store> findStoreByStoreCategoryByPaging(final StoreCategory storeCategory, final Long offset, finet Long size);
}
@Override
public List<Store> findStoreByStoreCategory(final StoreCategory storeCategory) {
return jpaQueryFactory
.select(store)
.from(store)
.where(store.storeCategory.eq(storeCategory))
.fetch();
}
selectFrom()
메서드를 사용할 수 있음 .select(store)
.from(store)
.selectFrom(store)
store.storeName.eq("store1")
store.storeName.ne("store1")
store.storeName.eq("store1").not
store.storeName.isNotNull()
store.id.in(0, 10)
store.id.notIn(0, 10)
store.id.between(0, 10)
store.id.goe(10)
store.id.gt(10)
store.id.loe(10)
store.id.lt(10)
store.storeName.like("store%")
store.storeName.contains("store")
store.storeName.startswith("store")
.fetchOne() // return Store
.fetch() // return List<Store>
.fetchFirst() // limit(1).fetchOne()
@Override
public List<Store> findStoreByStoreCategory(final StoreCategory storeCategory) {
return jpaQueryFactory
.select(store)
.from(store)
.leftJoin(store.orders, order)
.leftJoin(order.review, review)
.where(store.storeCategory.eq(storeCategory))
.groupBy(store)
.orderBy(review.rate.sum().desc())
.fetch();
}
innerJoin, join
outerJoin(), leftJoin(), rightJoin()도 제공함
.innerJoin(order.review, review)
.on(review.content.contains("맛있어요"))
.join(order.review, review)
.on(review.content.contains("맛있어요"))
.join(order.review, review)
.where(review.content.contains("맛있어요"))
.leftJoin(order.review, review)
.on(review.content.contains("맛있어요")
.rightJoin(order.review, review)
.on(review.content.contains("맛있어요"))
.join(order, review)
.where(order.review.eq(review.content))
.from(order)
.leftJoin(review)
.on(order.review.content.eq(review.content))
.leftJoin(store.orders, order)
.fetchJoin()
.leftJoin(store.orders, order)
.fetchJoin()
.leftJoin(store.review, review)
.fetchJoin()
.orderBy(review.rate.sum().desc())
.orderBy(review.rate.avg().asc())
.orderBy(review.rate.count().desc().nullsLast())
.orderBy(review.rate.count().desc().nullsFirst())
.groupBy(Store)
.orderBy(review.rate.sum().desc())
@Override
public List<Store> findStoreByStoreCategory(final StoreCategory storeCategory, final Long offset, final Long size) {
return jpaQueryFactory
.select(store)
.from(store)
.leftJoin(store.orders, order)
.leftJoin(order.review, review)
.where(store.storeCategory.eq(storeCategory))
.groupBy(store)
.orderBy(review.rate.sum().desc())
.offset(offset)
.limit(size)
.fetch();
}
final QReview notJoinedReview = new QReview("review2");
JPAExpressions
.select(notJoinedReview.rate.avg())
.from(notJoinedReview);
having(review.rate.avg().goe(
JPAExpressions
.select(notJoinedReview.rate.avg())
.from(notJoinedReview)
)
)
가게에 상관없이 계산된 전체 리뷰 평점보다 특정 가게의 모든 주문의 리뷰 평점이 높은 가게만 골라서 평점 순으로 조회해주세요
@Override
public List<Store> findStoreByStoreCategory(final StoreCategory storeCategory, final Long offset, final Long size) {
return jpaQueryFactory
.select(
Projections.fields(
// 가게, 가게의 평점 조회
).from(store)
.leftJoin(store.orders, order)
.leftJoin(order.review, review)
.groupBy(store.id)
.having(
review.rate.avg().goe(
JPAExpressions
.select(notJoinedReview.rate.avg())
.from(notJoinedReview)
)
)
.orderBy(review.rate.sum().desc())
.fetch();
}
.having(review.reate.avg().goe(
JPAExpressions.select(reviewforSelectAllAvg.rate.avg())
.from(reviewforSelectAllAvg)
))
.having(review.reate.avg().goe(calculateTotalRateAvg()))
private JPQLQuery<Double> calculateTotalRateAvg() {
final QReview notJoinedReview = new QReview("review2");
return review.rate.avg().goe(
JPAExpressions.select(reviewforSelectAllAvg.rate.avg())
.from(notJoinedReview)
))
가게 평점 순, 주문 순 중 선택해서 조회
원하는 기준 이상의 가게들만 조회
조건
- 평점순 / 주문순
원하는 기준 값 자유
- 최소값 / 미지정 : 필터링X
- 기준이 주문 순인지 평점 순인지에 따라 계산식, 정렬 순서가 달라진다.
- 기준이 되는 최소값의 선택 여부에 따라 가게들을 필터링 여부가 결정된다.
.from(store)
.where(store.name.eq("덮밥"))
.from(store)
.where(expression1, expression2)
private BooleanExpression expression1(final String name) {
return null이 아닌 expression;
}
private BooleanExpression expression2(final String name) {
return null이 아닌 expression;
}
컴마, and, or
로 조합해서 하나의 더 큰 Expression을 만들 수도 있음.from(store)
.where(expression1, expression2)
private BooleanExpression expression1(final String name) {
if (name == null) {
return null;
}
return null이 아닌 expression;
}
private BooleanExpression expression2(final String name) {
return null이 아닌 expression;
}
.having(matchesCondition(type, min))
private BooleanExpression matchesCondition(final String type, final String min) {
// 주문순
if(type.equals("orderCount")) {
return filterByOrderCount(min);
}
// 평점순
if(type.equals("rate")) {
return filterByRate(min);
}
// 그 외 나머지 타입의 경우 조건 무시(필터링 안함)
return null;
.orderBy(review.rate.avg().desc(), store.id.desc())
public OrderSpecifier(Order order, Expression<T> target) {
this(order, target, NullHandling.Default);
// Order : ASC, DESC enum class
// target : 정렬 기준이 되는 QClass 필드
// OrderSpecifier[] : 여러 정렬 조건을 순서대로 순차 정렬(배열 형태 가능, 첫번째꺼로 정렬 두번째, ...)
}
Pageable의 정렬인 Sort를 사용하여 동적인 OrderSpecifier 생성
ORderSpecifier... 코드 추후 다시 돌려보며 추가 예정..
.orderBy(getSortCondition(sort))
QClass.필드
로 접근하기에 필드명 잘못 써서 생기는 문제 절대 생기지 않음