흔히 C언어에 포인터가 있다면 JPA에는 mappedBy가 있다고들 한다.
이 글에서는 예제를 통해 양방향 연관관계와 mappedBy를 알아보자.
지난 글에서 단방향 연관관계를 가졌던 객체를 양방향으로 매핑해본다.
양방향으로 매핑된 객체의 구조는 다음과 같다.
코드 역시 아래와 같이 변경한다.
팀
@Entity
public class Team {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
회원
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name="USERNAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
조회
Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers();
mappedBy를 이해하려면..? 객체와 테이블이 관계를 맺는 차이를 잘 알아야 한다.
객체 연관관계는 2개
- 회원 -> 팀 (단방향)
- 팀 -> 회원 (단방향)
반면 테이블의 연관관계는 1개
- 회원 <-> 팀 (양방향)
객체의 양방향 연관관계
객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개이다.
즉, 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
- A->B(a.getB())
- B->A(b.getA())
테이블의 양방향 연관관계
테이블은 외래키 하나로 두 테이블의 연관관계를 관리한다.
앞의 예제에서도 MEMBER.TEAM_ID 외래키 하나로 양방향 연관관계를 가진다.
SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
SELECT *
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID
이 경우 혼란스러운 문제가 생긴다.
한 회원이 팀을 변경하려고 한다.
그럼 Member의 Team을 바꿔야 할지, Team에서 member로 바꿔야 할지 애매해진다.
데이터베이스 입장에서는 외래키 하나만 바뀌면 되지만 객체는 다르다.
둘 중 하나로 외래 키를 관리해야한다.
이때 '연관관계의 주인'이라는 개념이 등장한다.
연관관계의 주인(Owner)
양방향 매핑 규칙
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정한다.
- 연관관계의 주인만이 외래 키를 관리한다.(등록, 수정)
- 주인이 아닌 쪽은 읽기만 가능하다.
- 주인은 mappedBy 속성을 사용하지 않는다.(의미부터 매핑당함 임)
- 주인이 아니면 mappedBy 속성으로 주인을 지정한다.
그럼 누구를 주인으로?
외래키가 있는 곳을 주인으로 정하자.
이번 예제에서는 Member.team이 연관관계의 주인이다.
데이터베이스의 입장에서는 외래키가 있는 곳이 무조건 N이 된다.
즉, 연관관계의 주인이 된다.
양방향 매핑시 많이 하는 실수
연관관계의 주인에 값을 입력하지 않음
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);
em.persist(member);
순수한 객체 관계를 고려하면 항상 양쪽 다 값을 입력해야 한다.
이 경우에도 연관관계의 주인에 값을 입력해야 한다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
team.getMembers().add(member);
//연관관계의 주인에 값 설정
member.setTeam(team);
em.persist(member);
연관관계 편의 메소드를 생성하고 무한 루프를 조심해야 한다.
(예: toString(), lombok, JSON 생성 라이브러리-Controller에서 Entity를 반환하지 말고 DTO로 변환하여 반환)
toString() -> 양쪽에서 호출하여 무한루프 발생
//Member
@Override
public String toString() {
return "Member{" +
"id=" + id +
", name='" + name + '\'' +
", team=" + team +
'}';
}
//Team
@Override
public String toString() {
return "Team{" +
"id=" + id +
", name='" + name + '\'' +
", members=" + members +
'}';
}
양방향 매핑을 다시한번 정리해보자면...
- 단방향 매핑만으로 이미 연관관계 매핑은 완료된 것이다.
- 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐이다.
- JPQL에서 역방향으로 탐색할 일이 많다.
- 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 괜찮다.(테이블에 영향을 주지 않음)
'💻dev > 🌱Java+Spring' 카테고리의 다른 글
JPA | 다양한 연관관계 매핑 총정리 (다대일, 일대다, 일대일, 다대다) (0) | 2023.04.30 |
---|---|
JPA | 예제로 알아보는 연관관계 매핑 (예제-2) (0) | 2023.04.30 |
JPA | 연관관계 매핑 - 단방향 연관관계 (0) | 2023.04.29 |
JPA | 요구사항 분석과 기본 매핑 (예제-1) (0) | 2023.04.29 |
JPA | 필드와 컬럼 매핑, 기본 키 매핑 (0) | 2023.04.29 |