Skip to content

Conversation

@NCookies
Copy link
Collaborator

@NCookies NCookies commented Dec 8, 2025

📌 이슈 상황 (Problem)

테스트케이스 삭제 API(DELETE /testcases/{id})를 호출하면 응답은 204 No Content로 정상 반환되나, 실제 DB에서는 데이터가 삭제되지 않음.

Hibernate 로그 확인 결과, DELETE 쿼리 자체가 생성되지 않음.

@transactional 및 deleteById 등을 점검했으나 정상이었음.

🔍 원인 분석 (Cause)

JPA 영속성 컨텍스트의 특성: getProblem()을 통해 부모 엔티티(Problem)가 이미 영속 상태(Managed)로 로딩되어 있음.

이때 Problem 엔티티 내부의 List 컬렉션은 여전히 삭제 대상인 Testcase 객체를 참조하고 있음.

testcaseRepository.delete()를 호출했더라도, 트랜잭션 Commit(Flush) 시점에 JPA가 부모 엔티티의 상태를 감지함.

부모 리스트에 자식이 남아있으므로 Cascade 옵션(또는 Dirty Checking)에 의해 삭제하려던 객체가 다시 저장되거나 삭제가 취소됨.

✅ 해결 방법 (Solution)

DB 삭제 요청뿐만 아니라, 객체 수준에서도 부모-자식 관계를 끊어줌.

problem.getTestcases().remove(testcase) 로직을 추가하여 영속성 컨텍스트 내의 데이터 일관성 확보.

📸 테스트 결과

수정 후 DELETE 쿼리 정상 발생 및 DB 데이터 삭제 확인 완료.

Summary by CodeRabbit

릴리스 노트

  • Bug Fixes

    • 테스트 케이스 제거 기능의 안정성 개선
  • Chores

✏️ Tip: You can customize this high-level summary in your review settings.

- Problem(부모) 엔티티가 영속성 컨텍스트에 로딩된 상태에서 Testcase(자식)만 삭제 시도 시, 트랜잭션 커밋 시점에 부모의 컬렉션에 남아있는 자식 객체가 Cascade 설정으로 인해 다시 저장되는 현상 수정
- 삭제 로직 수행 전, 부모 객체의 컬렉션에서도 해당 자식 객체를 제거하는 로직 수행하도록 함
@NCookies NCookies self-assigned this Dec 8, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 8, 2025

둘러보기

세 개의 파일이 수정되었습니다. TestcaseDomainService에서 테스트 케이스 제거 시 검증 로직을 제거하고 단순 컬렉션 제거로 변경했습니다. TestcaseRepositoryImpl에서는 delete 메서드를 deleteById 호출로 교체했고, SwaggerConfig에서는 프로덕션 서버 URL을 https://ezcode.my에서 https://api.ezcode.my로 업데이트했습니다.

변경 사항

응집 단위 / 파일(들) 변경 요약
테스트케이스 도메인 서비스
src/main/java/org/ezcode/codetest/domain/problem/service/TestcaseDomainService.java
removeTestcase 메서드에서 TestcaseException 검증 로직을 제거하고, 단순히 문제의 testcases 리스트에서 테스트 케이스를 제거하는 방식으로 변경; 예외 발생 경로 삭제
테스트케이스 저장소 구현
src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/TestcaseRepositoryImpl.java
delete(Testcase) 호출을 deleteById(testcase.getId())로 변경하여 엔티티 인스턴스 대신 ID 기반 삭제로 전환
Swagger 설정
src/main/java/org/ezcode/codetest/infrastructure/swagger/config/SwaggerConfig.java
OpenAPI 정의의 프로덕션 서버 URL을 https://api.ezcode.my로 업데이트

예상 코드 리뷰 소요 시간

🎯 2 (단순) | ⏱️ ~10분

  • TestcaseDomainService의 검증 로직 제거로 인한 동작 변화 영향 범위 검토
  • 예외 처리 제거가 기존 호출자 코드에 미치는 영향 확인

관련된 가능성 있는 PR들

제안하는 라벨

bug

제안하는 리뷰어

  • Kimminu7
  • thezz9
  • minjee2758

🐰 검증 없이 삭제하네,
아이디로 제거하고,
API 주소도 다시 쓰여,
세 가지 작은 손질로
코드는 더 간결해져!

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 문제(테스트케이스 삭제 시 쿼리 미발생)와 해결책(JPA 양방향 관계 동기화)을 명확히 설명하며, 변경 사항의 핵심을 정확히 반영합니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/testcase-delete-bug

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.

Copy link

@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: 0

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/org/ezcode/codetest/domain/problem/service/TestcaseDomainService.java (1)

45-56: Add ownership validation to prevent cross-problem testcase deletion.

The current removeTestcase() method lacks verification that the testcase actually belongs to the provided problem. This allows removeTestcase(problemA, testcaseIdOfProblemB) to delete problemB's testcase without raising an exception.

Since Testcase already has a problemIdMatched() method, use it to validate ownership before deletion:

Testcase findTestcase = testcaseRepository.findByTestcase(testcaseId);

+// Verify testcase belongs to the problem
+if (!findTestcase.problemIdMatched(problem.getId())) {
+    throw new TestcaseException(TestcaseExceptionCode.TESTCASE_NOT_FOUND);
+}

