Skip to content

Dating Exam Answer Personality type 추가#437

Merged
hainho merged 10 commits into
mainfrom
feat/dating-exam-personality-type
Mar 17, 2026
Merged

Dating Exam Answer Personality type 추가#437
hainho merged 10 commits into
mainfrom
feat/dating-exam-personality-type

Conversation

@hainho
Copy link
Copy Markdown
Contributor

@hainho hainho commented Mar 4, 2026

Summary by CodeRabbit

  • 신규 기능

    • 인증된 사용자의 우세 성격 유형을 조회하는 API 엔드포인트와 응답 포맷이 추가되었습니다.
  • 도메인 / 영속성

    • 답변에 성격 타입(enum) 필드 및 제출 결과를 저장·갱신하는 엔티티와 저장소가 추가되었습니다.
    • 제출 시 집계 로직이 실시간으로 결과를 업데이트하도록 변경되었습니다.
  • 데이터베이스 마이그레이션

    • 답변 테이블에 성격 타입 컬럼이 추가되고, 제출 결과 테이블과 초기 시험 데이터가 추가/초기화되었습니다.
  • 테스트

    • 제출 결과 집계 및 우세 유형 결정 로직을 검증하는 단위 테스트가 보강되었습니다.

참고 자료

노트

  • Dating Exam Answer에 Personality Type 컬럼 추가
  • Dating Exam 을 Submit 할때 Answer 의 Personality Type 을 구분 하여 count 하여 submit result 에 반영
  • count가 가장 높은 personality type 조회 api 구현
  • 기존 dating exam 데이터 제거 및 새로운 데이터 추가 flyway 스크립트 작성

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

이 PR은 사용자별 dominant personality type 집계 및 조회 기능을 추가합니다. AnswerPersonalityType enum과 DatingExamAnswer.personalityType 필드를 도입하고, 사용자 집계용 JPA entity DatingExamSubmitResult 및 관련 Repository(DatingExamSubmitResultRepository, DatingExamAnswerRepository)를 추가했습니다. submit 흐름에서 제출된 answerId로 Answer를 조회해 personality별 counts를 집계하고, 비관적 락(findByMemberIdForUpdate)으로 DatingExamSubmitResult를 생성·갱신(updateSubmitResult)합니다. Query layer(DatingExamQueryService, DatingExamFinder)에 findDominantPersonalityType(memberId)를 추가하고, API(DatingExamApi)에 DominantPersonalityTypeResponse DTO를 반환하는 엔드포인트를 노출합니다. Flyway migrations(V11–V14)과 seed 데이터도 포함됩니다.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant API as DatingExamApi
    participant ModifyService as DatingExamModifyService
    participant AnswerRepo as DatingExamAnswerRepository
    participant ResultRepo as DatingExamSubmitResultRepository
    participant Database as Database

    Client->>API: POST /dating-exam/submit
    API->>ModifyService: submitDatingExam(request, memberId)
    ModifyService->>AnswerRepo: findAllByIdIn(answerIds)
    AnswerRepo->>Database: SELECT answers (includes personality_type)
    Database-->>AnswerRepo: answers
    AnswerRepo-->>ModifyService: answers
    ModifyService->>ModifyService: aggregate counts by AnswerPersonalityType
    ModifyService->>ResultRepo: findByMemberIdForUpdate(memberId)
    ResultRepo->>Database: SELECT ... FOR UPDATE
    Database-->>ResultRepo: existing result or empty
    ResultRepo-->>ModifyService: result
    ModifyService->>ModifyService: create/update DatingExamSubmitResult.addCounts(...)
    ModifyService->>ResultRepo: save(result)
    ResultRepo->>Database: INSERT/UPDATE dating_exam_submit_result
    Database-->>ResultRepo: OK
    ResultRepo-->>ModifyService: saved result
    ModifyService-->>API: submission complete
    API-->>Client: 200 OK

    Client->>API: GET /dating-exam/dominant-personality
    API->>QueryService: findDominantPersonalityType(memberId)
    participant QueryService as DatingExamQueryService
    QueryService->>ResultRepo: findByMemberId(memberId)
    ResultRepo->>Database: SELECT *
    Database-->>ResultRepo: submit result
    ResultRepo-->>QueryService: submit result
    QueryService->>API: DominantPersonalityTypeResponse
    API-->>Client: BaseResponse<DominantPersonalityTypeResponse>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 변경사항의 핵심을 명확하게 요약하고 있습니다. 'Dating Exam Answer Personality type 추가'는 이번 PR의 주요 변경사항인 Personality Type 컬럼 추가를 정확하게 반영합니다.
