연관관계를 매핑한다?
일반적으로 DB에서 테이블의 연관관계를 매핑할 땐 외래키를 사용하고 양방향관계를 사용한다.
select *
from team t
join member m on t.team_id = m.team.id;
select *
from member m
join team t on m.team.id = t.team_id;
- 이와 같이 하나의 외래키로 양방향 매핑이 되어 양방향 조회가 가능하다.
하지만 ORM에서 객체는 참조를 사용하여 매핑하고 방향성이 존재한다.
@Entity()
@Table(name = "MEMBER")
public class Member {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
- team 객체의 team_id 컬럼을 참조하여 단방향 매핑을 하는 예이다.
- @JoinColumn을 사용해서 외래키 TEAM_ID를 명확하게 지정해 줄 수 있다.
객체에서 양방향 매핑을 하려면 어떻게 해야할까?
- 객체는 항상 단방향 매핑만 가능하다.
- 양방향이라고 말하는 것은 사실 두 개의 단방향 연관관계이다.
- 엔티티를 양방향 관계로 설정할 경우 객체의 참조는 2개인데 외래키는 하나이기 때문에 차이가 발생하게된다.
고로 두 개의 연관관계에서 관리자가 필요하게되고 연관관계의 주인이라고 표현한다. - 일반적으로 연관관계의 주인 쪽만 등록, 수정, 삭제를 하고 아닌 쪽은 읽기만 가능하다.
연관관계의 주인을 정한다. ==> 외래키 관리자를 선택한다.
MappedBy 속성을 사용하여 연관관계의 주인을 결정한다.
- mappedBy속성이 없는 곳이 연관관계의 주인이다
@Entity
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "team")
private List<Member> memberList;
}
- USER (1) <---> TEAM (N) 의 연관관계이다.
- USER객체에서 team으로 참조를 하기 때문에 mappedBy 속성에 "team"을 넣어준다.
- Team 객체는 mappedBy 지정이 되었으므로 주인이 아니고 읽기만 가능하다.
- 일반적으로 N:1, 즉 다대일 관계의 경우 N을 주인으로 정한다.
양방향 연관관계에서 관계 맺기
@Test
void 양방향연관관계_테스트_주인아닌쪽() {
Team team = teamRepository.findById(1L).get();
Member member = userRepository.findById(1L).get();
team.getMemberList().add(member);
teamRepository.save(team);
Member reGetMember = userRepository.findById(1L).get();
System.out.println(reGetMember.getTeam()); // print : null
assertEquals(team, reGetMember.getTeam()); // fail
}
- 연관관계의 주인이 아닌 team에서 member의 추가를 강행해도 연관관계는 설정이 되지 않는다.
member에 team은 null이된다.
@Test
void 양방향연관관계_테스트_주인() {
Member member = userRepository.findById(1L).get();
Team team = teamRepository.findById(1L).get();
member.setTeam(team);
userRepository.save(member);
Member savedMember = userRepository.findById(1L).get();
System.out.println(savedMember.getTeam()); // print : team
asssertEquals(team, reGetMember.getTeam()); // success
}
- 연관관계의 주인인 member에서 team을 setting하면 연관관계가 설정된다.
순수 객체 관계를 고려해서 양 쪽 다 값을 세팅하자.
@Entity()
public class Member {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
// 묶어서 설정
public void setTeam(Team team) {
this.setTeam(team);
team.getMemberList().add(this);
}
}
- 양방향 매핑으로 사용한다 할지라도 객체이기에 한 쪽의 값을 변경해도 반대 쪽 객체가 변화하지 않을 것이다.
- 순수 객체 관계를 고려해서 양 쪽 다 값을 세팅할 수 있도록 로직을 구현하자.
그럼 orm에서 양방향 관계를 어떻게 써야할까?
- 기본적으로 단방향 관계만으로 설계가 가능하므로 단방향으로 설계한다.
- 추후 반대 쪽에서도 접근이 필요하다면 양방향 연관관계를 추가하는 식으로 설정하는게 좋다.!
- 왜냐하면 위에서 봤다시피 단방향참조의 user에서, 양방향 관계를 team에서 설정해줘도 user에선 변한게 없고
당연히 DB테이블에 영향을 주지 않기 때문에 확장이 간편하다.
다대일, 일대다가 아닌 관계에서는 어떨까?
일대일관계
- 어떤 테이블이든 외래키를 가질 수 있다.
- 주테이블에 외래키를 놓을 때의 장점
- 외래키를 객체 참조와 비슷하게 사용할 수 있다.
- 주테이블만 확인해도 연관관계를 알 수 있다.
- 대상 테이블에 외래키
- 일대다관계로 변경할때 구조를 그대로 갖고갈 수 있다는 장점이있다.
다대다관계
- RDB에선 정규화된 테이블 2개로 다대다를 표현할 수 없다.
- @Jointable로 연결테이블을 설정할 순 있지만.. 새로운 식별자를 사용하는게 좋다.
- jpa에서 @ManyToMany를 제공해주긴 하지만 한계가 있어 중간테이블을 두어 관계를 풀어주는 것이 일반적이다.
'jpa' 카테고리의 다른 글
ORM과 JPA 개념정리 (0) | 2022.08.15 |
---|
댓글