[Spring JPA] JPA Relation Mapping

SeongWon Oh·2021년 9월 30일
0

Spring Framework

목록 보기
17/33
post-thumbnail

Relation Mapping

  • DB를 설계해본 경험이 있다면 Entity들이 서로 관계를 맺고 있다는 사실을 알고 있을 것이다.

  • 연관관계 매핑(Relation mapping)이란 객체의 참조 column과 참조 테이블의 외래키를 매핑하는 것을 의미한다.

  • JPA에서의 relation mapping은 JDBC(MyBatis)를 사용했을 때와 다르게 연관 관계에 있는 테이블의 PK를 멤버 변수로 갖지 않고 엔티티 객체 자체를 참조한다.

  • JPA에서의 사용은 연관관계를 맺고자 하는 엔티티 객체를 변수로 생성한 후 1:1, 1:n, n:1, n:n와 같은 관계에 따른 annotation을 붙이고 get mapping을 통해 사용을 하면 된다.


@OneToOne

@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;
}
  • JPA에서는 다음 코드와 같이 참조하고자 하는 Table(entity)변수를 선언해주고 @OneToOne을 붙여주면 자동으로 연관관계를 만들 수 있다.
  • BookReviewInfo에서 1:1로 연결된 Book의 정보를 가져오려면 getBook 메서드를 사용하면 된다.
  • 위의 예시에서는 Book Entity에 대한 1:1관계를 JPA relation annotation을 사용하여 mapping하였더니 book의 PK인 book_id의 column이 자동으로 생성된 것을 확인할 수 있다.
  • @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를 붙여준다.


@OneToMany

// 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<>();

  • 이러한 중간 테이블은 @JoinColumn을 통해 join할 column을 지정해줌으로써 생성하지 않을 수 있다.
    아래의 코드는 UserHistory의 user_id를 이용해 join을 하겠다~라는 의미를 가진다.
    @JoinColumn(name = "user_id")을 붙인 결과 위와 다르게 중간 테이블이 생성되지 않은 것을 볼 수 있다.
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
List<UserHistory> userHistories = new ArrayList<>();

  • DB를 설계할 때 값을 읽기만 할 뿐 DB값 조작은 하지 못하게 하는 설정을 해야할 때가 많이 존재할 것이다. 그럴때는 @JoinColumn의 option중 insertable, updatable의 값을 false로 변경해주면 된다. insertable, updatable의 기본 값은 true이며 false로 변경시 위의 예제로 설명하자면 User entity에서 UserHistory entity의 값을 변경하지 못하게 된다.
    즉, read-only설정이 되는 것이다!
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id", insertable = false, updatable = false)
List<UserHistory> userHistories = new ArrayList<>();

@ManyToOne

  • N:1은 @ManyToOne annotation을 사용하여 설정할 수 있다.
  • 일반적으로는 OneToMany보다 ManyToOne이 더 깔끔하게 구성할 수 있다.
  • ManyToOne도 위의 다른 연관관계와 같게 get method를 통해 객체를 가져올 수 있다.
  • 아래의 예시는 User와 Review를 1:N의 관계를 양방향 관계로 설정한 예시이다.
// 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를 붙여줘야한다.


@ManyToMany

  • N:N은 @ManyToMany annotation을 사용하여 설정할 수 있다.
  • 1:N, N:1에서는 두개의 테이블 사이에 자동으로 생성되는 새로운 테이블을 JoinColumn을 설정함으로써 중간 테이블을 생성하지 않고 직접 참조를 할 수 있었다. 하지만 ManyToMany의 경우는 Foreign key를 통해 특정 데이터를 분리할 수 없기에 중간 테이블을 통해서 데이터를 조회해야한다.
// 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관계를 만들어 조회한다.


Reference

profile
블로그 이전했습니다. -> https://seongwon.dev/

0개의 댓글