From beaf0b1a0911031e0fbb2b647076b0bf1392e320 Mon Sep 17 00:00:00 2001 From: dungbik Date: Tue, 9 Sep 2025 23:19:59 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Feat:=20=EC=B9=B4=EB=93=9C=EC=85=8B=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=EC=88=9C=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cardset/model/CardSetSearchRequest.java | 20 ++-- .../cardset/model/CardSetSortField.java | 15 +++ .../cardset/repository/CardSetRepository.java | 21 +--- .../repository/CardSetRepositoryCustom.java | 16 +++ .../CardSetRepositoryCustomImpl.java | 97 +++++++++++++++++++ .../cardset/service/CardSetService.java | 5 +- .../common/model/request/PagingRequest.java | 24 +++-- 7 files changed, 157 insertions(+), 41 deletions(-) create mode 100644 src/main/java/project/flipnote/cardset/model/CardSetSortField.java create mode 100644 src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java create mode 100644 src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java diff --git a/src/main/java/project/flipnote/cardset/model/CardSetSearchRequest.java b/src/main/java/project/flipnote/cardset/model/CardSetSearchRequest.java index 5833b52a..84ac8c63 100644 --- a/src/main/java/project/flipnote/cardset/model/CardSetSearchRequest.java +++ b/src/main/java/project/flipnote/cardset/model/CardSetSearchRequest.java @@ -13,23 +13,23 @@ @Setter public class CardSetSearchRequest extends PagingRequest { - private static final Set ALLOWED_SORT_FIELDS = Set.of("id"); + private static final Set ALLOWED_SORT_FIELDS = CardSetSortField.getFieldNames(); private String keyword; private String category; @Override public PageRequest getPageRequest() { - String sortBy = this.getSortBy(); - String effectiveSortBy = (sortBy != null && ALLOWED_SORT_FIELDS.contains(sortBy)) ? sortBy : "id"; - - Sort.Direction direction; - try { - direction = Sort.Direction.fromString(this.getOrder()); - } catch (IllegalArgumentException e) { - direction = Sort.Direction.DESC; + return PageRequest.of(getPage() - 1, getSize(), Sort.by(getOrder(), getSortBy())); + } + + @Override + public String getSortBy() { + String sortBy = super.getSortBy(); + if (sortBy != null && ALLOWED_SORT_FIELDS.contains(sortBy)) { + return sortBy; } - return PageRequest.of(getPage() - 1, getSize(), Sort.by(direction, effectiveSortBy)); + return "ID"; } } diff --git a/src/main/java/project/flipnote/cardset/model/CardSetSortField.java b/src/main/java/project/flipnote/cardset/model/CardSetSortField.java new file mode 100644 index 00000000..0282178c --- /dev/null +++ b/src/main/java/project/flipnote/cardset/model/CardSetSortField.java @@ -0,0 +1,15 @@ +package project.flipnote.cardset.model; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +public enum CardSetSortField { + ID, LIKE; + + public static Set getFieldNames() { + return Arrays.stream(values()) + .map(CardSetSortField::name) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/java/project/flipnote/cardset/repository/CardSetRepository.java b/src/main/java/project/flipnote/cardset/repository/CardSetRepository.java index 42a30727..9a15d987 100644 --- a/src/main/java/project/flipnote/cardset/repository/CardSetRepository.java +++ b/src/main/java/project/flipnote/cardset/repository/CardSetRepository.java @@ -2,31 +2,12 @@ import java.util.Optional; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import project.flipnote.cardset.entity.CardSet; -import project.flipnote.group.entity.Category; @Repository -public interface CardSetRepository extends JpaRepository { - - @Query(""" - SELECT c FROM CardSet c - WHERE (:name IS NULL OR c.name LIKE CONCAT('%', :name, '%')) - AND (:category IS NULL OR c.category = :category) - AND c.publicVisible = TRUE - """) - Page findByNameContainingAndCategory( - @Param("name") String name, - @Param("category") Category category, - Pageable pageable - ); - +public interface CardSetRepository extends JpaRepository, CardSetRepositoryCustom { Optional findByIdAndGroup_Id(Long id, Long groupId); - } diff --git a/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java b/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java new file mode 100644 index 00000000..22d3c60d --- /dev/null +++ b/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java @@ -0,0 +1,16 @@ +package project.flipnote.cardset.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import project.flipnote.cardset.entity.CardSet; +import project.flipnote.group.entity.Category; + +public interface CardSetRepositoryCustom { + + Page findByNameContainingAndCategory( + String name, + Category category, + Pageable pageable + ); +} diff --git a/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java b/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java new file mode 100644 index 00000000..7458c5d5 --- /dev/null +++ b/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java @@ -0,0 +1,97 @@ +package project.flipnote.cardset.repository; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Repository; +import org.springframework.util.StringUtils; + +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; +import project.flipnote.cardset.entity.CardSet; +import project.flipnote.cardset.entity.QCardSet; +import project.flipnote.cardset.entity.QCardSetMetadata; +import project.flipnote.cardset.model.CardSetSortField; +import project.flipnote.group.entity.Category; + +@RequiredArgsConstructor +@Repository +public class CardSetRepositoryCustomImpl implements CardSetRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public Page findByNameContainingAndCategory( + String name, + Category category, + Pageable pageable + ) { + QCardSet c = QCardSet.cardSet; + QCardSetMetadata m = QCardSetMetadata.cardSetMetadata; + + List> orders = new ArrayList<>(); + + boolean useMetadata = false; + boolean hasIdSort = false; + for (Sort.Order order : pageable.getSort()) { + CardSetSortField sortField = CardSetSortField.valueOf(order.getProperty()); + if (sortField == CardSetSortField.LIKE) { + orders.add(toOrderSpecifier(m.likeCount, order)); + useMetadata = true; + } else { + orders.add(toOrderSpecifier(c.id, order)); + hasIdSort = true; + } + } + + if (!hasIdSort) { + orders.add(c.id.desc()); + } + + JPAQuery selectQuery = queryFactory + .select(c) + .from(c) + .where( + StringUtils.hasText(name) ? c.name.contains(name) : null, + category == null ? null : c.category.eq(category), + c.publicVisible.isTrue() + ); + + if (useMetadata) { + selectQuery.join(m).on(c.id.eq(m.id)); + } + + List content = selectQuery + .orderBy(orders.toArray(new OrderSpecifier[0])) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + long total = queryFactory + .select(c.count()) + .from(c) + .where( + StringUtils.hasText(name) ? c.name.contains(name) : null, + category == null ? null : c.category.eq(category), + c.publicVisible.isTrue() + ) + .fetchOne(); + + return new PageImpl<>(content, pageable, total); + } + + private OrderSpecifier toOrderSpecifier( + NumberPath path, + Sort.Order order + ) { + return order.isAscending() ? path.asc() : path.desc(); + } +} diff --git a/src/main/java/project/flipnote/cardset/service/CardSetService.java b/src/main/java/project/flipnote/cardset/service/CardSetService.java index 41b8e02a..de32bfc4 100644 --- a/src/main/java/project/flipnote/cardset/service/CardSetService.java +++ b/src/main/java/project/flipnote/cardset/service/CardSetService.java @@ -120,10 +120,9 @@ public CreateCardSetResponse createCardSet(Long groupId, AuthPrinciple authPrinc * @author 윤정환 */ public PagingResponse getCardSets(CardSetSearchRequest req) { - - // TODO: Projection 및 카운트 쿼리 튜닝 필요, 좋아요 수 및 즐겨찾기 수 등 다양한 정렬 조건 추가 필요 + // TODO: Projection 튜닝 필요 Page cardSetPage = cardSetRepository.findByNameContainingAndCategory( - req.getKeyword(), Category.from(req.getCategory()), req.getPageRequest() + req.getKeyword(), Category.from(req.getCategory()), req.getPageRequest() ); Page res = cardSetPage.map(CardSetSummaryResponse::from); diff --git a/src/main/java/project/flipnote/common/model/request/PagingRequest.java b/src/main/java/project/flipnote/common/model/request/PagingRequest.java index bc53bbee..0d4c53a6 100644 --- a/src/main/java/project/flipnote/common/model/request/PagingRequest.java +++ b/src/main/java/project/flipnote/common/model/request/PagingRequest.java @@ -29,14 +29,22 @@ public PageRequest getPageRequest() { if (sortBy == null || sortBy.isEmpty()) { return PageRequest.of(page - 1, size); } else { - Sort.Direction direction; - try { - direction = Sort.Direction.fromString(order); - } catch (IllegalArgumentException e) { - direction = Sort.Direction.DESC; - } - - return PageRequest.of(page - 1, size, Sort.by(direction, sortBy)); + return PageRequest.of(page - 1, size, Sort.by(getOrder(), sortBy)); } } + + public Sort.Direction getOrder() { + Sort.Direction direction; + try { + direction = Sort.Direction.fromString(order); + } catch (IllegalArgumentException e) { + direction = Sort.Direction.DESC; + } + + return direction; + } + + public String getSortBy() { + return sortBy.toUpperCase(); + } } From a954328fdb876f1cd92728d981713d6b1d8dee5a Mon Sep 17 00:00:00 2001 From: dungbik Date: Tue, 16 Sep 2025 00:04:19 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Refactor:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=82=B4=EC=9A=A9=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CardSetRepositoryCustom.java | 2 +- .../CardSetRepositoryCustomImpl.java | 70 ++++++++++++------- .../cardset/service/CardSetService.java | 2 +- .../common/model/request/PagingRequest.java | 2 +- 4 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java b/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java index 22d3c60d..df2c9688 100644 --- a/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java +++ b/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java @@ -8,7 +8,7 @@ public interface CardSetRepositoryCustom { - Page findByNameContainingAndCategory( + Page searchByNameContainingAndCategory( String name, Category category, Pageable pageable diff --git a/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java b/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java index 7458c5d5..5f950ea2 100644 --- a/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java +++ b/src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java @@ -1,6 +1,7 @@ package project.flipnote.cardset.repository; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.springframework.data.domain.Page; @@ -11,62 +12,69 @@ import org.springframework.util.StringUtils; import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.NumberPath; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import project.flipnote.cardset.entity.CardSet; import project.flipnote.cardset.entity.QCardSet; import project.flipnote.cardset.entity.QCardSetMetadata; import project.flipnote.cardset.model.CardSetSortField; import project.flipnote.group.entity.Category; +@Slf4j @RequiredArgsConstructor @Repository public class CardSetRepositoryCustomImpl implements CardSetRepositoryCustom { private final JPAQueryFactory queryFactory; + private final QCardSet cardSet = QCardSet.cardSet; + private final QCardSetMetadata cardSetMetadata = QCardSetMetadata.cardSetMetadata; + @Override - public Page findByNameContainingAndCategory( + public Page searchByNameContainingAndCategory( String name, Category category, Pageable pageable ) { - QCardSet c = QCardSet.cardSet; - QCardSetMetadata m = QCardSetMetadata.cardSetMetadata; - List> orders = new ArrayList<>(); boolean useMetadata = false; boolean hasIdSort = false; for (Sort.Order order : pageable.getSort()) { - CardSetSortField sortField = CardSetSortField.valueOf(order.getProperty()); + CardSetSortField sortField = null; + try { + sortField = CardSetSortField.valueOf(order.getProperty()); + } catch (IllegalArgumentException iae) { + log.warn( + "Unknown sort property: {}. Valid values are {}", + order.getProperty(), Arrays.toString(CardSetSortField.values()), iae + ); + } if (sortField == CardSetSortField.LIKE) { - orders.add(toOrderSpecifier(m.likeCount, order)); + orders.add(toOrderSpecifier(cardSetMetadata.likeCount, order)); useMetadata = true; } else { - orders.add(toOrderSpecifier(c.id, order)); + orders.add(toOrderSpecifier(cardSet.id, order)); hasIdSort = true; } } if (!hasIdSort) { - orders.add(c.id.desc()); + orders.add(cardSet.id.desc()); } JPAQuery selectQuery = queryFactory - .select(c) - .from(c) - .where( - StringUtils.hasText(name) ? c.name.contains(name) : null, - category == null ? null : c.category.eq(category), - c.publicVisible.isTrue() - ); + .select(cardSet) + .from(cardSet) + .where(buildCardSetSearchFilterConditions(name, category)); if (useMetadata) { - selectQuery.join(m).on(c.id.eq(m.id)); + selectQuery.join(cardSetMetadata).on(cardSet.id.eq(cardSetMetadata.id)); } List content = selectQuery @@ -75,17 +83,13 @@ public Page findByNameContainingAndCategory( .limit(pageable.getPageSize()) .fetch(); - long total = queryFactory - .select(c.count()) - .from(c) - .where( - StringUtils.hasText(name) ? c.name.contains(name) : null, - category == null ? null : c.category.eq(category), - c.publicVisible.isTrue() - ) + Long total = queryFactory + .select(cardSet.count()) + .from(cardSet) + .where(buildCardSetSearchFilterConditions(name, category)) .fetchOne(); - return new PageImpl<>(content, pageable, total); + return new PageImpl<>(content, pageable, total != null ? total : 0L); } private OrderSpecifier toOrderSpecifier( @@ -94,4 +98,20 @@ private OrderSpecifier toOrderSpecifier( ) { return order.isAscending() ? path.asc() : path.desc(); } + + private BooleanExpression nameContains(String name) { + return StringUtils.hasText(name) ? cardSet.name.contains(name) : null; + } + + private BooleanExpression categoryEquals(Category category) { + return category == null ? null : cardSet.category.eq(category); + } + + private BooleanExpression[] buildCardSetSearchFilterConditions(String name, Category category) { + return new BooleanExpression[]{ + nameContains(name), + categoryEquals(category), + cardSet.publicVisible.isTrue() + }; + } } diff --git a/src/main/java/project/flipnote/cardset/service/CardSetService.java b/src/main/java/project/flipnote/cardset/service/CardSetService.java index de32bfc4..22e4ca63 100644 --- a/src/main/java/project/flipnote/cardset/service/CardSetService.java +++ b/src/main/java/project/flipnote/cardset/service/CardSetService.java @@ -121,7 +121,7 @@ public CreateCardSetResponse createCardSet(Long groupId, AuthPrinciple authPrinc */ public PagingResponse getCardSets(CardSetSearchRequest req) { // TODO: Projection 튜닝 필요 - Page cardSetPage = cardSetRepository.findByNameContainingAndCategory( + Page cardSetPage = cardSetRepository.searchByNameContainingAndCategory( req.getKeyword(), Category.from(req.getCategory()), req.getPageRequest() ); diff --git a/src/main/java/project/flipnote/common/model/request/PagingRequest.java b/src/main/java/project/flipnote/common/model/request/PagingRequest.java index 0d4c53a6..68a11046 100644 --- a/src/main/java/project/flipnote/common/model/request/PagingRequest.java +++ b/src/main/java/project/flipnote/common/model/request/PagingRequest.java @@ -45,6 +45,6 @@ public Sort.Direction getOrder() { } public String getSortBy() { - return sortBy.toUpperCase(); + return sortBy != null ? sortBy.toUpperCase() : null; } } From e52bf503c54dce46cdc3a15ed9a65516c2e4f0ce Mon Sep 17 00:00:00 2001 From: dungbik Date: Tue, 16 Sep 2025 00:07:22 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Chore:=20import=20=EB=B9=A0=EC=A7=84=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/flipnote/cardset/repository/CardSetRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/project/flipnote/cardset/repository/CardSetRepository.java b/src/main/java/project/flipnote/cardset/repository/CardSetRepository.java index 332bd1fa..59affad9 100644 --- a/src/main/java/project/flipnote/cardset/repository/CardSetRepository.java +++ b/src/main/java/project/flipnote/cardset/repository/CardSetRepository.java @@ -4,6 +4,8 @@ import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import project.flipnote.cardset.entity.CardSet;