From 483bdb65860d1ff03e890624d2b3e60435121b8d Mon Sep 17 00:00:00 2001 From: asowjdan Date: Sat, 11 Oct 2025 06:28:55 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix[member]:=20=EC=B1=97=EB=B4=87=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=EB=82=B4=EC=9A=A9=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=ED=95=AD=EB=AA=A9=EC=97=90=20cascade=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=97=B0=EA=B4=80?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/lawyer/domain/chatbot/entity/Chat.java | 4 +- .../chatbot/repository/ChatLawRepository.java | 10 +++ .../repository/ChatPrecedentRepository.java | 10 +++ .../chatbot/repository/ChatRepository.java | 10 +++ .../domain/member/service/MemberService.java | 63 ++++++++++--------- 5 files changed, 67 insertions(+), 30 deletions(-) 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 5067209f..80765d9b 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 be1674a5..bea2a9a9 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 a0e37661..820456d1 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 99d31525..08ca84db 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 e2f6f62a..2b2fc888 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; From c4dc46e24bd449cc03dc222b466200d213544aa3 Mon Sep 17 00:00:00 2001 From: asowjdan Date: Sat, 11 Oct 2025 06:29:22 +0900 Subject: [PATCH 2/2] =?UTF-8?q?test[member]:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/MemberServiceOAuth2Test.java | 14 +++++++++++++- .../member/service/MemberServiceTest.java | 19 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) 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 36ba2ae6..549e9027 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 23e61165..0b0659c2 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());