포스트

삭제 로직과 데이터 연관성으로 엮인 양방향 매핑의 함정

삭제 로직과 데이터 연관성으로 엮인 양방향 매핑의 함정

“한 줄 요약: 삭제와 같은 상호 작용 때문에 맺어진 양방향 관계는 데이터 정합성을 맞추기 위한 추가적인 노력이 필요하다.”

1. 발단: “삭제할 때 둘이 엮여있다…?”

Category에서 상품을 삭제(removeProduct)하거나, Product가 속한 카테고리를 변경할 때, 두 객체의 상태를 모두 동기화해야 한다는 필요성을 느꼈습니다. 예를 들어 상품이 삭제되면 카테고리의 상품 리스트에서도 사라져야 하고, 상품의 카테고리 정보도 지워져야 합니다. 이러한 데이터의 유기적인 연관성 때문에 자연스럽게 양방향 매핑을 선택하게 되었습니다.

2. 부가 정보 1: 순수 자바에서 안전하게 양방향 매핑하는 방법 (연관관계 편의 메서드)

양방향 관계에서는 한쪽만 수정하고 다른 쪽을 깜빡하면 데이터가 불일치하는 ‘객체 정합성’ 문제가 발생합니다. 이를 방지하기 위해 한 번의 호출로 양쪽을 모두 수정하는 연관관계 편의 메서드를 작성해야 합니다.

[적용 예시]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Product {
    private Category category;
    private final String name;

    // 카테고리를 지정하거나 변경할 때, 양쪽 객체의 상태를 한 번에 바꾼다.
    public void setCategory(Category category) {
        // 1. 기존 카테고리와의 연결을 끊는다. (이미 연결된 경우)
        if (this.category != null) {
            this.category.getProducts().remove(this);
        }
        
        // 2. 새로운 카테고리를 설정한다.
        this.category = category;
        
        // 3. 새로운 카테고리 쪽에 나(Product)를 추가한다.
        if (category != null && !category.getProducts().contains(this)) {
            category.getProducts().add(this);
        }
    }
}

3. 부가 정보 2: 양방향 매핑 시 주의할 점

1. 무한 루프 (StackOverflowError)

양방향 매핑이 되어 있는 상태에서 두 객체의 toString(), equals(), hashCode()를 무심코 호출하면 서로를 무한 참조하다가 프로그램이 터진다.

  • 해결책: toString() 등을 오버라이딩할 때 연관된 객체는 출력 대상에서 제외하거나, 식별자(이름 등)만 출력하도록 제한해야 한다.

2. JSON 직렬화 오류

나중에 Spring 같은 프레임워크를 쓸 때, 객체를 JSON으로 변환하여 웹에 뿌려주려고 하면 무한 루프에 빠져 에러가 난다. (Category -> Product -> Category -> …)

해결책: 직렬화 시 한쪽 방향의 참조를 무시하는 설정(@JsonIgnore 등)이 필수적이다.

3. 데이터 정합성 유지 비용

데이터 하나를 지우거나 수정할 때 신경 써야 할 로직이 늘어난다. 연관관계 편의 메서드가 조금이라도 잘못 작성되면 시스템 전체의 데이터가 꼬이게 된다.


4. 📝 오늘의 결론 및 다짐

  • 정합성의 책임: 삭제나 수정 시 데이터의 일관성을 유지하기 위해 양방향 매핑을 쓸 때는 반드시 양쪽을 원자적(Atomic)으로 묶어주는 편의 메서드가 필수임을 배웠습니다.

  • 설계적 유연성: 양방향 매핑은 편리해 보이지만 그만큼 위험 부담이 크다. 앞으로는 “진짜 양방향이 필요한가?”를 고민해 보고, 가능하다면 식별자(ID) 참조를 통해 물리적인 결합을 끊어내는 설계를 우선적으로 고려해야겠습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.