예제로 연관관계 매핑 해보기
단방향 연관관계
시나리오
- 회원과 팀이 있다.
- 회원은 하나의 팀에만 소속될 수 있다.
- 회원과 팀은 다대일 관계다.
객체지향 모델링
Member class 의 teamId는 멤버가 어느 팀에 속해있는지 알려주는 변수이다.
teamId는 테이블에서 FK에 해당된다.
그리고 Table 에서는 Member 와 TEAM 이 다대일 관계라는 것을 표현한다.
모델링코드
package hellojpa;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
// @Column(name = "TEAM_ID")
// private Long teamId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Team getTeam() {
return team;
}
public void setTeam(Team team) {
this.team = team;
}
}
Member class 에서
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
테이블 연관관계가 아닌 객체 연관관계를 표현해야 하기 때문에
private int teamId;
가 아닌
private Team team;
으로 객체 연관관계를 표현한다.
또한, @ManyToOne 어노테이션으로 Member 객체와 Team 객체가 다대일 관계라는 것을 표현해준다.
@JoinColumn(name = "TEAM_ID")로 Team 객체에 연관관계를 매핑한다.
그리고 실제 Hibernate 에서 join SQL이 나가는 것을 볼 수 있다.
양방향 연관관계
객체와 테이블의 양방향 연관관계 차이
테이블의 연관관계에서는 FK 하나로 된다.
하지만 객체에서는 참조이기때문에 값을 넣어줘야 한다.
- 객체 : 양방향이 아니라 단방향 2개이다.
- 테이블 : FK 하나로 양방향 연관관계를 표현한다.
mappedBy
- 뜻 : (by : ~에 의해 mapped : 매핑됐다) -> 연관관계의 주인을 표현
- 딜레마 : Member 의 team 과 Team 의 members 중 어느것으로 Member 테이블의 TEAM_ID를 업데이트 해야하나?
- 결론 : 둘중 하나로 외래키를 관리해야 한다. (룰) 연관관계의 주인은 FK가 있는 쪽이다.
주의점
- 주인이 아닌쪽은 읽기만 가능.
- 주인은 mappedBy 사용 불가
누구를 주인으로 ? : 외래 키가 있는 곳을 주인으로.
왜 ? : 비즈니스적인 이유가 있는것이 아니라 단지 Table에서 FK가 있는 곳을 따라가는 것이다. 그래야 모든것이 깔끔하게 해결됨
매핑코드
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
...
가장 많이하는 실수
1. 연관관계의 주인에 값을 입력하자.
team 에서 Members는 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);
결과
읽기만 가능하므로 member테이블에 team_id가 들어가있지 않다.
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);
결과
2. 객체지향 설계를 위해 항상 양쪽에 값을 설정하자! 연관관계 편의 메소드를 구현하자! 둘 중 한곳에만!
주인이 아닌곳에 mappedBy를 걸면 테이블에 적용이 안된다고는 하지만, 사실 영속성컨텍스트에는 등록되어있기 때문에 자바코드상에서는 적용이 되어있다.
그래서 객체지향적인 설계를 위해서 양방향 메소드에서는 연관관계 편의 메소드를 구현하자!
addMember 메소드 구현
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
public void addMember(Member member) {
member.setTeam(this);
members.add(member);
}
...
3. 무한루프를 조심하자
(toString(), lombok, JSON 생성 라이브러리) -> 서로 계속 부름
Member
...
@Override
public String toString() {
return "Member{" +
"id=" + id +
", username='" + username + '\'' +
", team=" + team +
'}';
}
...
Team
...
@Override
public String toString() {
return "Team{" +
"id=" + id +
", name='" + name + '\'' +
", members=" + members +
'}';
}
...
Main
...
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
em.persist(member);
team.addMember(member);
em.flush();
em.clear();
Team findTeam = em.find(Team.class, team.getId());
List<Member> members = findTeam.getMembers();
System.out.println("==============");
System.out.println("findTeam = " + findTeam);
System.out.println("==============");
...
실행결과
무한루프를 돌아서 StackOverFlowError가 난다.
양방향 매핑 설계 꿀팁
- 처음 설계할 때 단방향 매핑으로만 연관관계 매핑을 끝내라
- 양방향 매핑은 개발 하다가 필요할 때 추가해라
'Web > JPA' 카테고리의 다른 글
EP7. 다양한 연관관계 매핑 (0) | 2021.02.08 |
---|---|
EP6. 연관관계 매핑 (실전예제) (0) | 2021.02.06 |
EP4. 요구사항 분석과 기본 매핑 (0) | 2021.02.04 |
EP3. 영속성 컨텍스트, 엔티티 매핑 (0) | 2021.02.03 |
EP2. JPA 기본동작과정과 내부동작방식 (0) | 2021.02.03 |