Skip to content

Comments

fix: JPQL 쿼리를 통해 User 삭제 직접 수행#316

Merged
JanooGwan merged 2 commits intodevelopfrom
fix/cant-withdraw-v2
Feb 23, 2026
Merged

fix: JPQL 쿼리를 통해 User 삭제 직접 수행#316
JanooGwan merged 2 commits intodevelopfrom
fix/cant-withdraw-v2

Conversation

@JanooGwan
Copy link
Contributor

🔍 개요

  • close #이슈번호

🚀 주요 변경 내용


💬 참고 사항


✅ Checklist (완료 조건)

  • 코드 스타일 가이드 준수
  • 테스트 코드 포함됨
  • Reviewers / Assignees / Labels 지정 완료
  • 보안 및 민감 정보 검증 (API 키, 환경 변수, 개인정보 등)

@JanooGwan JanooGwan requested a review from Copilot February 23, 2026 18:41
@JanooGwan JanooGwan self-assigned this Feb 23, 2026
@JanooGwan JanooGwan added the 버그 정상적으로 동작하지 않는 문제 상황 관련 이슈입니다. label Feb 23, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 794b28f and d6bb171.

📒 Files selected for processing (1)
  • src/main/java/gg/agit/konect/domain/user/service/UserService.java

📝 Walkthrough

Walkthrough

사용자 저장소에 JPQL 기반 ID 삭제 메서드 deleteByUserId(Integer userId)가 추가되었고, 서비스의 deleteUser 구현이 엔티티 삭제에서 해당 ID 기반 삭제 호출로 변경되었습니다. Apple 토큰 취소와 UserWithdrawnEvent 발행 흐름은 유지됩니다.

Changes

Cohort / File(s) Summary
저장소 변경
src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java
@Modifying@Query("DELETE FROM User u WHERE u.id = :userId")로 구현된 void deleteByUserId(Integer userId) 메서드 추가 및 관련 import 추가.
서비스 로직 변경
src/main/java/gg/agit/konect/domain/user/service/UserService.java
deleteUser에서 엔티티 기반 userRepository.delete(user) + flush() 대신 userRepository.deleteByUserId(userId) 호출로 변경. Apple 토큰 취소 및 UserWithdrawnEvent 발행은 그대로 유지.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Service as UserService
    participant Auth as AppleAuthClient
    participant Repo as UserRepository
    participant DB as Database
    participant Events as EventPublisher

    Client->>Service: deleteUser(userId)
    Service->>Auth: revokeTokenIfPresent(user)
    Auth-->>Service: tokenRevoked
    Service->>Repo: deleteByUserId(userId)
    Repo->>DB: execute JPQL DELETE (id = userId)
    DB-->>Repo: deleteResult
    Repo-->>Service: deletionAcknowledged
    Service->>Events: publish(UserWithdrawnEvent)
    Events-->>Service: published
    Service-->>Client: deletionComplete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 바삭한 당근 손에 쥐고 말하네,
"id로 쓱— 삭제하니 발걸음 가볍구나."
JPQL로 한 줄에 일을 맡기고,
토큰은 끊고, 소식은 전하니,
행운의 똑똑한 굴이 완성되었네! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명이 템플릿 플레이스홀더로만 구성되어 있으며, 구체적인 변경 내용, 동기, 문제 해결 방법 등의 실질적인 정보가 부재합니다. 개요, 주요 변경 내용, 참고 사항 섹션을 구체적인 설명으로 채우고 관련 이슈 번호를 명시해주세요.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경 사항의 주요 목적을 명확하게 반영합니다. JPQL 쿼리를 통한 User 삭제 직접 수행이라는 변경 내용과 정확히 일치합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/cant-withdraw-v2

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

@JanooGwan JanooGwan merged commit 9a38f54 into develop Feb 23, 2026
1 check passed
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

이 PR은 사용자 삭제 로직을 변경하여 JPA의 엔티티 기반 삭제 방식에서 JPQL DELETE 쿼리를 사용한 직접 삭제 방식으로 전환합니다. 그러나 PR 설명에 변경 이유와 해결하려는 문제가 명시되어 있지 않아 변경의 목적을 파악하기 어렵습니다.

Changes:

  • UserRepository에 JPQL DELETE 쿼리를 사용하는 deleteByUserId 메서드 추가
  • UserService의 deleteUser 메서드에서 delete(user)를 deleteByUserId(userId)로 변경

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java JPQL DELETE 쿼리를 사용하는 새로운 deleteByUserId 메서드 추가
src/main/java/gg/agit/konect/domain/user/service/UserService.java 기존 delete(user) 호출을 deleteByUserId(userId)로 변경
Comments suppressed due to low confidence (1)

