JPA 005 - 다양한 연관관계 매핑
다대일, 일대다, 일대일, 다대다 매핑을 알아보자. (앞에 있는 객체가 외래키를 가지는 연관관계 주인이라고 가정한다.)
1. 다대일 (N:1)
저번시간에 JPA - 단방향, 양방향 연관관계 매핑 글에서
Comment (N) - Post (1) 관계에서 연관관계 주인을 Comment로 선택하여 외래키를 Comment가 관리하게 하는 예제를 보았다.
다대일 관계는 이를 말한다.
될 수 있으면 단방향 관계만 설정하고, 반대편 (Post) 에서도 조회하고 싶다면 양방향 관계를 추가하고 양방향 매핑을 주의해서 관리해주면 된다.
2. 일대다 (1:N)
일대다 단방향 매핑
일대다 관계는 자바 컬렉션(Collection, List, Set, Map) 중 하나를 사용해서 참조할 객체를 매핑하면 된다.
아래 예제를 보자. @OneToMany
로 연관관계를 설정했지만, 실제 외래키는 Comment 테이블에 생긴다.
@Entity
public class Post {
@Id
@GeneratedValue
private Long id;
private String title;
private String content;
@OneToMany
@JoinColumn(name = "post_id") // Comment 테이블의 post_id 를 말한다. (FK)
private List<Comment> comments = new ArrayList<>();
}
@Entity
public class Comment {
@Id @GeneratedValue
private Long id;
private String content;
}
일대다(일 → 다 객체 잠조) 단방향 매핑은 권장하지 않는데 이유는 아래와 같다.
- 복잡성 증가 : 테이블에서는 항상 외래키를 다 쪽에서 관리한다. 따라서 Comment 에 외래키 컬럼이 생성되지만, 일대다 단방향에서는 Post가 외래키를 관리한다.
- 쿼리 성능 악화 : Post 를 저장하면 외래키를 관리하기 위해 Comment 의 UPDATE SQL 이 실행된다. 기존에는 Comment. Post 를 저장하는 쿼리만 실행되었다면, 이 때는 Comment 의 UPDATE 쿼리도 실행된다.
주의사항
@JoinColumn
를 사용해야 한다. 그렇지 않으면 의도치 않게 조인 테이블을 추가하여 사용하게 된다.
일대다 양방향
@OneToMany
를 사용한 객체의 필드는 연관관계의 주인이 될 수 없다. 관계형 데이터베이스에서 외래키는 항상 '다' 쪽에 있기 때문이다. 연관관계 주인은 항상 @ManyToOne
에 있으며, @OneToMany
에 mappedBy
가 없는 이유가 이것 때문이다.
@Entity
public class Comment {
@Id @GeneratedValue
private Long id;
private String content;
@ManyToOne
@JoinColumn(name = "post_id", insertable = false, updatable = false)
private Post post;
}
외래키는 현재 Post 의 comments 가 관리하기로 했는데, Comment 의 post 도 관리하면 문제가 발생할 수 있으므로 insertable = false, updatable = false
옵션을 사용해서 양방향 매핑을 할 수 있다.
하지만 일대다 단방향의 단점을 그대로 이어받기도 하고, 관리의 복잡성이 증가해서 단점이 크다.
3. 일대일 (1:1)
두 테이블 관계를 보면 주 테이블이 있고 대상이 되는 테이블이 있다.
대상 테이블을 주인으로 만들어 외래키를 매핑하는 것은 전통적인 관계형 데이터베이스 논리에 잘 맞는다.
하지만 JPA 를 사용하는 개발자에게는 주 테이블을 주인으로 하여 외래키를 매핑하는 것이 더 자연스럽고 사용하기 편리할 수 있다. 이 점을 잘 조절하는 것이 바람직 하다.
주 테이블에 외래키
Husband 를 주 테이블, Wife 를 부 테이블로 가정해보자.
단방향
@Entity
public class Husband {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "wife_id")
private Wife wife;
}
@Entity
public class Wife {
@Id @GeneratedValue
private Long id;
private String name;
}
- 남편(Husband) 이 자기 아내(Wife)에 해당하는 외래키를 가지고 있다. 그러므로 남편은 아내를 참조하여 조회할 수 있다.
- 1에서 말한 다대일(
@ManyToOne
) 매핑과 똑같다. - 외래키가 주 테이블에 들어 있다.
양방향
@Entity
public class Husband {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "wife_id")
private Wife wife;
}
@Entity
public class Wife {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne(mappedBy = "wife")
private Husband husband;
}
- 아내도 남편을 참조하려면 Wife → Husband 참조를 만들어 양방향 매핑을 하면된다.
- 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인이 가능하다.
대상 테이블에 외래키
단방향
이번엔 @OneToMany
의 상황이랑 비슷한데, JPA 는 대상 테이블이 외래키를 갖게 하는 일대일 단방향 매핑을 지원하지 않는다. 하지만 대상 테이블에 외래키를 갖게하는 일대다 단방향은 지원한다. (그래도 권장하지 않는다. 이유는 JPA - 단방향, 양방향 연관관계 매핑 에서 참고)
양방향
@Entity
public class Husband {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne(mappedBy = "husband")
private Wife wife;
}
@Entity
public class Wife {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "husband_id")
private Husband husband;
}
- 이번에는 대상 테이블 (Wife) 에 외래키가 존재한다.
- 관계형 데이터베이스에서는 이 구성이 뭔가 자연스럽다.
- 단점 : 프록시 기능의 한계로 Wife.husband 는 지연로딩이 가능한 반면, Husband.wife 는 지연로딩으로 설정하더라도 즉시 로딩된다.
4. 다대다 (N:N)
다대다 연관관계를 매핑하면, 두 테이블 사이에 조인 테이블이 생성된다. 관계형 데이터베이스에서는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없기 때문이다.
하지만 이 테이블은 두 테이블의 id 를 매핑하는 이외의 필드를 추가하지 못한다는 단점이 있다. 실제로는 조인 테이블 안에 추가 정보를 넣을 경우가 많으므로 이 조인 테이블을 실제 엔티티로 승격시켜 만드는 방법을 사용하는 것이 좋다.
@Entity
public class Book {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany(mappedBy = "book")
private List<BookStore> bookStores = new ArrayList<>();
}
조인 테이블
@Entity
public class BookStore {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "book_id")
private Book book;
@ManyToOne
@JoinColumn(name = "store_id")
private Store store;
}
@Getter @Setter
@Entity
public class Store {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "store")
private List<BookStore> bookStores = new ArrayList<>();
}
1번에서 다루었던 다대일 양방향 관계를 각각 조인 테이블에 매핑해주면 된다.
'Java, Kotlin, Spring > JPA' 카테고리의 다른 글
일대다(1:N) 페이징 처리(OneToMany Pagination) (1) | 2021.11.28 |
---|---|
JPA - 기본키 생성 전략 (0) | 2021.09.26 |
JPA - 단방향, 양방향 연관관계 매핑 (0) | 2021.09.20 |
JPA - 상속 객체와 테이블 매핑 (0) | 2021.09.19 |
Spring Data JPA - Entity, 관계 매핑 (0) | 2021.02.11 |
댓글