Skip to content

Commit aa6d72e

Browse files
authored
refactor: 질문 정렬에 queryDsl 적용 (#59)
* build: 의존성 추가 * refactor: querydsl 적용 * refactor: 서비스 코드 수정 * test: 테스트 코드 수정 * style: 불필요한 import 제거 * test: repo 테스트들 config 추가
1 parent 2e24d58 commit aa6d72e

File tree

12 files changed

+215
-147
lines changed

12 files changed

+215
-147
lines changed

build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ dependencies {
4949

5050
// S3
5151
implementation 'software.amazon.awssdk:s3:2.31.77'
52+
53+
// querydsl
54+
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
55+
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
56+
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
57+
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
58+
5259
// bucket4j
5360
implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0'
5461
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.oronaminc.join.global.config;
2+
3+
import com.querydsl.jpa.impl.JPAQueryFactory;
4+
import jakarta.persistence.EntityManager;
5+
import jakarta.persistence.PersistenceContext;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
9+
@Configuration
10+
public class QueryDslConfig {
11+
12+
@PersistenceContext
13+
private EntityManager entityManager;
14+
15+
@Bean
16+
public JPAQueryFactory jpaQueryFactory() {
17+
return new JPAQueryFactory(entityManager);
18+
}
19+
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.oronaminc.join.question.dao;
2+
3+
import com.oronaminc.join.question.domain.QuestionSort;
4+
import com.oronaminc.join.question.dto.QuestionFlatResponse;
5+
import java.util.List;
6+
import org.springframework.data.domain.Pageable;
7+
8+
public interface QuestionCustomRepository {
9+
10+
List<QuestionFlatResponse> findQuestionsOrderBy(Long lastId, Long lastEmojiCount,
11+
Long memberId, Long roomId, QuestionSort sortType, Pageable pageable);
12+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.oronaminc.join.question.dao;
2+
3+
import static com.oronaminc.join.answer.domain.QAnswer.answer;
4+
import static com.oronaminc.join.emoji.domain.QEmoji.emoji;
5+
import static com.oronaminc.join.member.domain.QMember.member;
6+
import static com.oronaminc.join.question.domain.QQuestion.question;
7+
8+
import com.oronaminc.join.question.domain.QuestionSort;
9+
import com.oronaminc.join.question.dto.QuestionFlatResponse;
10+
import com.querydsl.core.BooleanBuilder;
11+
import com.querydsl.core.types.Order;
12+
import com.querydsl.core.types.OrderSpecifier;
13+
import com.querydsl.core.types.Predicate;
14+
import com.querydsl.core.types.Projections;
15+
import com.querydsl.jpa.JPAExpressions;
16+
import com.querydsl.jpa.impl.JPAQuery;
17+
import com.querydsl.jpa.impl.JPAQueryFactory;
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import lombok.RequiredArgsConstructor;
21+
import org.springframework.data.domain.Pageable;
22+
23+
@RequiredArgsConstructor
24+
public class QuestionCustomRepositoryImpl implements QuestionCustomRepository {
25+
26+
private final JPAQueryFactory jpaQueryFactory;
27+
28+
@Override
29+
public List<QuestionFlatResponse> findQuestionsOrderBy(Long lastId, Long lastEmojiCount,
30+
Long memberId, Long roomId, QuestionSort sortType, Pageable pageable) {
31+
32+
Predicate where = createPredicate(lastId, lastEmojiCount, memberId, roomId,
33+
sortType);
34+
35+
JPAQuery<QuestionFlatResponse> query = jpaQueryFactory
36+
.select(Projections.constructor(QuestionFlatResponse.class,
37+
question.id,
38+
question.content,
39+
question.emojiCount,
40+
JPAExpressions.selectOne().from(answer).where(answer.question.eq(question))
41+
.exists(),
42+
JPAExpressions.selectOne().from(emoji).where(emoji.member.id.eq(memberId)).exists(),
43+
member.id,
44+
member.nickname,
45+
question.createdAt
46+
))
47+
.from(question)
48+
.join(question.member, member)
49+
.where(where)
50+
.orderBy(createOrderSpecifiers(sortType));
51+
52+
return query
53+
.offset(pageable.getOffset())
54+
.limit(pageable.getPageSize())
55+
.fetch();
56+
}
57+
58+
private Predicate createPredicate(Long lastId, Long lastEmojiCount,
59+
Long memberId, Long roomId, QuestionSort sortType) {
60+
61+
BooleanBuilder builder = new BooleanBuilder();
62+
builder.and(question.room.id.eq(roomId));
63+
64+
switch (sortType) {
65+
case CREATEDAT -> {
66+
if (lastId != null) {
67+
builder.and(question.id.lt(lastId));
68+
}
69+
}
70+
case EMOJI -> {
71+
if (lastEmojiCount != null) {
72+
builder.and(
73+
question.emojiCount.lt(lastEmojiCount)
74+
.or(question.emojiCount.eq(lastEmojiCount).and(question.id.lt(lastId)))
75+
);
76+
}
77+
}
78+
case MYQUESTION -> {
79+
builder.and(question.member.id.eq(memberId));
80+
if (lastId != null) {
81+
builder.and(question.id.lt(lastId));
82+
}
83+
}
84+
}
85+
86+
return builder;
87+
}
88+
89+
private OrderSpecifier<?>[] createOrderSpecifiers(QuestionSort sortType) {
90+
91+
List<OrderSpecifier<?>> orders = new ArrayList<>();
92+
93+
// 공감순인 경우에만 emojiCount 정렬 조건 추가
94+
if (sortType.equals(QuestionSort.EMOJI)) {
95+
orders.add(new OrderSpecifier<>(Order.DESC, question.emojiCount));
96+
}
97+
98+
// 최신순, 내 질문
99+
orders.add(new OrderSpecifier<>(Order.DESC, question.id));
100+
101+
return orders.toArray(new OrderSpecifier[0]);
102+
}
103+
}

src/main/java/com/oronaminc/join/question/dao/QuestionRepository.java

Lines changed: 1 addition & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.oronaminc.join.question.domain.Question;
44
import java.util.Optional;
5-
import com.oronaminc.join.question.dto.QuestionFlatResponse;
65
import java.util.List;
76

87
import com.oronaminc.join.room.dto.TopQnADto;
@@ -11,98 +10,10 @@
1110
import org.springframework.data.jpa.repository.Query;
1211
import org.springframework.data.repository.query.Param;
1312

14-
public interface QuestionRepository extends JpaRepository<Question, Long> {
13+
public interface QuestionRepository extends JpaRepository<Question, Long>, QuestionCustomRepository {
1514

1615
Optional<Question> findByIdAndRoomId(Long questionId, Long roomId);
1716

18-
@Query("""
19-
SELECT new com.oronaminc.join.question.dto.QuestionFlatResponse(
20-
q.id,
21-
q.content,
22-
q.emojiCount,
23-
(CASE WHEN EXISTS (
24-
SELECT a FROM Answer a
25-
WHERE a.question = q
26-
) THEN true ELSE false END),
27-
(CASE WHEN EXISTS (
28-
SELECT e FROM Emoji e
29-
WHERE e.member.id = :memberId
30-
AND e.targetType = com.oronaminc.join.emoji.domain.TargetType.QUESTION
31-
AND e.targetId = q.id
32-
) THEN true ELSE false END),
33-
m.id,
34-
m.nickname,
35-
q.createdAt
36-
)
37-
FROM Question q
38-
JOIN q.member m
39-
WHERE :roomId = q.room.id
40-
AND (:lastId IS NULL OR q.id < :lastId)
41-
ORDER BY q.id DESC
42-
""")
43-
List<QuestionFlatResponse> findByCreatedAt(@Param("lastId") Long lastId,
44-
@Param("memberId") Long memberId, @Param("roomId") Long roomId, Pageable pageable);
45-
46-
@Query("""
47-
SELECT new com.oronaminc.join.question.dto.QuestionFlatResponse(
48-
q.id,
49-
q.content,
50-
q.emojiCount,
51-
(CASE WHEN EXISTS (
52-
SELECT a FROM Answer a
53-
WHERE a.question = q
54-
) THEN true ELSE false END),
55-
(CASE WHEN EXISTS (
56-
SELECT e FROM Emoji e
57-
WHERE e.member.id = :memberId
58-
AND e.targetType = com.oronaminc.join.emoji.domain.TargetType.QUESTION
59-
AND e.targetId = q.id
60-
) THEN true ELSE false END),
61-
m.id,
62-
m.nickname,
63-
q.createdAt
64-
)
65-
FROM Question q
66-
JOIN q.member m
67-
WHERE :roomId = q.room.id
68-
AND (:lastEmojiCount IS NULL OR (
69-
q.emojiCount < :lastEmojiCount OR (q.emojiCount = :lastEmojiCount AND q.id < :lastId)
70-
))
71-
ORDER BY q.emojiCount DESC, q.id DESC
72-
""")
73-
List<QuestionFlatResponse> findByEmojiCount(@Param("lastId") Long lastId,
74-
@Param("lastEmojiCount") Long lastEmojiCount,
75-
@Param("memberId") Long memberId, @Param("roomId") Long roomId, Pageable pageable);
76-
77-
@Query("""
78-
SELECT new com.oronaminc.join.question.dto.QuestionFlatResponse(
79-
q.id,
80-
q.content,
81-
q.emojiCount,
82-
(CASE WHEN EXISTS (
83-
SELECT a FROM Answer a
84-
WHERE a.question = q
85-
) THEN true ELSE false END),
86-
(CASE WHEN EXISTS (
87-
SELECT e FROM Emoji e
88-
WHERE e.member.id = :memberId
89-
AND e.targetType = com.oronaminc.join.emoji.domain.TargetType.QUESTION
90-
AND e.targetId = q.id
91-
) THEN true ELSE false END),
92-
m.id,
93-
m.nickname,
94-
q.createdAt
95-
)
96-
FROM Question q
97-
JOIN q.member m
98-
WHERE :roomId = q.room.id
99-
AND q.member.id = :memberId
100-
AND (:lastId IS NULL OR q.id > :lastId)
101-
ORDER BY q.id DESC
102-
""")
103-
List<QuestionFlatResponse> findByMyQuestion(@Param("lastId") Long lastId,
104-
@Param("memberId") Long memberId, @Param("roomId") Long roomId, Pageable pageable);
105-
10617
@Query("""
10718
select q.room.id, count(q)
10819
from Question q

src/main/java/com/oronaminc/join/question/service/QuestionReader.java

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
package com.oronaminc.join.question.service;
22

3-
import java.util.List;
4-
import java.util.Optional;
5-
6-
import com.oronaminc.join.room.dto.TopQnADto;
7-
import org.springframework.data.domain.PageRequest;
8-
import org.springframework.data.domain.Pageable;
9-
import org.springframework.stereotype.Component;
10-
113
import com.oronaminc.join.global.exception.ErrorCode;
124
import com.oronaminc.join.global.exception.ErrorException;
135
import com.oronaminc.join.question.dao.QuestionRepository;
146
import com.oronaminc.join.question.domain.Question;
7+
import com.oronaminc.join.question.domain.QuestionSort;
158
import com.oronaminc.join.question.dto.QuestionFlatResponse;
16-
9+
import com.oronaminc.join.room.dto.TopQnADto;
10+
import java.util.List;
11+
import java.util.Optional;
1712
import lombok.RequiredArgsConstructor;
13+
import org.springframework.data.domain.PageRequest;
14+
import org.springframework.data.domain.Pageable;
15+
import org.springframework.stereotype.Component;
1816

1917
@Component
2018
@RequiredArgsConstructor
2119
public class QuestionReader {
20+
2221
private final QuestionRepository questionRepository;
2322

2423
public Optional<Question> findById(Long questionId) {
@@ -27,7 +26,7 @@ public Optional<Question> findById(Long questionId) {
2726

2827
public Question getById(Long questionId) {
2928
return this.findById(questionId)
30-
.orElseThrow(() -> new ErrorException(ErrorCode.NOT_FOUND_QUESTION));
29+
.orElseThrow(() -> new ErrorException(ErrorCode.NOT_FOUND_QUESTION));
3130
}
3231

3332
public Optional<Question> findByIdAndRoomId(Long questionId, Long roomId) {
@@ -36,19 +35,13 @@ public Optional<Question> findByIdAndRoomId(Long questionId, Long roomId) {
3635

3736
public Question getByIdAndRoomId(Long questionId, Long roomId) {
3837
return this.findByIdAndRoomId(questionId, roomId)
39-
.orElseThrow(() -> new ErrorException(ErrorCode.NOT_FOUND_ROOM_QUESTION));
38+
.orElseThrow(() -> new ErrorException(ErrorCode.NOT_FOUND_ROOM_QUESTION));
4039
}
4140

42-
public List<QuestionFlatResponse> findByCreatedAt(Long lastId, Long memberId, Long roomId, Pageable pageable) {
43-
return questionRepository.findByCreatedAt(lastId, memberId, roomId, pageable);
44-
}
45-
46-
public List<QuestionFlatResponse> findByEmojiCount(Long lastId, Long lastEmojiCount, Long memberId, Long roomId, Pageable pageable) {
47-
return questionRepository.findByEmojiCount(lastId, lastEmojiCount, memberId, roomId, pageable);
48-
}
49-
50-
public List<QuestionFlatResponse> findByMyQuestion(Long lastId, Long memberId, Long roomId, Pageable pageable) {
51-
return questionRepository.findByMyQuestion(lastId, memberId, roomId, pageable);
41+
public List<QuestionFlatResponse> findQuestionsOrderBy(Long lastId, Long lastEmojiCount,
42+
Long memberId, Long roomId, QuestionSort sortType, Pageable pageable) {
43+
return questionRepository.findQuestionsOrderBy(lastId, lastEmojiCount,
44+
memberId, roomId, sortType, pageable);
5245
}
5346

5447
public List<Question> findByRoomId(Long roomId) {
@@ -63,7 +56,9 @@ public boolean existsInRoom(Long roomId) {
6356
return !questionRepository.findByRoomId(roomId).isEmpty();
6457
}
6558

66-
public Long countByRoomId(Long roomId) { return questionRepository.countByRoomId(roomId);}
59+
public Long countByRoomId(Long roomId) {
60+
return questionRepository.countByRoomId(roomId);
61+
}
6762

6863
public List<Question> findTop3Question(Long roomId) {
6964
return questionRepository.findTop3QuestionByRoomId(roomId, PageRequest.of(0,3));

0 commit comments

Comments
 (0)