05장. 연관관계 매핑 기초
어제 Team
과 Player
관계에서 영속성 컨텍스트 내에서 수동으로 (Team → Player), (Player → Team) 설정을 해야 제가 원하는 값을 얻을 수 있다고 말씀드렸는데요. 오늘 Pobi에게 질문을 통해서 왜 그렇게 해야하는지 알게되었습니다.
우선 해당 영속성 컨텍스트 내에선 영속성을 부여할 때 알아서 관계가 매핑되지 않습니다. 이 때는 수동으로 어제 말씀드린바와 같이 객체 간 양방향 매핑해줘야 하는 것이죠. 하지만 이 수동으로 매핑하는 과정을 하지 않고도 제가 원하는 정보를 구할 수 있는데요. 이전 관계값들을 넣어준 Entity Manager 가 종료된 후 새로운 Entity Manger 를 생성하여 저희가 원하는 정보를 구하면 그때는 관계가 매핑되어 있습니다.
왜 그럴까요?
그 이유는 새로운 Entity Manager가 생성되고 그 안의 정보가 없기 때문에 DB에서 가져오기 때문입니다. DB에서는 관계가 모두 매핑되어있기 때문에 그때는 제가 원하는 정보를 제대로 찾을 수 있었던 것이죠. 좀 길지만 어제 했던 코드를 그대로 아래에 넣어볼게요.
public class Main {
public static void main(String[] args) {
//엔티티 매니저 팩토리 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성
EntityManager em2 = emf.createEntityManager(); //엔티티 매니저 생성
EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
try {
tx.begin(); //트랜잭션 시작
//TODO 비즈니스 로직
logic(em);
tx.commit();//트랜잭션 커밋
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); //트랜잭션 롤백
} finally {
em.close(); //엔티티 매니저 종료
}
// 하지만 여기서 될 수 있었던 것은 DB에서 Foreign Key로 매핑된 정보를 가져오고
// 여기선 DB에서 가져오는 것이기 때문에 관계가 다 매핑되어 있기 때문입니다.
Team team = em2.find(Team.class, "team1");
List<Player> players = team.getPlayers();
System.out.println("사이즈 : " + players.size());
for (Player player : players) {
System.out.println("player : " + player.getUserName());
}
emf.close(); //엔티티 매니저 팩토리 종료
}
private static void logic(EntityManager em) {
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Player player1 = new Player("player1", "회원1");
// team1.add(player1);
player1.setTeam(team1);
em.persist(player1);
Player player2 = new Player("player2", "회원2");
player2.setTeam(team1);
em.persist(player2);
// team1.add(player2);
// 여기서 안됐었던 이유는 em이 관리하는 영속성 컨텍스트 내에서는 관계를 매핑해주지 않았기 때문입니다.
// Team team = em.find(Team.class, "team1");
// List<Player> players = team.getPlayers();
//
// System.out.println("사이즈 : " + players.size());
// for (Player player : players) {
// System.out.println("player : " + player.getUserName());
// }
}
}
연관관계의 주인
사실 객체에는 양방향 연관관계라는 것은 없습니다. 위에서 보았듯이 단방향인 두 개의 엔티티를 잘 엮어서 양방향인 것처럼 보이게하는 것 뿐이죠. 여기서 문제가 발생합니다. 참조하는 것은 두 개인데 외래키는 하나밖에 없는 것입니다. 그렇기 때문에 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리하고 이것을 연관관계의 주인이라고 합니다.
몇 가지 내용들을 짧게 정리해볼게요. 조금만 더 생각해보면 납득할만한 내용입니다.
- 연관관계의 주인이 됨으로써 외래 키를 관리(등록, 수정, 삭제)를 할 수 있고, 주인이 아닌 쪽은 읽기만 할 수 있습니다.
- 주인이 아닌 쪽에서 등록, 수정, 삭제를 하여도 DB에 정상 반영되지 않습니다.
- 주인이 아닌 쪽은
mappedBy
속성을 통해 연관관계의 주인을 설정할 수 있습니다. - 연관관계의 주인은 외래 키가 있는 곳입니다.
양방향 연관관계의 주의점
주의할 점은 어제 제가 미궁에 빠졌던 부분인데요. 주인에만 값을 저장하고 주인이 아닌 곳에는 값을 저장하지 않는 것입니다. 물론 DB에는 주인에만 값을 저장해도 제대로 저장될 것이지만, 해당 영속성 컨텍스트 내에서는 객체 관점에서 (주인에만 값이 저장되었기 때문에) 양방향으로 매핑되어 있지 않기 때문입니다. 이러한 위험때문에 객체 관점에서 양쪽 방향에 모두 값을 입력하는 것이 가장 안전하다고 말합니다. 즉, 아래와 같이 양방향 모두 관계를 매핑해줘야 하는 것이죠.
public void add(Player player) {
this.players.add(player); // Team -> Player
player.setTeam(this); // Player -> Team
}
물론 연관관계의 주인이 아닌 Team
에서 관계를 매핑하는 this.players.add(player);
는 DB에 반영이 되진 않습니다. 위의 add()
와 같이 양방향 관계를 설정하는 메소드를 '연관관계 편의 메서드'라고 하네요.
'연관관계 편의 메서드'를 사용할 때 또 주의할 점이 있습니다. 다음과 같은 상황일 때인데요.
player1.setTeam(teamA);
player2.setTeam(teamB);
Member findMember = teamA.getMember(); // team을 바꿨음에도 여전히 member1이 조회됩니다.
이는 양방향 연관관계라 그런건데요. Player
에서 바라보는 Team
은 바꾸었지만 여전히 Team
에서 바라보는 Player
는 안 바뀌었기 때문입니다. 따라서 (Team
→ Player
)을 바꾸면 관계를 끊어줘야 합니다.
public void add(Player player) {
// 기존팀과 관계 제거
if(player.hasTeam()) { // 해당 플레이어가 팀을 가지고 있으면
player.remove(); // 그 플레이어가 속한 팀에서 해당 플레이어를 지움
}
this.players.add(player);
player.setTeam(this);
}
정리
양방향 매핑은 단방향 매핑에서 좀 더 부가적인 코드 추가로 서로 바라볼 수 있도록 한 것입니다. 이 말은 단방향 매핑만으로도 테이블과 객체 간의 연관관계 매핑은 끝났다는 것입니다.
테이블의 연관관계, 객체의 연관관계는 다릅니다.
- 테이블 연관관계는 외래 키 컬럼 사용합니다.
- 객체 연관관계는 주인 엔티티의 외래 키 필드를 사용합니다.
양방향 연관관계를 매핑하기 위해선 객체의 양쪽에서 모두 매핑해줘야 합니다
연관관계의 주인을 정할 때 비즈니스 로직 보다는 외래키가 있는 곳으로 고려해야 합니다.
'Book > programming' 카테고리의 다른 글
웹을 지탱하는 기술1 - 웹 개론 (0) | 2019.02.07 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 08장. 프록시와 연관관계 관리 (0) | 2019.01.02 |
[자바 ORM 표준 JPA 프로그래밍] 05장. 연관관계 매핑 기초2 (0) | 2018.12.26 |
[자바 ORM 표준 JPA 프로그래밍] 05장. 연관관계 매핑 기초 (0) | 2018.12.26 |
[자바 ORM 표준 JPA 프로그래밍] 03장. 영속성 관리 (0) | 2018.12.18 |