-if (problem.getTestcases() != null) {
-    problem.getTestcases().remove(findTestcase);
-}
+problem.getTestcases().remove(findTestcase);

testcaseRepository.delete(findTestcase);

The null-check for problem.getTestcases() can be removed since it's initialized as an empty ArrayList in the Problem entity. With @OneToMany(orphanRemoval=true), the removal from the collection is sufficient for cleanup.

🧹 Nitpick comments (1)
src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/TestcaseRepositoryImpl.java (1)

45-45: deleteById 사용 시 중복 조회·예외 동작 한 번만 점검해 주세요.

현재 removeTestcase 흐름에서는 이미 findByTestcase 로 엔티티를 로딩한 뒤 여기로 내려오기 때문에, Spring Data JPA 기본 구현 기준으로는 deleteById 내부에서 다시 한 번 findById 를 호출해 동일 엔티티를 두 번 조회하게 됩니다.
특별히 ID 기반 삭제가 꼭 필요한 다른 사용처가 없다면, 기존처럼 delete(testcase) 를 유지하는 편이 더 단순하고 약간 더 효율적일 수 있습니다. 반대로 “detached 엔티티도 안전하게 삭제하고 싶다”는 의도라면, 그 의도를 주석으로 남겨 두시면 이후 코드를 읽는 사람이 이해하기 쉬울 것 같습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a2618e and f1c9ebe.

📒 Files selected for processing (3)
  • src/main/java/org/ezcode/codetest/domain/problem/service/TestcaseDomainService.java (1 hunks)
  • src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/TestcaseRepositoryImpl.java (1 hunks)
  • src/main/java/org/ezcode/codetest/infrastructure/swagger/config/SwaggerConfig.java (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-06-15T04:37:29.231Z
Learnt from: chat26666
Repo: ezcode-my/backend PR: 64
File: src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/game/EncounterChoiceRepositoryImpl.java:0-0
Timestamp: 2025-06-15T04:37:29.231Z
Learning: EncounterChoiceRepositoryImpl in src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/game/EncounterChoiceRepositoryImpl.java is intentionally a skeleton implementation that is work-in-progress and will be completed later.

Applied to files:

  • src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/TestcaseRepositoryImpl.java
📚 Learning: 2025-07-02T12:05:54.917Z
Learnt from: Kimminu7
Repo: ezcode-my/backend PR: 133
File: src/test/java/org/ezcode/codetest/domain/problem/service/ProblemDomainServiceTest.java:92-99
Timestamp: 2025-07-02T12:05:54.917Z
Learning: ProblemDomainService의 removeProblem 메서드는 DB에서 Problem을 삭제한 후 Elasticsearch에서도 해당 ProblemSearchDocument를 찾아서 삭제합니다. 만약 Elasticsearch에서 문서를 찾지 못하면 ProblemException(PROBLEM_NOT_FOUND)을 던지므로, 테스트에서는 problem.getId()와 searchRepository.findById() 모두 적절하게 mock해야 합니다.

Applied to files:

  • src/main/java/org/ezcode/codetest/domain/problem/service/TestcaseDomainService.java
📚 Learning: 2025-06-06T07:59:41.806Z
Learnt from: thezz9
Repo: ezcode-my/backend PR: 36
File: src/main/java/org/ezcode/codetest/domain/submission/dto/SubmissionData.java:47-58
Timestamp: 2025-06-06T07:59:41.806Z
Learning: SubmissionData.withAggregatedStats() 메서드에서 testCaseList는 상위 프로세스(base() 메서드의 problemInfo.testcaseList())에서 이미 null 체크가 처리되어 오므로 추가적인 null 체크가 불필요함

Applied to files:

  • src/main/java/org/ezcode/codetest/domain/problem/service/TestcaseDomainService.java
📚 Learning: 2025-06-05T00:52:54.460Z
Learnt from: chat26666
Repo: ezcode-my/backend PR: 31
File: src/main/java/org/ezcode/codetest/domain/problem/model/entity/ProblemSearchDocument.java:49-56
Timestamp: 2025-06-05T00:52:54.460Z
Learning: ProblemSearchDocument.from() 메서드는 트랜잭션 내에서 DB에서 실제로 조회한 Problem 엔티티만 사용하므로 null 체크가 불필요함. 이 컨텍스트에서는 null Problem이 있을 경우 이미 이전 단계에서 예외가 발생함.

Applied to files:

  • src/main/java/org/ezcode/codetest/domain/problem/service/TestcaseDomainService.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
🔇 Additional comments (1)
src/main/java/org/ezcode/codetest/infrastructure/swagger/config/SwaggerConfig.java (1)

17-17: 운영 서버 URL 변경 의도만 한 번 더 확인해주세요.

https://api.ezcode.my 로 변경 자체는 문제 없어 보입니다. 실제 운영 환경에서 사용하는 도메인/게이트웨이 주소와 완전히 일치하는지만 배포 전에 한 번만 더 확인해 주세요.

@NCookies NCookies merged commit 34c899a into dev Dec 8, 2025
2 checks passed
@NCookies NCookies deleted the fix/testcase-delete-bug branch December 8, 2025 10:11
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