Description check ✅ Passed PR description은 repository의 template 구조를 준수하고 있으며, 참고 자료 링크와 4가지 주요 구현 사항을 명확하게 나열하고 있습니다. Template의 필수 섹션이 모두 작성되었습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dating-exam-personality-type
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can use TruffleHog to scan for secrets in your code with verification capabilities.

Add a TruffleHog config file (e.g. trufflehog-config.yml, trufflehog.yml) to your project to customize detectors and scanning behavior. The tool runs only when a config file is present.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 4, 2026

Test results

780 tests   780 ✅  16s ⏱️
236 suites    0 💤
236 files      0 ❌

Results for commit b2a52bf.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/main/java/deepple/deepple/datingexam/domain/AnswerPersonalityType.java (1)

3-8: LGTM!

Enum이 domain layer에 적절히 위치하고, 명확한 naming convention을 따르고 있습니다. Korean comment로 각 type의 의미가 잘 문서화되어 있습니다.

단, DatingExamSubmitResult.recalculateDominant()에서 ordinal 순서를 tie-breaking 우선순위로 사용하고 있으므로, enum 선언 순서가 business logic에 영향을 준다는 점을 Javadoc으로 명시하면 향후 유지보수 시 실수를 방지할 수 있습니다.

,

📝 Optional: Javadoc 추가 제안
 package deepple.deepple.datingexam.domain;

