1. 양방향 연관관계
양방향 연관관계는 두 객체가 서로 상대편을 참조하고 있는 연관관계를 의미한다.
기존의 단방향 연관관계에서는 참조하는 객체만 반대편 객체를 외래키와 매핑하여서 참조하고 있었다. 하지만 양방향 연관관계에서는 참조되는 객체도 참조하는 객체를 바라보고 있어서 반대 방향에서도 객체 그래프 탐색이 가능하다.
// 참조되던 쪽에서는 참조하는 쪽을 바라보기 위한 멤버가 추가된다.
class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team") // 일대다 관계. Member 객체의 team 에 매핑된다.
List<Member> members = new ArrayList<>(); // 해당 team 에 해당하는 member 들을 List 로 저장한다.
// ...
}
// 참조하는 쪽의 연관관계 구현은 변경이 없다.
class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne // 다대일 관계
@JoinColumn(name = "TEAM_ID") // 외래키 team_id 와 객체 참조가 매핑된다.
private Team team;
// ...
}
public class Test {
public static void main(String[] args) {
// ...
Team team = new Team();
em.persist(team);
Member member = new Member();
member.setTeam(team); // Team 객체를 참조로 저장한다.
em.persist(member);
// Team 객체에서도 해당 team 에 포함된 member 들에 접근할 수 있다.
Team findTeam = em.find(Team.class, team.getTeamId());
int numOfMembers = findTeam.getMembers().size(); // team -> member 로의 역방향 조회
// ...
}
}
양방향 연관관계를 사용하면 양쪽 방향에서 서로를 조회할 수 있다. 이때 mappedBy 속성을 통해서 어떤 멤버와 매핑을 하게될 지 지정해주어야 한다.
객체간의 연관관계는 테이블의 연관관계와 달리 2개로 나뉘어져 있다. 테이블은 외래키 하나만으로 연관관계가 구성이 되고, 조인으로 조회를 한다. 하지만 객체에서는 team -> member, member -> team 의 2개의 연관관계가 존재한다. 즉 양방향 관계는 서로 다른 2개의 단방향 관계이다.
2. 연관관계의 주인
위에서 살펴본 것과 같이 양방향 연관관계는 두개의 단방향 연관관계로 이루어진다. 이렇게 되는 경우에 두 단방향 연관관계 중 어떤 관계가 외래키를 관리해야하는지 정해줘야 한다. 이를 지정하지 않는경우 두 관계가 서로 다른 값을 업데이트하면서 어떤 값을 실제 외래키 값으로 저장할지 혼란이 발생할 수 있는 경우가 있다.
양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정한다.
- 연관관계의 주인만이 외래키를 관리 (등록, 수정) 할 수 있다.
- 주인이 아닌 쪽은 외래키의 값을 조회, 읽을 수만 있다.
- 주인은 mappedBy 속성을 사용할 수 없다.
- 주인이 아니면 mappedBy 속성으로 주인 객체를 지정해준다.
그렇다면 연관관계의 주인은 누가 되어야할까?
연관관계의 주인은 비즈니스 로직이 아니라 외래키의 위치를 기준으로 지정해야한다. 주로 실제 외래키를 가지고 있는 테이블의 객체를 주인으로 지정한다. 일대다의 관계에서는 '다' 의 위치에 있는 객체, @ManyToOne 을 가지는 객체가 연관관계의 주인을 가지게 된다. 위의 예제에서는 Team.members 가 아닌 Member.team 이 연관관계의 주인이 된다.
3. 양방향 연관관계의 주의사항
양방향 연관관계를 구성할 때 주의해야할 점들이 있다.
객체 참조값 변경
연관관계의 값을 변경할 때는 양쪽에 값을 모두 변경하는 것이 좋다.
양방향 연관관계에서 값을 수정할 때, 연관관계의 주인이 아닌 경우에는 값을 수정하여도 실제 DB 에 반영되지 않는다. 연관관계의 주인이 아닌 반대편은 읽기만 가능하기 때문이다. 그렇기 때문에 연관관계의 주인쪽만 값을 변경해주어도 DB 에 반영이 되지만, 코드상의 실수로 반대편 객체에 업데이트를 하는 경우의 방지를 위해서 양쪽 모두 변경해주는 것이 좋다.
또한 객체지향적인 접근에서도 양쪽 모두 변경해주는 것이 좋다. entity manager 에서 flush() 와 clear() 를 호출하기 전까지는 persistence context 의 1차 캐시에서 값을 가져온다. 이때는 아직 쿼리를 처리하기 전이기 때문에 반대편에는 값이 아직 반영되기 전이다.
이러한 문제들을 방지하기 위해서 양쪽 모두에 값을 변경해주는 것이 좋다.
- 연관관계 메서드 정의
이를 위해서 연관관계 쪽의 메서드를 구현하기도 한다. setter 와 같이 연관관계 메서드를 구현하여 값을 변경할 때, 참조 변수를 통해 반대편 객체의 값도 변경해주는 로직을 구현한다.
class Member {
// ...
public void changeTeam(Team team) {
this.team = team; // team 을 변경해준다.
team.addMember(this); // team 의 members 에 현재 객체를 추가한다.
}
}
양방향 매핑시 무한 루프 주의
객체의 기능을 구현하면서 toString(), lombok 메서드, JSON 생성 라이브러리 등 객체의 멤버들을 추출하여 다루는 경우가 있다. 이러한 경우에 양방향 연관관계에서 객체들이 서로를 계속해서 호출하는 무한 루프가 발생할 수 있다.
예를 들어 member.toString() 을 호출하는 경우 member 의 멤버변수인 team 의 값을 추출해야 하는데, team 의 members 에서 다시 해당 member 를 재호출하게 되는 경우가 발생할 수 있다. 이러한 경우 team 과 member 가 서로를 반복해서 호출하면서 무한루프에 빠지게 된다. 이러한 경우를 주의하여 프로그래밍해야한다.
'Tech > JPA' 카테고리의 다른 글
[JPA] 프록시와 지연로딩 (0) | 2022.05.23 |
---|---|
[JPA] 상속관계 매핑 (0) | 2022.05.23 |
[JPA] 연관관계 매핑 종류 (0) | 2022.05.17 |
[JPA] 연관관계 매핑 (1) - 단방향 연관관계 (0) | 2022.05.09 |
[JPA] 영속성 관리 (0) | 2022.05.09 |