[JAVA] JPA 연관관계 매핑( 양방향 )

[JAVA] JPA 연관관계 매핑( 양방향 )

양방향 연관관계

@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 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 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 members = findTeam.getMembers(); System.out.println("================"); for(Member m : members){ System.out.println("m : " + m.getUsername()); } tx.commit(); // commit을 실행하면서 쿼리를 DB에 날림

flush 를 사용하지 않으면 1차캐시에만 저장되어 있는 채로 데이터를 DB로 조회

때문에 데이터를 찾아오지 못 할 수 있다.( 아래 실행 예시 참고 )

member 값이 하나도 찍히지 않음

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. 연관관계의 주인은 외래키의 위치를 기준으로 정해야 한다.

from http://eunji-dev.tistory.com/52 by ccl(A) rewrite - 2021-11-17 10:01:39