+/**
+ * 답변에 연결된 성격 유형.
+ * <p>
+ * 선언 순서(ordinal)가 동점 시 우선순위로 사용됩니다: A > B > C > D
+ */
 public enum AnswerPersonalityType {
     DECISIVE_INDEPENDENT,    // (A) 단호한 독립주의자
     GROWING_RUNNING_MATE,    // (B) 성장하는 러닝메이트
     DEVOTED_ROMANTIC,        // (C) 헌신적인 로맨티스트
     REALISTIC_SHELTER        // (D) 현실적인 안식처
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/deepple/deepple/datingexam/domain/AnswerPersonalityType.java`
around lines 3 - 8, Add a Javadoc to the AnswerPersonalityType enum explaining
that the declaration order is significant because
DatingExamSubmitResult.recalculateDominant() uses enum ordinal() as a
tie-breaking priority; mention that changing the enum order will affect business
logic and that any reordering must be reflected in the tie-breaker logic or
tests.
src/main/java/deepple/deepple/datingexam/application/dto/DominantPersonalityTypeResponse.java (1)

6-10: Type safety 관련 고려 사항

personalityType field가 String으로 정의되어 있습니다. API 응답에서 유연성을 위해 String을 사용하는 것은 유효한 선택이지만, AnswerPersonalityType enum을 직접 사용하면 type safety와 serialization 일관성을 보장할 수 있습니다.

현재 구현이 의도된 것이라면 문제없습니다. 다만, AnswerPersonalityType enum 값이 변경될 경우 이 DTO를 생성하는 코드에서 compile-time 검증이 이루어지지 않는다는 점을 유의하세요.

💡 Alternative: enum type 직접 사용
 public record DominantPersonalityTypeResponse(
-    `@Schema`(implementation = AnswerPersonalityType.class)
-    String personalityType
+    AnswerPersonalityType personalityType
 ) {
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/deepple/deepple/datingexam/application/dto/DominantPersonalityTypeResponse.java`
around lines 6 - 10, The DTO DominantPersonalityTypeResponse currently declares
personalityType as String which loses compile-time safety; change its type to
the enum AnswerPersonalityType (i.e., public record
DominantPersonalityTypeResponse(AnswerPersonalityType personalityType)) and
update the `@Schema` annotation to reference the enum (or remove if redundant),
ensure imports for AnswerPersonalityType are added, and adjust any
callers/serializers that construct this record so they pass the enum value
instead of a raw String to preserve type safety and consistent serialization.
src/test/java/deepple/deepple/datingexam/adapter/database/DatingExamQueryRepositoryImplTest.java (1)

9-9: Wildcard import 사용에 대한 선택적 개선 제안

Wildcard import (import deepple.deepple.datingexam.domain.*;)는 명시적인 import에 비해 어떤 class가 사용되는지 파악하기 어렵습니다. 다만, 같은 module 내 domain type들이므로 critical한 issue는 아닙니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/deepple/deepple/datingexam/adapter/database/DatingExamQueryRepositoryImplTest.java`
at line 9, Replace the wildcard import in DatingExamQueryRepositoryImplTest
(import deepple.deepple.datingexam.domain.*) with explicit imports of the
specific domain classes referenced by that test; open the test, identify the
exact types used from package deepple.deepple.datingexam.domain and add
individual import statements for each (e.g., the concrete classes referenced in
the test) to improve clarity and maintainability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/deepple/deepple/datingexam/application/DatingExamModifyService.java`:
- Around line 81-84: The read-modify-write in DatingExamModifyService (calls
datingExamSubmitResultRepository.findByMemberId -> result.addCounts -> save) can
race; replace it with an atomic update: either implement a DB upsert (e.g.,
repository method using a single ON CONFLICT / MERGE native query or `@Modifying`
JPQL that increments counts by member_id) or acquire a row-level lock in a
transactional boundary (add `@Transactional` on the service method and use a
repository method like findByMemberIdForUpdate annotated with
`@Lock`(PESSIMISTIC_WRITE) to load the row or create it if absent before calling
DatingExamSubmitResult.addCounts and save). Ensure the chosen approach operates
on member_id atomically so concurrent submits don’t overwrite increments.

In
`@src/main/resources/db/migration/V11__reset_dating_exams_and_add_personality_type.sql`:
- Around line 4-11: This migration unconditionally deletes all rows from
dating_exam_submit, dating_exam_answer, dating_exam_question, and
dating_exam_subject which will remove production users' exam history; modify the
migration to be safe by either (a) adding a pre-delete backup step that copies
data from those tables into archive/backup tables (e.g.,
dating_exam_submit_backup, etc.) before DELETE, or (b) gating execution with an
environment-specific conditional/placeholder so it only runs in non-production,
or (c) restrict deletes with an explicit WHERE targeting only test/staging data;
update the migration to include the chosen safety mechanism and a clear comment
describing the backup/condition and reference the tables dating_exam_submit,
dating_exam_answer, dating_exam_question, dating_exam_subject.
- Around line 22-23: ALTER TABLE statement adding personality_type as "ALTER
TABLE dating_exam_answer ADD COLUMN personality_type VARCHAR(50) NOT NULL" is
unsafe under MySQL implicit DDL commits; change the migration so the NOT NULL
column is added with a safe default (e.g., ADD COLUMN ... VARCHAR(50) NOT NULL
DEFAULT '') or split into two migrations: first ADD COLUMN nullable (ALTER TABLE
dating_exam_answer ADD COLUMN personality_type VARCHAR(50)), backfill values,
then ALTER TABLE ... SET NOT NULL; also review Flyway transaction settings for
DDL and ensure migrations are idempotent when run after a partial failure.

---

Nitpick comments:
In
`@src/main/java/deepple/deepple/datingexam/application/dto/DominantPersonalityTypeResponse.java`:
- Around line 6-10: The DTO DominantPersonalityTypeResponse currently declares
personalityType as String which loses compile-time safety; change its type to
the enum AnswerPersonalityType (i.e., public record
DominantPersonalityTypeResponse(AnswerPersonalityType personalityType)) and
update the `@Schema` annotation to reference the enum (or remove if redundant),
ensure imports for AnswerPersonalityType are added, and adjust any
callers/serializers that construct this record so they pass the enum value
instead of a raw String to preserve type safety and consistent serialization.

In `@src/main/java/deepple/deepple/datingexam/domain/AnswerPersonalityType.java`:
- Around line 3-8: Add a Javadoc to the AnswerPersonalityType enum explaining
that the declaration order is significant because
DatingExamSubmitResult.recalculateDominant() uses enum ordinal() as a
tie-breaking priority; mention that changing the enum order will affect business
logic and that any reordering must be reflected in the tie-breaker logic or
tests.

In
`@src/test/java/deepple/deepple/datingexam/adapter/database/DatingExamQueryRepositoryImplTest.java`:
- Line 9: Replace the wildcard import in DatingExamQueryRepositoryImplTest
(import deepple.deepple.datingexam.domain.*) with explicit imports of the
specific domain classes referenced by that test; open the test, identify the
exact types used from package deepple.deepple.datingexam.domain and add
individual import statements for each (e.g., the concrete classes referenced in
the test) to improve clarity and maintainability.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 706a9d92-cd13-45d0-9aaa-c8acb3101dcd

📥 Commits

Reviewing files that changed from the base of the PR and between 65b2d95 and f3192f6.

📒 Files selected for processing (17)
  • src/main/java/deepple/deepple/datingexam/adapter/webapi/DatingExamApi.java
  • src/main/java/deepple/deepple/datingexam/application/DatingExamModifyService.java
  • src/main/java/deepple/deepple/datingexam/application/DatingExamQueryService.java
  • src/main/java/deepple/deepple/datingexam/application/dto/DominantPersonalityTypeResponse.java
  • src/main/java/deepple/deepple/datingexam/application/provided/DatingExamFinder.java
  • src/main/java/deepple/deepple/datingexam/application/required/DatingExamAnswerRepository.java
  • src/main/java/deepple/deepple/datingexam/application/required/DatingExamSubmitResultRepository.java
  • src/main/java/deepple/deepple/datingexam/domain/AnswerPersonalityType.java
  • src/main/java/deepple/deepple/datingexam/domain/DatingExamAnswer.java
  • src/main/java/deepple/deepple/datingexam/domain/DatingExamSubmitResult.java
  • src/main/resources/db/migration/V11__reset_dating_exams_and_add_personality_type.sql
  • src/main/resources/db/migration/V12__create_dating_exam_submit_result.sql
  • src/test/java/deepple/deepple/datingexam/adapter/database/DatingExamQueryRepositoryImplTest.java
  • src/test/java/deepple/deepple/datingexam/application/DatingExamModifyServiceTest.java
  • src/test/java/deepple/deepple/datingexam/application/DatingExamSubmitRequestValidatorTest.java
  • src/test/java/deepple/deepple/datingexam/domain/DatingExamAnswerTest.java
  • src/test/java/deepple/deepple/datingexam/domain/DatingExamSubmitResultTest.java

Comment thread src/main/java/deepple/deepple/datingexam/application/DatingExamModifyService.java Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/main/java/deepple/deepple/datingexam/application/DatingExamModifyService.java (1)

81-84: ⚠️ Potential issue | 🟠 Major

row 부재 케이스에서는 현재 lock 전략만으로 insert race를 막지 못합니다.

Line 81-84에서 findByMemberIdForUpdate가 empty면 lock 대상 row가 없어 동시 요청이 모두 create+save로 진입할 수 있습니다. memberId unique constraint로 한 요청이 실패(보통 500)할 수 있으니 upsert 또는 duplicate key retry 경로를 추가해야 합니다.

Diff 제안 (duplicate key retry 경로)
+import org.springframework.dao.DataIntegrityViolationException;
...
-        DatingExamSubmitResult result = datingExamSubmitResultRepository.findByMemberIdForUpdate(memberId)
-            .orElseGet(() -> DatingExamSubmitResult.create(memberId));
-        result.addCounts(counts);
-        datingExamSubmitResultRepository.save(result);
+        DatingExamSubmitResult result = datingExamSubmitResultRepository.findByMemberIdForUpdate(memberId)
+            .orElse(null);
+
+        if (result == null) {
+            try {
+                result = datingExamSubmitResultRepository.save(DatingExamSubmitResult.create(memberId));
+            } catch (DataIntegrityViolationException e) {
+                result = datingExamSubmitResultRepository.findByMemberIdForUpdate(memberId)
+                    .orElseThrow(() -> new IllegalStateException("submit result를 재조회하지 못했습니다.", e));
+            }
+        }
+
+        result.addCounts(counts);
+        datingExamSubmitResultRepository.save(result);
#!/bin/bash
set -euo pipefail

# 1) memberId unique constraint 존재 확인
fd "DatingExamSubmitResult.java" -x sed -n '1,120p' {}

# 2) updateSubmitResult의 동시성 처리 경로 확인
fd "DatingExamModifyService.java" -x sed -n '68,125p' {}

# 3) duplicate key retry/예외 처리 부재 여부 확인
rg -n "findByMemberIdForUpdate|orElseGet\\(|DataIntegrityViolationException|Duplicate|upsert|merge|on conflict" \
  src/main/java/deepple/deepple/datingexam

As per coding guidelines "Performance and scalability: consider high traffic scenarios, database query optimization, proper indexing, caching strategies, connection pool management, and async processing where appropriate".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/deepple/deepple/datingexam/application/DatingExamModifyService.java`
around lines 81 - 84, The current logic using
datingExamSubmitResultRepository.findByMemberIdForUpdate(...) plus
DatingExamSubmitResult.create(memberId) can race when the row is absent;
implement a duplicate-key retry or upsert path around the create/save to avoid
unique constraint failures: after or instead of orElseGet(...), attempt an
update-first or perform the insert inside a try/catch that catches the DB
duplicate-key/DataIntegrityViolationException, and on that exception re-query
with findByMemberIdForUpdate() and apply result.addCounts(counts) then save;
reference the methods findByMemberIdForUpdate, DatingExamSubmitResult.create,
result.addCounts, and datingExamSubmitResultRepository.save when making this
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/deepple/deepple/datingexam/application/DatingExamModifyService.java`:
- Around line 73-79: The code in DatingExamModifyService using
datingExamAnswerRepository.findAllByIdIn(answerIds) should validate that all
requested answerIds were returned; compare the returned answers' IDs (from
DatingExamAnswer::getId) with the input answerIds set, compute the missing IDs
(set difference), and if any are missing throw a clear exception (e.g.,
IllegalArgumentException or a domain NotFoundException) rather than proceeding
to compute counts, so dominant personality calculation isn't corrupted.

