Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ public interface AdviceRepository extends JpaRepository<Advice, Long> {
Page<Advice> findAllByUserId(Long userId, Pageable pageable);
Page<Advice> findAllByExpertIdAndUserId(Long expertId, Long userId, Pageable pageable);
Optional<Advice> findTop1ByUserIdOrderByCreatedDateDescIdDesc(Long userId);
void deleteByUserId(Long userId);
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

파생 삭제 쿼리의 프로퍼티 경로 확인 필요 (userId vs user.id)

deleteByUserId(Long userId)가 올바로 동작하려면 Advice 엔티티에 실제 필드명이 userId여야 합니다. 연관매핑으로 User user만 있고 PK를 통해 접근하는 구조라면 deleteByUser_Id(Long userId)가 맞습니다. 아래 스크립트로 엔티티 정의를 확인해 주세요.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Advice 엔티티 위치/구조 확인
fd -t f -a 'Advice.java'

# 클래스 선언 주변 출력
rg -nP -C3 'class\s+Advice\b'

# userId 필드 또는 User 연관 필드 존재 여부 확인
rg -nP -C2 '\b(Long|long)\s+userId\b|\bUser\s+user\b'

# (선택) user FK 컬럼 어노테이션 힌트
rg -nP -C2 '@ManyToOne|@JoinColumn\([^)]*user_?id'

Length of output: 110324


AdviceRepository의 삭제 메소드명 수정 필요

Advice 엔티티에 실제로는 userId 필드가 없고, @ManyToOne으로 매핑된 User user 속성만 존재합니다. Spring Data JPA의 파생 쿼리는 엔티티 프로퍼티명을 기준으로 이름을 해석하므로, 현재 정의된

void deleteByUserId(Long userId);

는 올바르게 동작하지 않습니다. 대신 user.id 경로를 지정하려면 메소드명을 deleteByUser_Id로 변경해야 합니다.

– 대상 파일
• src/main/java/com/my_medi/domain/advice/repository/AdviceRepository.java line 14
– 변경 전

- void deleteByUserId(Long userId);

– 변경 후

+ void deleteByUser_Id(Long userId);

이렇게 수정하면 Advice 엔티티의 user.id 값을 기준으로 모든 관련 레코드를 삭제하도록 파생 쿼리가 올바르게 생성됩니다.

🤖 Prompt for AI Agents
In src/main/java/com/my_medi/domain/advice/repository/AdviceRepository.java
around line 14, the repository method void deleteByUserId(Long userId) is
incorrect because the Advice entity has a User user field (mapped via
@ManyToOne) rather than a userId property; rename the method to void
deleteByUser_Id(Long userId) so Spring Data JPA will generate a derived delete
query that navigates the association and deletes by Advice.user.id.

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ ConsultationRequestRepository.RequestedAgg findRequestedAgg(

int deleteByIdAndUserIdAndRequestStatus(Long id, Long userId, RequestStatus status);


void deleteByUserId(Long userId);



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@

public interface UserNotificationRepository extends JpaRepository<UserNotification, Long> {
Page<UserNotification> findAllByUserId(Long userId, Pageable pageable);
void deleteByUserId(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ public interface ProposalRepository extends JpaRepository<Proposal, Long> {
where u.id in :userIds
""")
List<Proposal> findAllByUserIdInWithUser(@Param("userIds") Set<Long> userIds);

void deleteByUserId(Long userId);
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

유저당 Proposal 단건 가정 확인 및 반환형(long)으로 변경 권장

위쪽에 Optional<Proposal> findByUserId(Long userId)가 있는 것으로 보아 유저당 Proposal이 1개라는 도메인 가정이 있는 듯합니다. 그렇다면 삭제도 정확히 1건이 삭제되는지 모니터링하는 편이 좋습니다. 삭제 건수를 반환하도록 바꾸면 데이터 이상(중복 레코드 등)을 조기에 탐지할 수 있습니다.

-    void deleteByUserId(Long userId);
+    long deleteByUserId(Long userId);

또한 유니크 제약(예: proposal.user_id unique)이 실제로 잡혀 있는지 확인 부탁드립니다. 없다면 findByUserId가 Optional임에도 중복이 허용될 수 있습니다.

유니크 제약 및 매핑을 점검하려면:


🏁 Script executed:

#!/bin/bash
# Proposal ↔ User 매핑과 unique 제약 흔적 검색
rg -nP -C3 '(class\s+Proposal\b|@OneToOne|@ManyToOne|@JoinColumn|unique\s*=\s*true)' --type=java
# 마이그레이션/DDL에서 proposal.user_id 유니크/인덱스 확인
rg -nP -i -C2 '(create\s+unique\s+index|unique\s*\(|add\s+constraint).*proposal.*user_id' -g 'src/**/*.{sql,yml,xml}'

Length of output: 14213


Proposal 삭제 메서드 반환 타입 변경 및 유니크 제약 확인/추가 필요

현재 Proposal 엔티티에 @OneToOne(fetch = FetchType.LAZY) 매핑이 존재하나, DDL/마이그레이션 파일에서는 proposal.user_id에 대한 명시적 유니크 제약이나 인덱스가 검색되지 않았습니다.
또한 deleteByUserId 메서드는 반환 타입이 void라 삭제 건수를 알 수 없어, 중복 레코드 등 데이터 이상을 조기에 감지하기 어렵습니다.

아래 조치를 권장드립니다:

  • 메서드 반환 타입을 long으로 변경하고 삭제 건수를 반환받아 모니터링
-    void deleteByUserId(Long userId);
+    long deleteByUserId(Long userId);
  • Proposal ↔ User 1:1 매핑 확인
    • src/main/java/com/my_medi/domain/proposal/entity/Proposal.java (23~25행)
      @OneToOne(fetch = FetchType.LAZY)
      @JoinColumn(name = "user_id", nullable = false)
      private User user;
  • proposal.user_id 유니크 제약 확인
    • 마이그레이션/DDL 자동 생성 설정을 검토하거나
    • migration 파일에 다음과 같은 unique index/constraint를 추가
      ALTER TABLE proposal
        ADD CONSTRAINT uq_proposal_user_id UNIQUE (user_id);

위 개선 사항은 데이터 무결성 강화를 위해 필수적으로 반영하는 것을 권장드립니다.

🤖 Prompt for AI Agents
In src/main/java/com/my_medi/domain/proposal/repository/ProposalRepository.java
around line 23, change the deleteByUserId method to return the number of deleted
rows (use long/Long) instead of void so callers can detect unexpected multiple
deletions; update any calling code/tests accordingly. Also verify the Proposal
entity mapping at src/main/java/com/my_medi/domain/proposal/entity/Proposal.java
(lines ~23-25) keeps the @OneToOne and @JoinColumn(name="user_id",
nullable=false) and ensure the database enforces the 1:1 relationship by adding
a unique constraint/index on proposal.user_id in your migration or DDL (or
enable schema generation to produce it), and run/adjust migrations to apply the
unique constraint.

}
1 change: 0 additions & 1 deletion src/main/java/com/my_medi/domain/report/entity/Report.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public class Report extends BaseTimeEntity {
@JoinColumn(name = "additional_test_id")
private AdditionalTest additionalTest;


@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ SELECT MAX(r3.id)
)
""")
List<UserLatestReportStatusDto> findLatestReportStatusByUserIds(@Param("userIds") Set<Long> userIds);

List<Long> findIdsByUserId(Long userId);

void deleteAllByUserId(Long userId);
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

벌크 삭제 주의사항 + 삭제 건수 반환(long)으로 변경 권장

  • 파생 삭제 쿼리는 벌크 연산이라 엔티티 콜백이 동작하지 않고 1차 캐시와 불일치가 생길 수 있습니다. 같은 트랜잭션에서 Report 엔티티를 로드하지 않도록 주의해 주세요.
  • 운영 가시성을 위해 삭제 행 수 반환을 권장합니다.
-    void deleteAllByUserId(Long userId);
+    long deleteAllByUserId(Long userId);

성능 관점에서 report.user_id에 인덱스가 없다면 대량 삭제 시 테이블 스캔이 발생할 수 있습니다. 마이그레이션에 인덱스 존재 여부를 확인/추가해 주세요. 또한 DB FK에 ON DELETE CASCADE를 설정하면 결과(ReportResult) 선삭제 없이도 Report 삭제만으로 정리가 가능해져 애플리케이션 레벨 삭제 순서를 단순화할 수 있습니다(정책에 따라 선택).

인덱스/제약을 점검하려면:


🏁 Script executed:

#!/bin/bash
# 마이그레이션 파일에서 report.user_id 인덱스/외래키/온딜리트 검색
rg -nPi -C2 '(create\s+index|add\s+constraint|foreign\s+key|on\s+delete\s+cascade).*report.*user_id' -g 'src/**/**/*.{sql,yml,xml}'

Length of output: 133


다음 스크립트로 저장소 전체의 SQL/YAML/XML 마이그레이션 파일을 다시 검색하여 report.user_id 관련 인덱스·제약·ON DELETE CASCADE 설정 여부를 확인해 주세요. 결과가 없다면 수동으로 마이그레이션을 검토해 인덱스 및 FK 설정을 추가해야 합니다.


🏁 Script executed:

#!/bin/bash
# 전역 검색: report.user_id 인덱스/제약/ON DELETE CASCADE 확인
rg -nPi -C2 '(create\s+index|add\s+constraint|foreign\s+key|on\s+delete\s+cascade).*report.*user_id' \
  -g '*.sql' -g '*.xml' -g '*.yml' -g '*.yaml'

Length of output: 147


리턴 타입 변경 및 인덱스·제약 검토 필요

  • deleteAllByUserId 메서드 시그니처를 voidlong으로 변경해 삭제 건수 반환
    -    void deleteAllByUserId(Long userId);
    +    long deleteAllByUserId(Long userId);
  • 벌크 삭제는 엔티티 콜백이 동작하지 않고 1차 캐시와 불일치가 발생할 수 있으므로, 같은 트랜잭션에서 Report 엔티티를 로드하지 않도록 주의
  • 마이그레이션 파일 전역 검색 결과 report.user_id 인덱스가 발견되지 않음 → 대량 삭제 시 테이블 스캔 방지를 위해 인덱스 추가 필수
  • 연관 테이블(ReportResult) FK에 ON DELETE CASCADE 설정 검토 (정책에 따라 선택)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void deleteAllByUserId(Long userId);
long deleteAllByUserId(Long userId);
🤖 Prompt for AI Agents
In src/main/java/com/my_medi/domain/report/repository/ReportRepository.java
around line 45, change the method signature from void deleteAllByUserId(Long
userId) to long deleteAllByUserId(Long userId) so the repository returns the
number of rows deleted; ensure callers handle the returned long. When performing
bulk deletes, avoid loading Report entities in the same transaction or clear the
persistence context (EntityManager.clear()) after the delete to prevent
1st-level cache inconsistency. Add a DB migration to create an index on
report.user_id to prevent full table scans during large deletes, and review/add
ON DELETE CASCADE on the ReportResult FK if your deletion policy requires
cascading cleanup.

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import com.my_medi.domain.reportResult.entity.ReportResult;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.Optional;

Expand All @@ -11,4 +14,13 @@ public interface ReportResultRepository extends JpaRepository<ReportResult, Long
void deleteByReportId(Long reportId);

Optional<ReportResult> findByReportId(Long id);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
delete from ReportResult rr
where rr.report.id in (
select r.id from Report r where r.user.id = :userId
)
""")
void deleteAllByUserId(@Param("userId") Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ List<Schedule> findUpcomingSchedulesByExpert(
List<Schedule> findAllByExpertIdAndMeetingDate(Long expertId, LocalDate meetingDate);

List<Schedule> findAllByUserIdAndMeetingDate(Long userId, LocalDate meetingDate);

void deleteAllByUserId(Long userId);
}

Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package com.my_medi.domain.user.service;
import com.my_medi.api.member.dto.RegisterMemberDto;

import com.my_medi.domain.advice.repository.AdviceRepository;
import com.my_medi.domain.consultationRequest.repository.ConsultationRequestRepository;
import com.my_medi.domain.member.entity.Role;
import com.my_medi.api.user.dto.UpdateUserDto;
import com.my_medi.domain.notification.repository.UserNotificationRepository;
import com.my_medi.domain.proposal.repository.ProposalRepository;
import com.my_medi.domain.report.repository.ReportRepository;
import com.my_medi.domain.reportResult.repository.ReportResultRepository;
import com.my_medi.domain.schedule.repository.ScheduleRepository;
import com.my_medi.domain.user.entity.User;
import com.my_medi.domain.user.repository.UserRepository;
import com.my_medi.domain.user.exception.UserHandler;
Expand All @@ -11,6 +18,7 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.UUID;

@Service
Expand All @@ -20,6 +28,13 @@ public class UserCommandServiceImpl implements UserCommandService {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final AdviceRepository adviceRepository;
private final ConsultationRequestRepository consultationRequestRepository;
private final UserNotificationRepository userNotificationRepository;
private final ProposalRepository proposalRepository;
private final ReportRepository reportRepository;
private final ReportResultRepository reportResultRepository;
private final ScheduleRepository scheduleRepository;

@Override
public Long registerUser(RegisterMemberDto registerMemberDto){
Expand Down Expand Up @@ -52,9 +67,19 @@ public Long updateUserInformation(Long userId, UpdateUserDto dto) {
public Long deleteUserAccount(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> UserHandler.NOT_FOUND);

adviceRepository.deleteByUserId(userId);
consultationRequestRepository.deleteByUserId(userId);
userNotificationRepository.deleteByUserId(userId);
proposalRepository.deleteByUserId(userId);

reportResultRepository.deleteAllByUserId(userId);
Copy link
Contributor

Choose a reason for hiding this comment

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

이 method 제대로 실행되는지 테스트 부탁드립니다. reportResult는 리포트와 oneToOne 매핑이라서 이 메서드가 정확한지 불분명하다고 판단됩니다

reportRepository.deleteAllByUserId(userId);

scheduleRepository.deleteAllByUserId(userId);

userRepository.delete(user); // TODO : Hard delete이나 추후 soft로 변경예정
return user.getId();
}

}

2 changes: 1 addition & 1 deletion src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ spring:
### JPA ###
jpa:
hibernate:
ddl-auto: update
ddl-auto: create
### SQL INIT for Batch ###
sql:
init:
Expand Down