0. 개요
- 이번 포스팅은 양방향 연관 관계에 대해 알아보도록 하자.
1. 양방향 연관 관계
- 단방향 연관 관계에서 사용했던 예시를 확장하여 양방향 연관 관계로 만들어보자.
a) 관계 예시
- 구현하려는 양방향 연관 관계는 아래의 테이블과 같다.
- 이전 포스팅에서 단방향 연관 관계를 설명할 때의 구조와 동일한 테이블 구조다.
- 분명 양방향 연관 관계를 구현한다고 했으나 왜 동일한 테이블을 사용할까?
- 그 이유는 RDB는 FK를 이용한 Join으로 양방향 연관 관계를 형성한다.
- 즉, RDB는 양방향 연관 관계를 기본으로 한다.
- 다만 객체의 관점에서 보았을 때 연관 관계의 방향성이 유의미하다.
b) 양방향 연관 관계란?
- 위의 테이블 구조를 예시로 생각해보자.
- Player 테이블의 FK를 이용하여 Team 테이블과 Join을 할 수 있다.
- 반대로 Team 테이블의 PK를 이용하여 Player 테이블과 Join을 할 수 있다.
- 즉, team_id 필드를 이용하여 선수 또는 팀을 검색할 수 있다.
- 이처럼 RDB는 FK를 이용한 양방향 데이터 검색이 가능하다.
- 그러나 객체(Entity)에서는 이것이 불가능하다.
- 객체 간 양방향 검색이 가능하려면, 상대 객체를 참조하는 필드를 양쪽 모두가 가져야 한다.
2. 양방향 연관 관계 구현
a) Player Entity
- Player 객체는 다음과 같다.
- 하나의 teamId는 다수의 player를 가지므로 @ManyToOne을 명시한다.
- 그러므로 Many는 Player 객체, One은 Team 객체가 된다.
@Entity
public class Player {
@Id @GeneratedValue
@Column(name = "PLAYER_ID")
private Long id;
@Column(name = "PLAYER_NAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team teamId;
// Getter, Setter 생략
}
b) Team Entity
- Team 객체는 다음과 같다.
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
@Column(name = "TEAM_NAME")
private String name;
@OneToMany(mappedBy = "teamId")
private List<Player> players = new ArrayList<>();
// Getter, Setter 생략
}
- 하나의 Team은 다수의 Player를 가지므로 @OneToMany를 명시한다.
- mappedBy 옵션은 관계를 형성하는 객체의 필드 중 어떤 필드를 기준으로 검색을 수행하는지 명시한다.
- 즉, 상대 객체의 필드 중 FK 역할을 수행하는 필드의 이름을 명시한다.
- 그러므로 Player Entity의 teamId 필드를 명시한다.
- mappedBy에 관한 내용은 아래에서 자세히 언급할 예정이다.
c) 실행 결과
- 다음과 같은 코드를 실행한다.
- 아래의 코드를 실행하면 2명의 선수 이름이 콘솔에 출력되어야 한다.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
// 트랜잭션 생성
EntityTransaction tx = em.getTransaction();
// 트랜잭션 시작
tx.begin();
try {
Team team = new Team();
team.setName("ManUnited");
em.persist(team);
Player player1 = new Player();
player1.setName("JI-SUNG PARK");
player1.setTeamId(team);
em.persist(player1);
Player player2 = new Player();
player2.setName("Cristiano Ronaldo");
player2.setTeamId(team);
em.persist(player2);
em.flush();
em.clear();
Player findPlayer = em.find(Player.class, player1.getId());
List<Player> players = findPlayer.getTeamId().getPlayers();
for(Player p: players) {
System.out.println("player = " + p.getName());
}
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
- 콘솔에 결과가 잘 출력되는 것을 확인할 수 있다.
- 위에 빨간 줄이 쳐진 부분이 양방향 데이터 검색을 확인할 수 있는 부분이다.
- player로 team을 검색하고, 다시 player를 검색하였다.
3. 연관 관계의 주인
- 위에서 mappedBy 옵션을 사용하여 연관 관계의 기준이 되는 필드를 명시했다.
- mappedBy의 역할과 기능을 이해하려면 연관 관계의 주인에 대한 이해가 필요하다.
- 이에 대해서 알아보도록 하자.
a) 객체와 테이블이 형성하는 관계의 차이
- 테이블은 FK를 이용하여 연관 관계를 형성한다.
- FK를 이용한 Join으로 양방향 데이터 검색이 가능하다.
- 즉, 테이블은 FK 하나로 양방향 연관 관계를 구축한다.
- 객체는 참조 필드를 이용하여 연관 관계를 형성한다.
- 즉, 2개의 단방향 연관 관계를 형성하여 하나의 양방향 연관 관계를 구축한다.
- 결론적으로 서로 다른 단방향 연관 관계 2개를 양방향이라고 부를 뿐이다.
b) 객체의 양방향 관계에서 오는 문제점
- RDB의 경우 FK가 Join의 기준점이 된다.
- 그러므로 FK를 통해 양방향 검색이 가능하다.
- 그러나 객체는 서로 다른 단방향 관계 2개를 구축하여 양방향 관계를 구축한다.
- 그렇다면 기준이 되는 관계가 무엇일까?
- 여기서 기준이 되는 관계란 다음과 같은 상황을 의미한다.
→ Player 객체의 teamId 값을 기준으로 Team 객체의 players 값이 바뀌는 것일까?
→ Team 객체의 players 값을 기준으로 Player 객체의 teamId 값이 바뀌는 것일까?
- 객체의 입장에서 보면 무엇이 기준이라고 명확하게 말할 수 없는 상황이다.
- 그러므로 기준이 되는 필드를 명시하는데, 이를 연관 관계의 주인이라고 표현한다.
- 즉, FK를 명시하는 것이다.
c) 양방향 연관 관계 매핑 규칙
- 양방향 연관 관계를 형성할 때에는 다음의 규칙을 기반한다.
1. 2개의 단방향 관계 중 하나를 연관 관계의 주인으로 지정한다.
2. 연관 관계의 주인이 FK의 등록 및 수정을 관리한다.
3. 주인이 아닌 경우, 읽기(= 조회)만 가능하다.
4. 주인은 mappedBy 옵션을 사용하지 않는다.
5. 주인이 아닌 경우, mappedBy 옵션을 사용하여 관계의 주인을 명시한다.
d) 어떤 필드가 주인이 되는가?
- 테이블 구조를 기반으로, FK가 존재하는 테이블을 구현한 Entity가 관계의 주인이다.
- 아래의 그림을 살펴보자.
- 위의 그림은 DB 테이블의 구조를 표현한 것이다.
- Player 테이블의 team_id 필드가 FK다.
- 그러므로 Player Entity에서 team_id에 해당하는 필드가 연관 관계의 주인이 된다.
- 규칙에 의해 Player Entity의 teamId 필드가 연관 관계의 주인이다.
- 그러므로 주인이 아닌 Team Entity에서 mappedBy 옵션을 사용하여 주인을 명시한다.
4. 양방향 매핑의 핵심 정리
a) 단방향 매핑만으로 충분하다.
- 단방향 매핑만으로도 연관 관계는 충분히 구현된다.
- 그러므로 최초에 백엔드를 설계할 때에는 양방향을 구현하지 말 것을 권장한다.
- 최초에는 단방향 매핑 만으로 모든 연관 관계는 충분히 구현되기 때문이다.
b) 양방향 매핑이 필요한 이유
- 양방향 매핑이 필요한 경우는 단방향 매핑된 관계를 역방향으로 조회할 때다.
- 개발을 하다보면 단방향으로 매핑된 연관 관계를 역순으로 조회해야 하는 경우가 발생한다.
- 이처럼 필요한 경우에 한하여, 양방향 매핑을 추가적으로 구현하는 것을 권장한다.
c) 왜 단방향 매핑만으로 충분한가?
- 무조건 FK를 가진 테이블이라고 해서 양방향 매핑을 구현할 필요가 없다.
- 데이터를 조회할 때, 테이블을 Join하는 방향이 일방적이라면 단방향 매핑으로 충분하기 때문이다.
- 그러므로 위에서 설명한 것처럼, 역방향 조회가 필요할 때 양방향 매핑을 구현하자.
d) 연관 관계 주인을 정하는 기준
- 연관 관계의 주인을 설정할 때에는 비즈니스 로직을 기준으로 생각하면 안된다.
- 반드시 FK를 가진 테이블을 구현한 Entity가 연관 관계의 주인이 된다.
- 이는 규칙처럼 생각하고 이행하자.
'Back-end > JPA 개념' 카테고리의 다른 글
10. 연관 관계의 종류(일대다, 다대일) (0) | 2022.04.04 |
---|---|
9. 양방향 연관 관계 사용 시 주의사항 (0) | 2022.04.01 |
7. 단방향 연관 관계 (0) | 2022.03.30 |
6. Primary key(기본 키) Mapping (0) | 2022.03.29 |
5. Field(칼럼) Mapping (0) | 2022.03.28 |
댓글