In `@src/main/resources/db/migration/V12__add_personality_type_column.sql`:
- Around line 1-2: The migration adds a NOT NULL column personality_type to
table dating_exam_answer which will fail if existing rows exist; modify
V12__add_personality_type_column.sql to either add the column with a safe
DEFAULT (e.g., ALTER TABLE dating_exam_answer ADD COLUMN personality_type
VARCHAR(50) NOT NULL DEFAULT '<default>') and then, if desired, backfill/update
and drop the DEFAULT, or delete/clear existing dating_exam_answer rows before
adding the NOT NULL column within the same migration; locate the ALTER TABLE
statement in V12__add_personality_type_column.sql and implement one of these
safe approaches so the migration does not fail on existing data.

---

Duplicate comments:
In
`@src/main/java/deepple/deepple/datingexam/application/DatingExamModifyService.java`:
- Around line 81-84: The current logic using
datingExamSubmitResultRepository.findByMemberIdForUpdate(...) plus
DatingExamSubmitResult.create(memberId) can race when the row is absent;
implement a duplicate-key retry or upsert path around the create/save to avoid
unique constraint failures: after or instead of orElseGet(...), attempt an
update-first or perform the insert inside a try/catch that catches the DB
duplicate-key/DataIntegrityViolationException, and on that exception re-query
with findByMemberIdForUpdate() and apply result.addCounts(counts) then save;
reference the methods findByMemberIdForUpdate, DatingExamSubmitResult.create,
result.addCounts, and datingExamSubmitResultRepository.save when making this
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ceafde3e-eb40-45a0-b922-c3d431d12886

