JAVA/JPA

[JPA] 연관관계 매핑

4Legs 2021. 3. 16. 10:59

테이블 기준 객체 모델링

아래 코드들을 통해 Member와 Team의 연관관계를 모델링 해보자.

@Entity
public class Member{
	@Id @GeneratedValue
	private Long id;

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

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

※ 참고 : 나중에 설명하겠지만 JPA에서 DB 접근은 영속성 컨텍스트라 불리는 EntityManager(em)를 통해 이루어진다.

만약 객체들을 테이블에 맞춰 모델링한다면, 다음과 같은 방식으로 조회하게 된다.

//조회
Member findMember = em.find(Member.class, member.getId());
Long teamId = findMember.getTeamId();
//연관관계 없음
Team findTeam = em.find(Team.class, teamId);

findMember에 Team 객체 자체가 포함되어 있는게 아니라, 외래 키에 해당하는 TeamId 가 포함되어 있다.

따라서, Member 조회를 한 후 그 결과의 FK 값으로 쿼리를 한 번 더 해주어야 한다. 이는 객체 지향적인 방법과는 거리가 멀다.

JPA에서는 우리가 객체 지향적인 방법으로 연관관계를 설정할 수 있도록 지원해 준다.

 

단방향 매핑

단방향 매핑이란, 어느 한 방향으로만 참조 가능한 매핑을 의미한다.

우리의 예시에서는 Member는 Team을 조회할 수 있지만, Team에서는 Member를 참조할 수 없는 경우와 같다.

@Entity
public class Member{
	@Id @GeneratedValue
	private Long id;

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

	//연관관계 설정 : 멤버 입장에서 다대일 매핑
	@ManyToOne
	@JoinColumn(name = "TEAM_ID")
	private Team team;
}

JPA에서는 연관관계 설정을 위해 @ManyToOne, @OneToMany 등의 어노테이션을 사용한다.

어노테이션으로 연관관계의 유형을 명시해 준 다음, 실제 데이터베이스 테이블에서 조인에 사용될 칼럼 명을 지정해 준다.

위 코드에서는 Team 객체에 대해 @ManyToOne 어노테이션이 붙어 있다. 이는, 하나의 팀에 다수의 멤버가 존재할 수 있음을 의미한다. 즉, 다대일 관계이다.

@JoinColumn 어노테이션을 통해 TEAM_ID라는 칼럼을 조인 칼럼으로 지정했다. 이 칼럼이 데이터베이스 테이블에서 외래 키(FK)가 된다.

이제 아래처럼 객체 지향적으로 연관관계를 조회할 때, JPA는 이 명시를 통해 자동으로 테이블을 조인해 준다.

//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

//회원 저장
Member member = new Member();
member.setName("member1");
//member.setTeamId(team.getId());
member.setTeam(team);  //연관관계 설정, 참조 지정
em.persist(member);

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

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

이제는 Member 객체에 Team 객체 자체가 포함되어도, Member 객체 내의 getTeam 메소드를 이용해 바로 Team 객체에 접근할 수 있다.

만약 Team과의 연관관계에 수정이 필요하다면, setTeam 등을 통해 다른 팀을 할당한다. JPA에서 쿼리를 통해 FK의 값을 자동으로 바꿔 준다.

 

양방향 매핑과 mappedBy

하지만, Team 객체 내에서도 이 Team에 속해 있는 Member 들의 정보가 필요할 수 있지 않을까?

이처럼 Member에서 Team을 참조할 수 있고, Team에서도 Member를 참조할 수 있는 매핑을 양방향 매핑이라 한다.

양방향 매핑은 방향이 반대인 단방향 매핑 두 개로 이루어져 있다.

Team 객체 내에서 Member 객체를 참조 가능하도록 하려면, Team 내에 다음과 같이 Member들의 List가 포함되어야 할 것이다. (Team과 Member는 일대다 관계이기 때문)

@Entity
public class Team{
	@Id @GeneratedValue
	private Long id;
	private String name;

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

하지만, 아직 해결해야 할 문제가 남았다.

Member의 team 필드를 업데이트 할 때 FK를 업데이트 해 주어야 할까? 아니면 Team의 members 필드를 업데이트할 때 FK를 업데이트 해 주어야 할까?

연관관계의 주인 (Owner)

위의 문제는 데이터베이스 테이블에서와 객체에서의 양방향 매핑 방식 차이에 의해 발생한다.

데이터베이스 테이블에서의 양방향 매핑은 실제 양방향 매핑 하나로 이루어져 있다.

SELECT * FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
SELECT * FROM TEAM T JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID

MEMBER.TEAM_ID 외래 키 하나로 두 방향 모두 조인이 가능하다.

 

하지만 객체에서의 양방향 매핑은 단방향 매핑 두 개로 이루어져 있다.

Member.getTeam();		//Member->Team 단방향
Team.getMembers();		//Team->Member 단방향

객체의 참조는 둘이지만, 데이터베이스 테이블은 여전히 하나의 외래 키만을 사용한다.

따라서, 두 객체의 하나만으로 외래 키를 관리해야 한다. 즉, 어느 한 쪽에서만 테이블의 외래 키를 바꿀 수 있도록 제한하는 것이다.

테이블의 외래 키를 변경할 수 있는 쪽을 연관관계의 주인(Owner)이라 한다.

Owner가 아닌 쪽은 실제 값을 변경할 수 없고, 오직 그 값을 읽기만 가능하다.

@Entity
public class Team{
	@Id @GeneratedValue
	private Long id;
	private String name;

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

 

아까의 Team 클래스를 다시 가져와 보자. 우리는 Member 객체를 연관관계의 주인으로 지정했다.

따라서, Team에서는 테이블의 외래 키를 변경할 수 없다. Member 객체의 Team이 바뀌었을 때만 실제 데이터도 변한다는 의미와 같다. (Team의 members를 변경해도 실제 데이터는 변하지 않는다.)

이를 지정하는 것이 @OneToMany 어노테이션의 옵션 mappedBy이다.

mappedBy가 붙으면 해당 연관관계에서 Owner가 아님을 의미한다. 실제로 Team 객체는 Member와의 연관관계에서 주인이 아니다. 이는 Member 객체의 team 필드에 해당 객체가 매핑됨을 의미한다.

일반적으로 외래 키가 있는 곳을 Owner로 지정한다. 보통 일대다 또는 다대일 관계에서 '다' 쪽에 해당하는 곳을 지정하게 된다.

 

 

Reference

자바 ORM 표준 JPA 프로그래밍

'JAVA > JPA' 카테고리의 다른 글

[JPA] 영속성 컨텍스트 (Persistence Context) - (1)  (0) 2021.03.30
[JPA] 상속관계 매핑  (0) 2021.03.30
[JPA] 다양한 연관관계  (0) 2021.03.19
[JPA] JPA 매핑 - 기초  (0) 2021.03.16
[JPA] JPA 소개  (0) 2021.03.16