[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차 캐시에 보관한다
member.setTeam(team)을 호출해도, 이미 1차 캐시에 올라와 있는team객체의 자식 리스트(List<Member> members)는 자바 객체 레벨에서 자동으로 업데이트되지 않는다
- 조회 실패: 이 상태에서
team.getMembers()를 호출하면 DB를 새로 조회하지 않고 1차 캐시의 비어있는 리스트를 반환하여 비즈니스 로직 오류가 발생한다
3. [영속성 컨텍스트 동기화가 필요한 이유]
- 객체지향적 관점: DB 반영 여부와 관계없이, 메모리 상의 객체 그래프는 항상 양쪽 방향 모두 올바른 참조를 유지해야 한다
- 테스트 코드 신뢰성:
EntityManager.flush()나clear()를 수행하지 않은 상태에서 수행되는 단위 테스트들이 객체 불일치로 인해 실패할 수 있다 - 순수 객체 상태 유지: JPA 프레임워크 없이 순수한 자바 환경에서도 로직이 돌아가려면 양방향 참조를 수동으로 맞춰야 한다
4. [해결책: 연관관계 편의 메서드 (Convenience Method)]
양쪽의 상태를 한 번에 맞추는 원자적(Atomic)인 메서드를 구현해야 한다
- 구현 위치: 비즈니스 로직의 흐름상 자연스러운 쪽(보통 일(1) 측인
Team)에 작성하는 것을 권장한다 - 핵심 로직:
- 현재 객체의 컬렉션에 상대방 추가 (
this.members.add(member)) - 상대방 객체의 주인 필드 설정 (
member.setTeam(this))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 반영 준비) } }
- 현재 객체의 컬렉션에 상대방 추가 (
📌 요약 테이블
| 키워드 | 설명 | 비고 |
|---|---|---|
| 연관관계 주인 | 외래 키(FK)를 가진 엔티티 | 데이터 변경의 권한을 가짐 |
| mappedBy | 주인이 아님을 명시하는 속성 | 비주인(1) 쪽에서 설정 |
| 1차 캐시 | 영속성 컨텍스트 내 객체 저장소 | 불일치 현상이 발생하는 장소 |
| 편의 메서드 | 양방향 참조를 맞추는 메서드 | addMember(), setTeam() 등 |
This post is licensed under CC BY 4.0 by the author.