📥 Commits

Reviewing files that changed from the base of the PR and between f3192f6 and 5aa564c.

📒 Files selected for processing (6)
  • src/main/java/deepple/deepple/datingexam/application/DatingExamModifyService.java
  • src/main/java/deepple/deepple/datingexam/application/required/DatingExamSubmitResultRepository.java
  • src/main/resources/db/migration/V11__reset_dating_exams.sql
  • src/main/resources/db/migration/V12__add_personality_type_column.sql
  • src/main/resources/db/migration/V13__insert_dating_exam_data.sql
  • src/main/resources/db/migration/V14__create_dating_exam_submit_result.sql
✅ Files skipped from review due to trivial changes (1)
  • src/main/resources/db/migration/V14__create_dating_exam_submit_result.sql

Comment on lines +73 to +79
List<DatingExamAnswer> answers = datingExamAnswerRepository.findAllByIdIn(answerIds);

Map<AnswerPersonalityType, Integer> counts = answers.stream()
.collect(Collectors.groupingBy(
DatingExamAnswer::getPersonalityType,
Collectors.summingInt(e -> 1)
));
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

조회 누락 answerId를 검증하지 않아 personality count가 왜곡될 수 있습니다.

Line 73-79은 findAllByIdIn 결과 개수 검증이 없어, 요청 ID 일부가 누락되면 under-count가 그대로 저장됩니다. dominant personality type 계산이 틀어질 수 있으니 요청 ID 집합과 조회 결과를 비교해 mismatch 시 예외 처리하세요.

