Skip to content

Conversation

@move-hoon
Copy link
Member

@move-hoon move-hoon commented Aug 17, 2025

🔗 관련 이슈

📘 작업 유형

  • ✨ Feature (기능 추가)
  • 🐞 Bugfix (버그 수정)
  • 🔧 Refactor (코드 리팩토링)
  • ⚙️ Chore (환경 설정)
  • 📝 Docs (문서 작성 및 수정)
  • ✅ Test (기능 테스트)
  • 🎨 style (코드 스타일 수정)

📙 작업 내역

  • SQLRestriction에 명시한 "deleted_at IS NULL"이 JPQL에서도 작동하여 회원 탈퇴 유저가 복구되지 않고 새롭게 가입이 되어 중복 저장되는 문제를 해결했습니다.

🧪 테스트 내역

  • 브라우저/기기에서 동작 확인
  • 엣지 케이스 테스트 완료
  • 기존 기능 영향 없음

🎨 스크린샷 또는 시연 영상 (선택)

기능 미리보기 기능 미리보기
기능 설명 기능 설명

✅ PR 체크리스트

  • 커밋 메시지가 명확합니다
  • PR 제목이 컨벤션에 맞습니다
  • 관련 이슈 번호를 작성했습니다
  • 기능이 정상적으로 작동합니다
  • 불필요한 코드를 제거했습니다

💬 추가 설명 or 리뷰 포인트 (선택)

  • 앞으로 soft-delete된 정보를 복구하는 로직을 작성할 때는 Native Query로 작업을 진행해야 할 것 같습니다!!

Summary by CodeRabbit

  • Refactor
    • 사용자 연동 정보(제공자 유형/ID)로 사용자를 조회하는 내부 처리 방식을 재구성했습니다. 삭제된 계정을 포함한 조회의 일관성과 안정성이 강화되었으며, 화면 또는 API 동작의 변경은 없습니다.
  • Chores
    • 내부 매개변수 바인딩 정의를 명시적으로 정리했습니다.

@coderabbitai
Copy link

coderabbitai bot commented Aug 17, 2025

Walkthrough

JPQL 기반 조회 메서드를 네이티브 쿼리로 교체하고, 파라미터에 @param을 명시하여 provider_type/provider_id 기준으로 삭제 여부와 무관하게 users 테이블에서 단일 사용자 행을 조회하도록 변경했습니다.

Changes

