# 강의/[인프런] 자바 ORM 표준 JPA 프로그래밍

엔티티 매핑 2 (연관관계)

연관관계 매핑 기초

 

목표 🤔

  1. 객체와 테이블 연관관계의 차이를 이해
  2. 객체의 참조와 테이블의 외래 키를 매핑

객체가 지향하는 패러다임과 관계가 지향하는 패러다임의 차이를 이해하고 적절하게 매핑하는 방법을 이해한다.

 

용어

방향(Direction) : 단방향, 양방향

다중성(Multiplicity) : 다대일, 일대다, 일대일, 다대다

연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리 주인이 필요함

 

연관관계가 필요한 이유

[예제 시나리오]

  1. 회원과 팀이 있다.
  2. 회원은 하나의 팀에만 소속될 수 있다.
  3. 회원과 팀은 다대일 관계이다.
@Entity
public class Member{
    ... 

    private Long teamId;

    ...
}

연관관계가 없다면, 엔티티에 매번 외래키를 매핑해야 하고, 연관된 테이블을 찾기 위해 외래키를 통해 조회해야 한다. 즉 객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 패러다임의 불일치 발생 (테이블은 외래키로 조회 vs 객체는 참조를 통해 조회)

 

간단한 연관관계 예시

다대일 관계(단방향)를 @ManyToOne, @JoinColumn 어노테이션을 통해 표현할 수 있다.

@Entity
public class Member{
    ... 

    @ManyToOne
    @JoinColumn
    private Team team;

}

-------------------------------------

// Team 생성
Team team = new Team("Team A");
Team team2 = new Team("Team B");

// Member 생성, 연관관계 매핑
Member member = new Member();
member.setTeam(team);

// Member 엔티티로부터 Team을 조회할 때
Member foundMember = em.find(Member.class, 1L);
Team foundTeam = foundMember.getTeam();

// Member 엔티티 새로운 Team 매핑
foundMember.setTeam(team2);

멤버와 팀의 다대일 관계를 표현하고, 멤버로부터 팀을 가져오거나 팀을 설정할 수 있다.

 

단방향과 양방향

테이블 연관관계에서는 외래 키만 있다면 서로 찾아갈 수 있기에 단방향, 양방향이라는 개념이 없다. 하지만 객체는 방향이 없다면 서로의 참조관계를 알 수 없다.

상단의 코드는 Member 엔티티는 Team을 알 수 있지만, Team은 Member를 알 수 없었다. (단방향) Team에서 어떤 Member들이 속하고 있는지 알 수 있도록 양방향으로 구성하는 방법을 알아본다.

@Entity
public class Team{
		...

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

}

@OneToMany를 통해 Team을 참조하고 있는 Member List를 가져올 수 있도록 양방향을 설정하였다. mappedBy 속성은 Member 엔티티에서 Team의 field명을 적어준다.

 

연관관계의 주인과 mappedBy

객체와 테이블이 각각 연관관계를 맺는 차이를 이해하면 사용하기 쉽다.

  1. 객체 연관관계 (2개)
    • 회원 → 팀 (단방향)
    • 팀 → 회원 (단방향)
    ※ 양방향이라는 건 사실 단방향 2개를 이어준 것
  2. 테이블 연관관계 1개
    • 회원 <-> 팀 (양방향)
    ※ 외래키를 통해 서로를 알 수 있다.

 

양방향 매핑

객체의 두 관계중 하나를 연관관계의 주인으로 지정해야 함. 즉 주인이 외래 키를 관리(등록, 수정)하고, 주인이 아닌 쪽은 외래 키를 읽기만 가능해야 한다.

규칙

  1. 외래 키가 있는 곳을 주인으로 정한다.
  2. 주인은 mappedBy 속성을 사용하지 않는다.
  3. 주인이 아니면 mappedBy 속성으로 주인을 지정한다.
@Entity
public class Member {
		...

		@OneToMany(mappedBy = “주인”)
		List<Member> members = new ArrayList<>();
}

// 1. Member는 주인이 아니니까 mappedBy 속성을 통해 Team을 주인으로 정했고
// 2. members는 읽기만 가능하다.
@Entity
public class Member {
		...
		
		@ManyToOne
		Team team;
}

// 1. Team은 주인이다. mappedBy 속성을 지정하지 않는다.
// 2. Team을 get/set 하여 참조하거나 참조하도록 설정할 수 있다.

주의점

  1. 연관관계를 매핑한다.
  2. 순수한 객체 관계를 고려하면 항상 양쪽 다 값을 입력해야 함
    • Member에 member.setTeam(team);
    • Team에도 findTeam.getMembers().add(member);
  3. 무한루프 주의 (양쪽 엔티티에 toString(), json 생성 등을 사용하지 말 것)

한 쪽만 연관관계 매핑 시 오류가 나는 시나리오 예시

  1. 새로운 멤버를 생성하고 팀을 매핑 시킨다.
  2. 팀의 멤버들을 알아보기 위해 1차 캐시에서 팀을 가져온다. (DB에서 새로 find한 것이 아닌!)
  3. 이때 가져온 팀은 새로운 멤버를 포함하지 않은 팀이다.

올바른 시나리오

  1. 새로운 멤버를 생성하고 팀을 매핑 시킨다.
  2. 팀에 새로운 멤버를 추가한다.
  3. 팀의 멤버들을 알아보기 위해 1차 캐시에서 팀을 가져온다.
  4. 이때 가져온 팀은 새로운 멤버를 포함하지 않은 팀이다.

이를 보완하기 위한 “연관관계 편의 메소드”

@Entity
public class Member{
		...

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

// 혹은

@Entity
public class Team{
		...
		
		public void addMember(Member member){
				member.setTeam(team);
				members.add(member);
		}
}
  1. 연관관계 매핑하는 메소드의 이름은 “setXXX()”를 사용하지 않는다. 단순히 “setXXX()”로 하면 어떤 로직이 존재하지 않고 값만 변하는 것으로 보이기 때문에 일부러 “changeXXX()” 라는 이름의 메소드를 사용
  2. 양쪽 관계를 보완하기 위한 코드를 작성한다.
  3. 연관관계 편의 메소드는 “一” 쪽에 작성해도 되고 “多”쪽에 작성해도 되나 무한루프 등을 방지하기 위해 양쪽에 사용하지는 않는다.
  4. “一”을 기준으로 “多”를 설정할지, “多”를 기준으로 “一”을 넣을지는 본인이 결정

정리

단방향 매핑만으로 이미 연관관계 매핑은 완료한 것이다. (양방향은 추가하면 되니까)

단방향 매핑으로 다 끝낸다는 생각으로 설계를 끝내고, 필요할 때 고민하고 양방향을 추가한다.

연관관계의 주인은 비즈니스 로직을 기준으로 선택하지 않고, 외래 키의 위치를 기준으로 정해야 한다.

728x90