src/main/java/gg/agit/konect/domain/user/service/UserService.java:227

  • JPQL DELETE 쿼리를 사용하면 JPA의 엔티티 라이프사이클을 우회하게 되어 문제가 발생합니다:
  1. User 엔티티를 조회한 후(line 215) JPQL DELETE로 삭제하면, 이미 조회된 user 객체는 여전히 영속성 컨텍스트에 남아있지만 데이터베이스에서는 삭제된 상태가 됩니다.

  2. UserWithdrawnEvent를 발행할 때(line 225-227) 이미 삭제된 user 객체를 참조하게 되어 데이터 불일치가 발생할 수 있습니다.

  3. 데이터베이스의 ON DELETE CASCADE 설정으로 관련 테이블의 데이터는 자동 삭제되지만, JPA 1차 캐시에는 삭제되지 않은 채로 남아있어 트랜잭션 내에서 예상치 못한 동작이 발생할 수 있습니다.

기존의 userRepository.delete(user)를 사용하면 JPA가 적절히 엔티티 상태를 관리하고, ON DELETE CASCADE로 관련 데이터도 자동으로 삭제되므로 원래 방식을 유지하는 것이 안전합니다.

        User user = userRepository.getById(userId);

        validateNotClubPresident(userId);

        if (user.getProvider() == Provider.APPLE) {
            appleTokenRevocationService.revoke(user.getAppleRefreshToken());
        }

        userRepository.deleteByUserId(userId);

        applicationEventPublisher.publishEvent(
            UserWithdrawnEvent.from(user.getEmail(), user.getProvider().name())
        );

Comment on lines +62 to +64
@Modifying
@Query("DELETE FROM User u WHERE u.id = :userId")
void deleteByUserId(@Param("userId") Integer userId);
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

@Modifying 어노테이션은 있지만 clearAutomatically 속성이 설정되지 않았습니다. JPQL을 사용한 벌크 연산(INSERT, UPDATE, DELETE)은 영속성 컨텍스트를 우회하므로, @Modifying(clearAutomatically = true)를 추가하여 쿼리 실행 후 영속성 컨텍스트를 자동으로 클리어해야 합니다.

다만, 앞서 지적한 것처럼 이 접근 방식 자체가 문제가 있으므로 기존 delete(user) 방식으로 되돌리는 것을 권장합니다.

Copilot uses AI. Check for mistakes.
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: 1

