// 에드센스

이 카테고리는 인프런 김영한님의 JPA 강의를 보고 정리하는 공간입니다.


연관관계가 필요한 이유

"객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다"

 

예를 들어보자.

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 회원과 팀은 N:1 관계다.
@Entity
public class Member {
  @Id @GeneratedValue
  private Long id;
  
  @Column(name = "USERNAME")
  private String name;
  
  @Column(name = "TEAM_ID")
  private Long teamId;
}

@Entity
public class Team {
  @Id @GeneratedValue
  private Long id;
  
  private String name;
}
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);

라는 상황이 있을때, 특정 회원이 속한 팀을 찾으려면 어떻게 해야할까?

 

 

일반적인 경우는 다음과 같은 방법을 사용한다.

Member findMember = em.find(Member.class, member.getId());

Long findTeamId = findMember.getId();
Team findTeam = em.find(Team.class, findTeamId);

먼저 특정 회원을 조회하고, 그 회원의 팀 ID를 getter로 가져와서

팀 ID로 다시 조회한다. 

 

벌써 두번이나 쿼리가 발생했다. 이런것을 보고 객체지향스럽지 못하다고 한다.

즉, 객체를 테이블에 맞춰서 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.

  • 테이블은 외래키를 사용한 조인을 통해 연관된 테이블을 찾는다.
  • 객체는 참조를 통해서 연관된 객체를 찾는다.

이것이 객체와 테이블이 가지는 가장 큰 차이점.

 

 

 


단방향 연관관계

객체지향 모델링

위에서 작성한 Member 엔티티를 조금 수정해보자.

@Entity
public class Member {

  @Id @GeneratedValue
  private Long id;

  @Column(name = "USERNAME") 
  private String name;

  // @Column(name = "TEAM_ID")
  // private Long teamId;

  //Team에 대한 참조를 넣음
  @ManyToOne
  @JoinColumn(name = "TEAM_ID")
  private Team team;
  
}

 

 

Team을 참조하는 Long형 teamId 대신 Team에 대한 참조값 team을 넣어서 매핑을 한다면 어떻게 될까?

이렇게 모델링을 한다면 위에서 예시로 들었던 특정 멤버의 팀을 조회하는 코드가 한결 간결해지고 쿼리도 줄어든다.

 

 

 

//조회
Member findMember = em.find(Member.class, member.getId());

//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();

해당 회원의 팀 ID를 조회하고 그것으로 다시 팀을 조회하는것이 아닌,

한번에 해당 회원의 팀을 조회할 수 있다. team 참조가 있기 때문에.

 

 

 

하지만 Team에서는 어떤 Member가 있는지 조회할 수 없다. 단방향 연관관계이기 때문이다.

  • 객체는 team이라는 참조를 통해 단방향으로 연관관계를 가진다. Member에서는 team을 통해 팀에 대한 정보를 알 수 있지만 Team에서는 Member를 알 수 있는 방법이 없기에 단방향 연관관계다.
  • 테이블에서는 TEAM_ID라는 외래키를 통해 연관관계를 가진다. 이는 Team에서도 Member를 조회할 수 있기에 양방향 관계라고 할 수 있다.

 

즉, 정리하자면

  • 객체는 참조를 통해 연관관계 가진다.
  • 객체의 연관관계는 단방향이다.
  • 테이블은 외래키를 통해 연관관계 가진다.
  • 테이블의 연관관계는 양방향이다.

 

 

 


양방향 연관관계

 

팀 1개에 N명의 멤버가 있을 수 있으므로 Team 엔티티에 리스트형으로 member의 참조값을 넣었다.

@Entity
public class Team {
  @Id @GeneratedValue
  private Long id;
  
  private String name;
  
  @OneToMany(mappedBy = "team")
  List<Member> members = new ArrayList<Member>();

 

 

이를 통해 객체에서도 Team -> Member로의 조회가 가능하다.

//조회
Team findTeam = em.find(Team.class, team.getId());
int memberSize = findTeam.getMembers().size(); //역방향 조회

 

 

 


연관관계의 주인

  • 양방향 연관관계에서는 주인이 필요하다.
  • 테이블의 연관관계는 외래키 하나로 양쪽을 다 조회할 수 있지만,
  • 객체에서는 양쪽에서 서로를 참조하는 값이 있어야 서로간의 조회가 가능하다. 즉 까보면 단방향 연관관계 두개로 인해 양방향처럼 보이는 것이다.
class A {
	B b;
}
class B {
	A a;
}

이런 윈리지만 우리는 이를 양방향 연관관계라고 부른다.

 

 

  • 엔티티를 양방향으로 연관관계를 가지게 하기 위해 참조가 2개 필요한데,
  • 데이터베이스에서는 외래키는 1개 필요하다.
  • 이 차이로인해 JPA에서는 객체 연관관계 중 하나를 주인으로 삼고 이 외래키를 보관하도록 한다.
  • 연관관계의 주인만이 데이터베이스의 연관관계와 매핑되고 외래키를 관리할 수 있다. 주인이 아닌쪽은 읽기만 간으하다.

 

 

 


누구를 주인으로?

외래키가 있는 곳을 주인으로.

외래키는 N이 있는 곳.

(이 예시에서는 Member쪽이다)

 

 

양방향 매핑 규칙

  • 연관관계의 주인만이 외래키를 관리(등록, 수정)
  • 주인이 아닌쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용X
  • 주인이 아닌쪽에서 mappedBy 속성을 통해 주인 지정

 

 

주의할 점

연관관계의 주인에 값을 입력하지 않음

Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");

//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);
em.persist(member);

 

 

순수한 객체 관계를 고려하면 연관관계 주인과 반대편 양쪽 다 값을 입력 해야한다.

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);

DB에 올라가지 않고 1차 캐시에서 엔티티를 다시 바로 가져오는 경우를 순수한 객체상태라고 하는데 이때 연관관계의 주인쪽에만 값을 입력해주면 다른 한쪽엔 반영이 안된다. 1차 캐시를 없애고 조회하는 순간부터 반대편에서도 참조가 가능한 문제가 생기기에 영속성 컨텍스트로 올릴때 부터 양쪽에 값을 넣어주자.

 

 

이때 한가지 팁이 있다면,

연관관계 주인(Member)쪽 엔티티에서 setter 메소드를

public void setTeam(Team team){
    this.team = team;
    team.getMembers.add(this);
}

이처럼 수정하여 주인쪽은 자신의 인스턴스를 한번에 넣어주도록 수정하면 된다.

이때 setter 메소드는 관례적인 setter와 다르므로 메소드 이름을 changeTeam과 같이 이례적으로 바꾸면 더 좋다.

 

 

 

 

 

참고:

더보기

'JPA' 카테고리의 다른 글

[JPA] 다양한 연관관계  (2) 2021.07.24
[JPA] 엔티티 매핑  (0) 2021.07.19
[JPA] 영속성 관리  (0) 2021.07.17
[JPA] JPA란?  (0) 2021.07.15

+ Recent posts