연관관계가 필요한 이유
예제 시나리오
- 회원과 팀이 있다.
- 회원은 하나의 팀에만 소속될 수 있다.
- 회원과 팀은 다대일 관계이다.
객체를 테이블에 맞추어 모델링
⇒ 연관관계가 없는 객체
⇒ 참조 대신 외래키를 그대로 사용
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@Column(name = "username")
private String name;
@Column(name = "team_id")
private String teamId;
...
}
@Entity
public class Team {
@Id @generatedValue
@Column(name = "team_id")
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());
// 연관관계가 존재하지 않음
Team findTeam = em.find(Team.class, team.getId());
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
- 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
- 객체는 참조를 사용해서 여관된 객체를 찾는다.
- 테이블과 객체 사이에는 이런 큰 간격이 있다.
단방향 연관관계
객체지향 모델링
⇒ 객체 연관관계 사용
⇒ 객체의 참조와 테이블의 외래 키를 매핑
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@Column(name = "username")
private String name;
// @Column(name = "team_id")
// private String teamId;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
...
}
@Entity
public class Team {
@Id @generatedValue
@Column(name = "team_id")
private Long id;
private String name;
}
⇒ 객체 지향 모델링
⇒ 연관관계 저장
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
// 회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); // 단방향 연관관계 설정, 참조 저장
em.persist(member);
⇒ 참조로 연관관계 조회(객체 그래프 탐색) 및 연관관계 수정
// 조회
Member findMember = em.find(Member.class, 1L);
// 참조를 통한 연관관계 조회
Team findTeam = findMember.getTeam();
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
// 회원1에 새로운 팀B 설정
findMember.setTeam(teamB);
양방향 연관관계와 연관관계의 주인
양방향 매핑
⇒ Member 엔티티는 단방향과 동일하나 Team 엔티티는 컬렉션 추가(양방향)
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@Column(name = "username")
private String name;
// @Column(name = "team_id")
// private String teamId;
// 연관관계의 주인
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
...
}
@Entity
public class Team {
@Id @generatedValue
@Column(name = "team_id")
private Long id;
private String name;
// 연관관계의 주인의 반대편
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<>();
}
⇒ 반대 방향으로 객체 그래프를 탐색한다.
// 조회
Team findTeam = em.find(Team.class, team.getId());
int membersize = findteam.getMembers().size(); // 역방향 조회
연관관계의 주인과 mappedBy
- 객체와 테이블간의 연관관계를 맺는 차이를 이해해야 한다.
- 객체 연관관계 = 2개
- 회원 → 팀 연관관계 1개 (단방향)
- 팀 → 회원 연관관계 1개 (단방향) - 테이블 연관관계 = 1개
- 회원 ↔ 팀의 연관관계 1개(양방향)
객체의 양방향 관계
- 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개이다.
- 객체를 양방향으로 참조하려면 단방향 연관관계 2개 만들어야 한다.
테이블의 양방향 연관관계
- 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리한다.
- MEMBER.TEAM_ID 외래키 하나로 양방향 연관관계를 갖는다. (양쪽으로 조인할 수 있다)
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;
연관관계의 주인(Owner) - 양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정한다.
- 연관관계의 주인만이 외래 키를 관리한다. (등록/수정)
- 주인이 아닌 쪽은 읽기만 가능하며 mappedBy 속성을 사용할 수 없다.
- 주인이 아니면 mappedBy 속성으로 주인을 지정한다.
누구를 주인으로??
- 비즈니스 로직을 기준으로 연관관계의 주인을 선택해선 안되며 외래 키가 있는 곳을 주인으로 정해야 한다.
- Member와 Team의 관계에서는 Member.team이 연관관계의 주인이다.
양방향 매핑 시 가장 많이 하는 실수
⇒ 연관관계의 주인에 값을 입력하지 않음
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("Member1");
// 역방향(주인이 아닌 방향)만 연관관계를 설정한다.
// 이럴경우 member의 team_id는 갱신되지 않는다.
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에 member파라미터 설정
team.getmembers().add(member);
// 연관관계의 주인에 값을 설정한다.
member.setTeam(team);
em.persist(member);
- 순수 객체 상태를 고려하여 항상 양쪽에 값을 설정하자.
- 연관관계 편의 메서드를 생성하자.
public void setTeam(Team team) { // 기존에 이미 팀이 존재한다면 if(this.team != null) { this.team.getMembers().remove(this); // 해당 팀의 관계 제거 } this.Team = team; team.getMembers().add(this); }
- 양방향 매핑 시에는 무한 루프를 조심하자 ( 예 : toString(), lombok, JSON 생성 라이브러리)
양방향 매핑 정리
- 단방향 매핑만으로도 이미 연관관계의 매핑은 완료되었으나 편의상 조회용도로 양방향을 사용하기도 한다.
- 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐이다.
- JPQL에서 역방향으로 탐색할 일이 많다.
- 단방향 매핑을 잘 하고 양방향은 필요시마다 추가해도 무방하다. (테이블에 영향 X)
'BackEnd > JPA' 카테고리의 다른 글
[JPA] 프록시와 연관관계 관리 (0) | 2024.02.27 |
---|---|
[JPA] 상속관계 매핑 전략(@Inheritance, @DiscriminatorColumn) (0) | 2024.02.23 |
[JPA] 연관관계 매핑 (0) | 2024.02.21 |
[JPA] 엔티티 매핑 (0) | 2024.02.13 |
[JPA] 영속성 컨텍스트(Persistence Context)란? (0) | 2024.02.13 |