본문 바로가기
Spring/JPA

[JPA] *중요* 양방향 연관관계와 연관관계의 주인

by 김긍수 2021. 3. 7.
객체는 참조를 활용하고 테이블은 외래키를 사용한다. 이 두 개 사이에서 오는 차이를 알아야한다.

 

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에서 역방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않기 때문에)

댓글