Skip to content

Commit

Permalink
refactor: 동적 쿼리 생성 툴을 도입하여 중복 쿼리 개선 (#684)
Browse files Browse the repository at this point in the history
* chore: QueryDsl 도입을 위한 초기 세팅

* refactor: QueryDsl 도입 - 최근 공모 조회

* test: 날짜 변동으로 테스트 깨짐 해결

* refactor: QueryDsl 도입 - 마감임박 공모 조회

* refactor: QueryDsl 도입 - 높은할인율순 공모 조회

* refactor: QueryDsl 도입 - 참여가능 공모 조회

* refactor: 비슷한 로직의 메서드간 위치 조정 및 IN절 메서드 추출

* style: EOF 추가

* refactor: QueryDslConfig 패키지 위치 변경

* refactor: QueryDsl 적용 레포지토리명 변경
  • Loading branch information
helenason authored Jan 13, 2025
1 parent fe97f16 commit 1df8992
Show file tree
Hide file tree
Showing 13 changed files with 364 additions and 178 deletions.
3 changes: 3 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ openapi3.yaml

### FCM ###
/src/main/resources/fcm

### QueryDSL ###
/src/main/generated
4 changes: 4 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
implementation 'com.google.firebase:firebase-admin:9.3.0'
implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta"
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.18.2'
testImplementation 'com.epages:restdocs-api-spec-restassured:0.18.2'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.zzang.chongdae.global.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QueryDslConfig {

@PersistenceContext
EntityManager entityManager;

@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ protected List<OfferingEntity> fetchWithLast(OfferingEntity lastOffering, String

private List<OfferingEntity> fetchOfferings(Long lastId, double lastDiscountRate,
String searchKeyword, Pageable pageable) {
if (searchKeyword == null) {
return offeringRepository.findHighDiscountOfferingsWithoutKeyword(lastDiscountRate, lastId, pageable);
}
List<OfferingEntity> offeringsSearchedByTitle = offeringRepository.findHighDiscountOfferingsWithTitleKeyword(
lastDiscountRate,
lastId,
Expand All @@ -45,7 +42,7 @@ private List<OfferingEntity> fetchOfferings(Long lastId, double lastDiscountRate

private Comparator<OfferingEntity> sortCondition() {
return Comparator
.comparing(OfferingEntity::getDiscountRate)
.comparing(OfferingEntity::getDiscountRate, Comparator.reverseOrder())
.thenComparing(OfferingEntity::getId, Comparator.reverseOrder());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ protected List<OfferingEntity> fetchWithLast(OfferingEntity lastOffering, String

private List<OfferingEntity> fetchOfferings(Long lastId, LocalDateTime lastMeetingDate,
String searchKeyword, Pageable pageable) {
if (searchKeyword == null) {
return offeringRepository.findImminentOfferingsWithoutKeyword(lastMeetingDate, lastId, pageable);
}
List<OfferingEntity> offeringsSearchedByTitle = offeringRepository.findImminentOfferingsWithTitleKeyword(
lastMeetingDate,
lastId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ protected List<OfferingEntity> fetchWithLast(OfferingEntity lastOffering, String
}

private List<OfferingEntity> fetchOfferings(Long outOfRangeId, String searchKeyword, Pageable pageable) {
if (searchKeyword == null) {
return offeringRepository.findJoinableOfferingsWithoutKeyword(outOfRangeId, pageable);
}
return offeringRepository.findJoinableOfferingsWithKeyword(outOfRangeId, searchKeyword, pageable);
return offeringRepository.findJoinableOfferings(outOfRangeId, searchKeyword, pageable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ protected List<OfferingEntity> concat(Pageable pageable,
List<OfferingEntity>... offerings) {
return Stream.of(offerings)
.flatMap(Collection::stream)
.distinct()
.sorted(sortCondition)
.limit(pageable.getPageSize())
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,12 @@ public RecentOfferingStrategy(OfferingRepository offeringRepository) {

@Override
protected List<OfferingEntity> fetchWithoutLast(Long outOfRangeId, String searchKeyword, Pageable pageable) {
return fetchOfferings(outOfRangeId, searchKeyword, pageable);
return offeringRepository.findRecentOfferings(outOfRangeId, searchKeyword, pageable);
}

@Override
protected List<OfferingEntity> fetchWithLast(OfferingEntity lastOffering, String searchKeyword, Pageable pageable) {
Long lastOfferingId = lastOffering.getId();
return fetchOfferings(lastOfferingId, searchKeyword, pageable);
}

private List<OfferingEntity> fetchOfferings(Long lastOfferingId, String searchKeyword, Pageable pageable) {
if (searchKeyword == null) {
return offeringRepository.findRecentOfferingsWithoutKeyword(lastOfferingId, pageable);
}
return offeringRepository.findRecentOfferingsWithKeyword(lastOfferingId, searchKeyword, pageable);
return offeringRepository.findRecentOfferings(lastOfferingId, searchKeyword, pageable);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.zzang.chongdae.offering.repository;

import com.zzang.chongdae.offering.repository.entity.OfferingEntity;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.domain.Pageable;

public interface CustomizedOfferingRepository {

List<OfferingEntity> findRecentOfferings(Long lastId, String keyword, Pageable pageable);

List<OfferingEntity> findJoinableOfferings(Long lastId, String keyword, Pageable pageable);

List<OfferingEntity> findImminentOfferingsWithTitleKeyword(
LocalDateTime lastMeetingDate, Long lastId, String keyword, Pageable pageable);

List<OfferingEntity> findImminentOfferingsWithMeetingAddressKeyword(
LocalDateTime lastMeetingDate, Long lastId, String keyword, Pageable pageable);

List<OfferingEntity> findHighDiscountOfferingsWithTitleKeyword(
double lastDiscountRate, Long lastId, String keyword, Pageable pageable);

List<OfferingEntity> findHighDiscountOfferingsWithMeetingAddressKeyword(
double lastDiscountRate, Long lastId, String keyword, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.zzang.chongdae.offering.repository;

import static com.zzang.chongdae.offering.domain.OfferingStatus.AVAILABLE;
import static com.zzang.chongdae.offering.domain.OfferingStatus.FULL;
import static com.zzang.chongdae.offering.domain.OfferingStatus.IMMINENT;
import static com.zzang.chongdae.offering.repository.entity.QOfferingEntity.offeringEntity;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.zzang.chongdae.offering.domain.OfferingStatus;
import com.zzang.chongdae.offering.repository.entity.OfferingEntity;
import java.time.LocalDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;

@RequiredArgsConstructor
public class CustomizedOfferingRepositoryImpl implements CustomizedOfferingRepository {

private final JPAQueryFactory queryFactory;

@Override
public List<OfferingEntity> findRecentOfferings(Long lastId, String keyword, Pageable pageable) {
return queryFactory.selectFrom(offeringEntity)
.where(offeringEntity.id.lt(lastId),
likeTitleOrMeetingAddress(keyword))
.orderBy(offeringEntity.id.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
}

@Override
public List<OfferingEntity> findJoinableOfferings(Long lastId, String keyword, Pageable pageable) {
return queryFactory.selectFrom(offeringEntity)
.where(offeringEntity.id.lt(lastId),
inOfferingStatus(AVAILABLE, IMMINENT),
likeTitleOrMeetingAddress(keyword))
.orderBy(offeringEntity.id.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
}

@Override
public List<OfferingEntity> findImminentOfferingsWithTitleKeyword(
LocalDateTime lastMeetingDate, Long lastId, String keyword, Pageable pageable) {
return findImminentOfferings(lastMeetingDate, lastId, pageable, likeTitle(keyword));
}

@Override
public List<OfferingEntity> findImminentOfferingsWithMeetingAddressKeyword(
LocalDateTime lastMeetingDate, Long lastId, String keyword, Pageable pageable) {
return findImminentOfferings(lastMeetingDate, lastId, pageable, likeMeetingAddress(keyword));
}

private List<OfferingEntity> findImminentOfferings(
LocalDateTime lastMeetingDate, Long lastId, Pageable pageable, BooleanExpression keywordCondition) {
return queryFactory.selectFrom(offeringEntity)
.where(keywordCondition,
inOfferingStatus(IMMINENT),
offeringEntity.meetingDate.gt(lastMeetingDate)
.or(offeringEntity.meetingDate.eq(lastMeetingDate).and(offeringEntity.id.lt(lastId))))
.orderBy(offeringEntity.meetingDate.asc(), offeringEntity.id.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
}

@Override
public List<OfferingEntity> findHighDiscountOfferingsWithTitleKeyword(
double lastDiscountRate, Long lastId, String keyword, Pageable pageable) {
return findHighDiscountOfferings(lastDiscountRate, lastId, pageable, likeTitle(keyword));
}

@Override
public List<OfferingEntity> findHighDiscountOfferingsWithMeetingAddressKeyword(
double lastDiscountRate, Long lastId, String keyword, Pageable pageable) {
return findHighDiscountOfferings(lastDiscountRate, lastId, pageable, likeMeetingAddress(keyword));
}

private List<OfferingEntity> findHighDiscountOfferings(
double lastDiscountRate, Long lastId, Pageable pageable, BooleanExpression keywordCondition) {
return queryFactory.selectFrom(offeringEntity)
.where(keywordCondition,
inOfferingStatus(AVAILABLE, FULL, IMMINENT),
offeringEntity.discountRate.lt(lastDiscountRate)
.or(offeringEntity.discountRate.eq(lastDiscountRate).and(offeringEntity.id.lt(lastId))))
.orderBy(offeringEntity.discountRate.desc(), offeringEntity.id.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
}

private BooleanExpression likeTitleOrMeetingAddress(String keyword) {
if (keyword == null) {
return null;
}
return likeTitle(keyword).or(likeMeetingAddress(keyword));
}

private BooleanExpression likeTitle(String keyword) {
if (keyword == null) {
return null;
}
return offeringEntity.title.like(keyword + '%');
}

private BooleanExpression likeMeetingAddress(String keyword) {
if (keyword == null) {
return null;
}
return offeringEntity.meetingAddress.like(keyword + '%');
}

private BooleanExpression inOfferingStatus(OfferingStatus... offeringStatus) {
return offeringEntity.offeringStatus.in(offeringStatus);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;

public interface OfferingRepository extends JpaRepository<OfferingEntity, Long> {
public interface OfferingRepository extends JpaRepository<OfferingEntity, Long>, CustomizedOfferingRepository {

@Query(value = """
SELECT *
Expand All @@ -19,106 +18,6 @@ public interface OfferingRepository extends JpaRepository<OfferingEntity, Long>
""", nativeQuery = true)
Optional<OfferingEntity> findByIdWithDeleted(Long offeringId);

@Query("""
SELECT o
FROM OfferingEntity o
WHERE o.id < :lastId
ORDER BY o.id DESC
""")
List<OfferingEntity> findRecentOfferingsWithoutKeyword(Long lastId, Pageable pageable);

@Query("""
SELECT o
FROM OfferingEntity o
WHERE o.id < :lastId
AND (o.title LIKE :keyword% OR o.meetingAddress LIKE :keyword%)
ORDER BY o.id DESC
""")
List<OfferingEntity> findRecentOfferingsWithKeyword(Long lastId, String keyword, Pageable pageable);

@Query("""
SELECT o
FROM OfferingEntity o
WHERE (o.meetingDate > :lastMeetingDate OR (o.meetingDate = :lastMeetingDate AND o.id < :lastId))
AND (o.offeringStatus = 'IMMINENT')
ORDER BY o.meetingDate ASC, o.id DESC
""")
List<OfferingEntity> findImminentOfferingsWithoutKeyword(
LocalDateTime lastMeetingDate, Long lastId, Pageable pageable);

@Query("""
SELECT o
FROM OfferingEntity o
WHERE (o.meetingAddress LIKE :keyword%)
AND (o.offeringStatus = 'IMMINENT')
AND (o.meetingDate > :lastMeetingDate OR (o.meetingDate = :lastMeetingDate AND o.id < :lastId))
ORDER BY o.meetingDate ASC, o.id DESC
""")
List<OfferingEntity> findImminentOfferingsWithMeetingAddressKeyword(
LocalDateTime lastMeetingDate, Long lastId, String keyword, Pageable pageable);

@Query("""
SELECT o
FROM OfferingEntity o
WHERE (o.title LIKE :keyword%)
AND (o.offeringStatus = 'IMMINENT')
AND (o.meetingDate > :lastMeetingDate OR (o.meetingDate = :lastMeetingDate AND o.id < :lastId))
ORDER BY o.meetingDate ASC, o.id DESC
""")
List<OfferingEntity> findImminentOfferingsWithTitleKeyword(
LocalDateTime lastMeetingDate, Long lastId, String keyword, Pageable pageable);

@Query("""
SELECT o
FROM OfferingEntity o
WHERE ((o.discountRate < :lastDiscountRate) or (o.discountRate = :lastDiscountRate AND o.id < :lastId))
AND (o.offeringStatus IN ('AVAILABLE', 'FULL', 'IMMINENT'))
ORDER BY o.discountRate DESC, o.id DESC
""")
List<OfferingEntity> findHighDiscountOfferingsWithoutKeyword(
double lastDiscountRate, Long lastId, Pageable pageable);

@Query("""
SELECT o
FROM OfferingEntity o
WHERE (o.title LIKE :keyword%)
AND (o.offeringStatus IN ('AVAILABLE', 'FULL', 'IMMINENT'))
AND ((o.discountRate < :lastDiscountRate) or (o.discountRate = :lastDiscountRate AND o.id < :lastId))
ORDER BY o.discountRate DESC, o.id DESC
""")
List<OfferingEntity> findHighDiscountOfferingsWithTitleKeyword(
double lastDiscountRate, Long lastId, String keyword, Pageable pageable);

@Query("""
SELECT o
FROM OfferingEntity o
WHERE (o.meetingAddress LIKE :keyword%)
AND (o.offeringStatus IN ('AVAILABLE', 'FULL', 'IMMINENT'))
AND ((o.discountRate < :lastDiscountRate) or (o.discountRate = :lastDiscountRate AND o.id < :lastId))
ORDER BY o.discountRate DESC, o.id DESC
""")
List<OfferingEntity> findHighDiscountOfferingsWithMeetingAddressKeyword(
double lastDiscountRate, Long lastId, String keyword, Pageable pageable);

@Query("""
SELECT o
FROM OfferingEntity o
WHERE (o.id < :lastId)
AND (o.offeringStatus IN ('AVAILABLE', 'IMMINENT'))
ORDER BY o.id DESC
""")
List<OfferingEntity> findJoinableOfferingsWithoutKeyword(Long lastId, Pageable pageable);

@Query("""
SELECT o
FROM OfferingEntity o
WHERE (o.id < :lastId)
AND (o.title LIKE :keyword% OR o.meetingAddress LIKE :keyword%)
AND (o.offeringStatus IN ('AVAILABLE', 'IMMINENT'))
ORDER BY o.id DESC
""")
List<OfferingEntity> findJoinableOfferingsWithKeyword(Long lastId, String keyword, Pageable pageable);

@Query("SELECT MAX(o.id) FROM OfferingEntity o")
Long findMaxId();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ public OfferingEntity createOffering(MemberEntity member, Integer totalCount) {
return createOffering(member, "title", totalCount, 33.3, OfferingStatus.AVAILABLE, CommentRoomStatus.GROUPING);
}

public OfferingEntity createOffering(MemberEntity member, String title, OfferingStatus offeringStatus) {
return createOffering(member, title, 5, 33.3, offeringStatus, CommentRoomStatus.GROUPING);
}

public OfferingEntity createOffering(MemberEntity member, String title, Double discountRate) {
return createOffering(member, title, 5, discountRate, OfferingStatus.AVAILABLE, CommentRoomStatus.GROUPING);
}

public void deleteOffering(OfferingEntity offering) {
offeringRepository.delete(offering);
}
Expand Down
Loading

0 comments on commit 1df8992

Please sign in to comment.