diff --git a/backend/src/main/java/com/ai/lawyer/domain/chatbot/entity/Chat.java b/backend/src/main/java/com/ai/lawyer/domain/chatbot/entity/Chat.java index 5067209..80765d9 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/chatbot/entity/Chat.java +++ b/backend/src/main/java/com/ai/lawyer/domain/chatbot/entity/Chat.java @@ -33,10 +33,10 @@ public class Chat { @Lob private String message; - @OneToMany(mappedBy = "chatId") + @OneToMany(mappedBy = "chatId", cascade = CascadeType.ALL, orphanRemoval = true) private List chatPrecedents; - @OneToMany(mappedBy = "chatId") + @OneToMany(mappedBy = "chatId", cascade = CascadeType.ALL, orphanRemoval = true) private List chatLaws; @CreationTimestamp diff --git a/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatLawRepository.java b/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatLawRepository.java index be1674a..bea2a9a 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatLawRepository.java +++ b/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatLawRepository.java @@ -2,8 +2,18 @@ import com.ai.lawyer.domain.chatbot.entity.ChatLaw; 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 org.springframework.stereotype.Repository; @Repository public interface ChatLawRepository extends JpaRepository { + + /** + * member_id에 해당하는 모든 ChatLaw 삭제 (회원 탈퇴 시 사용) + */ + @Modifying + @Query("DELETE FROM ChatLaw cl WHERE cl.chatId.historyId.memberId.memberId = :memberId") + void deleteByMemberIdValue(@Param("memberId") Long memberId); } diff --git a/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatPrecedentRepository.java b/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatPrecedentRepository.java index a0e3766..820456d 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatPrecedentRepository.java +++ b/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatPrecedentRepository.java @@ -2,6 +2,16 @@ import com.ai.lawyer.domain.chatbot.entity.ChatPrecedent; 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; public interface ChatPrecedentRepository extends JpaRepository { + + /** + * member_id에 해당하는 모든 ChatPrecedent 삭제 (회원 탈퇴 시 사용) + */ + @Modifying + @Query("DELETE FROM ChatPrecedent cp WHERE cp.chatId.historyId.memberId.memberId = :memberId") + void deleteByMemberIdValue(@Param("memberId") Long memberId); } diff --git a/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatRepository.java b/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatRepository.java index 99d3152..08ca84d 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatRepository.java +++ b/backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatRepository.java @@ -2,8 +2,18 @@ import com.ai.lawyer.domain.chatbot.entity.Chat; 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 org.springframework.stereotype.Repository; @Repository public interface ChatRepository extends JpaRepository { + + /** + * member_id에 해당하는 모든 Chat 삭제 (회원 탈퇴 시 사용) + */ + @Modifying + @Query("DELETE FROM Chat c WHERE c.historyId.memberId.memberId = :memberId") + void deleteByMemberIdValue(@Param("memberId") Long memberId); } diff --git a/backend/src/main/java/com/ai/lawyer/domain/member/service/MemberService.java b/backend/src/main/java/com/ai/lawyer/domain/member/service/MemberService.java index e2f6f62..2b2fc88 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/member/service/MemberService.java +++ b/backend/src/main/java/com/ai/lawyer/domain/member/service/MemberService.java @@ -8,6 +8,9 @@ import com.ai.lawyer.domain.post.repository.PostRepository; import com.ai.lawyer.domain.poll.repository.PollVoteRepository; import com.ai.lawyer.domain.chatbot.repository.HistoryRepository; +import com.ai.lawyer.domain.chatbot.repository.ChatRepository; +import com.ai.lawyer.domain.chatbot.repository.ChatPrecedentRepository; +import com.ai.lawyer.domain.chatbot.repository.ChatLawRepository; import com.ai.lawyer.global.jwt.TokenProvider; import com.ai.lawyer.global.jwt.CookieUtil; import com.ai.lawyer.global.email.service.EmailService; @@ -33,6 +36,9 @@ public class MemberService { private final PostRepository postRepository; private final PollVoteRepository pollVoteRepository; private final HistoryRepository historyRepository; + private final ChatRepository chatRepository; + private final ChatPrecedentRepository chatPrecedentRepository; + private final ChatLawRepository chatLawRepository; public MemberService( MemberRepository memberRepository, @@ -43,7 +49,10 @@ public MemberService( EmailAuthService emailAuthService, PostRepository postRepository, PollVoteRepository pollVoteRepository, - HistoryRepository historyRepository) { + HistoryRepository historyRepository, + ChatRepository chatRepository, + ChatPrecedentRepository chatPrecedentRepository, + ChatLawRepository chatLawRepository) { this.memberRepository = memberRepository; this.passwordEncoder = passwordEncoder; this.tokenProvider = tokenProvider; @@ -53,6 +62,9 @@ public MemberService( this.postRepository = postRepository; this.pollVoteRepository = pollVoteRepository; this.historyRepository = historyRepository; + this.chatRepository = chatRepository; + this.chatPrecedentRepository = chatPrecedentRepository; + this.chatLawRepository = chatLawRepository; } @org.springframework.beans.factory.annotation.Autowired(required = false) @@ -225,37 +237,32 @@ public void deleteMember(String loginId) { // 2. 연관된 데이터 명시적 삭제 (순서 중요: FK 제약조건 고려) log.info("연관 데이터 삭제 시작: memberId={}", memberId); - // 2-1. 채팅 히스토리 삭제 (Chat 엔티티도 cascade로 함께 삭제됨) - try { - historyRepository.deleteByMemberIdValue(memberId); - log.info("채팅 히스토리 삭제 완료: memberId={}", memberId); - } catch (Exception e) { - log.error("채팅 히스토리 삭제 실패: memberId={}, error={}", memberId, e.getMessage()); - } + // 2-1. ChatPrecedent, ChatLaw 삭제 (Chat의 FK 참조) + chatPrecedentRepository.deleteByMemberIdValue(memberId); + log.info("채팅 판례 삭제 완료: memberId={}", memberId); - // 2-2. 투표 내역 삭제 - try { - pollVoteRepository.deleteByMemberIdValue(memberId); - log.info("투표 내역 삭제 완료: memberId={}", memberId); - } catch (Exception e) { - log.error("투표 내역 삭제 실패: memberId={}, error={}", memberId, e.getMessage()); - } + chatLawRepository.deleteByMemberIdValue(memberId); + log.info("채팅 법령 삭제 완료: memberId={}", memberId); - // 2-3. 게시글 삭제 (Poll 엔티티도 cascade로 함께 삭제됨) - try { - postRepository.deleteByMemberIdValue(memberId); - log.info("게시글 삭제 완료: memberId={}", memberId); - } catch (Exception e) { - log.error("게시글 삭제 실패: memberId={}, error={}", memberId, e.getMessage()); - } + // 2-2. Chat 삭제 (History의 FK 참조) + chatRepository.deleteByMemberIdValue(memberId); + log.info("채팅 삭제 완료: memberId={}", memberId); + + // 2-3. History 삭제 (Member의 FK 참조) + historyRepository.deleteByMemberIdValue(memberId); + log.info("채팅 히스토리 삭제 완료: memberId={}", memberId); + + // 2-4. 투표 내역 삭제 + pollVoteRepository.deleteByMemberIdValue(memberId); + log.info("투표 내역 삭제 완료: memberId={}", memberId); + + // 2-5. 게시글 삭제 (Poll 엔티티도 cascade로 함께 삭제됨) + postRepository.deleteByMemberIdValue(memberId); + log.info("게시글 삭제 완료: memberId={}", memberId); // 3. Redis 토큰 삭제 - try { - tokenProvider.deleteAllTokens(loginId); - log.info("Redis 토큰 삭제 완료: loginId={}", loginId); - } catch (Exception e) { - log.error("Redis 토큰 삭제 실패: loginId={}, error={}", loginId, e.getMessage()); - } + tokenProvider.deleteAllTokens(loginId); + log.info("Redis 토큰 삭제 완료: loginId={}", loginId); // 4. 회원 정보 삭제 final Long finalMemberId = memberId; diff --git a/backend/src/test/java/com/ai/lawyer/domain/member/service/MemberServiceOAuth2Test.java b/backend/src/test/java/com/ai/lawyer/domain/member/service/MemberServiceOAuth2Test.java index 36ba2ae..549e902 100644 --- a/backend/src/test/java/com/ai/lawyer/domain/member/service/MemberServiceOAuth2Test.java +++ b/backend/src/test/java/com/ai/lawyer/domain/member/service/MemberServiceOAuth2Test.java @@ -66,6 +66,15 @@ class MemberServiceOAuth2Test { @Mock private com.ai.lawyer.domain.chatbot.repository.HistoryRepository historyRepository; + @Mock + private com.ai.lawyer.domain.chatbot.repository.ChatRepository chatRepository; + + @Mock + private com.ai.lawyer.domain.chatbot.repository.ChatPrecedentRepository chatPrecedentRepository; + + @Mock + private com.ai.lawyer.domain.chatbot.repository.ChatLawRepository chatLawRepository; + @Mock private HttpServletResponse response; @@ -86,7 +95,10 @@ void setUp() { emailAuthService, postRepository, pollVoteRepository, - historyRepository + historyRepository, + chatRepository, + chatPrecedentRepository, + chatLawRepository ); memberService.setOauth2MemberRepository(oauth2MemberRepository); diff --git a/backend/src/test/java/com/ai/lawyer/domain/member/service/MemberServiceTest.java b/backend/src/test/java/com/ai/lawyer/domain/member/service/MemberServiceTest.java index 23e6116..0b0659c 100644 --- a/backend/src/test/java/com/ai/lawyer/domain/member/service/MemberServiceTest.java +++ b/backend/src/test/java/com/ai/lawyer/domain/member/service/MemberServiceTest.java @@ -61,6 +61,15 @@ class MemberServiceTest { @Mock private com.ai.lawyer.domain.chatbot.repository.HistoryRepository historyRepository; + @Mock + private com.ai.lawyer.domain.chatbot.repository.ChatRepository chatRepository; + + @Mock + private com.ai.lawyer.domain.chatbot.repository.ChatPrecedentRepository chatPrecedentRepository; + + @Mock + private com.ai.lawyer.domain.chatbot.repository.ChatLawRepository chatLawRepository; + @Mock private HttpServletResponse response; @@ -85,7 +94,10 @@ void setUp() { emailAuthService, postRepository, pollVoteRepository, - historyRepository + historyRepository, + chatRepository, + chatPrecedentRepository, + chatLawRepository ); memberService.setOauth2MemberRepository(oauth2MemberRepository); @@ -315,7 +327,10 @@ void withdraw_Success() { // 1. 회원 조회 verify(memberRepository).findByLoginId(loginId); - // 2. 연관 데이터 명시적 삭제 (순서 중요) + // 2. 연관 데이터 명시적 삭제 (순서 중요: FK 제약조건 고려) + verify(chatPrecedentRepository).deleteByMemberIdValue(member.getMemberId()); + verify(chatLawRepository).deleteByMemberIdValue(member.getMemberId()); + verify(chatRepository).deleteByMemberIdValue(member.getMemberId()); verify(historyRepository).deleteByMemberIdValue(member.getMemberId()); verify(pollVoteRepository).deleteByMemberIdValue(member.getMemberId()); verify(postRepository).deleteByMemberIdValue(member.getMemberId());