엔티티의 연관관계를 매핑할 때는 다음을 고려해야 한다.
- 다중성 (다대일, 일대다, 일대일, 다대다)
- 단방향, 양방향
- 연관관계의 주인: 5장에서 연관관계의 주인인 테이블에서 외래키를 관리할 수 있었다.
관계에서 왼쪽에 있는 엔티티가 연관관계의 주인이다.
ex) 다대일 -> 다가 주인
일대다 -> 일이 주인
다대일
다대일 단방향 vs 다대일 양방향
위 그림에서 실선은 연관관계의 주인이고, 점선은 연관관계의 주인이 아니다.
Member 가 연관관계의 주인이고, Team 은 연관관계의 주인이 아니다.
다대일 단방향
다대일 단방향은 회원 엔티티에만 매핑 관련 코드를 넣으면 된다.
다대일 양방향도 같은 회원 엔티티를 사용한다.
@Entity
public class Member {
....
@ManyToOne
@JoinColumn(name = "TEAM_ID") // 외래 키
private Team team;
....
}
다대일 양방향
다대일 양방향은 팀 엔티티에도 매핑 관련 코드를 넣어야 한다.
@Entity
public class Team {
....
@OneToMany(mappedBy = "team") // 연관관계의 주인이 아니므로 mappedBy를 사용한다.
private List<Member> members = new ArrayList<>();
....
}
양방향 연관관계에서는 양쪽 엔티티가 무한루프에 빠지지 않도록 주의해야 한다.
@Entity
@ToString(exclude = "team") // 1. 무한루프 체크: team 필드 제외하고 스트링 출력
public class Member {
....
@ManyToOne
@JoinColumn(name = "TEAM_ID") // 외래 키
private Team team;
public void setTeam (Team team) {
this.team = team;
// 2. 무한루프 체크
if(!team.getMembers().contains(this)){
// Team 엔티티의 addMember 메서드를 호출한다.
// addMember 내부에서는 member.setTeam(team); 코드가 있으므로
// addMember 내부에서도 무한루프에 빠지지 않도록 체크해야 한다.
team.addMember(this);
}
}
....
}
일대다
일대다 단방향 vs 일대다 양방향
일대다 단방향
일대다 단방향 관계는 JPA2.0부터 지원한다.
일대다 단방향 관계는 Team.members 를 이용해서 Member 테이블의 TEAM_ID 외래키를 관리한다.
따라서 Team 정보를 저장할 때 Team 정보는 INSERT, Member 정보는 UPDATE 를 해야하는 번거로움이 생긴다.
그리고 최초에 Member 가 저장될 때는 TEAM_ID 외래키 정보는 null로 둔 채 저장되므로 테이블에 외래키 값이 있는 데이터, 없는 데이터가 생긴다.
따라서 일대다 단방향 매핑보다는 다대일 양방향 매핑을 권장한다.
일대다 양방향
일대다 양방향 매핑은 다대일 양방향 매핑을 이용해서 구현할 수 있다.
일대다 양방향 매핑에서는 다 쪽이 연관관계의 주인이 아니므로 mappedBy를 사용해야 하지만,
외래키는 항상 다 쪽에 있으므로 @ManyToOne 어노테이션에는 mappedBy 속성이 없다.
따라서 다 쪽에 읽기 전용 @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) 어노테이션을 추가해야 한다.
Team 테이블은 일대다 단방향, 일대다 양방향 모두 동일하다.
@Entity
public class Team {
....
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
....
}
일대다 양방향일 때만 매핑 코드를 추가한다.
@Entity
public class Member {
....
@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
private Team team;
....
}
일대다 단방향과 비슷한 단점을 가지므로 일대다 양방향보다는 다대일 양방향 매핑을 사용하자.
일대일
회원(Member)은 사물함(Locker) 1개를 가질 수 있고,
각 사물함은 회원 1명이 가질 수 있다고 한다.
주 테이블에 외래 키
주 테이블인 Member에 외래키를 넣은 상태다.
외래 키를 객체 참조와 비슷하게 사용할 수 있다. Member.locker 를 Member 테이블의 LOCKER_ID 외래키로 바로 조인해서 조회할 수 있다.
Member 엔티티는 단방향, 양방향이 같다.
@Entity
public class Member {
....
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
....
}
양방향인 경우 Locker 엔티티에 매핑 코드를 추가한다.
@Entity
public class Locker {
....
@OneToOne(mappedBy = "locker")
private Member member;
....
}
대상 테이블에 외래 키
단방향
단방향은 JPA에서 지원하지 않는다고 한다.
인프런 질문에 보면 김영한님도 정확한 이유는 모른다고 답변이 달려있다.
가능하지 않나...?
이 부분에 대해 공부하게 되면 추가 내용 적겠습니다.
양방향
@Entity
public class Member {
....
@OneToOne(mappedBy = "member")
private Locker locker;
....
}
@Entity
public class Locker {
....
@OneToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
....
}
프록시의 한계로 외래 키를 직접 관리하지 않는 일대일 관계는 즉시로딩으로 실행된다.
Locker.member -> 지연로딩 가능
Member.locker -> 지연로딩 불가능, 즉시로딩으로 실행됨.
이 문제는 프록시 대신 bytecode instrumentation을 사용하면 해결된다.
다대다
데이터베이스 테이블로는 다대다를 표현할 수 없다.
따라서 중간에 연결테이블을 추가한 형태로 설계해야 한다.
반면, 객체는 연결 객체에 추가되는 변수들이 없는 한, 연결 객체가 필요 없다.
다대다:단방향, 다대다:양방향 (연결 객체가 없는 버전)
다대다:단방향, 다대다:양방향은 같은 Member 엔티티를 사용한다.
@Entity
public class Member {
....
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT",
joinColumns = @JoinColumn(name = "MEMBR_ID"),
inverseJoinColmns = @JoinColumn(name = "PRODUCT_ID")
)
private List<Product> products = new ArrayList<>();
....
}
다대다:양방향은 Product엔티티에 매핑 정보를 추가한다.
@Entity
public class Product {
....
@ManyToMany(mappedBy = "products")
private List<Member> members;
....
}
다대다 연결 엔티티 사용
@ManyToMany는 회원 상품 테이블에 주문수량, 주문 날짜 칼럼이 필요해질 때 사용할 수 없다.
이때는 연결 엔티티를 추가해야 한다.
@Entity
public class Member {
....
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts;
....
}
@Entity
public class Product {
....
// 매핑 코드 없음.
....
}
@Entity
@IdClass (MemberProductId.class) // 복합키, 식별 관계
pubic class MemberProduct {
@Id
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@Id
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
....
}
// 복합키를 사용하는 경우 복합키 클래스도 추가해야 한다.
public class MemberProductId implements Serializable {
private String member;
private String product;
// hashcode and equals method
// 기본 생성자 있어야 함.
...
}
복합키, 식별 관계의 한계
컬럼 1개만 사용해서 기본키를 만드는 것보다, 복합키를 사용하는 것은 추가 클래스를 만들어야 하고, 내부에 여러 메소드도 추가해야 하기때문에 복잡하다.
극복 방법
이 문제를 해결하기 위해 새로운 기본키를 추가할 수 있다.
다대다: 새로운 기본키 사용
Order 객체에 id 변수를 추가하면 된다.
@Entity
pubic class Order {
@Id
@GeneratedValue
@Column(name = "ORDER_ID")
private Long id; // 비식별 관계
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
....
}
새로운 기본키 사용(비식별 관계) -> 이 방법이 새로운 ID 클래스를 추가하지 않아도 되므로 추천하는 방법이다.
정리
- 이번 장에서는 다대일,일대다, 일대일, 다대다 연관관계를 단방향, 양방향으로 매핑하는 방법에 대해 공부했다.
- 양방향 연관관계에서는 양쪽 엔티티가 무한루프에 빠지지 않도록 주의해야 한다.
- 일대다 단방향 매핑보다는 다대일 양방향 매핑을 권장한다.
- 일대다 단방향과 비슷한 단점을 가지므로 일대다 양방향보다는 다대일 양방향 매핑을 사용하자.
- 새로운 기본키 사용(비식별 관계) -> 이 방법이 새로운 ID 클래스를 추가하지 않아도 되므로 추천하는 방법이다.
참고자료
- https://www.yes24.com/Product/Goods/19040233 "자바 ORM 표준 JPA 프로그래밍-김영한"
'Coding > computer science' 카테고리의 다른 글
자바 ORM 표준 JPA 프로그래밍 | 08. 프록시와 연관관계 관리 (0) | 2023.10.19 |
---|---|
자바 ORM 표준 JPA 프로그래밍 | 07. 고급 매핑 (0) | 2023.10.05 |
자바 ORM 표준 JPA 프로그래밍 | 05. 연관관계 매핑 기초 (0) | 2023.09.12 |
자바 ORM 표준 JPA 프로그래밍 | 04. 엔티티 매핑 (0) | 2023.09.06 |
자바 ORM 표준 JPA 프로그래밍 | 03. 영속성 관리 (0) | 2023.08.29 |