Member에서 Team으로(Member.team) 관계 접근이 가능하고 Team에서 Member로(Team.member) 접근이 가능한 것이 양방향 관계를 의미합니다. Team에서 Member는 다대일 관계이고, Member에서 Team은 일대다 관계입니다.
일대다 관계에서는 여러 건과 연관관계를 맺을 수 있기 때문에 컬렉션을 사용해야 합니다.
JPA는 List를 포함하여 Collection, Set, Map 등의 다양한 컬렉션을 지원하고 있습니다.
일대다 관계를 매핑하기 위해서는 @OneToMany 매핑 정보를 사용합니다.
그리고 일대다 관계를 맺은 객체에 컬렉션 List<Member> members를 사용합니다.
테이블은 연관관계는 외래 키 하나로 양방향 조회가 가능합니다. 외래 키를 갖는 순간 처음부터 양방향 연관관계를 의미합니다.
MEMBER JOIN TEAM도 가능하며 TEAM JOIN MEMBER도 가능합니다.
Team에서 Member 컬렉션으로 객체 그래프를 탐색하여 member 조회를 보겠습니다.
Team team = em.find(Team.class, "team1");
List<Member> members = team.getMembers();
mappedBy 사용하는 이유는 객체 연관관계에는 양방향 연관관계가 없습니다.
별개의 단뱡향 연관관계 2개를 의미합니다.
반대로, 테이블은 외래키로 하나의 양방향 연관관계를 갖게 되기 때문에 참조를 두개를 사용할 수 있습니다.
Team -> Member, Member -> Team 이것은 객체의 연관관계를 관리하는 포인트가 2곳이라고 할 수 있습니다.
엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘이지만 외래 키는 하나입니다. 둘 중 어떤 관계를 사용하여 외래 키를 관리해야 하는 것인지 선택의 문제가 발생합니다. 이러한 차이로 JPA는 두 객체 연관관계 중 하나를 지정하여 테이블의 외래키를 관리해야 하는데 이것을 연관관계의 주인 이라고 합니다.
양방향 매핑 규칙 연관관계 주인
연관관계 주인만 데이터베이스 연관관계와 매핑할 수 있고 외래 키를 등록, 수정, 삭제 할 수 있습니다.
주인이 아닌 곳은 읽기 기능만 사용이 가능합니다.
여기서 주인을 구분하는 방법이 mappedBy 속성입니다.
주인은 mappedBy 속성을 사용하지 않습니다. ( = 주인이 아님 )
주인이 아닌 객체는 mappedBy 속성을 사용하여 속성의 값으로 연관관계 주인을 지정해야 합니다.
class Member {
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
class Team {
@OneToMany
private List<Member> members = new ArrayList<Member>();
...
}
Member.team, Team.member
여기서 연관관계 주인은 무엇일까요?
연관관계의 주인을 결정하는 것은 외래 키 관리자를 선택하는 것과 같은 의미입니다.
위 소스에서 Member 엔티티에 있는 Member.team을 주인으로 선택하면 자기 테이블에 있는 외래 키를 관리하면 됩니다.
반대로 Team 엔티티에 있는 Team.members를 주인으로 선택하면 전혀 다른 테이블 외래 키를 관리해야 합니다. 그 이유는 Team.members가 있는 Team 엔티티는 TEAM 테이블에 매핑되어 있는데 관리해야할 외래 키는 MEMBER 테이블에 있기 때문입니다.
연관관계 주인은 테이블에 외래 키가 있는 곳으로 지정해야 합니다.
Member.team 이 주인이 됩니다.
주인이 아닌 Team.members에는 mappedBy = "team" 속성을 사용하여 주인이 아니라고 설정해야 합니다.
class Team {
@OneToMany(mappedBy="team")
private List<Member> members = new ArrayList<Member>();
...
}
다대일, 일대다 관계에서 항상 다 쪽이 외래 키를 갖습니다. 다 쪽 @ManyToOne은 항상 연관관계 주인으로 mappedBy를 설정할 수 없습니다. (설정도 제공되지 않습니다)
양방향 연관관계는 연관관계의 주인이 외래 키를 관리합니다. 주인이 아닌 방향은 값을 설정하지 않고도 데이터베이스에 외래 키 값이 정상적으로 입력됩니다.
team1.getMembers().and(member1);
team1.getMembers().and(member2);
주인이 아닌 곳에 입력된 값은 외래 키에 영향을 주지 않습니다.
member1.setTeam(team1);
member2.setTeam(team2);
Member는 team의 주인으로 이곳에 입력된 값을 사용하여 외래 키를 관리합니다.
다만, 객체 관점에서는 양방향에 모두 값을 입력해주는 것이 가장 안전합니다.
Team team1 = new Team("team1", "팀1");
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
member1.setTeam(team1);
team1.getMembers().add(member1);
member2.setTeam(team1);
team1.getMembers().add(member2);
반대 방향에도 값을 입력. 객체 관점에서 올바른 양방향 입력된 모습
team 객체에도 member1, member2 입력한 모습
위 코드 리펙토링 버전
set() 메서드 하나로 양방향 관계를 모두 설정하도록 변경
class Member {
private Team team;
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
버그가 있다
team 객체가 변경될 때 기존 team의 관계는 제거해야 한다.
set 할 때 항상 기존 관계를 삭제해야 한다.
setTeam(Team team) {
if(this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
객체에서 양방향 연관관계를 사용하려면 로직을 견고하게 작성해야 합니다.
관계를 변경하고 영속성 컨텍스트가 아직 살아있는 상태로 teamA의 getMembers()를 호출하면 member1이 반환됩니다.
변경된 연관관계는 제거하는 것이 안전합니다.
양방향 매핑은 1. 주인을 정해야 하며 2. 양방향 로직도 잘 작성해야하는 단점(?)이 존재하지만
장점은 반대방향으로 객체 그래프 탐색을 사용할 수 있습니다.
양방향 매핑 무한루프 주의
Member.toString().getTeam()을 호출하고
Team.toString().getMember()를 호출하면 무한루프가 발생할 수 있다.
JPA 연관관계 매핑 (일대다, 다대일, 일대일, 다대다) (0) | 2024.01.17 |
---|---|
타입 변환 다형성 (필드, 매개변수) (0) | 2024.01.16 |
매핑 어노테이션 (@Enumerated, @Temporal, @Lob, @Transient, @Access) (0) | 2024.01.12 |
엔티티 매핑 (@Table, Sequence) (0) | 2024.01.11 |
클래스 상속 (생성자 호출) 자바 (1) | 2024.01.10 |
댓글 영역