Diff 제안
         List<DatingExamAnswer> answers = datingExamAnswerRepository.findAllByIdIn(answerIds);
+        long requestedUniqueCount = answerIds.stream().distinct().count();
+        if (answers.size() != requestedUniqueCount) {
+            throw new IllegalArgumentException("유효하지 않은 답변 ID가 포함되어 있습니다.");
+        }

         Map<AnswerPersonalityType, Integer> counts = answers.stream()
             .collect(Collectors.groupingBy(
                 DatingExamAnswer::getPersonalityType,
                 Collectors.summingInt(e -> 1)
             ));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/deepple/deepple/datingexam/application/DatingExamModifyService.java`
around lines 73 - 79, The code in DatingExamModifyService using
datingExamAnswerRepository.findAllByIdIn(answerIds) should validate that all
requested answerIds were returned; compare the returned answers' IDs (from
DatingExamAnswer::getId) with the input answerIds set, compute the missing IDs
(set difference), and if any are missing throw a clear exception (e.g.,
IllegalArgumentException or a domain NotFoundException) rather than proceeding
to compute counts, so dominant personality calculation isn't corrupted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DatingExamSubmitRequestValidator 로 이미 검증하고 있습니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and display DatingExamSubmitRequestValidator
fd 'DatingExamSubmitRequestValidator.java' --exec cat {}

Length of output: 3849


@hainho, 확인했습니다. DatingExamSubmitRequestValidator.validateAnswer()에서 각 answerId가 해당 questionId의 유효한 답변 집합(validAnswers.contains(answerId))에 속하는지 이미 검증하고 있어, findAllByIdIn 호출 전에 유효하지 않은 ID는 모두 차단됩니다. 제 지적이 잘못되었습니다.

@hainho hainho requested review from Kong-TaeHyeon and stemmmm March 4, 2026 19:05
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/test/java/deepple/deepple/datingexam/application/DatingExamModifyServiceTest.java`:
- Around line 96-97: 테스트가 save(any(DatingExamSubmitResult.class))처럼 상호작용만 검증해 집계
로직(성격별 count 누적 및 우세 성격 선택)이 깨져도 놓치기 때문에, DatingExamModifyServiceTest에서
datingExamSubmitResultRepository.save 호출을
ArgumentCaptor<DatingExamSubmitResult>로 캡처한 뒤 캡처한 객체의 필드(성격별 count 맵,
dominantPersonality 등)를 직접 assert 하도록 변경하세요; 구체적으로
datingExamSubmitResultRepository.save를 호출하는 테스트 케이스들(현재 save 검증이 있는 곳들)에서
ArgumentCaptor를 생성하고 verify(...).save(captor.capture())를 사용해 저장 대상 객체를 얻은 다음 예상
누적값과 우세 성격을 assertEquals/assertThat로 검사하고 동일한 패턴으로 다른 관련 테스트(현재 save(any(...))로만
검사된 케이스들)에도 동일한 보강을 적용하세요.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1dff6ce2-8054-4cd0-ae88-2003288fc557

📥 Commits

Reviewing files that changed from the base of the PR and between 5aa564c and fdc9006.

📒 Files selected for processing (1)
  • src/test/java/deepple/deepple/datingexam/application/DatingExamModifyServiceTest.java

DatingExamAnswer mockAnswer = mock(DatingExamAnswer.class);
when(mockAnswer.getPersonalityType()).thenReturn(AnswerPersonalityType.DECISIVE_INDEPENDENT);
when(datingExamAnswerRepository.findAllByIdIn(List.of(answerId))).thenReturn(List.of(mockAnswer));
when(datingExamSubmitResultRepository.findByMemberIdForUpdate(memberId)).thenReturn(Optional.empty());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Pessimistic lock 경로 호출을 assert하지 않아 동시성 보호 의도가 테스트에 고정되지 않습니다.

