[feat] 안읽은 알림 여부 조회 api 구현#318
Conversation
Walkthrough유저의 읽지 않은 알림 존재 여부를 조회하는 신규 API(GET /notifications/exists-unchecked)를 추가했다. 이를 위해 IN 포트/서비스/어댑터/리포지토리 계층 전반에 existsUnchecked(Long userId) 흐름을 도입하고, 응답 DTO(NotificationUncheckedExistsResponse)와 통합 테스트를 추가했다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor C as Client
participant WC as NotificationQueryController
participant S as NotificationExistsUncheckedService
participant QP as NotificationQueryPort
participant PA as NotificationQueryPersistenceAdapter
participant R as NotificationQueryRepository(Impl)
participant DB as Database
C->>WC: GET /notifications/exists-unchecked (userId)
WC->>S: existsUnchecked(userId)
S->>QP: existsUnchecked(userId)
QP->>PA: existsUnchecked(userId)
PA->>R: existsByUserIdAndIsCheckedFalse(userId)
R->>DB: Query (userId, isChecked=false)
DB-->>R: Exists? (row/null)
R-->>PA: boolean
PA-->>S: boolean
S-->>WC: boolean
WC-->>C: 200 OK { data: { exists: boolean } }
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
Test Results481 tests 481 ✅ 44s ⏱️ Results for commit c6b5e7b. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepositoryImpl.java (1)
50-59: LGTM! 성능을 고려한 올바른 존재 여부 확인 구현입니다.
selectOne()+fetchFirst()패턴을 사용하여 효과적으로 최적화했습니다:
selectOne()은 전체 엔티티 대신 상수 값만 조회하여 데이터 전송량을 최소화합니다fetchFirst()는 데이터베이스 레벨에서LIMIT 1을 적용하여 조건에 맞는 첫 번째 레코드 발견 시 즉시 중단합니다- 이를 통해 notifications 테이블 전체 스캔을 방지하고 조기 종료(early termination)가 가능합니다
result != null체크를 통한 boolean 변환도 정확합니다.참고 (선택 사항): 의미론적으로 더 명확하게 하려면
selectOne()대신select(notification.notificationId)를 사용할 수도 있지만, 현재 접근 방식도 존재 여부 확인을 위한 유효한 최적화 패턴입니다.src/test/java/konkuk/thip/notification/adapter/in/web/NotificationExistsUncheckedApiTest.java (1)
53-71: JdbcTemplate 직접 사용으로 인한 잠재적 불일치를 확인하세요.Line 59-62에서
JdbcTemplate.update()로is_checked를 직접 수정하면 JPA 영속성 컨텍스트와 DB 상태가 불일치할 수 있습니다.@Transactional환경에서는 문제없지만, 향후 테스트 격리 문제가 발생할 가능성이 있습니다.다음 중 하나를 고려하세요:
- 현재 방식 유지 (현재는 안전함)
- JPA 엔티티를 직접 수정 후
flush()호출:- jdbcTemplate.update( - "UPDATE notifications SET is_checked = TRUE WHERE notification_id = ?", - n1.getNotificationId() - ); + n1.setIsChecked(true); + notificationJpaRepository.flush();Based on learnings (seongjunnoh는 구체적인 기술적 근거를 선호함):
JdbcTemplate사용은 JPA 1차 캐시를 우회하므로, 만약 테스트 도중notificationJpaRepository.findById(n1.getId())를 호출하면 여전히is_checked = false인 엔티티를 반환할 수 있습니다. 현재 테스트는 조회 없이 바로 API를 호출하므로 문제없지만, 일관성을 위해 JPA 방식을 권장합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
src/main/java/konkuk/thip/notification/adapter/in/web/NotificationQueryController.java(3 hunks)src/main/java/konkuk/thip/notification/adapter/in/web/response/NotificationUncheckedExistsResponse.java(1 hunks)src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepository.java(1 hunks)src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/notification/application/port/in/NotificationExistsUncheckedUseCase.java(1 hunks)src/main/java/konkuk/thip/notification/application/port/out/NotificationQueryPort.java(1 hunks)src/main/java/konkuk/thip/notification/application/service/NotificationExistsUncheckedService.java(1 hunks)src/test/java/konkuk/thip/notification/adapter/in/web/NotificationExistsUncheckedApiTest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#285
File: src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java:1-7
Timestamp: 2025-08-31T05:25:14.845Z
Learning: seongjunnoh는 enum 의존성에 대해 유연한 접근을 선호하며, 도메인→어댑터 레이어 참조와 같은 아키텍처 layering 원칙보다 실용적인 구현을 우선시한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.287Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.
🧬 Code graph analysis (1)
src/test/java/konkuk/thip/notification/adapter/in/web/NotificationExistsUncheckedApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(35-403)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (11)
src/main/java/konkuk/thip/notification/adapter/in/web/response/NotificationUncheckedExistsResponse.java (1)
3-9: LGTM! 간결하고 명확한 응답 DTO 구조입니다.단일 boolean 필드를 가진 record와 정적 팩토리 메서드 구성이 적절합니다.
src/main/java/konkuk/thip/notification/application/port/out/NotificationQueryPort.java (1)
15-15: LGTM! Port 인터페이스에 적절하게 추가되었습니다.메서드 시그니처가 명확하고 기존 쿼리 메서드들과 일관성을 유지하고 있습니다.
src/main/java/konkuk/thip/notification/application/port/in/NotificationExistsUncheckedUseCase.java (1)
3-6: LGTM! Use-case 인터페이스가 단일 책임 원칙을 잘 따르고 있습니다.명확한 목적을 가진 간결한 인터페이스 설계입니다.
src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepository.java (1)
15-15: LGTM! Spring Data 네이밍 컨벤션을 잘 따르는 메서드 선언입니다.메서드명이 의미론적으로 명확하며, 조회 조건이 메서드명에 잘 표현되어 있습니다.
src/test/java/konkuk/thip/notification/adapter/in/web/NotificationExistsUncheckedApiTest.java (2)
25-30: 테스트 설정이 적절합니다.
@SpringBootTest,@Transactional,@AutoConfigureMockMvc(addFilters = false)조합으로 통합 테스트 환경이 올바르게 구성되었습니다. 필터 비활성화로 인증/인가 로직을 우회하여 테스트에 집중할 수 있습니다.
37-51: 읽지 않은 알림 존재 케이스 검증이 올바릅니다.테스트 로직이 명확하고 예상 동작을 정확히 검증합니다.
is_checked = false인 알림이 생성된 상태에서 API 호출 시exists = true를 반환하는지 확인합니다.src/main/java/konkuk/thip/notification/application/service/NotificationExistsUncheckedService.java (2)
9-11: 서비스 계층 구성이 적절합니다.
@Service,@RequiredArgsConstructor를 사용한 표준 Spring 서비스 구조이며, 헥사고날 아키텍처의 유스케이스 구현체로 올바르게 위치합니다.
15-19: 읽기 전용 트랜잭션이 적절하게 적용되었습니다.
@Transactional(readOnly = true)로 조회 작업의 의도가 명확하며, 성능 최적화(flush 스킵, DB 읽기 모드)에도 도움이 됩니다. 포트로의 단순 위임 구조도 헥사고날 아키텍처에 부합합니다.src/main/java/konkuk/thip/notification/adapter/in/web/NotificationQueryController.java (2)
11-12: 필요한 import가 추가되었습니다.새로운 엔드포인트와 응답 DTO를 위한 import가 올바르게 추가되었습니다.
31-31: 유스케이스 의존성이 적절하게 주입되었습니다.생성자 기반 주입(
@RequiredArgsConstructor)을 통해NotificationExistsUncheckedUseCase가 올바르게 주입됩니다.src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationQueryPersistenceAdapter.java (1)
43-46: PR 설명과 구현이 일치합니다.
NotificationQueryRepositoryImpl에서 QueryDSL의selectOne().fetchFirst()를 사용해existsByUserIdAndIsCheckedFalse가 구현되어 있어 별도 변경이 필요 없습니다.
| @Operation( | ||
| summary = "유저의 안읽은 알림 존재 여부 확인", | ||
| description = "유저가 읽지 않은 알림이 존재하는지 여부를 확인합니다." | ||
| ) | ||
| @GetMapping("/notifications/exists-unchecked") | ||
| public BaseResponse<NotificationUncheckedExistsResponse> existsUnchecked(@Parameter(hidden = true) @UserId final Long userId) { | ||
| return BaseResponse.ok(NotificationUncheckedExistsResponse.of( | ||
| notificationExistsUncheckedUseCase.existsUnchecked(userId))); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Swagger 문서에 예외 처리 설명이 누락되었습니다.
Lines 38, 51에서 다른 엔드포인트들은 @ExceptionDescription 어노테이션으로 예외 케이스를 문서화하고 있으나, 이 엔드포인트에는 누락되었습니다. API 문서의 일관성을 위해 추가를 권장합니다.
다음과 같이 추가하세요:
@Operation(
summary = "유저의 안읽은 알림 존재 여부 확인",
description = "유저가 읽지 않은 알림이 존재하는지 여부를 확인합니다."
)
+ @ExceptionDescription(NOTIFICATION_EXISTS_UNCHECKED)
@GetMapping("/notifications/exists-unchecked")
public BaseResponse<NotificationUncheckedExistsResponse> existsUnchecked(@Parameter(hidden = true) @UserId final Long userId) {그리고 SwaggerResponseDescription 클래스에 상수를 추가하세요:
public static final String NOTIFICATION_EXISTS_UNCHECKED = """
### 발생 가능한 예외
- [인증/인가 관련 예외]
""";다음 스크립트로 SwaggerResponseDescription 클래스 위치 및 패턴을 확인하세요:
🏁 Script executed:
#!/bin/bash
# Description: SwaggerResponseDescription 클래스 확인 및 패턴 분석
# SwaggerResponseDescription 클래스 찾기
fd -e java SwaggerResponseDescription
# 기존 NOTIFICATION 관련 상수 패턴 확인
rg -nP 'NOTIFICATION_[A-Z_]+\s*=' --type=java -A 3Length of output: 72
🏁 Script executed:
#!/bin/bash
# Find usages of @ExceptionDescription in Java files
rg -n '@ExceptionDescription' -g '*.java'
# Display first 200 lines of SwaggerResponseDescription.java
sed -n '1,200p' src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
# List all public static final String constants in SwaggerResponseDescription.java
rg -n 'public static final String' src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.javaLength of output: 12502
🏁 Script executed:
rg -n 'NOTIFICATION_' src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.javaLength of output: 312
existsUnchecked 엔드포인트에 @ExceptionDescription 및 SwaggerResponseDescription enum constant 추가
- src/main/java/konkuk/thip/notification/adapter/in/web/NotificationQueryController.java 에서
@GetMapping("/notifications/exists-unchecked")바로 위에추가@ExceptionDescription(NOTIFICATION_EXISTS_UNCHECKED)
- src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java 에서
기존 알림 처리 enum(NOTIFICATION_MARK_TO_CHECKED) 다음에형태로 새 enum constant 등록 (실제 던질 수 있는 ErrorCode로 Set.of(...) 보강)NOTIFICATION_EXISTS_UNCHECKED(new LinkedHashSet<>(Set.of( USER_NOT_FOUND ))),
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/notification/adapter/in/web/NotificationQueryController.java
around lines 63 to 71, add the
@ExceptionDescription(NOTIFICATION_EXISTS_UNCHECKED) annotation immediately
above the @GetMapping("/notifications/exists-unchecked") declaration so the
endpoint is annotated for documented exceptions; in
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java,
directly after the existing NOTIFICATION_MARK_TO_CHECKED enum constant add a new
enum constant NOTIFICATION_EXISTS_UNCHECKED(...) that supplies a LinkedHashSet
containing the appropriate ErrorCode(s) that this endpoint can actually throw
(replace Set.of(...) with the real error codes, e.g., USER_NOT_FOUND if
applicable), ensuring imports and enum formatting follow the surrounding enum
structure.
hd0rable
left a comment
There was a problem hiding this comment.
수고하셨습니다~!! 단순궁금 리뷰하나남겨봤습니닷 ㅎㅎ
| private final NotificationQueryPort notificationQueryPort; | ||
|
|
||
| @Override | ||
| @Transactional(readOnly = true) |
|
|
||
| @Override | ||
| public boolean existsByUserIdAndIsCheckedFalse(Long userId) { | ||
| Integer result = queryFactory.selectOne() |
There was a problem hiding this comment.
단순 궁금인데 간단한 쿼리같은데 JPQL대신 queryDsl을 사용한 이유가 따로 있을까요??
There was a problem hiding this comment.
역시 예리하시네요.
첨부한 블로그 내용 확인해주시면 될 것 같습니다!!
https://mirrorofcode.tistory.com/362
There was a problem hiding this comment.
오호 jpql에서는 count 함수밖에 사용하지 못하는데 이는 모든 테이블을 스캔하는 것이고, exists 함수 또는 현재 성준님이 구현하신 것처럼 select 1 limit 1을 사용하면 특정 매치되는 row가 하나만 있으면 바로 스캔이 중단되어서 성능이 더 좋은 것 같네요! 꼼꼼하십니다 굿굿
|
|
||
| @Override | ||
| public boolean existsByUserIdAndIsCheckedFalse(Long userId) { | ||
| Integer result = queryFactory.selectOne() |
There was a problem hiding this comment.
오호 jpql에서는 count 함수밖에 사용하지 못하는데 이는 모든 테이블을 스캔하는 것이고, exists 함수 또는 현재 성준님이 구현하신 것처럼 select 1 limit 1을 사용하면 특정 매치되는 row가 하나만 있으면 바로 스캔이 중단되어서 성능이 더 좋은 것 같네요! 꼼꼼하십니다 굿굿
#️⃣ 연관된 이슈
📝 작업 내용
📸 스크린샷
💬 리뷰 요구사항
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit
신규 기능
테스트