Tripin 프로젝트를 진행하던 와중 삭제가 되지 않는 문제가 발생했다.
문제 상황
TravelSchedule과 Expense는 양방향 OneToOne 관계이다.
아래는 간략한 엔티티 코드이다.
- TravelSchedule
@Getter
@Entity
public class TravelSchedule extends BaseEntity {
@OneToOne(mappedBy = "travelSchedule", cascade = CascadeType.PERSIST, orphanRemoval = true)
private Expense expense;
}
영속성 전이와 고아 객체를 설정해두었다.
TravelSchedule이 하나의 expense를 가지고 있다.
- Expense
@Getter
@Entity
public class Expense extends BaseEntity {
@OneToOne
@JoinColumn(name = "travel_schedule_id")
private TravelSchedule travelSchedule;
}
Expense는 schedule을 참조하고 있다.
expense가 schedule 없이 독립적으로 존재하지 못하고
schedule이 삭제될 때 expense도 삭제되어야하기 때문에
cascade = CascadeType.PERSIST, orphanRemoval = true를 설정하여 관리하였다.
테스트 결과
- expense 삭제 로직
@Transactional
public void deleteExpense(Long expenseId) {
Expense expense = expenseRepository.findById(expenseId)
.orElseThrow(() -> new NotFoundException(Message.EXPENSE_NOT_FOUND));
expenseRepository.delete(expense);
}
- 테스트 코드
@DisplayName("지출을 삭제합니다.")
@Test
void deleteExpense() {
// given
Expense expense = Expense.builder()
.expectPrice(10000)
.payedPrice(8000)
.travelSchedule(schedule)
.build();
expenseRepository.save(expense);
Long expenseId = expense.getExpenseId();
// when
expenseService.deleteExpense(expenseId);
em.flush();
em.clear();
// then
assertThat(expenseRepository.findById(expenseId)).isEmpty();
}
expense를 삭제하는 테스트를 작성하였다.


로그를 보니 delete 쿼리가 아예 나가지 않아 삭제가 되지 않았고 그렇기에 테스트도 실패하였다.
해결 방법
왜 delete 쿼리가 나가지 않는지 이해가 되지 않아
JPA 연관관계 매핑과 영속성 컨텍스트를 다시 공부하여 문제를 해결할 수 있었다.
@Transactional
public void deleteExpense(Long expenseId) {
Expense expense = expenseRepository.findById(expenseId)
.orElseThrow(() -> new NotFoundException(Message.EXPENSE_NOT_FOUND));
TravelSchedule schedule = expense.getTravelSchedule();
if (schedule != null) {
schedule.deleteExpense();
}
expenseRepository.delete(expense);
}
em.remove()와 schedule -> expense의 cascade 연관관계가 서로 충돌해서
expense를 삭제해도 schedule -> expense 연관관계가 cascade로 남아있기 때문에 삭제가 되지 않았다.
영속성 컨텍스트 입장에서는 부모(schedule)가 여전히 자식을 참조하고 있어
delete와 persist(cascade)의 상태가 모순되어 flush 시점에 delete를 누락시킨 것이다.
그렇기 때문에 부모-자식 관계를 먼저 해제한 뒤 자식을 삭제해야한다.
schedule -> expense의 연관관계를 끊어주었더니 delete 쿼리가 나가는 것을 확인할 수 있었다.
연관관계가 없으니 cascade 효과도 사라지게 되어 정상적으로 작동하게 된 것이다.

cascade 옵션을 쓸 때 부모 엔티티를 삭제 or 수정할 때는 문제가 되지 않지만
자식 엔티티를 직접 삭제하고 싶을 때에는 객체 간의 연관관계를 반드시 먼저 끊고 삭제해야함을 알 수 있었다.
정리
1. 부모 컬렉션/참조에서 먼저 제거
2. 그 다음 delete() 호출
=> delete 쿼리 정상적으로 발생
참고 - 김영한님 답변 링크
https://www.inflearn.com/community/questions/56718/cascade-cascadetype-all%EC%A7%88%EB%AC%B8
'트러블슈팅' 카테고리의 다른 글
| [트러블 슈팅] 페이징 쿼리 최적화 - offset, no offset, covering index (1) | 2025.09.21 |
|---|---|
| [트러블슈팅] 인덱스로 쿼리 최적화하기 (0) | 2025.09.17 |
| [트러블슈팅] 동시성 문제 해결하기 - 낙관적 락, 비관적 락 (0) | 2025.09.06 |
| [트러블슈팅] JPA N+1 문제와 해결법 (0) | 2025.06.27 |