Line 77, Line 159에서 findByMemberIdForUpdate(memberId)를 stub만 설정하고 호출 검증이 없습니다. 서비스가 non-lock 경로로 회귀해도 테스트가 통과할 수 있으니 Then 블록에서 명시적으로 verify해 주세요.

검증 추가 예시
                 verify(datingExamSubmitRepository).save(mockSubmit);
+                verify(datingExamSubmitResultRepository).findByMemberIdForUpdate(memberId);
                 verify(datingExamSubmitResultRepository).save(any(DatingExamSubmitResult.class));

Also applies to: 159-159

Comment on lines +96 to 97
verify(datingExamSubmitResultRepository).save(any(DatingExamSubmitResult.class));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

DatingExamSubmitResult 검증이 interaction 수준에 머물러 핵심 집계 로직 회귀를 놓칠 수 있습니다.

Line 96, Line 184에서 save(any(...)) 호출만 확인하면 personality count 누적/갱신과 dominant personality selection이 깨져도 테스트가 통과합니다. save 대상 객체의 집계 결과를 직접 assert하도록 보강해 주세요.
Based on learnings: **/*Test.java의 service test는 mocks/test slice를 사용하더라도 service behavior를 검증해야 합니다.

Also applies to: 184-185

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/deepple/deepple/datingexam/application/DatingExamModifyServiceTest.java`
around lines 96 - 97, 테스트가 save(any(DatingExamSubmitResult.class))처럼 상호작용만 검증해
집계 로직(성격별 count 누적 및 우세 성격 선택)이 깨져도 놓치기 때문에, DatingExamModifyServiceTest에서
datingExamSubmitResultRepository.save 호출을
ArgumentCaptor<DatingExamSubmitResult>로 캡처한 뒤 캡처한 객체의 필드(성격별 count 맵,
dominantPersonality 등)를 직접 assert 하도록 변경하세요; 구체적으로
datingExamSubmitResultRepository.save를 호출하는 테스트 케이스들(현재 save 검증이 있는 곳들)에서
ArgumentCaptor를 생성하고 verify(...).save(captor.capture())를 사용해 저장 대상 객체를 얻은 다음 예상
누적값과 우세 성격을 assertEquals/assertThat로 검사하고 동일한 패턴으로 다른 관련 테스트(현재 save(any(...))로만
검사된 케이스들)에도 동일한 보강을 적용하세요.

Copy link
Copy Markdown
Contributor

@stemmmm stemmmm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다 :)

public interface DatingExamSubmitResultRepository extends Repository<DatingExamSubmitResult, Long> {
Optional<DatingExamSubmitResult> findByMemberId(Long memberId);

@Lock(LockModeType.PESSIMISTIC_WRITE)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 경우 동시성 문제가 발생하나요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서로 다른 과목 답변 제출이 동시에 처리되면 동시성 문제 발생 가능해요

앱이라서 실제 과목 답변 제출 사이에 텀이 있겠지만, 네트워크 지연이나 등의 이유로 발생이 가능하긴해서
방지하는게 안전할듯 하네요

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/java/deepple/deepple/datingexam/application/dto/DominantPersonalityTypeResponse.java (1)

8-9: personalityTypeString 대신 enum으로 유지하세요

Line 9/17에서 enum을 String으로 평탄화하면 compile-time validation이 사라집니다. AnswerPersonalityType를 그대로 DTO field로 두는 편이 API contract와 domain 모델 정합성에 더 안전합니다.

♻️ Proposed refactor
 public record DominantPersonalityTypeResponse(
     `@Schema`(implementation = AnswerPersonalityType.class)
-    String personalityType,
+    AnswerPersonalityType personalityType,
     int decisiveIndependentCount,
     int growingRunningMateCount,
     int devotedRomanticCount,
     int realisticShelterCount
 ) {
     public static DominantPersonalityTypeResponse from(DatingExamSubmitResult result) {
         return new DominantPersonalityTypeResponse(
-            result.getDominantPersonalityType().name(),
+            result.getDominantPersonalityType(),
             result.getDecisiveIndependentCount(),
             result.getGrowingRunningMateCount(),
             result.getDevotedRomanticCount(),
             result.getRealisticShelterCount()
         );
     }
 }

Also applies to: 17-17

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/deepple/deepple/datingexam/application/dto/DominantPersonalityTypeResponse.java`
around lines 8 - 9, The DTO currently flattens the enum into a String (field
personalityType) which loses compile-time validation; change the field type from
String to the enum AnswerPersonalityType in DominantPersonalityTypeResponse (and
any other similar DTO field noted) so the class uses AnswerPersonalityType
directly, update the corresponding getter/setter/constructor signatures and
imports to use AnswerPersonalityType, and keep or adjust the
`@Schema`(implementation = AnswerPersonalityType.class) annotation to reflect the
enum type for correct API contract and serialization.
src/main/java/deepple/deepple/datingexam/application/DatingExamQueryService.java (1)

43-48: Query method에는 @Transactional(readOnly = true)를 명시하세요

Line 44 메서드는 read-only 조회이므로 method-level로 readOnly = true를 주는 편이 transaction 비용 측면에서 유리합니다.

♻️ Proposed refactor
     `@Override`
+    `@Transactional`(readOnly = true)
     public DominantPersonalityTypeResponse findDominantPersonalityType(Long memberId) {
         DatingExamSubmitResult result = datingExamSubmitResultRepository.findByMemberId(memberId)
             .orElseThrow(() -> new IllegalStateException("연애고사 제출 결과가 없습니다. memberId: " + memberId));
         return DominantPersonalityTypeResponse.from(result);
     }

As per coding guidelines, "Performance and scalability: consider high traffic scenarios, database query optimization, proper indexing, caching strategies, connection pool management, and async processing where appropriate".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/deepple/deepple/datingexam/application/DatingExamQueryService.java`
around lines 43 - 48, The findDominantPersonalityType method in
DatingExamQueryService is a read-only DB query; annotate this method with
`@Transactional`(readOnly = true) to reduce transaction overhead and mark intent.
Add the annotation directly on the findDominantPersonalityType method (or at
class-level if you prefer all query methods to be read-only) and ensure you
import org.springframework.transaction.annotation.Transactional; no other logic
changes required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/deepple/deepple/datingexam/application/DatingExamQueryService.java`:
- Around line 45-47: Replace the IllegalStateException in DatingExamQueryService
when calling datingExamSubmitResultRepository.findByMemberId(memberId) with the
project’s domain/application "not found" exception (e.g., a NotFoundException or
ResourceNotFoundException that carries an ErrorCode) so the API layer can map it
to the intended HTTP status; update the thrown exception instance and message
accordingly and ensure DominantPersonalityTypeResponse.from(result) is still
used after the lookup.

---

Nitpick comments:
In
`@src/main/java/deepple/deepple/datingexam/application/DatingExamQueryService.java`:
- Around line 43-48: The findDominantPersonalityType method in
DatingExamQueryService is a read-only DB query; annotate this method with
`@Transactional`(readOnly = true) to reduce transaction overhead and mark intent.
Add the annotation directly on the findDominantPersonalityType method (or at
class-level if you prefer all query methods to be read-only) and ensure you
import org.springframework.transaction.annotation.Transactional; no other logic
changes required.

In
`@src/main/java/deepple/deepple/datingexam/application/dto/DominantPersonalityTypeResponse.java`:
- Around line 8-9: The DTO currently flattens the enum into a String (field
personalityType) which loses compile-time validation; change the field type from
String to the enum AnswerPersonalityType in DominantPersonalityTypeResponse (and
any other similar DTO field noted) so the class uses AnswerPersonalityType
directly, update the corresponding getter/setter/constructor signatures and
imports to use AnswerPersonalityType, and keep or adjust the
`@Schema`(implementation = AnswerPersonalityType.class) annotation to reflect the
enum type for correct API contract and serialization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7cb4ee70-6cba-461f-81f4-c5e8e080ae27

📥 Commits

Reviewing files that changed from the base of the PR and between ae72605 and b2a52bf.

📒 Files selected for processing (2)
  • src/main/java/deepple/deepple/datingexam/application/DatingExamQueryService.java
  • src/main/java/deepple/deepple/datingexam/application/dto/DominantPersonalityTypeResponse.java

@hainho hainho merged commit 072cfe4 into main Mar 17, 2026
4 checks passed
@hainho hainho deleted the feat/dating-exam-personality-type branch March 17, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants