Post

[JPA] One-to-Many 양방향 매핑과 객체 상태 불일치

1. [핵심 개념: 연관관계의 주인과 mappedBy]

  • 연관관계의 주인 (Owner): 외래 키(FK)를 관리하는 실제 주체입니다. 보통 외래 키가 있는 다(N) 쪽이 주인이 되며, @JoinColumn을 사용한다
  • 비주인 (Inverse): 주인의 반대편으로, 데이터를 읽기만 가능하며 @OneToMany(mappedBy = “주인의 연관필드명”)를 사용해 “나는 주인이 아니다”라고 선언한다
  • JPA의 규칙: JPA는 연관관계의 주인(Owner)이 관리하는 필드를 기준으로만 DB의 외래 키를 업데이트한다

2. [문제점: 객체 상태 불일치 (In-memory Inconsistency)]

현상: 주인(Member) 측에서 팀을 설정해 DB에는 저장되었으나, 비주인(Team) 측의 리스트(members)로 조회하면 데이터가 보이지 않는 현상이다

  • 영속성 컨텍스트(1차 캐시)의 함정: * JPA는 한 트랜잭션 내에서 조회된 객체를 1차 캐시에 보관한다
  • 조회 실패: 이 상태에서 team.getMembers()를 호출하면 DB를 새로 조회하지 않고 1차 캐시의 비어있는 리스트를 반환하여 비즈니스 로직 오류가 발생한다

3. [영속성 컨텍스트 동기화가 필요한 이유]

  • 객체지향적 관점: DB 반영 여부와 관계없이, 메모리 상의 객체 그래프는 항상 양쪽 방향 모두 올바른 참조를 유지해야 한다
  • 테스트 코드 신뢰성: EntityManager.flush()나 clear()를 수행하지 않은 상태에서 수행되는 단위 테스트들이 객체 불일치로 인해 실패할 수 있다
  • 순수 객체 상태 유지: JPA 프레임워크 없이 순수한 자바 환경에서도 로직이 돌아가려면 양방향 참조를 수동으로 맞춰야 한다

4. [해결책: 연관관계 편의 메서드 (Convenience Method)]

양쪽의 상태를 한 번에 맞추는 원자적(Atomic)인 메서드를 구현해야 한다

  • 구현 위치: 비즈니스 로직의 흐름상 자연스러운 쪽(보통 일(1) 측인 Team)에 작성하는 것을 권장한다
  • 핵심 로직:
    1
    2
    3
    4
    5
    6
    7
    
    // Team.java (비주인 측)
    public void addMember(Member member) {
      this.members.add(member);      // 1. 비주인 객체 리스트에 추가 (메모리 동기화)
      if (member.getTeam() != this) {
          member.setTeam(this);      // 2. 주인 객체의 FK 세팅 (DB 반영 준비)
      }
    }
    

📌 요약 테이블

This post is licensed under CC BY 4.0 by the author.