목표
- 객체와 테이블 연관관계의 차이를 이해
- 객체의 참조와 테이블의 외래 키를 매핑
- 용어 이해
- 방향(Direction) : 단방향, 양방향
- 다중성(Multiplicity) : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) 이해
- 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리 주인이 필요
객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다. - 조영호 (객체지향의 사실과 오해)
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
- 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
- 객체는 참조를 사용해서 연관된 객체를 찾는다.
- 테이블과 객체 사이에는 이런 큰 간격이 있다.
회원 저장시: 팀 저장 → 저장된 팀 ID 조회 → 회원 객체에 팀 ID 세팅 → 회원 저장
팀 조회시: 회원 조회 → 조회된 회원 객체의 팀 ID(식별자)로 다시 팀 조회
객체 지향적인 방법이 아님!
단방향 연관관계
객체 지향 모델링 (객체 연관관계를 사용)
- (App) Member 객체가 Team 객체 참조하는 구조로 설계
// 객체의 참조와 테이블의 외래 키를 매핑
@Entity
public class Member {
@Id
private Long id;
@Column(name = "name")
private String username;
private Integer age;
@ManyToOne //단방향 연관관계
@JoinColumn(name = "TEAM_ID")
private Team team;
}
// 연관관계 저장
// 팀 저장
Team team = new Team();
team.setName("TeamA");
// 회원 저장
Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // 단방향 연관관계 설정, 참조 저장
em.persist(member);
// 참조로 연관관계 조회 - 객체 그래프 탐색
// 조회
Member findMember = em.find(Member.class, member.getId());
// 참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();
// 연관관계 수정
// 새로운 팀B
Team teamB = new Team();
team.setName("teamB");
em.persist(teamB);
// 회원1에 새로운 팀B 설정
member.setTeam(teamB);
양방향 연관관계와 연관관계의 주인
양방향 매핑 : 양쪽에서 모두 참조를 통해 접근할 수 있으면 양방향 연관관계
- 테이블의 연관관계 : FK만 있으면 양방향 접근이 가능!
- 객체의 연관관계 : 참조 객체를 양쪽에 다 넣어줘야만 양방향 접근이 가능
- → 결국 단방향 연관관계를 두 번 맺는 것!
// 양방향 매핑 - Member 엔티티는 단방향과 동일
@Entity
public class Member {
@Id
private Long id;
@Column(name = "name")
private String username;
private Integer age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
// 양방향 매핑 - Team 엔티티는 컬렉션 추가
@Entity
public class Team {
@Id
private Long id;
@Column(name = "name")
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
}
연관관계 주인과 mappedBy
- mappedBy = JPA의 가장 고난이도 개념
- mappedBy는 처음부터 이해하기 어려움
- 객체와 테이블간의 연관관계 맺는 차이를 이해해야 함!
객체의 양방향 관계
- 객체의 양방향 관계는 양방향 관계가 아니라 서로 다른 단방향 관계 2개이다.
- 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
- A → B (a.getB())
- B → A (b.getA())
테이블의 양방향 관계
- 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
- MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐 (양쪽 조인 가능)
- JOIN으로 양방향 관계 맺기만 하면 됨
연관관계의 주인(Owner)
- 두 Entity의 key 중 하나를 외래키로 관리해야 함
- 양방향 매핑 규칙
- 객체의 두 관계 중 하나르 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래키를 관리 (등록, 수정)
- 주인이 아닌 쪽은 읽기만 가능!!
- 주인은 mappedBy 속성 사용 하지 않음
- 주인이 아니면 mappedBy 속성으로 주인 지정
- 누구를 주인으로?
- 외래키가 있는 곳을 주인으로 정해라 (진짜 매핑) → 가이드
- 예제에서는 Member.team이 연관관계의 주인
- DB의 N(다)쪽이 연관관계의 주인이 되는 것
- 연관관계 주인은 비즈니스 관련 개념이 아님. 단순 N쪽이 연관관계의 주인이라고 세팅하면 됨.
양방향 매핑시 가장 많이 하는 실수
→ 연관관계의 주인에 값을 입력하지 않는 실수!!
→ 순수한 객체 관계를 고려하면 항상 양쪽 다 값을 입력해야 한다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member); // mappedBy라 읽기전용! 가짜 관계이기 때문에 저장 안됨!!
em.persist(member);
//결과
// Team_ID = null
member.setTeam(team); // 주인 엔티티에 넣어줘야 저장이 됨 !!
em.persiste(member);
//결과
// Team_ID = 1
양방향 연관관계 주의
- 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
- flush, clear 하지 않은 상태 → 1차캐시에서 값 가져올 때 (DB 쿼리 날리지 않을 때) DB와 다른 값이 나올 수 있음
- TestCode 작성할 때도 값 일치하지 않을 가능성 있음
- 연관관계 편의 메소드를 생성하자
- 예) Member에 changeTeam() (or setTeam()) 메소드에 양쪽 값 세팅 로직 넣어주기
- 양방향 매핑시에 무한 루프를 조심하자
- 예) toString(), lombok, JSON 생성 라이브러리
- Controller에서 Entity를 바로 반환을 하면 Entity가 JSON형태로 생성되어 return되기 때문에 무한루프 생성될 확률 높음 ( + Entity 필드 변경되는 순간 API 스펙이 바로 변경되어 문제가 됨)
- → Entity는 DTO로 변환하여 반환하라!
- lombok 자동 toString() 기능은 쓰지 말고, Controller에서는 절대 Entity를 반환하지 말아라.
양방향 매핑 정리
- 단방향 매핑만으로도 이미 연관관계 매핑은 완료 ( 처음 설계시 단방향만! 그것만으로 관계 설계는 완료된 것. 반대방향에서 조회 기능이 필요할 때 양방향으로 바꾸기!)
- 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
- JPQL에서 역방향으로 탐색할 일이 많음
- 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨(테이블에 영향을 주지 않음)
연관관계의 주인을 정하는 기준
- 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
- 연관관계의 주인은 외래키의 위치를 기준으로 정해야 함
출처 : 자바 ORM 표준 JPA 프로그래밍-기본편 (김영한), 자바 ORM 표준 JPA 프로그래밍(김영한 저)
'Programming > Database & Query' 카테고리의 다른 글
[JPA] 자바 ORM 표준 JPA 프로그래밍 : 고급매핑 (0) | 2022.02.17 |
---|---|
[JPA] 자바 ORM 표준 JPA 프로그래밍 : 다양한 연관관계 매핑 (0) | 2022.02.10 |
[JPA] GenerationType.Sequence 설정시 next value 두 번 호출하는 이유 (0) | 2022.01.25 |
[JPA] 자바 ORM 표준 JPA 프로그래밍 : 엔티티 매핑 (0) | 2022.01.20 |
[JPA] 자바 ORM 표준 JPA 프로그래밍 : 영속성 관리 - 내부 동작 방식 (0) | 2022.01.16 |