Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Walkthrough노트 생성 시점을 이번달/지난달으로 분류해 Case 표현식으로 +1/−1/0을 계산하고 액션별로 합산(monthlyChange)한 뒤 Tuple로 받아 매핑해 최대/최솟값을 선택하도록 리포지토리 쿼리와 DTO 필드명이 변경되었고, Querydsl 관련 Gradle 의존성이 변경되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Service
participant Repo as NoteCustomRepositoryImpl
participant DB as Database
rect rgba(0,128,96,0.06)
Service->>Repo: findMostIncreasedPositiveActionChange(memberId, roomId)
Repo->>DB: SELECT action.id, SUM(CASE WHEN createdThisMonth THEN 1 WHEN createdLastMonth THEN -1 ELSE 0 END) AS monthlyChangeSum\nFROM note JOIN action ... WHERE room.id=? AND receiver.id=? AND actionCategory.emotionType=? GROUP BY action.id
DB-->>Repo: List<Tuple>
Repo->>Repo: map Tuple -> new ActionChange(action, monthlyChangeSum)
Repo->>Repo: results.stream().max(Comparator.comparing(ActionChange::monthlyChange)).orElse(null)
Repo-->>Service: ActionChange or null
end
rect rgba(0,64,128,0.04)
Service->>Repo: findMostDecreasedNegativeActionChange(memberId, roomId)
Repo->>DB: (same query)
DB-->>Repo: List<Tuple>
Repo->>Repo: map Tuple -> new ActionChange(...)
Repo->>Repo: results.stream().min(Comparator.comparing(ActionChange::monthlyChange)).orElse(null)
Repo-->>Service: ActionChange or null
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
📜 Recent review detailsConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
⏰ 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)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (1)
204-219: 지난달 구간 상한 계산 오프바이원 버그 가능성
today가 말일인 경우 지난달 마지막 날이 포함되지 않거나, 달 길이 차이(31↔30↔28/29)에 따라 구간 길이가 어긋날 수 있습니다.YearMonth로 동일한 일수 기준(1일~min(오늘 일, 지난달 말일))의 [시작, 다음날 시작] 범위를 명확히 계산하세요.- private NumberExpression<Long> countLastMonthNotes(LocalDate today) { - LocalDate lastMonthOfToday = today.minusMonths(1); - - LocalDateTime startOfLastMonth = lastMonthOfToday.withDayOfMonth(1).atStartOfDay(); - LocalDateTime endOfLastMonth = lastMonthOfToday.plusDays(1).atStartOfDay(); - if (today.getDayOfMonth() == today.lengthOfMonth()) { - endOfLastMonth = - lastMonthOfToday.with(TemporalAdjusters.lastDayOfMonth()).atStartOfDay(); - } + private NumberExpression<Long> countLastMonthNotes(LocalDate today) { + java.time.YearMonth lastYm = java.time.YearMonth.from(today).minusMonths(1); + LocalDateTime startOfLastMonth = lastYm.atDay(1).atStartOfDay(); + int day = Math.min(today.getDayOfMonth(), lastYm.lengthOfMonth()); + LocalDateTime endOfLastMonth = lastYm.atDay(day).plusDays(1).atStartOfDay(); return new CaseBuilder() .when(note.createdAt.goe(startOfLastMonth).and(note.createdAt.lt(endOfLastMonth))) .then(1L) .otherwise(0L) .sum(); }추가로
import java.time.YearMonth;를 파일 상단에 추가하세요.
🧹 Nitpick comments (3)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (3)
81-90: 해석 오류(InterpretationException) 원인 제거에 적합한 변경타입 안전한
new QActionChange(action, increaseCount)사용으로 Hibernate 타입 추론 이슈를 회피합니다. 다만 Action 엔티티 전체를 가져오므로 과도 로딩/지연초기화 이슈 가능성을 고려해, 응답에 필요한 필드만 스칼라로 투영하는 전용 DTO도 검토해 주세요(예:action.text,change).
101-110: MOST decreased 계산 투영 방식 적절
new QActionChange(action, decreaseCount)전환 적절합니다. 동률 발생 시 결정성을 위해 보조 정렬 기준(예:action.id.asc()) 추가를 고려해 주세요.- .orderBy(decreaseCount.asc()) + .orderBy(decreaseCount.asc(), action.id.asc())
193-201: 네이밍 혼동:endOfThisMonth는 ‘내일 0시’입니다실제 의미는 “오늘 다음날 0시”이므로 변수명을 정정하면 오독을 줄일 수 있습니다.
- LocalDateTime endOfThisMonth = today.plusDays(1).atStartOfDay(); + LocalDateTime endOfTodayPlusOne = today.plusDays(1).atStartOfDay(); ... - .when(note.createdAt.goe(startOfThisMonth).and(note.createdAt.lt(endOfThisMonth))) + .when(note.createdAt.goe(startOfThisMonth).and(note.createdAt.lt(endOfTodayPlusOne)))
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/main/java/com/example/wini/domain/log/dto/response/ActionChange.java(1 hunks)src/main/java/com/example/wini/domain/log/dto/response/SimpleActionChange.java(1 hunks)src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java(3 hunks)
⏰ 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-deploy
🔇 Additional comments (3)
src/main/java/com/example/wini/domain/log/dto/response/SimpleActionChange.java (1)
5-5: NPE 방지:from메서드에서actionChange또는actionChange.getAction()이 null일 경우 NPE 발생
호출부가 절대 null을 넘기지 않는지 확인하거나, 그렇지 않다면 해당 메서드에 방어 로직(Null 체크 및 기본값 처리)을 추가하세요.src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (1)
136-152: 루프 스텝을 7일 단위로 변경 검토 요청루프에서
endDateTime을 하루 단위가 아닌 1주 단위로 감소하도록 수정이 필요해 보입니다.- endDateTime = endDateTime.minusDays(1); + endDateTime = endDateTime.minusWeeks(1);UI/요구사항이 일별 슬라이딩 윈도우인지, 비중첩 주별(7일) 집계인지 확인 부탁드립니다.
src/main/java/com/example/wini/domain/log/dto/response/ActionChange.java (1)
7-18: ActionChange 클래스: 필드를 final로 선언하고 @EqualsAndHashCode·@tostring 추가
필드action과change를final로 선언하고 Lombok의@EqualsAndHashCode,@ToString을 추가하면 값 객체로서의 불변성·동등성을 보장합니다. QueryDSL@QueryProjection과도 충돌이 없습니다.
change가 절대 null이 아니라면Long대신 원시형long사용을 검토하세요. null 가능성이 있다면 현 상태를 유지하십시오.
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (1)
8-8: QActionChange 사용 시 APT(Annotation Processor) 설정/생성물 확인 필요
@QueryProjection기반QActionChange사용은 적절합니다. 다만 빌드 스크립트에 QueryDSL APT 설정이 없으면 컴파일 실패합니다. 이전 코멘트에서도 동일 지적이 있었으므로 재확인 바랍니다.다음 스크립트로 Gradle 설정 및 사용처를 점검해 주세요:
#!/bin/bash # QueryDSL APT 설정 및 QActionChange 사용 여부 확인 rg -n -C2 -g 'build.gradle*' -P 'annotationProcessor\\s+["'\'']com\\.querydsl:querydsl-apt.*(jakarta|jpa)["'\'']|kapt\\s+["'\'']com\\.querydsl:querydsl-apt.*(jakarta|jpa)["'\'']' rg -n -C2 -P '\bQActionChange\b' src/main/java필요하시면 Gradle(Groovy/KTS) 설정 스니펫을 만들어 드리겠습니다.
🧹 Nitpick comments (1)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (1)
25-25: 정렬 전략 확정 후 불필요한 import 정리현재
Comparator는 스트림 정렬에만 사용됩니다. DB 정렬로 복원하거나max/min로 대체할 경우 import가 불필요해질 수 있으니 최종 전략에 맞춰 정리해 주세요.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java(4 hunks)
⏰ 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-deploy
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java
Outdated
Show resolved
Hide resolved
| List<ActionChange> results = queryFactory | ||
| .select(new QActionChange(action, decreaseCount.longValue())) | ||
| .from(note) | ||
| .join(note.action, action) | ||
| .join(action.actionCategory, actionCategory) | ||
| .where(isThisRoom(roomId) | ||
| .and(isReceiver(memberId)) | ||
| .and(actionCategory.emotionType.eq(EmotionType.NEGATIVE))) | ||
| .groupBy(action) | ||
| .orderBy(decreaseCount.asc()) | ||
| .fetchFirst(); | ||
| .fetch(); | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
NEGATIVE 구간도 DB 정렬로 일관되게 처리하고 동순위 안정화
위와 동일한 이유로 인메모리 정렬 대신 DB에서 최소값 1건만 조회하세요. 동순위는 action.id로 안정화합니다.
- List<ActionChange> results = queryFactory
- .select(new QActionChange(action, decreaseCount.longValue()))
+ return queryFactory
+ .select(new QActionChange(action, decreaseCount.longValue()))
.from(note)
.join(note.action, action)
.join(action.actionCategory, actionCategory)
.where(isThisRoom(roomId)
.and(isReceiver(memberId))
.and(actionCategory.emotionType.eq(EmotionType.NEGATIVE)))
- .groupBy(action)
- .fetch();
-
- return results.stream()
- .sorted(Comparator.comparing(ActionChange::getChange))
- .findFirst()
- .orElse(null);
+ .groupBy(action)
+ .orderBy(decreaseCount.longValue().asc(), action.id.asc())
+ .fetchFirst();- 인메모리로 유지한다면 다음처럼 O(n)
min()을 사용하세요:
- return results.stream()
- .sorted(Comparator.comparing(ActionChange::getChange))
- .findFirst()
- .orElse(null);
+ return results.stream()
+ .min(Comparator.comparing(ActionChange::getChange))
+ .orElse(null);Also applies to: 116-120
다음 스크립트를 실행하여 decreaseCount 정의 및 queryFactory 타입을 확인하겠습니다.
🏁 Script executed:
# 1. 클래스 헤더 및 import 확인
sed -n '1,200p' src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java
# 2. decreaseCount 정의 위치 검색
rg -n "decreaseCount" -C3 src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java
# 3. queryFactory 선언부 검색
rg -n "queryFactory" -C3 src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.javaLength of output: 12325
DB 정렬·fetchFirst()로 in-memory 정렬 제거 및 동순위 안정화
findMostDecreasedNegativeActionChange 메서드에서 현재 결과를 모두 조회(fetch)한 뒤 Java 스트림으로 정렬하는 대신, DB 쿼리 단계에서 ORDER BY decreaseCount ASC, action.id ASC 와 fetchFirst()를 사용해 최소값 1건만 조회하세요.
- List<ActionChange> results = queryFactory
- .select(new QActionChange(action, decreaseCount.longValue()))
- .from(note)
- .join(note.action, action)
- .join(action.actionCategory, actionCategory)
- .where(isThisRoom(roomId)
- .and(isReceiver(memberId))
- .and(actionCategory.emotionType.eq(EmotionType.NEGATIVE)))
- .groupBy(action)
- .fetch();
-
- return results.stream()
- .sorted(Comparator.comparing(ActionChange::getChange))
- .findFirst()
- .orElse(null);
+ return queryFactory
+ .select(new QActionChange(action, decreaseCount.longValue()))
+ .from(note)
+ .join(note.action, action)
+ .join(action.actionCategory, actionCategory)
+ .where(isThisRoom(roomId)
+ .and(isReceiver(memberId))
+ .and(actionCategory.emotionType.eq(EmotionType.NEGATIVE)))
+ .groupBy(action)
+ .orderBy(decreaseCount.asc(), action.id.asc())
+ .fetchFirst();—인메모리 처리를 유지해야 한다면, 스트림 정렬 대신 min()을 사용하세요:
- return results.stream()
- .sorted(Comparator.comparing(ActionChange::getChange))
- .findFirst()
- .orElse(null);
+ return results.stream()
+ .min(Comparator.comparing(ActionChange::getChange))
+ .orElse(null);동일한 로직을 findMostIncreasedPositiveActionChange 에도 적용하세요 (increaseCount.desc(), action.id.asc() + fetchFirst()).
🤖 Prompt for AI Agents
In
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java
around lines 105 to 115, the query currently fetches all ActionChange rows and
sorts in-memory; change the QueryDSL query to order at the DB level and return
only the first row by adding .orderBy(decreaseCount.asc(), action.id.asc()) and
using .fetchFirst() so the database returns the single minimum entry; apply the
same pattern for findMostIncreasedPositiveActionChange with
.orderBy(increaseCount.desc(), action.id.asc()) and .fetchFirst(); if you must
keep in-memory processing instead, replace the stream sorting with
.min(Comparator.comparing(...)) to avoid unnecessary full sorting.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (3)
13-13: Tuple/Comparator import 제거하고 @QueryProjection(QActionChange)로 일관화하세요이 파일은 Tuple+스트림 매핑을 사용하지만, PR 설명대로 DTO에 @QueryProjection을 적용했다면 QActionChange로 직접 매핑하는 편이 타입 안정성과 성능 모두에 유리합니다. 아래 리팩토링을 적용하면 두 import가 불필요해집니다.
- import com.querydsl.core.Tuple; + import com.example.wini.domain.log.dto.response.QActionChange; ... - import java.util.Comparator;Also applies to: 25-25
82-96: DB에서 정렬 및 상위 1건만 조회(fetchFirst)로 변경 + QActionChange 직접 프로젝션
인메모리 전건 fetch 후 정렬(O(n log n))은 불필요합니다. DB가 group/order/limit 1을 수행하도록 바꾸고, 이미 적용한 @QueryProjection을 활용해 바로 QActionChange로 받으세요. 동순위 안정화를 위해 action.id ASC를 세컨더리 키로 둡니다. 아울러 2개월 범위로 where 절을 제한합니다.- List<Tuple> resultsTuple = queryFactory - .select(action, increaseCount) - .from(note) - .join(note.action, action) - .join(action.actionCategory, actionCategory) - .where(isThisRoom(roomId) - .and(isReceiver(memberId)) - .and(actionCategory.emotionType.eq(EmotionType.POSITIVE))) - .groupBy(action) - .fetch(); - - return resultsTuple.stream() - .map(tuple -> new ActionChange(tuple.get(action), tuple.get(increaseCount))) - .max(Comparator.comparing(ActionChange::getChange)) - .orElse(null); + return queryFactory + .select(new QActionChange(action, increaseCount)) + .from(note) + .join(note.action, action) + .join(action.actionCategory, actionCategory) + .where(isThisRoom(roomId) + .and(isReceiver(memberId)) + .and(actionCategory.emotionType.eq(EmotionType.POSITIVE)) + // 2개월 범위로 스캔 제한 + .and(note.createdAt.goe(today.minusMonths(1).withDayOfMonth(1).atStartOfDay())) + .and(note.createdAt.lt(today.plusDays(1).atStartOfDay()))) + .groupBy(action) + .orderBy(increaseCount.desc(), action.id.asc()) + .fetchFirst();
102-106: NEGATIVE 구간도 동일하게: DB 정렬 + fetchFirst, QActionChange 사용
위와 동일한 이유로 전건 fetch/인메모리 정렬을 제거하고, 최소값 1건만 DB에서 조회하세요. 동순위 안정화 포함.- NumberExpression<Long> decreaseCount = calculateMonthlyChange(today); + NumberExpression<Long> decreaseCount = calculateMonthlyChange(today); - List<Tuple> resultsTuple = queryFactory - .select(action, decreaseCount) - .from(note) - .join(note.action, action) - .join(action.actionCategory, actionCategory) - .where(isThisRoom(roomId) - .and(isReceiver(memberId)) - .and(actionCategory.emotionType.eq(EmotionType.NEGATIVE))) - .groupBy(action) - .fetch(); - - return resultsTuple.stream() - .map(tuple -> new ActionChange(tuple.get(action), tuple.get(decreaseCount))) - .min(Comparator.comparing(ActionChange::getChange)) - .orElse(null); + return queryFactory + .select(new QActionChange(action, decreaseCount)) + .from(note) + .join(note.action, action) + .join(action.actionCategory, actionCategory) + .where(isThisRoom(roomId) + .and(isReceiver(memberId)) + .and(actionCategory.emotionType.eq(EmotionType.NEGATIVE)) + // 2개월 범위로 스캔 제한 + .and(note.createdAt.goe(today.minusMonths(1).withDayOfMonth(1).atStartOfDay())) + .and(note.createdAt.lt(today.plusDays(1).atStartOfDay()))) + .groupBy(action) + .orderBy(decreaseCount.asc(), action.id.asc()) + .fetchFirst();Also applies to: 107-121
🧹 Nitpick comments (1)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (1)
77-81: 월별 증감치 계산 방식 전환은 합리적(빼기 → 부호합), 다만 타입 고정 옵션 고려sum(case…) - sum(case…) 대신 단일 CaseBuilder(+1/-1)의 합으로 바꾼 것은 Hibernate 6의 argType NPE 회피에 효과적입니다. 다만 특정 DB/방언 조합에서 여전히 타입 해석 문제가 재현되면 increase/decrease 식을 한 번 감싸 Long 타입을 강제하는 안전장치를 추가하세요.
+ import com.querydsl.core.types.dsl.Expressions; @@ - NumberExpression<Long> increaseCount = calculateMonthlyChange(today); + NumberExpression<Long> increaseCount = calculateMonthlyChange(today); + // (옵션) Hibernate 6 NPE 방지용 타입 고정 + // NumberExpression<Long> increaseCount = + // Expressions.numberTemplate(Long.class, "{0}", calculateMonthlyChange(today));또한 현재는 전 기간을 스캔하고 Case로 0을 누적합니다. where 절에 “지난달 1일 00:00 ≤ createdAt < 내일 00:00” 범위를 추가하면 스캔 범위가 2개월로 제한되어 성능에 유리합니다(아래 다른 코멘트의 diff에 포함). 기존 countThisMonthNotes/countLastMonthNotes는 더 이상 쓰이지 않으므로 제거 또는 @deprecated 권장.
Also applies to: 232-251
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java(4 hunks)
⏰ 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). (2)
- GitHub Check: build-deploy
- GitHub Check: build
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (2)
8-8: QActionChange 사용에 따른 APT(Annotation Processor) 설정 검증 필요
@QueryProjection→QActionChange전환은 적절합니다. 다만 QueryDSL APT 설정이 누락되면 빌드 시QActionChange생성 실패로 런타임 오류가 납니다. 빌드 스크립트(kapt/annotationProcessor)와 소스셋을 확인해 주세요.다음 스크립트로 빌드 설정을 점검해 보세요:
#!/bin/bash # QueryDSL APT 설정 및 Q타입 사용처 점검 rg -n -C2 -g 'build.gradle*' -P '(kapt|annotationProcessor).*querydsl-apt.*:jpa' rg -n -C2 '\bQActionChange\b' src/main/java
26-26: DB에서 정렬·상위 1건만 조회하세요(fetchFirst) — in-memory 정렬 제거 및 동순위 안정화현재 전체
fetch()후 Java 스트림으로max/min을 구하고 있어 불필요한 전송/정렬 비용이 큽니다. DB에서ORDER BY+fetchFirst()로 상위 1건만 가져오면 성능과 결정성이 개선되고, Hibernate 6의 타입 추론 이슈를 피할 가능성도 높습니다. 동순위는action.id로 안정화하세요.아래처럼 변경을 제안합니다:
- import java.util.Comparator; + // Comparator 불필요 → 제거 @@ - List<ActionChange> results = queryFactory - .select(new QActionChange(action, increaseCount)) + return queryFactory + .select(new QActionChange(action, increaseCount)) .from(note) .join(note.action, action) .join(action.actionCategory, actionCategory) .where(isThisRoom(roomId) .and(isReceiver(memberId)) .and(actionCategory.emotionType.eq(EmotionType.POSITIVE))) .groupBy(action) - .fetch(); - - return results.stream() - .max(Comparator.comparing(ActionChange::getChange)) - .orElse(null); + .orderBy(increaseCount.desc(), action.id.asc()) + .fetchFirst(); @@ - List<ActionChange> results = queryFactory - .select(new QActionChange(action, decreaseCount)) + return queryFactory + .select(new QActionChange(action, decreaseCount)) .from(note) .join(note.action, action) .join(action.actionCategory, actionCategory) .where(isThisRoom(roomId) .and(isReceiver(memberId)) .and(actionCategory.emotionType.eq(EmotionType.NEGATIVE))) .groupBy(action) - .fetch(); - - return results.stream() - .min(Comparator.comparing(ActionChange::getChange)) - .orElse(null); + .orderBy(decreaseCount.asc(), action.id.asc()) + .fetchFirst();참고: 만약 여전히 Hibernate 6에서
InterpretationException(argType == null)이 발생하면,orderBy(increaseCount.longValue().desc())처럼.longValue()를 적용하거나,Expressions.numberTemplate(Long.class, "{0}", increaseCount)로 숫자 타입을 강제하는 우회도 고려하세요.Also applies to: 80-89, 91-93, 101-110, 112-114
🧹 Nitpick comments (2)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (2)
78-79: 월 증감 계산식을 공통 메서드로 추출한 점 좋습니다두 메서드에서 동일 표현식을 재사용해 일관성과 유지보수성이 좋아졌습니다. 이 값은 “이번 달 - 지난 달” 순증을 나타내므로 명칭을
monthlyChange등으로 통일하면 의미 전달이 더 명확합니다(선택).Also applies to: 99-100
18-18: Expressions.asDateTime 대신 파라미터 바인딩 사용 권장 — 실행 계획 캐시 및 DB 호환성 개선
note.createdAt.goe(Expressions.asDateTime(...))는 리터럴 캐스팅을 유발해 DB 계획 캐시 재사용성이 떨어질 수 있습니다.LocalDateTime값을 직접 바인딩하면 단순하고 이식성이 좋습니다. 또한Expressionsimport도 제거 가능합니다.- import com.querydsl.core.types.dsl.Expressions; @@ - return new CaseBuilder() - .when(note.createdAt - .goe(Expressions.asDateTime(startOfThisMonth)) - .and(note.createdAt.lt(Expressions.asDateTime(endOfThisMonth)))) + return new CaseBuilder() + .when(note.createdAt.goe(startOfThisMonth) + .and(note.createdAt.lt(endOfThisMonth))) .then(1L) - .when(note.createdAt - .goe(Expressions.asDateTime(startOfLastMonth)) - .and(note.createdAt.lt(Expressions.asDateTime(endOfLastMonth)))) + .when(note.createdAt.goe(startOfLastMonth) + .and(note.createdAt.lt(endOfLastMonth))) .then(-1L) .otherwise(0L) .sum();Also applies to: 197-218
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java(3 hunks)
⏰ 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-deploy
🔇 Additional comments (1)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (1)
78-78: 시간대 기준 확인 요청(서버 TZ vs. DB TZ 불일치 가능성)
LocalDate.now()를 기준으로 월 경계를 계산합니다. 애플리케이션 JVM 시간대와 DB 서버/컬럼 시간대가 다르면 경계 오차가 날 수 있습니다. 운영 환경의 TZ 정책(예: Asia/Seoul 고정)과 JPA/Hibernate의 JDBC 시간대 설정을 확인해 주세요.Also applies to: 99-99
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/main/java/com/example/wini/domain/log/dto/response/ActionChange.java (1)
5-5: @QueryProjection 누락: 생성자에 부착하고 레포지토리에서 QActionChange 사용으로 일관화하세요QueryDSL 타입 세이프 프로젝션을 위해 레코드의 canonical 생성자에
@QueryProjection이 필요합니다. 현재 상태에선Projections.constructor(...)사용 시 런타임 타입 미스매치/해석 오류 재발 여지가 큽니다. 레포지토리에서는new QActionChange(...)로 교체하는 것을 권장합니다.다음 패치를 제안합니다:
-public record ActionChange(Action action, long monthlyChange) {} +public record ActionChange(Action action, long monthlyChange) { + @com.querydsl.core.annotations.QueryProjection + public ActionChange {} +}검증 스크립트:
#!/bin/bash # 1) @QueryProjection 적용 여부 rg -n '@QueryProjection' src/main/java/com/example/wini/domain/log/dto/response/ActionChange.java # 2) QActionChange 사용 여부 rg -n '\bQActionChange\b' src/main/java # 3) 기존 constructor 기반 프로젝션 잔존 여부 rg -nP --type=java -C2 'Projections\.constructor\(\s*ActionChange\.class' src/main/java # 4) Querydsl APT 설정 확인 (Gradle) rg -n -C2 -g 'build.gradle*' -P 'annotationProcessor\s+["'\'']com\.querydsl:querydsl-apt.*:(jpa|jakarta)["'\'']' # 5) 합산/계산식 Long 타입 강제 여부(예: numberTemplate(Long.class, ...)) rg -nP --type=java -C2 'Expressions\.numberTemplate\s*\(\s*Long\.class' src/main/java
🧹 Nitpick comments (1)
src/main/java/com/example/wini/domain/log/dto/response/ActionChange.java (1)
5-5: DTO에 엔티티(Action) 직접 포함 시 직렬화/N+1 리스크응답 DTO 단계까지 전달된다면 지연 로딩/직렬화 순환 참조 및 N+1 이슈가 생길 수 있습니다. 필요 필드만 담는 경량 DTO(
actionId,actionName등)로 매핑하거나, 조회 시점에 명시적 fetch/프로젝션으로 해결하는 방안을 고려해 주세요.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/main/java/com/example/wini/domain/log/dto/response/ActionChange.java(1 hunks)src/main/java/com/example/wini/domain/log/dto/response/SimpleActionChange.java(1 hunks)src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/com/example/wini/domain/log/dto/response/SimpleActionChange.java
- src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java
⏰ 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). (2)
- GitHub Check: build-deploy
- GitHub Check: build
🔇 Additional comments (1)
src/main/java/com/example/wini/domain/log/dto/response/ActionChange.java (1)
5-5: long으로의 통일 Good집계(SUM/CASE) 결과 타입과 정합성이 맞아 안정성이 올라갑니다.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (2)
88-97: 정렬·상위 1건 반환은 DB에 위임 + QActionChange로 직접 투영모든 그룹을 fetch 후 스트림 정렬하는 대신 DB에서 정렬하고 1건만 받으면 비용/결정성이 개선됩니다. 또한 @QueryProjection을 활용해 직접 DTO로 투영하세요. tie-breaker로 action.id ASC를 추가해 안정화하세요.
- List<Tuple> results = queryFactory - .select(action, monthlyChangeSum) - .from(note) - .join(note.action, action) - .join(action.actionCategory, actionCategory) - .where(isThisRoom(roomId) - .and(isReceiver(memberId)) - .and(actionCategory.emotionType.eq(EmotionType.POSITIVE))) - .groupBy(action.id) - .fetch(); - - return results.stream() - .map(t -> new ActionChange(t.get(action), t.get(monthlyChangeSum))) - .max(Comparator.comparing(ActionChange::monthlyChange)) - .orElse(null); + return queryFactory + .select(new QActionChange(action, monthlyChangeSum)) + .from(note) + .join(note.action, action) + .join(action.actionCategory, actionCategory) + .where(isThisRoom(roomId) + .and(isReceiver(memberId)) + .and(actionCategory.emotionType.eq(EmotionType.POSITIVE))) + .groupBy(action) + .orderBy(monthlyChangeSum.desc(), action.id.asc()) + .fetchFirst();참고: 만약 @QueryProjection을 당장 쓰기 어렵다면 Tuple을 유지하되
.orderBy(...).fetchFirst()로 바꾸고 null 체크 후 매핑만 수행하세요.Also applies to: 99-102
119-128: NEGATIVE도 DB 정렬+fetchFirst로 일관 처리최솟값 선정을 DB에 위임하고 tie-breaker를 명시하세요. DTO 직접 투영을 권장합니다.
- List<Tuple> results = queryFactory - .select(action, monthlyChangeSum) - .from(note) - .join(note.action, action) - .join(action.actionCategory, actionCategory) - .where(isThisRoom(roomId) - .and(isReceiver(memberId)) - .and(actionCategory.emotionType.eq(EmotionType.NEGATIVE))) - .groupBy(action.id) - .fetch(); - - return results.stream() - .map(t -> new ActionChange(t.get(action), t.get(monthlyChangeSum))) - .min(Comparator.comparing(ActionChange::monthlyChange)) - .orElse(null); + return queryFactory + .select(new QActionChange(action, monthlyChangeSum)) + .from(note) + .join(note.action, action) + .join(action.actionCategory, actionCategory) + .where(isThisRoom(roomId) + .and(isReceiver(memberId)) + .and(actionCategory.emotionType.eq(EmotionType.NEGATIVE))) + .groupBy(action) + .orderBy(monthlyChangeSum.asc(), action.id.asc()) + .fetchFirst();Also applies to: 130-133
🧹 Nitpick comments (6)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (6)
13-13: @QueryProjection을 쓸 거라면 Tuple import 제거 가능DTO에 @QueryProjection을 붙였다면 Tuple 기반 매핑을 없애고 QActionChange로 투영하세요. 그러면 타입 안정성과 가독성이 개선되고 런타임 타입 해석 이슈도 줄어듭니다.
-import com.querydsl.core.Tuple; +import com.example.wini.domain.log.dto.response.QActionChange;
25-25: DB 정렬+fetchFirst 적용 시 Comparator 불필요정렬을 DB로 옮기면 in-memory Comparator가 필요 없습니다.
-import java.util.Comparator;
78-86: sum + coalesce(Long) 처리 OK. 중복 로직은 헬퍼로 추출 권장타입을 Long으로 고정하고 coalesce(0L) 한 점은 적절합니다. 동일 블록이 아래 메서드에 중복되므로 헬퍼로 추출을 권장합니다.
아래 메서드를 추가하고 두 곳에서 재사용:
private NumberExpression<Long> monthlyChangeSum(LocalDate today) { NumberExpression<Long> mc = new CaseBuilder() .when(isCreatedThisMonth(today)).then(1L) .when(isCreatedLastMonth(today)).then(-1L) .otherwise(0L); return mc.sum().coalesce(0L); }
109-118: NEGATIVE 쿼리도 월변화 합계 생성 로직 중복 — 헬퍼 재사용 권장위와 동일한 monthlyChangeSum 생성이 반복됩니다. 앞서 제안한 헬퍼로 통일해 유지보수성을 높이세요.
96-96: groupBy(action.id) → groupBy(action)로 JPA 호환성 강화엔티티를 select 하면서 id만 groupBy 하면 DB/벼전 조합에 따라 경고나 오류가 나올 수 있습니다. 엔티티 Path로 groupBy 해 JPA 스펙과 일치시키는 편이 안전합니다.
- .groupBy(action.id) + .groupBy(action)Also applies to: 127-127
225-233: 지난 달 경계도 OK — 테스트 용이성 위해 Clock 주입 고려테스트에서 고정 시점을 다루기 쉽도록 Clock을 주입해 today를 계산하는 방식을 고려해 보세요.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/main/java/com/example/wini/domain/log/dto/response/ActionChange.java(1 hunks)src/main/java/com/example/wini/domain/log/dto/response/SimpleActionChange.java(1 hunks)src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/com/example/wini/domain/log/dto/response/ActionChange.java
- src/main/java/com/example/wini/domain/log/dto/response/SimpleActionChange.java
⏰ 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-deploy
🔇 Additional comments (2)
src/main/java/com/example/wini/domain/note/repository/NoteCustomRepositoryImpl.java (2)
216-223: 이번 달 경계 설정(포함/제외) 적절함 — TZ 차이만 확인[YYYY-MM-01 00:00, 다음 달 00:00) 범위 설정은 바람직합니다. 다만 createdAt이 DB UTC, 애플리케이션이 시스템 TZ라면 경계 오차가 생길 수 있으니 ZoneId/Clock 주입 여부를 확인해 주세요.
85-86: .longValue() 중복 캐스팅 여부 확인 필요
monthlyChange.sum()이 이미 NumberExpression을 반환하므로 불필요하다면 .longValue()를 제거해 단순화하세요. Hibernate 6 타입 추론 회피 목적이라면 유지 가능합니다. QueryDSL Q타입 생성 및 사용 여부를 직접 검증해 주세요.
duol9
left a comment
There was a problem hiding this comment.
호환성이 이리도 안맞는다니 ㅠㅠㅠ 고생하셧습니다!!!!!!!!!!!!!
🌱 관련 이슈
📌 작업 내용 및 특이사항
📝 참고사항
📚 기타
Summary by CodeRabbit