🤖 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/gg/agit/konect/domain/user/repository/UserRepository.java`:
- Around line 62-64: Update the `@Modifying` annotation on the deleteByUserId
method in UserRepository to clear and flush the persistence context after
executing the JPQL delete; change the annotation to
`@Modifying`(clearAutomatically = true, flushAutomatically = true) for the method
deleteByUserId(Integer userId) so the persistence context is flushed and cleared
after the delete.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ced8a53 and 794b28f.

📒 Files selected for processing (2)
  • src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java
  • src/main/java/gg/agit/konect/domain/user/service/UserService.java
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.

  • 코멘트는 반드시 한국어로 작성한다.
  • 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
  • 각 코멘트 첫 줄에 심각도를 [LEVEL: high|medium|low] 형식으로 반드시 표기한다.
  • 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
  • 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
  • 가능하면 재현 조건 및 실패 시나리오도 포함한다.
  • 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
  • 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
  • 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
  • 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
  • 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.

Files:

  • src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java
  • src/main/java/gg/agit/konect/domain/user/service/UserService.java
**/*

⚙️ CodeRabbit configuration file

**/*: 공통 리뷰 톤 가이드:

  • 모든 코멘트는 첫 줄에 [LEVEL: ...] 태그를 포함한다.
  • 과장된 표현 없이 사실 기반으로 작성한다.
  • 한 코멘트에는 하나의 이슈만 다룬다.
  • 코드 예시가 필요하면 최소 수정 예시를 제시한다.
  • 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.

Files:

  • src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java
  • src/main/java/gg/agit/konect/domain/user/service/UserService.java
🔇 Additional comments (1)
src/main/java/gg/agit/konect/domain/user/service/UserService.java (1)

223-223: 현재 코드는 안전합니다. 데이터베이스 스키마에 모든 user_id 외래 키에 대해 ON DELETE CASCADE가 설정되어 있으므로, 사용자 삭제 시 관련된 모든 레코드(club_member, notification_device_token, chat_room_member 등)가 자동으로 삭제됩니다. JPQL bulk delete는 JPA 콜백을 실행하지 않지만, User 엔티티에는 현재 @PreRemove 콜백이 없으므로 이는 영향을 주지 않습니다. 기존 제안의 핵심 우려사항(고아 데이터, FK 위반)은 존재하지 않습니다.

Comment on lines +62 to +64
@Modifying
@Query("DELETE FROM User u WHERE u.id = :userId")
void deleteByUserId(@Param("userId") Integer userId);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Locate and examine the UserRepository file
find . -type f -name "UserRepository.java" | head -20

Repository: BCSDLab/KONECT_BACK_END

Length of output: 140


🏁 Script executed:

# Check the structure and find Spring Data JPA configuration
git ls-files | grep -E "(pom\.xml|build\.gradle|UserRepository)" | head -20

Repository: BCSDLab/KONECT_BACK_END

Length of output: 235


🏁 Script executed:

# Search for the actual file
fd UserRepository.java

Repository: BCSDLab/KONECT_BACK_END

Length of output: 222


🏁 Script executed:

cat -n src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java

Repository: BCSDLab/KONECT_BACK_END

Length of output: 2862


🏁 Script executed:

cat build.gradle | grep -A 50 "dependencies"

Repository: BCSDLab/KONECT_BACK_END

Length of output: 1928


🏁 Script executed:

# Search for usage of deleteByUserId method
rg "deleteByUserId" --type java -B 3 -A 3

Repository: BCSDLab/KONECT_BACK_END

Length of output: 2704


🏁 Script executed:

# Search for event listeners or transaction boundaries that might be affected
rg -E "(ApplicationEvent|EventListener|@Transactional|ApplicationEventPublisher)" src/main/java/gg/agit/konect/domain/user --type java | head -30

Repository: BCSDLab/KONECT_BACK_END

Length of output: 177


🏁 Script executed:

# Search for UserService and the context around deleteByUserId
rg "deleteByUserId\|applicationEventPublisher" src/main/java/gg/agit/konect/domain/user/service/UserService.java -B 10 -A 10

Repository: BCSDLab/KONECT_BACK_END

Length of output: 49


🏁 Script executed:

# Search for UserWithdrawnEvent and its listeners
rg "UserWithdrawnEvent" --type java -B 2 -A 2

Repository: BCSDLab/KONECT_BACK_END

Length of output: 3406


🏁 Script executed:

# Check if UserService method has `@Transactional`
rg -B 5 "deleteByUserId" src/main/java/gg/agit/konect/domain/user/service/UserService.java | head -40

Repository: BCSDLab/KONECT_BACK_END

Length of output: 254


🏁 Script executed:

# Get the full method that calls deleteByUserId
rg -B 20 "userRepository.deleteByUserId" src/main/java/gg/agit/konect/domain/user/service/UserService.java | head -50

Repository: BCSDLab/KONECT_BACK_END

Length of output: 769


🏁 Script executed:

# Check for `@Transactional` annotation on the method
rg "@Transactional" src/main/java/gg/agit/konect/domain/user/service/UserService.java -B 2 -A 10

Repository: BCSDLab/KONECT_BACK_END

Length of output: 1635


🏁 Script executed:

# Check what happens after deleteByUserId in the deleteUser method
rg -A 5 "userRepository.deleteByUserId" src/main/java/gg/agit/konect/domain/user/service/UserService.java

Repository: BCSDLab/KONECT_BACK_END

Length of output: 259


🏁 Script executed:

# Check if there are any other `@Modifying` methods with similar pattern
rg "@Modifying" src/main/java --type java -A 2 | grep -E "(`@Modifying`|@Query|clearAutomatically)"

Repository: BCSDLab/KONECT_BACK_END

Length of output: 667


[LEVEL: medium] JPQL 삭제 후 영속성 컨텍스트가 정리되지 않습니다.

현재 @Modifying 쿼리는 1차 캐시를 자동으로 비우지 않아 deleteUser 직전 getById()로 로드된 User 엔티티가 그대로 남습니다. 이후 코드에서 같은 트랜잭션 내에 해당 User를 다시 조회하면 삭제된 엔티티가 반환되어 오동작할 수 있습니다.

@Modifying(clearAutomatically = true, flushAutomatically = true)로 변경하여 삭제 후 명시적으로 영속성 컨텍스트를 정리하세요.

최소 수정 예시
-    `@Modifying`
+    `@Modifying`(clearAutomatically = true, flushAutomatically = true)
     `@Query`("DELETE FROM User u WHERE u.id = :userId")
     void deleteByUserId(`@Param`("userId") Integer userId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java`
around lines 62 - 64, Update the `@Modifying` annotation on the deleteByUserId
method in UserRepository to clear and flush the persistence context after
executing the JPQL delete; change the annotation to
`@Modifying`(clearAutomatically = true, flushAutomatically = true) for the method
deleteByUserId(Integer userId) so the persistence context is flushed and cleared
after the delete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

버그 정상적으로 동작하지 않는 문제 상황 관련 이슈입니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant