[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.