Skip to content

Commit 81daaa7

Browse files
authored
Merge pull request #44 from FlipNoteTeam/feat/cardset-search
Feat: [FN-151] 카드셋 목록 조회시 좋아요순 정렬 기능
2 parents 9a4bc0c + e52bf50 commit 81daaa7

7 files changed

Lines changed: 178 additions & 39 deletions

File tree

src/main/java/project/flipnote/cardset/model/CardSetSearchRequest.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,23 @@
1313
@Setter
1414
public class CardSetSearchRequest extends PagingRequest {
1515

16-
private static final Set<String> ALLOWED_SORT_FIELDS = Set.of("id");
16+
private static final Set<String> ALLOWED_SORT_FIELDS = CardSetSortField.getFieldNames();
1717

1818
private String keyword;
1919
private String category;
2020

2121
@Override
2222
public PageRequest getPageRequest() {
23-
String sortBy = this.getSortBy();
24-
String effectiveSortBy = (sortBy != null && ALLOWED_SORT_FIELDS.contains(sortBy)) ? sortBy : "id";
25-
26-
Sort.Direction direction;
27-
try {
28-
direction = Sort.Direction.fromString(this.getOrder());
29-
} catch (IllegalArgumentException e) {
30-
direction = Sort.Direction.DESC;
23+
return PageRequest.of(getPage() - 1, getSize(), Sort.by(getOrder(), getSortBy()));
24+
}
25+
26+
@Override
27+
public String getSortBy() {
28+
String sortBy = super.getSortBy();
29+
if (sortBy != null && ALLOWED_SORT_FIELDS.contains(sortBy)) {
30+
return sortBy;
3131
}
3232

33-
return PageRequest.of(getPage() - 1, getSize(), Sort.by(direction, effectiveSortBy));
33+
return "ID";
3434
}
3535
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package project.flipnote.cardset.model;
2+
3+
import java.util.Arrays;
4+
import java.util.Set;
5+
import java.util.stream.Collectors;
6+
7+
public enum CardSetSortField {
8+
ID, LIKE;
9+
10+
public static Set<String> getFieldNames() {
11+
return Arrays.stream(values())
12+
.map(CardSetSortField::name)
13+
.collect(Collectors.toSet());
14+
}
15+
}

src/main/java/project/flipnote/cardset/repository/CardSetRepository.java

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,15 @@
33
import java.util.Optional;
44
import java.util.Set;
55

6-
import org.springframework.data.domain.Page;
7-
import org.springframework.data.domain.Pageable;
86
import org.springframework.data.jpa.repository.JpaRepository;
97
import org.springframework.data.jpa.repository.Query;
108
import org.springframework.data.repository.query.Param;
119
import org.springframework.stereotype.Repository;
1210

1311
import project.flipnote.cardset.entity.CardSet;
14-
import project.flipnote.group.entity.Category;
1512

1613
@Repository
17-
public interface CardSetRepository extends JpaRepository<CardSet, Long> {
18-
19-
@Query("""
20-
SELECT c FROM CardSet c
21-
WHERE (:name IS NULL OR c.name LIKE CONCAT('%', :name, '%'))
22-
AND (:category IS NULL OR c.category = :category)
23-
AND c.publicVisible = TRUE
24-
""")
25-
Page<CardSet> findByNameContainingAndCategory(
26-
@Param("name") String name,
27-
@Param("category") Category category,
28-
Pageable pageable
29-
);
30-
14+
public interface CardSetRepository extends JpaRepository<CardSet, Long>, CardSetRepositoryCustom {
3115
Optional<CardSet> findByIdAndGroup_Id(Long id, Long groupId);
3216

3317
@Query("""
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package project.flipnote.cardset.repository;
2+
3+
import org.springframework.data.domain.Page;
4+
import org.springframework.data.domain.Pageable;
5+
6+
import project.flipnote.cardset.entity.CardSet;
7+
import project.flipnote.group.entity.Category;
8+
9+
public interface CardSetRepositoryCustom {
10+
11+
Page<CardSet> searchByNameContainingAndCategory(
12+
String name,
13+
Category category,
14+
Pageable pageable
15+
);
16+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package project.flipnote.cardset.repository;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
7+
import org.springframework.data.domain.Page;
8+
import org.springframework.data.domain.PageImpl;
9+
import org.springframework.data.domain.Pageable;
10+
import org.springframework.data.domain.Sort;
11+
import org.springframework.stereotype.Repository;
12+
import org.springframework.util.StringUtils;
13+
14+
import com.querydsl.core.types.OrderSpecifier;
15+
import com.querydsl.core.types.dsl.BooleanExpression;
16+
import com.querydsl.core.types.dsl.NumberPath;
17+
import com.querydsl.jpa.impl.JPAQuery;
18+
import com.querydsl.jpa.impl.JPAQueryFactory;
19+
20+
import lombok.RequiredArgsConstructor;
21+
import lombok.extern.slf4j.Slf4j;
22+
import project.flipnote.cardset.entity.CardSet;
23+
import project.flipnote.cardset.entity.QCardSet;
24+
import project.flipnote.cardset.entity.QCardSetMetadata;
25+
import project.flipnote.cardset.model.CardSetSortField;
26+
import project.flipnote.group.entity.Category;
27+
28+
@Slf4j
29+
@RequiredArgsConstructor
30+
@Repository
31+
public class CardSetRepositoryCustomImpl implements CardSetRepositoryCustom {
32+
33+
private final JPAQueryFactory queryFactory;
34+
35+
private final QCardSet cardSet = QCardSet.cardSet;
36+
private final QCardSetMetadata cardSetMetadata = QCardSetMetadata.cardSetMetadata;
37+
38+
@Override
39+
public Page<CardSet> searchByNameContainingAndCategory(
40+
String name,
41+
Category category,
42+
Pageable pageable
43+
) {
44+
List<OrderSpecifier<?>> orders = new ArrayList<>();
45+
46+
boolean useMetadata = false;
47+
boolean hasIdSort = false;
48+
for (Sort.Order order : pageable.getSort()) {
49+
CardSetSortField sortField = null;
50+
try {
51+
sortField = CardSetSortField.valueOf(order.getProperty());
52+
} catch (IllegalArgumentException iae) {
53+
log.warn(
54+
"Unknown sort property: {}. Valid values are {}",
55+
order.getProperty(), Arrays.toString(CardSetSortField.values()), iae
56+
);
57+
}
58+
if (sortField == CardSetSortField.LIKE) {
59+
orders.add(toOrderSpecifier(cardSetMetadata.likeCount, order));
60+
useMetadata = true;
61+
} else {
62+
orders.add(toOrderSpecifier(cardSet.id, order));
63+
hasIdSort = true;
64+
}
65+
}
66+
67+
if (!hasIdSort) {
68+
orders.add(cardSet.id.desc());
69+
}
70+
71+
JPAQuery<CardSet> selectQuery = queryFactory
72+
.select(cardSet)
73+
.from(cardSet)
74+
.where(buildCardSetSearchFilterConditions(name, category));
75+
76+
if (useMetadata) {
77+
selectQuery.join(cardSetMetadata).on(cardSet.id.eq(cardSetMetadata.id));
78+
}
79+
80+
List<CardSet> content = selectQuery
81+
.orderBy(orders.toArray(new OrderSpecifier[0]))
82+
.offset(pageable.getOffset())
83+
.limit(pageable.getPageSize())
84+
.fetch();
85+
86+
Long total = queryFactory
87+
.select(cardSet.count())
88+
.from(cardSet)
89+
.where(buildCardSetSearchFilterConditions(name, category))
90+
.fetchOne();
91+
92+
return new PageImpl<>(content, pageable, total != null ? total : 0L);
93+
}
94+
95+
private OrderSpecifier<?> toOrderSpecifier(
96+
NumberPath<?> path,
97+
Sort.Order order
98+
) {
99+
return order.isAscending() ? path.asc() : path.desc();
100+
}
101+
102+
private BooleanExpression nameContains(String name) {
103+
return StringUtils.hasText(name) ? cardSet.name.contains(name) : null;
104+
}
105+
106+
private BooleanExpression categoryEquals(Category category) {
107+
return category == null ? null : cardSet.category.eq(category);
108+
}
109+
110+
private BooleanExpression[] buildCardSetSearchFilterConditions(String name, Category category) {
111+
return new BooleanExpression[]{
112+
nameContains(name),
113+
categoryEquals(category),
114+
cardSet.publicVisible.isTrue()
115+
};
116+
}
117+
}

src/main/java/project/flipnote/cardset/service/CardSetService.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,9 @@ public CreateCardSetResponse createCardSet(Long groupId, AuthPrinciple authPrinc
120120
* @author 윤정환
121121
*/
122122
public PagingResponse<CardSetSummaryResponse> getCardSets(CardSetSearchRequest req) {
123-
124-
// TODO: Projection 및 카운트 쿼리 튜닝 필요, 좋아요 수 및 즐겨찾기 수 등 다양한 정렬 조건 추가 필요
125-
Page<CardSet> cardSetPage = cardSetRepository.findByNameContainingAndCategory(
126-
req.getKeyword(), Category.from(req.getCategory()), req.getPageRequest()
123+
// TODO: Projection 튜닝 필요
124+
Page<CardSet> cardSetPage = cardSetRepository.searchByNameContainingAndCategory(
125+
req.getKeyword(), Category.from(req.getCategory()), req.getPageRequest()
127126
);
128127

129128
Page<CardSetSummaryResponse> res = cardSetPage.map(CardSetSummaryResponse::from);

src/main/java/project/flipnote/common/model/request/PagingRequest.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,22 @@ public PageRequest getPageRequest() {
2929
if (sortBy == null || sortBy.isEmpty()) {
3030
return PageRequest.of(page - 1, size);
3131
} else {
32-
Sort.Direction direction;
33-
try {
34-
direction = Sort.Direction.fromString(order);
35-
} catch (IllegalArgumentException e) {
36-
direction = Sort.Direction.DESC;
37-
}
38-
39-
return PageRequest.of(page - 1, size, Sort.by(direction, sortBy));
32+
return PageRequest.of(page - 1, size, Sort.by(getOrder(), sortBy));
4033
}
4134
}
35+
36+
public Sort.Direction getOrder() {
37+
Sort.Direction direction;
38+
try {
39+
direction = Sort.Direction.fromString(order);
40+
} catch (IllegalArgumentException e) {
41+
direction = Sort.Direction.DESC;
42+
}
43+
44+
return direction;
45+
}
46+
47+
public String getSortBy() {
48+
return sortBy != null ? sortBy.toUpperCase() : null;
49+
}
4250
}

0 commit comments

Comments
 (0)