목표
1. 객체와 테이블 연관관계의 차이를 이해
2. 객체의 참조와 테이블의 외래 키를 매핑
용어를 알고 가자
방향 (Direction) : 단방향, 양방향
다중성 (Multiplicity) : 다대일 (N:1), 일대다 (1:N), 일대일 (1:1), 다대다(N:M)
연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리 주인이 필요하다
연관관계가 필요한 이유가 무엇일까?
객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다
객체를 테이블에 맞추어 모델링
테이블 연관관계를 보면 Member가 N이고 Team이 1 이다.
N:1 관계 -> 다대일 관계 -> Member 대 Team 이라 보면된다.
Member Class
@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;
Team Class
@Id @GeneratedValue
@Column(name="TEAM_ID")
private Long id;
private String name;
아래는 팀과 회원을 저장하는 코드이다
//팀 저장
Team team = new Team();
team.setName("wooteco");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("conas");
member.setTeamId(team.getId());
em.persist(member);
만약 회원의 팀을 찾으려면 어떻게 해야할까?
아래와 코드처럼 하면 된다.
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getId();
Team findTeam = em.find(Team.class, findTeamId);
연관관계를 설정을 안하면 조회 시 중복 코드가 발생이 되고 Team이 어떤 소속인지 알기 위해 Member를 게속 호출해야하는 비효율성이 나타난다.
- 객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다
- 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다
- 객체 참조를 사용해서 연관된 객체를 찾는다
- 테이블과 객체 사이에는 이런 큰 간격이 있다
단방향 연관관계
@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;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
em.persist(member);
객체 연관 관계
회원 객체(Member)는 Member.team 필드(멤버 변수)로 팀 객체와 연관관계를 맺는다
- 회원 객체와 팀 객체는 단방향 관계이다. 회원은 Member.team 필드를 통해 팀을 알 수 있지만, 팀 객체로 소속된 회원들을 알 수 없기 때문이다.
- member ➡️ team 의 조회는 member.getTeam()으로 가능하지만, team ➡️ member 를 접근하는 필드는 없다.
테이블 연관관계
회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺습니다.
- 회원 테이블과 팀 테이블은 양방향 관계이다. 회원 테이블의 TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있고, 반대로 팀과 회원도 조인할 수 있다.
- 예를 들어, MEMBER 테이블의 TEAM_ID 외래 키 하나로 MEMBER JOIN TEAM 과 TEAM JOIN MEMBER 둘 다 가능하다.
회원과 팀을 조인하는 SQL
SELECT * FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
- 팀과 회원을 조인하는 SQL
SELECT * FROM TEAM T JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID
- 객체 연관관계와 테이블 연관관계의 가장 큰 차이
참조를 통한 연관관계는 언제나 단방향입니다. 객체간에 연관관계를 양방향으로 만드록 싶으면 반대쪽에도 필드를 추가해서 참조를 보관해야 합니다. 이렇게 양쪽에서 서로 참조하는 것을 양방향 연관관계라고 합니다.
하지만 정확히 이야기하면 이것은 양방향 관계가 아니라 서로 다른 단방향 관계 2개입니다. 반면에 테이블은 외래 키 하나로 양방향으로 조인할 수 있습니다. - 객체 연관관계 vs 테이블 연관관계 정리
+ 객체는 참조(주소)로 연관관계를 맺습니다.
+ 테이블은 외래 키로 연관관계를 맺습니다.
+ 참조를 사용하는 객체의 연관관계는 단방향입니다.
+ 외래 키를 사용하는 테이블의 연관관계는 양방향입니다. (A JOIN B가 가능하면 반대로 B JOIN A도 가능)
양방향 매핑
단방향 객체 연관관계에서 양방향 객체 연관관계는 변하지만 테이블 연관관계는 변하지 않는다
@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;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
//mappedBy 는 연관관계를 한곳이 이름이 무엇인지 알기위해 쓰는것
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
}
연관관계의 주인과 mappedBy
mapperdBy는 사용하기 힘든 이유 3가지
- mappedBy = JPA의 멘탈붕괴 난이도이다
- mappedBy는 처음에는 이해하기 어렵다
- 객체와 테이블간에 연관관계를 맺는 차이를 이해해야 한다.
그럼 객체와 테이블이 관계를 맺는 차이에 대해서 알아보자
- 객체 연관관계 = 2개
- 회원 → 팀 연관관계 1개(단방향) 팀 → 회원 연관관계 1개(단방향)
- 테이블 연관관계 = 1개
- 회원 ↔ 팀의 연관관계 1개(양방향)
- 위 그림 처럼 양방향 객체 연관관계를 보면 각각 한방향씩 관계를 맺고 있다
- 테이블 연관관계는 양방향으로 관계를 맺고 있는걸 확인 할수 있다
- 객체 연관관계는 2번을 반복해야 되서 코드가 늘어나고 나중에는 복잡해질수 있지만 테이블 연관관계는 1번 작동하기 때문에 더 편하고 좋다
객체의 양방향 관계
- 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개다
- 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
- A → B (a.getB())
- B → A (b.getA())
테이블 양방향 연관관계
- 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
- 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
둘 중 하나를 외래 키를 관리해야 한다
객체의 양방향은 MEMBER와 TEAM을 서로 누구한테 외래키를 줘야할지 고민해야하고 설정을 잘 해야하지만 테이블 연관관계같은 경우는 외래키 하나만 가지고 있으면 누구를 매핑할지에 대한 고민이 사라진다.
연관관계의 주인(Owner)
양방향 매핑 규칙
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)
- 주인이 아닌쪽은 읽기만 가능
- 주인 MappedBy 속성 사용 안됨
- 주인이 아니면 mappedBy 속성으로 주인 지정 해준다.
누구를 주인으로 해야할까?
- 외래 키가 있는 있는 곳을 주인으로 지정해라
- N:1, 다대일 같은 경우 ( N, 다 ) 쪽으로 외래 키를 설정하고 주인으로 설정하는것이 가장 좋다
- 여기서는 Member.team이 연관관계의 주인이다
Member.team이 주인으로 지정이 되면 Member는 수정, 읽기가 가능해진다
하지만 Team 같은경우는 읽기만 가능하다.
테이블 연관관계 매핑같은 경우에도 Member가 주인이라는것을 알수 있다. Member안에 TEAM_ID(FK) 즉, TEAM의 외래키를 가지고 있기 때문이다
양방향 매핑시 가장 많이 하는 실수
(연관관계의 주인에 값을 입력하지 않음)
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);
em.persist(member);
양방향 매핑시 연관관계의 주인에 값을 입력해야 한다.
순수한 객체 관계를 고려하면 항상 양쪽다 값을 입력해야 한다
//저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member memeber = new Member();
member.setUsername("member1");
//연관관계의 주인에 값 설정
member.setTeam(team);
em.persist(member);
- 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
- 연관관계 편의 메소드를 생성하자
- 양방향 매핑시에 무한 루프를 조심하자
- 예 : to String(), lombok, JSON 생성 라이브러리연관관계의 주인과 mappedBy
참고
'Spring' 카테고리의 다른 글
고급 매핑 - 상속관계 매핑, @MappedSuperclass (0) | 2022.11.01 |
---|---|
연관관계 매핑 고려사항 3가지 (0) | 2022.10.25 |
@Transactional (0) | 2022.09.29 |
변경 감지와 병합(merge) (0) | 2022.09.29 |
빈 생명주기 콜백 (0) | 2022.07.30 |