-
Notifications
You must be signed in to change notification settings - Fork 1
[WTH-65] @Query 기반 JPQL을 QueryDSL로 리팩토링 #228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
The head ref may contain hidden characters: "WTH-65-weeth-\uCFFC\uB9AC-\uCD5C\uC801\uD654-\uC791\uC5C5"
Changes from 6 commits
99e2639
63030e3
d369f79
d805a91
0f71fbd
90695aa
bd2642b
a6647fe
4997e03
33f8145
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package leets.weeth.domain.board.domain.repository; | ||
|
|
||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.data.domain.Slice; | ||
|
|
||
| import leets.weeth.domain.board.domain.entity.Post; | ||
|
|
||
| public interface PostRepositoryCustom { | ||
|
|
||
| Slice<Post> findRecentPart(Pageable pageable); | ||
|
|
||
| Slice<Post> searchPart(String kw,Pageable pageable); | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,64 @@ | ||||||||
| package leets.weeth.domain.board.domain.repository; | ||||||||
|
|
||||||||
| import java.util.List; | ||||||||
|
|
||||||||
| import org.springframework.data.domain.Pageable; | ||||||||
| import org.springframework.data.domain.Slice; | ||||||||
| import org.springframework.data.domain.SliceImpl; | ||||||||
| import org.springframework.stereotype.Repository; | ||||||||
|
|
||||||||
| import com.querydsl.jpa.impl.JPAQueryFactory; | ||||||||
|
|
||||||||
| import leets.weeth.domain.board.domain.entity.Post; | ||||||||
| import leets.weeth.domain.board.domain.entity.QPost; | ||||||||
| import leets.weeth.domain.board.domain.entity.enums.Category; | ||||||||
| import lombok.RequiredArgsConstructor; | ||||||||
|
|
||||||||
| @Repository | ||||||||
| @RequiredArgsConstructor | ||||||||
| public class PostRepositoryImpl implements PostRepositoryCustom { | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @repository 어노테이션이 누락되었습니다. 클래스에 다음 diff를 적용하세요: +@Repository
public class PostRepositoryImpl implements PostRepositoryCustom {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 방식의 구현법은 공식문서를 기반으로 구현해주신 걸까요?? 단순 궁금
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://sjh9708.tistory.com/174#google_vignette |
||||||||
|
|
||||||||
| private final JPAQueryFactory queryFactory; | ||||||||
| private final QPost post = QPost.post; | ||||||||
|
|
||||||||
| @Override | ||||||||
| public Slice<Post> findRecentPart(Pageable pageable) { | ||||||||
| List<Post> result = queryFactory | ||||||||
| .selectFrom(post) | ||||||||
| .where(post.category.in(Category.StudyLog, Category.Article)) | ||||||||
| .orderBy(post.id.desc()) | ||||||||
| .offset(pageable.getOffset()) | ||||||||
| .limit(pageable.getPageSize() + 1) | ||||||||
| .fetch(); | ||||||||
|
|
||||||||
| boolean hasNext = result.size() > pageable.getPageSize(); | ||||||||
|
|
||||||||
| if (hasNext) { | ||||||||
| result.remove(pageable.getPageSize()); | ||||||||
| } | ||||||||
|
|
||||||||
| return new SliceImpl<>(result, pageable, hasNext); | ||||||||
| } | ||||||||
|
|
||||||||
| @Override | ||||||||
| public Slice<Post> searchPart(String kw, Pageable pageable) { | ||||||||
| List<Post> result = queryFactory | ||||||||
| .selectFrom(post) | ||||||||
| .where(post.category.in(Category.StudyLog, Category.Article) | ||||||||
| .and(post.title.containsIgnoreCase(kw) | ||||||||
| .or(post.content.containsIgnoreCase(kw)) | ||||||||
|
Comment on lines
+60
to
+61
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 테스트 해보았는데 아무것도 입력하지 않는 경우 호출이 불가합니다! 따라서 npe발생이 안될거라 예상됩니당 Controller 확인 결과 , RequestParam이 붙여져있어서 괜찮을 것 같습니다! |
||||||||
| )) | ||||||||
| .orderBy(post.id.desc()) | ||||||||
| .offset(pageable.getOffset()) | ||||||||
| .limit(pageable.getPageSize() + 1) | ||||||||
| .fetch(); | ||||||||
|
|
||||||||
| boolean hasNext = result.size() > pageable.getPageSize(); | ||||||||
|
|
||||||||
| if (hasNext) { | ||||||||
| result.remove(pageable.getPageSize()); | ||||||||
| } | ||||||||
|
|
||||||||
| return new SliceImpl<>(result, pageable, hasNext); | ||||||||
| } | ||||||||
| } | ||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package leets.weeth.global.config; | ||
|
|
||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| import com.querydsl.jpa.impl.JPAQueryFactory; | ||
|
|
||
| import jakarta.persistence.EntityManager; | ||
|
|
||
| @Configuration | ||
| public class QuerydslConfig { | ||
|
|
||
| @Bean | ||
| public JPAQueryFactory jpaQueryFactory(EntityManager em) { | ||
| return new JPAQueryFactory(em); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| package leets.weeth.domain.board.domain.repository; | ||
|
|
||
| import static org.assertj.core.api.AssertionsForInterfaceTypes.*; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; | ||
| import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; | ||
| import org.springframework.boot.test.context.TestConfiguration; | ||
| import org.springframework.context.annotation.Import; | ||
| import org.springframework.data.domain.PageRequest; | ||
| import org.springframework.data.domain.Pageable; | ||
|
|
||
| import leets.weeth.config.TestContainersConfig; | ||
| import leets.weeth.domain.board.domain.entity.Post; | ||
| import leets.weeth.domain.board.domain.entity.enums.Category; | ||
| import leets.weeth.domain.board.domain.entity.enums.Part; | ||
| import leets.weeth.global.config.QuerydslConfig; | ||
|
|
||
| @DataJpaTest | ||
| @Import({TestContainersConfig.class , QuerydslConfig.class}) | ||
| @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) | ||
| public class PostRepositoryTest { | ||
|
|
||
| @Autowired | ||
| private PostRepository postRepository; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| Post post1 = Post.builder() | ||
| .title("dsl 테스트 제목") | ||
| .content("QueryDSL동작확인") | ||
| .category(Category.StudyLog) | ||
| .cardinalNumber(1) | ||
| .week(1) | ||
| .parts(List.of(Part.BE)) | ||
| .build(); | ||
|
|
||
| Post post2 = Post.builder() | ||
| .title("테스트 제목2") | ||
| .content("2동작2확인2") | ||
| .category(Category.Article) | ||
| .cardinalNumber(2) | ||
| .week(1) | ||
| .parts(List.of(Part.BE)) | ||
| .build(); | ||
|
|
||
| postRepository.saveAll(List.of(post1,post2)); | ||
|
|
||
| } | ||
|
|
||
|
|
||
| @Test | ||
| @DisplayName("findRecentPart(): StudyLog 또는 Article 카테고리 게시글 최신순 조회") | ||
| void findRecentPart_success() { | ||
| //given | ||
| Pageable pageable = PageRequest.of(0, 10); | ||
|
|
||
| //when | ||
| var result = postRepository.findRecentPart(pageable); | ||
|
|
||
| //then | ||
| assertThat(result).isNotNull(); | ||
| assertThat(result.getContent()).hasSize(2); | ||
| assertThat(result.getContent().get(0).getTitle()).isEqualTo("테스트 제목2"); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("searchPart(): 키워드 기반 검색") | ||
| void searchPart_success() { | ||
| //given | ||
| Pageable pageable = PageRequest.of(0, 10); | ||
|
|
||
| //when | ||
| var result = postRepository.searchPart("dsl", pageable); | ||
|
|
||
| //then | ||
| assertThat(result.getContent()).hasSize(1); | ||
| assertThat(result.getContent().get(0).getTitle()).isEqualTo("dsl 테스트 제목"); | ||
| } | ||
| } | ||
|
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
QueryDSL 버전 확인 필요
QueryDSL 5.0.0을 사용 중이지만, 이 버전이 최신 안정 버전인지 확인이 필요합니다. 보안 패치와 버그 수정이 포함된 최신 버전을 사용하는 것이 좋습니다.
🌐 Web query:
💡 Result:
The latest stable QueryDSL release for Jakarta EE is 5.1.0 (checked Nov 11, 2025). [1][2]
Sources:
QueryDSL을 5.1.0으로 업그레이드하세요
현재 QueryDSL 5.0.0을 사용 중이지만, 최신 안정 버전은 5.1.0입니다. 보안 패치와 버그 수정을 위해 build.gradle의 44-45번 줄을 다음과 같이 업데이트하세요:
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://mvnrepository.com/artifact/com.querydsl/querydsl-jpa/5.1.0
확인해보니 쿼리 DSL 전 버전에 취약점이 있네요. 지원 중단돼서 그런가...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
💡 Result:
Short answer: Yes — Querydsl has a reported HQL/SQL injection vulnerability identified as CVE-2024-49203 that affects Querydsl 5.x (including 5.0.0/<=5.1.0 in multiple advisories). The issue concerns injection via JPAQuery.orderBy when untrusted input is used. Recommend not passing untrusted input into orderBy and upgrading to a patched/non-vulnerable release or forked build where the fix is available. [1][2][3][4]
Sources
If you want, I can: (a) check the current latest Querydsl release and which exact versions include the fix, or (b) show concrete mitigations/workarounds and sample code to safely handle ordering parameters.
🌐 Web query:
💡 Result:
Short answer: Querydsl is actively maintained in 2025 and the CVE-2024-49203 HQL/SQL‑injection issue was fixed — upgrade to the patched releases (Querydsl 5.6.1, 6.10.1; OpenFeign Querydsl 7.0 also lists fixes). Apply the update or validate/whitelist any user-controlled orderBy input if you cannot upgrade immediately. [1][2][3]
Sources:
@hyxklee 좋은 지적 감사합니다! 취약점 확인 결과를 공유드립니다:
취약점 정보 (CVE-2024-49203)
JPAQuery.orderBy()에 검증되지 않은 사용자 입력이 전달될 때 발생합니다유지보수 상태
QueryDSL은 여전히 활발히 유지보수되고 있습니다. 지원 중단이 아니며, 해당 취약점은 이미 패치되었습니다.
권장사항
build.gradle의 QueryDSL 버전을 5.6.1 이상으로 업그레이드하세요:
이 PR에서 QueryDSL로 전환하는 시점에 보안 패치가 적용된 버전을 사용하는 것이 중요합니다. 현재 코드를 확인한 결과
orderBy(post.id.desc())처럼 정적 표현식을 사용하고 있어 직접적인 위험은 낮지만, 예방 차원에서 패치된 버전 사용을 강력히 권장합니다.