Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@
@Setter
public class CardSetSearchRequest extends PagingRequest {

private static final Set<String> ALLOWED_SORT_FIELDS = Set.of("id");
private static final Set<String> 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";
}
}
15 changes: 15 additions & 0 deletions src/main/java/project/flipnote/cardset/model/CardSetSortField.java
Original file line number Diff line number Diff line change
@@ -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<String> getFieldNames() {
return Arrays.stream(values())
.map(CardSetSortField::name)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,15 @@
import java.util.Optional;
import java.util.Set;

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<CardSet, Long> {

@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<CardSet> findByNameContainingAndCategory(
@Param("name") String name,
@Param("category") Category category,
Pageable pageable
);

public interface CardSetRepository extends JpaRepository<CardSet, Long>, CardSetRepositoryCustom {
Optional<CardSet> findByIdAndGroup_Id(Long id, Long groupId);

@Query("""
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CardSet> searchByNameContainingAndCategory(
String name,
Category category,
Pageable pageable
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package project.flipnote.cardset.repository;

import java.util.ArrayList;
import java.util.Arrays;
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.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<CardSet> searchByNameContainingAndCategory(
String name,
Category category,
Pageable pageable
) {
List<OrderSpecifier<?>> orders = new ArrayList<>();

boolean useMetadata = false;
boolean hasIdSort = false;
for (Sort.Order order : pageable.getSort()) {
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(cardSetMetadata.likeCount, order));
useMetadata = true;
} else {
orders.add(toOrderSpecifier(cardSet.id, order));
hasIdSort = true;
}
}

if (!hasIdSort) {
orders.add(cardSet.id.desc());
}

JPAQuery<CardSet> selectQuery = queryFactory
.select(cardSet)
.from(cardSet)
.where(buildCardSetSearchFilterConditions(name, category));

if (useMetadata) {
selectQuery.join(cardSetMetadata).on(cardSet.id.eq(cardSetMetadata.id));
}

List<CardSet> content = selectQuery
.orderBy(orders.toArray(new OrderSpecifier[0]))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

Long total = queryFactory
.select(cardSet.count())
.from(cardSet)
.where(buildCardSetSearchFilterConditions(name, category))
.fetchOne();

return new PageImpl<>(content, pageable, total != null ? total : 0L);
}

private OrderSpecifier<?> toOrderSpecifier(
NumberPath<?> path,
Sort.Order order
) {
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()
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,9 @@ public CreateCardSetResponse createCardSet(Long groupId, AuthPrinciple authPrinc
* @author 윤정환
*/
public PagingResponse<CardSetSummaryResponse> getCardSets(CardSetSearchRequest req) {

// TODO: Projection 및 카운트 쿼리 튜닝 필요, 좋아요 수 및 즐겨찾기 수 등 다양한 정렬 조건 추가 필요
Page<CardSet> cardSetPage = cardSetRepository.findByNameContainingAndCategory(
req.getKeyword(), Category.from(req.getCategory()), req.getPageRequest()
// TODO: Projection 튜닝 필요
Page<CardSet> cardSetPage = cardSetRepository.searchByNameContainingAndCategory(
req.getKeyword(), Category.from(req.getCategory()), req.getPageRequest()
);

Page<CardSetSummaryResponse> res = cardSetPage.map(CardSetSummaryResponse::from);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 != null ? sortBy.toUpperCase() : null;
}
}
Loading