Cohort / File(s) Summary
User repository native query update
infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
JPQL @query를 네이티브 쿼리로 교체(SELECT u.* FROM users ...), enum 이름 바인딩(:#{#providerType.name()}), @param 추가, import 추가. 시그니처는 반환 동일, 파라미터 애노테이션만 변경.

Sequence Diagram(s)

sequenceDiagram
  participant Service
  participant JpaUserRepository
  participant Database

  Service->>JpaUserRepository: findByProviderTypeAndProviderIdIncludingDeleted(providerType, providerId)
  JpaUserRepository->>Database: Native SQL: SELECT u.* FROM users WHERE provider_type = :providerType.name AND provider_id = :providerId
  Database-->>JpaUserRepository: User row (including soft-deleted)
  JpaUserRepository-->>Service: UserEntity?
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
회원탈퇴 유저 복구 시 @SQLRestriction로 인해 조회되지 않는 문제를 우회하도록 쿼리 수정 (#98)

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BOOK-269-fix/#98

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 4

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
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.

📥 Commits

Reviewing files that changed from the base of the PR and between 38ff6ce and b3d0730.

📒 Files selected for processing (2)
  • infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt (2 hunks)
  • infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt (1 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-validation
🔇 Additional comments (2)
infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt (2)

5-5: @param 추가 적절합니다

네이티브 쿼리의 네임드 파라미터 바인딩을 위해 필요한 import가 정확히 추가되었습니다.


19-22: 네이티브 쿼리로 soft-delete 필터(@SQLRestriction/@where) 우회한 방향성 적절

JPQL에 붙는 전역 조건을 회피하기 위해 nativeQuery를 채택한 결정은 문제 원인에 정면으로 대응합니다.

providerId: String
): User? {
return jpaUserRepository.findByProviderTypeAndProviderIdIncludingDeleted(providerType, providerId)?.toDomain()
return jpaUserRepository.findByProviderTypeAndProviderIdIncludingDeleted(providerType.name, providerId)?.toDomain()
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

DB 표현 누수 방지를 위해 .name 전달을 없애고 Enum 자체를 전달하세요

Repository 메서드 파라미터를 String으로 변경하면서 infra 계층에서 Enum → String 변환이 필요해졌습니다. 앞선 코멘트처럼 Repository 시그니처를 ProviderType으로 되돌리면, 여기서는 그대로 Enum을 넘기면 됩니다. 타입 안정성과 일관성을 모두 개선합니다.

적용 diff(Repository 시그니처를 ProviderType으로 되돌렸다는 전제):

-        return jpaUserRepository.findByProviderTypeAndProviderIdIncludingDeleted(providerType.name, providerId)?.toDomain()
+        return jpaUserRepository.findByProviderTypeAndProviderIdIncludingDeleted(providerType, providerId)?.toDomain()

시그니처 변경 후 다른 사용처가 없는지 확인:


🏁 Script executed:

#!/bin/bash
# 메서드 정의와 호출 모두 최신 시그니처(ProviderType, String)로 정렬되었는지 확인
rg -n -C2 'findByProviderTypeAndProviderIdIncludingDeleted\s*\('

Length of output: 2790


infra 계층: Repository 메서드에 Enum 직접 전달하도록 수정 필요

아래 두 군데를 함께 변경해주시면 타입 안정성과 일관성을 확보할 수 있습니다.

  • infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
    findByProviderTypeAndProviderIdIncludingDeleted 파라미터 타입 String → ProviderType으로 변경
  • infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt
    • 호출부에서 .name 제거하고 Enum 자체를 전달

적용 예시:

--- a/infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
+++ b/infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
@@ -21,7 +21,7 @@ interface JpaUserRepository : JpaRepository<UserEntity, Long> {
     nativeQuery = true
 )
 fun findByProviderTypeAndProviderIdIncludingDeleted(
-    @Param("providerType") providerType: String,
+    @Param("providerType") providerType: ProviderType,
     @Param("providerId") providerId: String
 ): UserEntity?
--- a/infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt
+++ b/infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt
@@ -42,7 +42,7 @@ class UserRepositoryImpl(
         jpaUserRepository.findByProviderTypeAndProviderIdIncludingDeleted(providerType.name, providerId)
             ?.toDomain()
 
-        return jpaUserRepository.findByProviderTypeAndProviderIdIncludingDeleted(providerType.name, providerId)?.toDomain()
+        return jpaUserRepository.findByProviderTypeAndProviderIdIncludingDeleted(providerType, providerId)?.toDomain()
🤖 Prompt for AI Agents
In
infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt
around line 42, the call passes providerType.name (String) to the JPA method;
change the JPA method signature in
infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt to
accept ProviderType (enum) instead of String, and update the call here to pass
the ProviderType enum directly (remove .name) so the repository uses the enum
type for type safety and consistency.

Comment on lines 19 to 26
@Query(
value = "SELECT * FROM users u WHERE u.provider_type = :providerType AND u.provider_id = :providerId",
nativeQuery = true
)
fun findByProviderTypeAndProviderIdIncludingDeleted(
@Param("providerType") providerType: String,
@Param("providerId") providerId: String
): UserEntity?
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

중복 레코드 존재 시 단일 결과 요구로 인한 런타임 에러 위험 (NonUniqueResultException/IncorrectResultSizeDataAccessException)

해당 버그로 이미 “soft-deleted + 새로 가입된 활성”의 중복이 존재할 수 있습니다. 현재 메서드는 단일 엔티티를 반환하도록 되어 있어, 두 건 이상이 반환되면 런타임 예외가 발생할 수 있습니다. 최소한 결정적 정렬과 1건 제한을 추가해 주세요.

  • 옵션 A(활성 우선): 운영 플로우에서 활성 계정을 우선시하려면 deleted_at IS NULL이 먼저 오도록 정렬
  • 옵션 B(복구 우선): 복구 로직에서 soft-deleted를 우선시하려면 deleted_at IS NOT NULL이 먼저 오도록 정렬
  • 어떤 정책이 맞는지 확인 후 반영 필요
  • DB 벤더 중립적으로는 FETCH FIRST 1 ROW ONLY 사용을 권장합니다.

예시 diff:

-    @Query(
-        value = "SELECT * FROM users u WHERE u.provider_type = :providerType AND u.provider_id = :providerId",
-        nativeQuery = true
-    )
+    @Query(
+        value = """
+            SELECT u.* 
+            FROM users u 
+            WHERE u.provider_type = :providerType 
+              AND u.provider_id = :providerId
+            ORDER BY CASE WHEN u.deleted_at IS NULL THEN 0 ELSE 1 END, u.updated_at DESC
+            FETCH FIRST 1 ROW ONLY
+        """,
+        nativeQuery = true
+    )

검증 스크립트(메서드 사용처 파악 및 정책 결정에 참고):

정책이 확정되지 않았다면, 반환 타입을 List로 바꾸고 상위 계층에서 해석(복구/병합/경고)하는 방안을 고려할 수도 있습니다.


🏁 Script executed:

#!/bin/bash
# 메서드 사용처 확인
rg -n -C3 '\bfindByProviderTypeAndProviderIdIncludingDeleted\s*\('

Length of output: 3644


중복 레코드로 인한 NonUniqueResultException 방지: ORDER BY + LIMIT 추가 필수

수정 대상:

  • infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt: findByProviderTypeAndProviderIdIncludingDeleted 메서드

설명:
soft-deleted된 레코드와 동일한 providerType/providerId로 활성 계정이 재가입된 경우, 현재 쿼리는 단일 결과를 기대하므로 2건 이상 반환 시 NonUniqueResultException 또는 IncorrectResultSizeDataAccessException이 발생할 수 있습니다. 이를 방지하기 위해 결정적 정렬 및 1건 제한(FETCH FIRST 1 ROW ONLY)을 추가해 주세요.

예시 diff:

@@ infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
-    @Query(
-        value = "SELECT * FROM users u WHERE u.provider_type = :providerType AND u.provider_id = :providerId",
-        nativeQuery = true
-    )
+    @Query(
+        value = """
+            SELECT u.*
+              FROM users u
+             WHERE u.provider_type = :providerType
+               AND u.provider_id = :providerId
+            ORDER BY CASE WHEN u.deleted_at IS NULL THEN 0 ELSE 1 END, u.updated_at DESC
+            FETCH FIRST 1 ROW ONLY
+        """,
+        nativeQuery = true
+    )
     fun findByProviderTypeAndProviderIdIncludingDeleted(
         @Param("providerType") providerType: String,
         @Param("providerId") providerId: String
     ): UserEntity?

정책 결정:

  • 옵션 A (활성 우선): deleted_at IS NULL 레코드를 먼저 정렬
  • 옵션 B (복구 우선): deleted_at IS NOT NULL 레코드를 먼저 정렬

위 정책 중 적합한 방안을 선택해 반영하거나, 정책이 확정되지 않았다면 반환 타입을 List<UserEntity>로 변경 후 호출부에서 처리하도록 고려해 주세요.

검증 스크립트:

rg -n -C3 '\bfindByProviderTypeAndProviderIdIncludingDeleted\s*\('
🤖 Prompt for AI Agents
In infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
around lines 19-26, the native query for
findByProviderTypeAndProviderIdIncludingDeleted can return multiple rows
(soft-deleted + re-registered active) causing NonUniqueResultException; modify
the query to add a deterministic ORDER BY that prioritizes active records and
limit to a single row (e.g., ORDER BY (deleted_at IS NULL) DESC or equivalent so
NULL deleted_at comes first) with FETCH FIRST 1 ROW ONLY, keeping the method
return type UserEntity?; alternatively if policy undecided, change the return
type to List<UserEntity> and handle deduplication in callers.

Comment on lines 23 to 26
fun findByProviderTypeAndProviderIdIncludingDeleted(
@Param("providerType") providerType: String,
@Param("providerId") providerId: String
): UserEntity?
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

타입 안정성을 위해 파라미터 타입을 ProviderType으로 되돌리는 것을 권장

Repository 메서드에서 ProviderType을 String으로 바꾸면 DB 표현(열 값)이 인프라 경계를 넘어 서비스/도메인 계층까지 전파됩니다. JPA는 Enum 매핑(@Enumerated)을 통해 네이티브 쿼리 파라미터도 적절히 바인딩할 수 있습니다. 가능하다면 ProviderType을 그대로 유지하세요.

아래와 같이 메서드 시그니처를 되돌리고, 구현부에서도 .name 전달을 제거하는 것을 제안합니다.

먼저 UserEntity의 providerType 필드가 @Enumerated(EnumType.STRING)으로 매핑되어 있는지 확인해 주세요. 확인 스크립트:

적용 diff (본 파일 내 변경):

-    fun findByProviderTypeAndProviderIdIncludingDeleted(
-        @Param("providerType") providerType: String,
-        @Param("providerId") providerId: String
-    ): UserEntity?
+    fun findByProviderTypeAndProviderIdIncludingDeleted(
+        @Param("providerType") providerType: ProviderType,
+        @Param("providerId") providerId: String
+    ): UserEntity?

그리고 호출부(UserRepositoryImpl.kt)의 해당 라인도 같이 수정해야 합니다(해당 파일 코멘트 참고).


🏁 Script executed:

#!/bin/bash
# UserEntity 선언과 providerType 매핑을 확인
rg -n -C3 'class\s+UserEntity\b'
rg -n -C3 'providerType\b'
rg -n -C3 '@Enumerated|@Column|EnumType'

Length of output: 52761


ProviderType Enum 사용으로 파라미터 타입 복원 필요

UserEntity.providerType가 @Enumerated(EnumType.STRING)으로 매핑되어 있으므로, 네이티브 쿼리 메서드 시그니처를 ProviderType으로 변경하고, 구현부에서 .name 호출을 제거해야 합니다.

• infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
findByProviderTypeAndProviderIdIncludingDeleted 파라미터를 StringProviderType으로 수정
• infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt
– 호출부에서 providerType.nameproviderType 으로 변경

--- a/infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
+++ b/infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
@@ -23,7 +23,7 @@ interface JpaUserRepository : JpaRepository<UserEntity, UUID> {
     @Query(
         value = "SELECT * FROM users u WHERE u.provider_type = :providerType AND u.provider_id = :providerId",
         nativeQuery = true
     )
-    fun findByProviderTypeAndProviderIdIncludingDeleted(
-        @Param("providerType") providerType: String,
+    fun findByProviderTypeAndProviderIdIncludingDeleted(
+        @Param("providerType") providerType: ProviderType,
         @Param("providerId") providerId: String
     ): UserEntity?
--- a/infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt
+++ b/infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt
@@ -39,7 +39,7 @@ class UserRepositoryImpl(
     override fun findByProviderTypeAndProviderIdIncludingDeleted(
         providerType: ProviderType,
         providerId: String
     ): User? {
-        return jpaUserRepository.findByProviderTypeAndProviderIdIncludingDeleted(providerType.name, providerId)
+        return jpaUserRepository.findByProviderTypeAndProviderIdIncludingDeleted(providerType, providerId)
             ?.toDomain()
     }
🤖 Prompt for AI Agents
In infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
around lines 23-26, change the method signature parameter types from String to
the ProviderType enum (e.g., @Param("providerType") providerType: ProviderType)
so the repository uses the enum mapped by @Enumerated(EnumType.STRING); then in
infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt
update the call site to pass the ProviderType value directly (remove .name) and
add any necessary import for ProviderType.

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: 2

♻️ Duplicate comments (1)
infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt (1)

19-26: 여러 행 반환 시 IncorrectResultSizeDataAccessException 위험 — 결정적 정렬 + 1건 제한 추가 필요

현재 메서드는 단일 엔티티(UserEntity?) 반환을 기대하지만, 이미 존재할 수 있는 “soft-deleted + 재가입된 활성” 중복 상황에서 2건 이상이 조회되면 런타임 예외(NonUniqueResultException/IncorrectResultSizeDataAccessException)가 발생할 수 있습니다. 결정적 정렬과 1건 제한을 추가해 주세요. 정렬 정책(활성 우선 vs 복구 우선)은 비즈니스 정책에 맞춰 선택이 필요합니다.

예시(활성 우선: deleted_at IS NULL 먼저, 동률 시 id DESC):

-    @Query(
-        value = "SELECT u.* FROM users u WHERE u.provider_type = :#{#providerType.name()} AND u.provider_id = :providerId",
-        nativeQuery = true
-    )
+    @Query(
+        value = """
+            SELECT u.*
+            FROM users u
+            WHERE u.provider_type = :#{#providerType.name()}
+              AND u.provider_id = :providerId
+            ORDER BY CASE WHEN u.deleted_at IS NULL THEN 0 ELSE 1 END, u.id DESC
+            FETCH FIRST 1 ROW ONLY
+        """,
+        nativeQuery = true
+    )

MySQL 등에서 FETCH FIRST 1 ROW ONLY가 지원되지 않는 경우:

-            FETCH FIRST 1 ROW ONLY
+            LIMIT 1

정책이 미정이라면 반환 타입을 List로 바꾸고 상위 레이어에서 해석/병합하는 방안도 고려해 주세요.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
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.

📥 Commits

Reviewing files that changed from the base of the PR and between b3d0730 and a5e70fd.

📒 Files selected for processing (1)
  • infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt (2 hunks)
🔇 Additional comments (2)
infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt (2)

5-5: 네이티브 쿼리에서의 파라미터 바인딩 안정화 — @param 추가 적절

네이티브 쿼리와 SpEL을 혼용하는 경우 이름 기반 바인딩을 명시하는 것이 안전합니다. 현재 변경은 적절합니다.


19-26: DB 벤더: MySQL8Dialect 사용 중이므로 LIMIT 문법 변경 불필요

infra 모듈의 application-persistence.yml을 확인한 결과

  • driver-class-name: com.mysql.cj.jdbc.Driver
  • spring.jpa.database-platform: org.hibernate.dialect.MySQL8Dialect

로 설정되어 있어, MySQL 기준의 LIMIT 문법만 사용하시면 됩니다.
현재 네이티브 쿼리에는 LIMIT 절이 포함되어 있지 않으므로 별도의 벤더별 처리 없이 유지하셔도 무방합니다.

Comment on lines +19 to +26
@Query(
value = "SELECT u.* FROM users u WHERE u.provider_type = :#{#providerType.name()} AND u.provider_id = :providerId",
nativeQuery = true
)
fun findByProviderTypeAndProviderIdIncludingDeleted(
@Param("providerType") providerType: ProviderType,
@Param("providerId") providerId: String
): UserEntity?
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

리그레션 방지 테스트 추가 제안

경계 케이스 중심의 리포지토리/통합 테스트를 추가해 주세요.

  • soft-deleted와 active가 공존할 때: 정책에 맞는 1건만 반환되는지
  • soft-deleted만 존재할 때: 정상 조회되는지
  • 존재하지 않을 때: null 반환되는지
  • Enum 바인딩(SpEL .name())이 실제 저장 값과 일치하는지

필요하시면 테스트 스켈레톤 초안 제공 가능합니다.

🤖 Prompt for AI Agents
In infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
around lines 19 to 26, add repository/integration tests to prevent regressions:
create tests that (1) insert both an active and a soft-deleted UserEntity with
the same providerType/providerId and assert that the query returns only the
policy-allowed one, (2) insert only a soft-deleted entity and assert it is
returned, (3) assert null is returned when none exist, and (4) verify the enum
binding used in the query (SpEL .name()) matches the stored DB value by
asserting persisted provider_type equals ProviderType.name() for sample entries;
implement cleanup/transactional isolation for each test.

🧹 Nitpick (assertive)

중복 계정 방지용 데이터 모델 가드레일 권장(인덱스/제약)

애플리케이션 레벨 방어(ORDER BY + 1건 제한)와 별개로, 활성 레코드의 중복 생성을 DDL로 차단하는 것을 권장합니다.

  • Postgres 권장: 부분 유니크 인덱스
    • CREATE UNIQUE INDEX CONCURRENTLY ux_users_provider_active ON users(provider_type, provider_id) WHERE deleted_at IS NULL;
  • MySQL 권장: 생성 칼럼(또는 가상 칼럼) 기반 유니크 인덱스
    • ALTER TABLE users ADD COLUMN is_active TINYINT(1) GENERATED ALWAYS AS (CASE WHEN deleted_at IS NULL THEN 1 ELSE 0 END) STORED;
    • CREATE UNIQUE INDEX ux_users_provider_active ON users(provider_type, provider_id, is_active) WHERE is_active = 1;
      • MySQL은 부분 인덱스를 지원하지 않으므로 WHERE 절은 생략하고 3컬럼 유니크로 대체하는 패턴도 흔합니다.

이런 제약을 두면 동일 provider에 대해 활성 상태 중복이 구조적으로 불가능해져, 본 PR과 같은 문제의 재발을 방지할 수 있습니다.

🤖 Prompt for AI Agents
infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt around
lines 19-26: add a DB-level uniqueness constraint to prevent duplicate active
provider accounts; for Postgres create a partial unique index on (provider_type,
provider_id) where deleted_at IS NULL, and for MySQL add a stored/generated
is_active column (true when deleted_at IS NULL) and a unique index on
(provider_type, provider_id, is_active) or equivalent 3-column unique index,
apply these changes via a new migration, and add/adjust integration tests to
verify attempts to insert duplicate active provider records fail at the database
level.

@sonarqubecloud
Copy link

Copy link
Member

@minwoo1999 minwoo1999 left a comment

Choose a reason for hiding this comment

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

복구 시에는 native query로 복구 하는 방향으로 컨벤션 맞추는 거 좋습니다~
고생하셨습니다.

@move-hoon move-hoon merged commit 450d0c2 into develop Aug 17, 2025
4 checks passed
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.

[BOOK-269/fix] 회원탈퇴 유저 복구가 안되는 문제

3 participants