양방향 연관관계
- @OneToMany, @ManyToOne 을 예로 들 수 있다.
- 객체에는 양방향이 없다. ( 단방향 두개를 잘 묶어서 양방향인 것 처럼 보이게 할 뿐! )
- 그럼 객체의 경우 단방향 두개 중 외래 키 관리를 누가 해야할까?? → 연관관계의 주인(Owner) 가 외래키를 관리!
양방향 연관관계 매핑의 규칙
- 객체의 두 관계 중 하나를 연관 관계의 주인으로 지정( 외래키 소유주를 주인으로 하는 걸 권장 )
- 일대 다 기준에서 보통 '다' 쪽이 연관관계의 주인이다.
- 연관관계 주인만이 외래 키를 관리(등록, 수정) 한다. ( 주인이 아닌 쪽은 읽기만 가능 )
- 주인은 mappedBy 사용 불가
- mappedBy 속성으로는 주인의 이름을 지정해준다.
Member.java
/* 연관관계 주인 */
package hellojpa;
import javax.persistence.*;
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
/*@Column(name = "TEAM_ID")
private Long teamId;*/
// 연관관계 매핑
// 늘 현재 엔티티 기준으로 작성해야함 - 여러명의 멤버(Many)는 하나의 팀(One) 을 가질수 있다.
@ManyToOne
@JoinColumn(name = "TEAM_ID") // 조인하는 컬럼
private Team team;
}
Team.java
package hellojpa;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
// 연관관계 매핑
@OneToMany(mappedBy = "team") // Member의 변수 team과 연결되어있다.( 주인 지정 )
private List<Member> members = new ArrayList<>();
}
JpaMain.java
package hellojpa;
import org.hibernate.boot.model.source.internal.hbm.XmlElementMetadata;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
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("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
// 양방향 연관관계를 이용하면 member -> team 으로 team -> member로 자유자재로 사용 할 수 있다.
Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers();
for (Member m : members){
System.out.println("m = " + m.getUsername());
}
tx.commit(); // commit을 실행하면서 쿼리를 DB에 날림
}catch (Exception e){
//중요!! 문제가 발생하면 rollback
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
※ 양방향 연관관계 주의점
1. 연관관계의 주인의 값을 입력하지 않았을 때 생기는 문제
- 위의 예시에서 연관관계의 주인은 Member의 team 이다.
- Team의 member는 읽기 전용이라 바꿔봐야 소용없음
// 잘못된 연관관계의 예시( 역방향-주인이 아닌 것에 연관관계를 설정 한 예시 )
Member member = new Member();
member.setUsername("member1");
em.persist(member);
Team team = new Team();
team.setName("TeamA");
// team.member는 주인이 아니기 때문에 변경해도 소용 없음
team.getMembers().add(member);
em.persist(team);
위 코드처럼 작성 후 실행하면 주인인 Member.team의 값이 입력되지 않아 아래처럼 외래키 값이 null 로 들어간다.
그럼 맞게 작성하려면 어떻게 해야 할 까?
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
// 연관관계중 주인에게 값을 입력해 줘야함
member.setTeam(team);
em.persist(member);
2. 양방향 연관관계 사용 시 객체 관계를 고려하면 항상 양쪽 값을 다 입력해주는게 맞다.
→ 자세한 내용 아래 코드의 주석 참고!
// 만약 양쪽 값을 다 입력하지 않고 주인만 입력 후 flush를 사용하지 않고
// 바로 find를 진행하면 아무것도 조회하지 못한다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
em.persist(member);
// 아래 주석을 풀어주므로써 양쪽에 값을 넣어 줄 수 있다.
// team.getMembers().add(member);
// em.flush();
// em.clear();
Team findTeam = em.find(Team.class, team.getId());
List<Member> members = findTeam.getMembers();
System.out.println("================");
for(Member m : members){
System.out.println("m : " + m.getUsername());
}
tx.commit(); // commit을 실행하면서 쿼리를 DB에 날림
flush 를 사용하지 않으면 1차캐시에만 저장되어 있는 채로 데이터를 DB로 조회
때문에 데이터를 찾아오지 못 할 수 있다.( 아래 실행 예시 참고 )
3. 연관관계 편의 메소드
: 양쪽에 값을 계속 넣어주려다 보면 실수를 하기 마련이다. 다음과 같은 방식을 이용하자!
- 주인이 중심일때
Main.java
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
// 편의 메소드 사용 부분
member.changeTeam(team);
em.persist(member);
// 연관관계 편의 메소드를 사용하면 아래처럼 주인이 아닌 값은 따로 안해줘도됨!
// team.getMembers().add(member);
Member.java
// Member.java
// 원래 setTeam 이지만 getter, setter 로는 잘 안쓰고 이름을 정해서 쓰는걸 권장한다.
public void changeTeam(Team team) {
this.team = team;
// 연관관계 편의 메소드
team.getMembers().add(this);
}
- 주인이 아닌 값이 중심일 때
Main.java
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
// member.changeTeam(team);
em.persist(member);
team.addMembers(member);
Team.java
// Team.java
public void addMembers(Member member) {
member.setTeam(this);
members.add(member);
}
★ 둘 중 하나의 경우만 정해서 사용해야 한다. 아니면 무한루프가 돌 수 있음!
양방향 매핑 정리
1. 처음에는 무조건 단방향 매핑으로 설계를 끝내야 한다. 그 후 필요 부분에 양방향 매핑을 추가하는 것을 권유
( 그렇게 수정해도 테이블에 영향을 주지 않는다. )
2. 양방향 매핑은 단방향에서 반대 방향으로 조회 기능이 추가된 것일 뿐이다.
3. JPQL에서 역방향으로 탐색할 일이 많음
4. 연관관계의 주인은 외래키의 위치를 기준으로 정해야 한다.
'JAVA' 카테고리의 다른 글
[JAVA] JPA 고급 매핑 (상속 관계 매핑) (0) | 2021.12.16 |
---|---|
[JAVA] JPA 다양한 연관관계 매핑 (0) | 2021.12.09 |
[JAVA] JPA 연관관계 매핑( 단방향 ) (0) | 2021.11.16 |
[JAVA] JPA 기본키 매핑 (0) | 2021.11.11 |
[JAVA] JPA 필드와 컬럼 매핑 (0) | 2021.11.10 |