DB를 설계해본 경험이 있다면 Entity들이 서로 관계를 맺고 있다는 사실을 알고 있을 것이다.
연관관계 매핑(Relation mapping)이란 객체의 참조 column과 참조 테이블의 외래키를 매핑하는 것을 의미한다.
JPA에서의 relation mapping은 JDBC(MyBatis)를 사용했을 때와 다르게 연관 관계에 있는 테이블의 PK를 멤버 변수로 갖지 않고 엔티티 객체 자체를 참조한다.
JPA에서의 사용은 연관관계를 맺고자 하는 엔티티 객체를 변수로 생성한 후 1:1, 1:n, n:1, n:n와 같은 관계에 따른 annotation을 붙이고 get mapping을 통해 사용을 하면 된다.
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class BookReviewInfo extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//private Long bookId;
@OneToOne(optional = false) // Book과 1:1로 연관관계를 갖겠다~
private Book book;
private float averageReviewScore;
private int reviewCount;
}
@OneToOne의 optional의 Default값은 true이다. optional의 값이 true이면 foreign key의 값은 null일 수도 있으며 join을 할때도 left outer join을 하게된다.
하지만 optional을 false로 변경하면 해당 column은 값이 무조건 존재하는 not null로 지정되며 join도 inner join으로 실행된다.
Entity간의 양방향 연결을 하려고 두개의 Table에 @OneToOne을 붙여주면 아래의 사진과 같이 각각의 Table의 다른 table의 PK값에 대한 column이 생성된다. 두개의 테이블 모두 참조 column이 생성되는 것은 메모리 적으로도 비효율적이다. 이러한 문제는 mappedBy를 사용하면 한개의 Table에만 생성되게 할 수 있다.
mappedBy을 사용하면 연관 key를 해당 Table에서는 갖지 않게된다.
// Book.java파일
@OneToOne(mappedBy = "book")
@ToString.Exclude
private BookReviewInfo bookReviewInfo;
//mappedBy = "bookReviewInfo" 속성을 넣으면 Book의 연관 key인 book_id를 BookReviewInfo에서 갖지 않게 된다.
📌주의!!
Entity Relationship을 사용하는 경우 ToString과 같은 것은 순환참조가 걸리게 된다.
그래서 특별히 필요한 경우가 아니면 Relation은 단방향으로 걸거나 양방향으로 걸면 ToString에서 제외하는 작업을 해줘야한다.
그러지 않으면 StackOverFlow오류가 발생한다.
-> @ToString.Exclude를 붙여준다.
// User.java내에 생성한 예시
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id", insertable = false, updatable = false)
@ToString.Exclude
List<UserHistory> userHistories = new ArrayList<>();
1:N의 관계를 연결할 때는 @OneToMany annotation을 사용하면 된다.
초기에 아래 코드와 같이 기본 OneToOne annotation만 붙일 경우 User와 UserHistory사이의 Join을 위해 새로운 중간 테이블이 생기는 걸 확인할 수 있다.
@OneToMany
List<UserHistory> userHistories = new ArrayList<>();
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
List<UserHistory> userHistories = new ArrayList<>();
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id", insertable = false, updatable = false)
List<UserHistory> userHistories = new ArrayList<>();
// User.java
@OneToMany
@JoinColumn(name = "user_id")
@ToString.Exclude
private List<Review> reviews = new ArrayList<>();
// Review.java
@ManyToOne
private User user;
@ManyToOne
private Book book;
양방향 연결을 하면 앞서 말한 듯이 순환 참조가 걸리게 되어서 StackOverFlow오류를 막기 위해 한쪽에 @ToString.Exclude를 붙여줘야한다.
// Book.java
@ManyToMany
@ToString.Exclude
private List<Author> authors = new ArrayList<>();
// Authors.java
@ManyToMany
@ToString.Exclude
private List<Book> books = new ArrayList<>();
일반적으로 ManyToMany(N:N)의 경우 복잡성때문에 현업에서는 잘 사용하지 않는다.
-> N:N보다는 1:1, 1:N, N:1을 많이 사용하려한다
Ex) User와 Product의 경우 User는 많은 Product를 구매할 수 있고 Product도 많은 User에게 팔릴 수 있어서 n:n관계이다. 하지만 이럴 경우 현업에서는 중간에 order table을 만들어서 user와 order, product와 order 테이블간에 n:1관계를 만들어 조회한다.