*/domain/content/repository
ContentRepository.java
import com.codeit.playlist.domain.content.entity.Content;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface ContentRepository extends JpaRepository<Content, UUID>, ContentRepositoryCustom {
boolean existsByApiId(Long apiId);
boolean existsByTypeAndApiId(String type, Long apiId);
}
ContentRepositoryCustom.java
import com.codeit.playlist.domain.content.dto.request.ContentCursorRequest;
import com.codeit.playlist.domain.content.entity.Content;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ContentRepositoryCustom {
List<Content> searchContents(ContentCursorRequest request, boolean ascending, int limit, String sortBy);
long countContents(ContentCursorRequest request);
}
커서기반 페이지네이션
ContentRepositoryImpl.java
import com.codeit.playlist.domain.content.dto.request.ContentCursorRequest;
import com.codeit.playlist.domain.content.entity.Content;
import com.codeit.playlist.domain.content.entity.QContent;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
@Slf4j
@Repository
@RequiredArgsConstructor
public class ContentRepositoryImpl implements ContentRepositoryCustom {
private final JPAQueryFactory query;
@Override
public List<Content> searchContents(ContentCursorRequest request, boolean ascending, int limit, String sortBy) {
QContent qContent = QContent.content;
BooleanBuilder builder = new BooleanBuilder(); // BooleanBuilder
log.info("[콘텐츠 데이터 관리] 콘텐츠 검색: ascending={}, sortBy={}, cursor={}, idAfter={}",
ascending, request.sortBy(), request.cursor(), request.idAfter());
// 검색 조건
if (request.typeEqual() != null) {
builder.and(qContent.type.eq(request.typeEqual()));
}
if (request.keywordLike() != null) {
builder.and(qContent.title.containsIgnoreCase(request.keywordLike()));
}
// 정렬 기준, 정렬 방향
// String sortBy = request.sortBy() == null ? "createdAt" : request.sortBy();
Order order = ascending ? Order.ASC : Order.DESC;
// 커서를 여기에서 씀
String cursor = request.cursor();
String after = request.idAfter();
if (cursor != null && after != null) {
UUID cursorId;
try {
cursorId = UUID.fromString(after);
} catch(IllegalArgumentException e) {
throw new IllegalArgumentException("[콘텐츠 데이터 관리] cursorId was something wrong" + after);
}
switch (sortBy) {
case "createdAt":
Instant cursorDt = Instant.parse(cursor);
if (ascending) {
builder.and(qContent.createdAt.gt(cursorDt)
.or(qContent.createdAt.eq(cursorDt).and(qContent.id.gt(cursorId))));
} else {
builder.and(qContent.createdAt.lt(cursorDt)
.or(qContent.createdAt.eq(cursorDt).and(qContent.id.lt(cursorId))));
}
break;
case "watcherCount":
long cursorWatch = Long.parseLong(cursor);
if (ascending) {
builder.and(qContent.watcherCount.gt(cursorWatch)
.or(qContent.watcherCount.eq(cursorWatch).and(qContent.id.gt(cursorId))));
} else {
builder.and(qContent.watcherCount.lt(cursorWatch)
.or(qContent.watcherCount.eq(cursorWatch).and(qContent.id.lt(cursorId))));
}
break;
case "rate":
Double cursorRate = Double.valueOf(cursor);
if (ascending) {
builder.and(qContent.averageRating.gt(cursorRate)
.or(qContent.averageRating.eq(cursorRate).and(qContent.id.gt(cursorId))));
} else {
builder.and(qContent.averageRating.lt(cursorRate)
.or(qContent.averageRating.eq(cursorRate).and(qContent.id.lt(cursorId))));
}
break;
default:
throw new IllegalArgumentException("[콘텐츠 데이터 관리] sortBy was something wrong" + sortBy);
}
}
// 정렬 + limit+1
return query.selectFrom(qContent)
.where(builder)
.orderBy(getOrderSpecifier(sortBy, order),
new OrderSpecifier<>(order, qContent.id))
.limit(limit + 1)
.fetch();
}
private OrderSpecifier<?> getOrderSpecifier(String sortBy, Order order) {
QContent c = QContent.content;
switch (sortBy) {
case "createdAt":
return new OrderSpecifier<>(order, c.createdAt);
case "watcherCount":
return new OrderSpecifier<>(order, c.watcherCount);
case "rate":
return new OrderSpecifier<>(order, c.averageRating);
default:
throw new IllegalArgumentException("[콘텐츠 데이터 관리] SortBy was something wrong =" + sortBy);
}
}
@Override
public long countContents(ContentCursorRequest request) {
QContent qContent = QContent.content;
BooleanBuilder builder = new BooleanBuilder();
// 검색 조건만 적용
if (request.typeEqual() != null) {
builder.and(qContent.type.eq(request.typeEqual()));
}
if (request.keywordLike() != null) {
builder.and(qContent.title.containsIgnoreCase(request.keywordLike()));
}
long result = query.select(qContent.count()).from(qContent).where(builder).fetchOne();
return result;
}
}'Playlist > Content' 카테고리의 다른 글
| TagService (0) | 2025.12.17 |
|---|---|
| TagRepository (0) | 2025.12.17 |
| ContentMapper (0) | 2025.12.17 |
| ContentException, ErrorCode (0) | 2025.12.17 |
| Content, DTO (0) | 2025.12.17 |