JPA

JPA시작(10)_연관관계 매핑(N:1 / 1:N 양방향)

개발자 배찌 2021. 6. 8. 13:35
728x90

양방향 객체 연관관계

회원 -> 팀 (Member.team)

팀 -> 회원 (Team.members)

 

참고 > 데이터베이스 테이블은 외래키 하나로 양방향으로 조회가 가능하다. 

 

 

양방향 객체 연관관계 매핑

양방향 연관관계를 매핑해봅시다!!!!!!!!!

 

매핑한 회원 엔티티

@Entity
public class Member {
     @Id
     @Column (name = "MEMBER_ID")
     private String id;

     private String username;

     //연관관계매핑
     @ManyToOne
     @JoinColumn (name = "TEAM_ID")
     private Team team;
     
     //연관관계설정
     public void setTeam(Team team) { 
         this.team = team;
     }

     //Getter, Setter ...
}

매핑한 팀 엔티티

@Entity
public class Team {
     @Id
     @Column (name = "TEAM_ID")
     private String id;
     
     private String name;

     @OneToMany (mappedBy = "team")
     private List<Member> members = new ArrayList<Member>();

     //Getter, Setter...
}

 @OneToMany (mappedBy = "team")

일대다 관계를 매핑하기위해 @OneToMany 사용

mappedBy 속성은 양방향일때만 사용.. 반대쪽 매핑의 필드이름을 값으로 주면 된다.

**mappedBy 는 연관관계의 주인과 관련이있는데 밑에서 자세히 설명!! 

 

일대다 컬렉션 조회

객체그래프 탐색을 사용해서 조회한 회원들을 출력

public void biDirection() {
     Team team = em.find(Team.class, "team1");
     List<Member> members = team.getMamebers();   팀->회원... 객체그래프탐색

     for (Member member : members ) {
         System.out.println("member.username = " + member.getUsername());
     }
}

결과

member.username = 회원1
member.username = 회원2

 

연관관계의 주인

@OneToMany 에서 MappedBy속성은 왜 필요할까??

 

객체연관관계

회원 -> 팀 연관관계 1개 (단방향)

팀 -> 호원 연관관계 1개 (단방향)

 

테이블 연관관계

회원 <-> 팀 연관관계 1개 (양방향)

 

테이블은 외래키 하나로 두 테이블의 연관관계를 관리한다.

 

엔티티를 단방향으로 매핑하면 참조를 하나만 사용하므로 이 참조로 외래키를 관리하면 되지만,,!!

양방향으로 매핑하면 회원->팀, 팀->회원 두곳에서 서로를 참조한다..

 

엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래키는 하나다.

따라서 둘 사이에서 차이가 발생한다.

 

이런 차이로 인해 jpa 에서는 두 객체 연관관계중 하나를 정해서 테이블의 외래키를 관리해야하는데 

이것을 연관관계의 주인 ( owner ) 이라고 한다.!!

 

 

양방향 매핑의 규칙 : 연관관계의 주인 정하기

연관관계의 주인만이 데이터베이스 연관관계와 매핑이 되고 외래키를 관리(등록, 수정, 삭제) 할 수 있다.

반면에 주인이 아닌 쪽은 읽기만 할 수 있다

 

어떤 연관관계를 주인으로 정할지는 mappedBy 속성을 사용!

  • 주인은 mappedBy 속성을 사용하지 않는다.
  • 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야한다.

 

연관관계의 주인을 정한다는것은 사실 외래키 관리자를 선택하는것!

 

연관관계의 주인은 외래키가 있는곳이다!!

 

★정리★

연관관계의 주인만 데이터베이스 연관관계와 매핑되고 외래키를 관리 할 수 있다.

주인이 아닌 반대편은 읽기만 가능하고 외래키를 변경하지 못한다

 

★참고★

데이터베이스 테이블의 다대일, 일대다 관계에서는 항상 다쪽이 외래키를 가짐!

따라서 @ManyToOne에는 mappedBy 속성이 없다

 

양방향 연관관계 저장

양방향연관관계를 사용해서 팀1, 회원1, 회원2 저장하기

private void testSave() {
     Team team1 = new Team ("team1", "팀1");
     em.persist(team1);

     Member member1 = new Member("member1", "회원1");
     member.setTeam(team1);
     em.persist(member1); 

     Member member2 = new Member("member2", "회원2");
     member.setTeam(team1);
     em.persist(member2); 
}

 

SELECT * FROM MEMBER;

조회결과 >>

MEMBER_ID : member1

USERNAME : 회원1

TEAM_ID : team1

 

MEMBER_ID : member2

USERNAME : 회원2

TEAM_ID : team1

 

TEAM_ID 외래키에 팀의 기본키값이 저장되어있다

양방향 연관관계는 연관관계의 주인이 외래키를 관리한다!

