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.필드 로 접근하기에 필드명 잘못 써서 생기는 문제 절대 생기지 않음