객체는 참조를 활용하고 테이블은 외래키를 사용한다. 이 두 개 사이에서 오는 차이를 알아야한다.
1. 양방향 연관관계
양쪽에서 서로 참조할 수 있는 것이다.
테이블 연관관계는 양방향이든 단방향이든 차이가 없다. 테이블의 연관관계는 사실상 외래키 하나로 양방향이 가능하다. 외래키만 있으면 다 알 수 있기 때문이다.
문제는 객체에서 나온다.
양쪽으로 참조하기위해서는 Team에 member LIst가 있어야한다.
//Team.class 파일의 부분 코드
private Long id;
private String name;
@OneToMany(mappedBy = "team") //Team에서 Member로 가는건 일대다 +
private List<Member> members = new ArrayList<>(); // 관례로 ArrayList로 초기화하기
@OneToMany(mappedBy = " ")
: 일대다 맵핑에서 어떤 것과 연결되어있는지 명시해주는것. (이 예시로 보면 Member의 Team "team")
Member findMember = em.find(Member.class, 101L);
List<Member> members = findMember.getTeam().getMembers();
2. mappedBy에 대하여
객체와 테이블간에 연관관계를 맺는 차이를 이해해야한다.
- 객체 연관관계 = 2개
- 회원 -> 팀 연관관계 1개 (단방향)
- 팀 -> 회원 연관관계 1개 (단방향) // 참조값을 회원, 팀에 각각 넣어줘야 양방향으로 참조할 수 있다.
- 테이블 연관관계 = 1개
- 회원 <---> 팀 연관관계 1개 (양방향)
객체의 양방향관계
- 객체의 양방향 관계는 정말 양방향 관계가 아니라 서로 다른 단방향 관계 2개이다.
- 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야한다.
- A -> B (a.getB()) class A { B b; }
- B -> A (b.getA()) class B { A a; }
객체의 양방향관계에서는 둘 중 하나로 외래키를 관리해야한다.
Member에 있는 Team으로 외래키 관리를 할지, Team에 있는 List members로 외래키를 관리해야할지 주인을 정해야한다.
테이블의 양방향관계
- 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리한다.
- 외래키 하나로 양방향 연관관계를 가진다. (양쪽으로 조인이 가능)
연관관계의 주인
양방향 매핑 규칙
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정한다.
- 연관관계의 주인만이 외래 키를 관리, 등록, 수정한다.
- 주인이 아닌 쪽은 읽기만 가능하다.
- 주인은 mappedBy 속성 사용을 하지 않는다.
- 주인이 아니면 mappedBy 속성으로 주인을 지정한다.
누구를 주인으로 해야할까?
- 외래 키가 있는 곳을 주인으로 정해야한다.
- 여기서는 Member.team이 연관관계의 주인이다.
- 외래키가 있는 쪽의 테이블에 대응되는 엔티티에 있는 참조를 연관관계의 주인으로 정하는 것이 헷갈리지않는다.
- 만약 외래키가 있는 곳이 아닌 Team.members를 연관관계의 주인으로 지정한다면?
- Team에 있는 members를 수정했는데, update쿼리는 다른 테이블 (Member)에 쿼리가 날아간다.
- 추가로 성능 이슈도 있다.
양방향 매핑 시 가장 많이 하는 실수 (연관관계의 주인에 값을 입력하지않음)
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);
em.persist(member);
이렇게 작성한 경우, Member 테이블을 확인해보면 TEAM_ID에 null이 들어가게된다.
Team team = new Team();
team.setName("TeamA");
em.persist(team); // team 영속 상태
Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // set Team
em.persist(member); // member 영속 상태
em.flush();
em.clear();
// 쿼리 보내고, 영속성 컨텍스트 비움
//만약 em.flush()와 clear()를 주석처리한다면, 위에서 team 엔티티는 1차캐시에 들어있는데, 하단 코드에서
//findTeam.getMembers()를 하면 당연히 getMembers()에는 값이 없다.
Team findTeam = em.find(Team.class, team.getId());
List<Member> members = findTeam.getMembers();
// member.setTeam(team)만 해도 findTeam.getMembers()가 가능하다.
*순수한 객체 관계를 고려하면 항상 양쪽에 값을 다 입력해야한다.
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 생성 라이브러리
//JpaMain.java
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.changeTeam(team);
em.persist(member);
//Member.class
private void changeTeam(Team team) { // 연관관계 편의 메소드 생성
this.team = team;
team.getMembers().add(this);
}
}
*Controller에서는 엔티티를 절대 반환하지마라.
1. 무한루프가 생길 수 있다.
2. 엔티티는 변경될 수 있는데, 이걸 api에 반환해버리면 엔티티를 변경하는 순간 api 스펙이 바뀌어버린다.
무조건, DTO로!!!
양방향 매핑 정리
- 단방향 매핑만으로도 이미 연관관계 매핑은 완료된다. (단방향으로 설계를 먼저 끝내라)
- 양방향 매핑은 반대방향으로 조회(객체 그래프탐색)기능이 추가된 것 뿐이다.
- JPQL에서 역방향으로 탐색할 일이 많음
- 단방향 매핑을 잘하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않기 때문에)
'Spring > JPA' 카테고리의 다른 글
[JPA] 상속관계 매핑 (0) | 2021.03.08 |
---|---|
[JPA] 다양한 연관 관계 매핑 (0) | 2021.03.07 |
[JPA] 연관관계 매핑 - 단방향 연관관계 ( 객체지향적으로 설계하는 방법 ) (0) | 2021.03.06 |
[JPA] 아주 간단한 쇼핑몰 만들기 (0) | 2021.03.06 |
[JPA] 엔티티 매핑 (0) | 2021.03.06 |
댓글