따라서 주인이 아닌 방향은 값을 설정하지 않아도 데이터베이스에 외래키값이 정상 입력된다

 

team1.getMembers().add(member1); //무시 (연관관계의 주인이 아님)

team2.getMembers().add(member2); //무시 (연관관계의 주인이 아님)

 

Team.members는 연관관계의 주인이 아니므로 입력된 값은 외래키에 영향을 주지 않는다

 

member1.setTeam(team1);  //연관관계설정 ( 연관관계의 주인) 

member2.setTeam(team1);  //연관관계설정 ( 연관관계의 주인) 

 

양방향 연관관계의 주의점

양방향 연관관계를 설정하고 가장 흔히 하는 실수는 무엇일까?

연관관계의 주인에는 값을 입력하지 않고, 주인이 아닌곳에만 값을 입력하는것이다.

데이터베이스에 외래키값이 정상적으로 저장되지 않음!

 

양방향연관관계의 주의점

public void testSaveNonOwner() {
     Member member1 = new Member("member1", "회원1");
     em.persist(member1);

     Member member2 = new Member("member2", "회원2");
     em.persist(member2);

     Team team1 = new Team("team1", "팀1");

     //주인이 아닌 곳에 연관관계 설정
     team1.getMembers().add(member1);
     team2.getMembers().add(member2);

     em.persist(team1);
}

SELECT * FROM MEMBER;

 

조회결과 >>

MEMBER_ID : member1
USERNAME : 회원1
TEAM_ID :  null

MEMBER_ID : member2
USERNAME : 회원2
TEAM_ID : null

"null"이 들어가는것을 알수 있음.

따라서 연관관계의 주인만이 외래키의 값을 변경할 수 있음!!!!

 

 

↓↓↓ 객체관점에서 양쪽방향에 모두 값을 입력해주는것이 가장 안전하다 ↓↓↓

public void test순수한객체_양방향() {
     Member member1 = new Member("member1", "회원1");
     Member member2 = new Member("member2", "회원2");
     Team team1 = new Team("team1", "팀1");
     
     member1.setTeam(team1); //연관관계설정 
     team1.getMembers().add(member1); //연관관계설정

     member2.setTeam(team1); //연관관계설정
     team1.getMembers().add(member2); //연관관계설정

     List<Member> members = team1.getMembers();
     System.out.println("members.size = " + members.size());
}

결과

members.size = 2

 

위 내용은 jpa를 사용하지 않는 순수한 객체이다.

 

jpa로 코드를 완성해보자!!

public void testORM_양방향() {
     Team team1 = new Team("team1", "팀1");
     em.persist(team1);

     Member member1 = new Member("member1", "회원1");
     Member member2 = new Member("member2", "회원2");

     member1.setTeam(team1); //연관관계설정 
     team1.getMembers().add(member1); //연관관계설정
     em.persist(member1);

     member2.setTeam(team1); //연관관계설정
     team1.getMembers().add(member2); //연관관계설정
     em.persist(member2);
}

 

연관관계 편의 메소드

     member1.setTeam(team1); //연관관계설정 
     team1.getMembers().add(member1); //연관관계설정

 

양방향 연관관계는 결국 양쪽 다 신경써야한다..!

 

<문제점>

다음처럼  member1.setTeam(team1); ,  team1.getMembers().add(member1);  를 각각 따로 호출하다보면

실수로 둘중 하나만 호출해서 양방향이 깨질 수 있음!!

 

 

양방향 관계에서 두 코드는 하나인것 처럼 사용하는것이 안전하다

 

setTeam()메소드 하나로 양방향 관게를 모두설정하기!

public class Member {
     private Team team;
     public void setTeam (Team team) {
          this.team = team;
          team.getMembers().add(this);
     }
}
public void testORM_양방향() {
     Team team1 = new Team("team1", "팀1");
     em.persist(team1);

     Member member1 = new Member("member1", "회원1");
     Member member2 = new Member("member2", "회원2");

     member1.setTeam(team1); //양방향설정
     em.persist(member1);

     member2.setTeam(team1); //양방향설정
     em.persist(member2);
}

코드가 훨~~씬 간결해졌다..!!

이렇게 한번에 양방향 관계를 설정하는것을 "연관관계 편의 메소드" 라고 한다

 

 

연관관계 편의 메소드 작성시 주의사항

문제점 > 

member1.setTeam(teamA);
member1.setTeam(teamB);
Member findMember = teamA.getMember(); // member1이 여전히 조회됨.....!

teamB로 변경할 때 teamA -> member1 관계를 제거하지 않았다..

 

연관관계를 변경할 떄는 기존 팀이 있으면 기존팀과 회원의 연관관계를 삭제하는 코드를 추가해야한다.

 

public void setTeam(Team team) {
     //기존 관계를 제거
     if ( this.team != null ) {
         this.team.getMembers().remove(this);
     }
     this.team = team;
     team.getMembers().add(this);
}