seaking110 님의 블로그
JPA를 이용한 일정관리 프로그램 트러블 슈팅 본문
트러블 슈팅
1. 단방향 연관 관계에서 부모 삭제 시 삭제가 되지 않는 문제
문제 상황
회원(Member)을 삭제할 때 해당 회원이 작성한 할 일이 존재하면 삭제되지 않는 문제 발생!!
- 기존에는 단방향 연관관계로 설정
- 회원을 삭제하려고 하면 연관된 Todo가 존재하므로 DB의 외래 키 제약 조건으로 인해 삭제가 불가능
❌ 문제점
- Member 삭제 시, Todo가 함께 삭제되지 않음
- 외래 키 제약 조건으로 인해 SQL 오류 발생
- 할 일을 수동으로 삭제하지 않으면 회원 삭제 불가능
💡 해결 방법 고민
방법 1 : 양방향 연관관계로 변경 및 JPA의 cascade = CascadeType.ALL 및 orphanRemoval = true 사용
@Entity
public class Member {
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Todo> todos = new ArrayList<>();
}
- 처음엔 단방향 연관관계에서 cascade = CascadeType.ALL 및 orphanRemoval = true 를 사용했는데 적용이 되지 않았다!
- 안되는 이유!!
- 단방향 연관관계 (Todo → Member) 로 설정하면, Member 엔티티에는 Todo에 대한 정보를 전혀 알지 못함.
- "이 회원이 어떤 할 일을 가지고 있는지" 를 JPA가 알 수 없기 때문에 자동 삭제가 불가능함.
- 양방향 연관관계로 변경
장점
✔ Member 삭제 시 연관된 Todo도 자동으로 삭제됨
✔ 명시적으로 todoRepository.delete()를 호출할 필요 없음 → 코드 간결화
✔ orphanRemoval = true 덕분에 리스트에서 제거하면 DB에서도 자동 삭제
단점
❌ Member 엔티티가 Todo 엔티티를 관리해야 해서 양방향 연관관계 설정이 필요함
방법 2 : DB의 on delete cascade 설정
FOREIGN KEY (member_id) REFERENCES member(id) ON DELETE CASCADE;
장점
✔ DB 차원에서 자동 삭제 → JPA 설정 없이도 적용 가능
✔ SQL에서 직접 처리하므로 성능이 더 좋을 수도 있음
단점
❌ DB에서만 처리하므로 JPA 영속성 컨텍스트와 동기화되지 않을 수도 있음
❌ JPA에서 삭제 로직을 직접 제어할 수 없음
📌 최종 결정
방법 1 : 양방향 연관관계로 변경 및 JPA의 cascade = CascadeType.ALL 및 orphanRemoval = true 사용
- JPA에서 자동으로 연관된 Todo 삭제 가능 → 코드 단순화
- 방법 2를 사용할 시 JPA로 제어하기 어려운 큰 문제 발생
- 확장성 & 유지보수 용이
2. 필터의 ResponseStatusException이 적용되지 않는 문제 발생
문제 상황
회ㅍ
필터의 ResponseStatusException이 적용되지 않는 문제 발생
- 로그인 여부를 확인하는 필터를 구현하여, 로그인하지 않은 사용자는 401 Unauthorized 에러를 반환하도록 설정
- 하지만 예상했던 401 에러가 아닌 500 서버 에러 발생
❌ 문제점
- 필터에서 ResponseStatusException을 던짐 → 500 Internal Server Error 발생
- ResponseStatusException은 Spring MVC (@RestController) 내부에서만 자동 처리됨
- 서블릿 필터에서는 Spring 예외 처리 메커니즘이 적용되지 않음 → 예외 미처리로 500 발생
💡 해결 방법 고민
방법 1 : HttpServletResponse.sendError() 사용
if (session == null || session.getAttribute(Const.LOGIN_MEMBER) == null) {
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "로그인이 필요합니다.");
return;
}
- 처음엔 단방향 연관관계에서 cascade = CascadeType.ALL 및 orphanRemoval = true 를 사용했는데 적용이 되지 않았다!
- 안되는 이유!!
- 단방향 연관관계 (Todo → Member) 로 설정하면, Member 엔티티에는 Todo에 대한 정보를 전혀 알지 못함.
- "이 회원이 어떤 할 일을 가지고 있는지" 를 JPA가 알 수 없기 때문에 자동 삭제가 불가능함.
- 양방향 연관관계로 변경
장점
✔ Spring 기본 예외 처리(BasicErrorController)를 활용할 수 있음
✔ Spring MVC와의 자연스러운 연동 가능
단점
❌ sendError()를 사용하면 Spring이 HTML로 에러 페이지를 반환할 수도 있음
방법 2 : HttpServletResponse.setStatus() + getWriter().write() 사용
if (session == null || session.getAttribute(Const.LOGIN_MEMBER) == null) {
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpResponse.getWriter().write("please login first");
return;
}
장점
✔ Spring MVC 예외 처리와 관계없이 401 응답을 직접 반환 가능
✔ 추가적인 설정 없이 바로 적용 가능
단점
❌ Spring의 예외 처리 체인을 활용하지 못함
📌 최종 결정
방법 2 : HttpServletResponse.setStatus() + getWriter().write() 사용
- 가장 직접적으로 401 Unauthorized 응답을 반환할 수 있음
- Spring 예외 처리 없이도 모든 요청에 대해 일관성 유지 가능
- 메시지를 함께 반환하여 프론트엔드에서 쉽게 처리 가능
'Today I Learned' 카테고리의 다른 글
[Git] Conventional Commits에 대해 (0) | 2025.02.18 |
---|---|
테이블 설계와 정규화 (0) | 2025.02.17 |
JPA (0) | 2025.02.10 |
JPA 꿀팁 (0) | 2025.02.10 |
일정 관리 피드백 (0) | 2025.02.07 |