[자바 표준 ORM JPA]6장 다양한 연관관계 매핑
다대일
다대일 관계의 반대 방향은 항상 일대다 관계고 일대다 관계의 반대 발향은 항상 다대일 관계다. 관계에서 외래키는 항상 다쪽에 있다. 따라서 객체 양방향 관계에서 연관관계의 주인은 항상 다쪽이다. 예를들어 회원N과 팀1이 있으면 회원 쪽이 연관관계의 주인이다.
다대일 단방향 [N:1]
@ManyToOne
@JoinColume(name = "team_id")
private Team team;
@JoinColumn(name=”team_id”)를 사용해서 Member.team 필드를 team_id 외래 키와 매핑했다. 따라서 Member.team 필드로 회원 테이블의 team_id 외래키를 관리한다.
다대일 양방향 [N:1, 1:N]
다대일 양방향의 객체 연관관계에서 실전이 연관관계의 주인이고 점선은 주인이 아니다.
- 양방향은 외래키가 있는 쪽이 연관관계의 주인이다.
- 양방향 연관관계에서는 항상 서로를 참조해야 한다.
일대다
일대 다 관계는 다대일 관계의 반대방향이다. 일대다 관계는 엔티티를 하나 이상 참조할 수 있으므로 자바 컬렉션 중에 하나를 사용한다.
일대다 단방향[1:N]
팀은 회원들을 참조하지만 반대로 회원은 팀을 참조하지 않으면 둘의 관계는 단방향이다.
일대다 단방향 관계는 약간 특이한데 팀 엔티티의 Team.members로 회원 테이블의 team_id 외래 키를 관리한다. 보통 자신이 매핑한 테이블의 외래 키를 관리하는데, 이 매핑은 반대쪽 테이블에 있는 외래 키를 관리한다. 그럴 수 밖에 없는 것이 일대다 관계에서 외래 키는 항상 다쪽 테이블에 있다. 하지만 다 쪽인 Member 엔티티에는 외래 키를 매핑할 수 있는 참조 필드가 없다. 대신에 반대쪽인 Team 엔티티에만 참조 필드인 members가 있다. 따라서 반대편 테이블의 외래 키를 관리하는 특이한 모습이 나타난다.
일대다 단방향 관계를 매핑할 때는 @JoinColumn을 명시해야 한다. 그렇지 않으면 JPA는 연결 테이블을 중간에 두고 연관관계를 관리하는 조인 테이블 전략을 기본으로 사용해서 매핑한다.
일대다 양방향 [1:N, N:1]
일대다 양방향 매핑은 존재하지 않는다. 다대일 양방향 매핑을 사용해야 한다.
더 정확히 말하자면 양방향 매핑에서 @OneToMany는 연관관계의 주인이 될 수 없다. 관계형 데이터베이스 특성상 일대다, 다대일 관계는 항상 다 쪽이 외래 키가 있다. 따라서 연관관계의 주인은 @ManyToOne을 사용한 곳이다. 이런 이유로 @ManyToOne에는 mappedBy 속성이 없다.
일대일 [1:1]
일대일 관계는 양쪽이 서로 하나의 관계만을 가진다.
일대일 관계는 다음과 같은 특징이 이싿.
- 일대일 관계는 그 반대도 일대일 관계다.
- 주 테이블이나 대상 테이블 중 어느 곳이나 외래 키를 가질 수 있다.
- 일대일 관계를 구성할 때 객체지향 개발자들은 주 테이블에 외래 키가 있는 것을 선호한다. JPA도 주 테이블에 외래 키가 있으면 좀 더 편리하게 매핑할 수 있다
단방향
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "locked_id")
private Locker locker;
...
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "locker_id")
private Long id;
private String name;
...
}
양방향
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "locker_id")
private Locker locker;
...
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "locker_id")
private Long id;
private String name;
@OneToOne(mappedby = "locker")
private Member member;
...
}
다대다
관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 그래서 보통 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용한다. 예를 들어 회원들은 상품을 주문한다고 하자. 반대로 상품들은 회원들에 의해 주문될 때 둘은 다대다 관계다.
그래서 중간에 연결 테이블을 추가해야 한다.
그러나 객체는 테이블과 다르게 객체 2개에서 컬렉션을 사용해 다대다 관계를 만들 수 있다. @ManyToMany를 사용하면 이런 다대다 관계를 편리하게 매핑할 수 있다.
다대다: 매핑의 한계와 극복, 연결 엔티티 사용
@MamyToMany는 실무에서 사용하는데에는 한계가 있다. 요구사항이 달라지게되면 테이블의 구조 자체(JoinTable)를 변경해야 하는 문제가 존재한다.
Member
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "member")
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
private String username;
@OneToMany(mappedBy = "member")
private List<Order> orderList = new ArrayList<>();
}
Product
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "product_id")
private Long id;
private String name;
}
Order
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "order_id")
private Long id;
private Integer orderAmount;
private LocalDateTime orderDate;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private Product product;
public void orderMember(Member member){
if(member.getOrderList().contains(this)){
member.getOrderList().remove(this);
}
this.member = member;
member.getOrderList().add(this);
}
public void orderProduct(Product product){
this.product = product;
}
}
Leave a comment