JPA

[JPA] 배워서 엔터프라이즈 개발하자 6일차 - 일대다, 다대다 관계의 단점

suhwanc 2022. 3. 12. 03:34

본 글은 김영한님의 [인프런 - 자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의와 자바 ORM 표준 JPA 프로그래밍 책을 기반으로 정리해 작성하였습니다.

자바 ORM 표준 JPA 프로그래밍 책

 

 

오늘은 실무에서 보통 쓰지 말라고하는 일대다(연관관계 주인을 일로 설정한 경우), 다대다 관계에 반항하는 말안듣는 잼민이들을 위한 필독 지침서이다. (사실 필자도 포함...)

이 포스팅에서는 일대다, 다대다 관계에 대해 살펴볼 것이고 단점들도 같이 알아볼 예정이다.

글을 읽으시면서 이것들을 그냥 무지성으로 쓰지 않는 것보단 이들이 어떤 문제를 갖고 있는지 알고 쓰지 않는 현명한 개발자가 되었으면 좋겠다. 

 

1. 일대다


일대다 관계는 다대일 관계의 반대 방향으로 "일"이 연관관계의 주인이 된다. (앞을 연관관계의 주인이라고 생각하자)

"일"에서는 엔티티를 하나 이상 참조할 수 있으므로 자바 컬렉션인 Collection, List, Set, Map 중에 하나를 사용해야 한다.

 

1) 일대다 단방향

 

팀과 회원(일대다) 관계라고 생각했을 때, 팀은 모든 회원들을 참조하지만 회원들은 팀을 참조하지 않으면 이를 일대다 단방향이라 한다.

 

일대다 단방향 연관관계 그림

이런 그림이 나오게 된다. 왜냐하면 참조는 Team이 가지고 있는데(객체) 반면, 일대다 관계에서 외래 키는 항상 다쪽 테이블에 존재하기 때문이다. 따라서 크로스 되는(매핑한 객체가 관리하는 외래 키가 다른 테이블에 있는 경우) 상황이 발생한다.

 

본인 테이블에 외래 키가 있으면 엔티티 저장과 연관관계 처리를 INSERT 쿼리 한 번으로 끝낼 수 있지만, 다른 테이블에 외래 키가 있으면 연관관계 처리를 위한 UPDATE 쿼리를 추가로 실행해야 한다.

 

 

2) 일대다 양방향

 

결론부터 말하자면, 일대다 양방향은 존재하지 않는다. 실제로 @ManyToOne 어노테이션에는 mappedBy 속성이 없는데, 이는 이 관계에서 One 쪽이 연관관계의 주인이 될 수 없음을 시사한다.

 

이유는 당연히 항상 다 쪽에 외래 키가 존재하기 때문이다. 따라서 @ManyToOne, @OneToMany 중 mappedBy를 쓸 수 있는 곳은 @OneToMany 뿐이다.

 

따라서 일대다(일이 연관관계의 주인) 관계로 설정하는 것은 다대일로 설정하는 것에 비해 단점이 많고, 일대다로 할 수 있는 것들은 당연히도 다대일로 전부 표현이 가능하기 때문에 다대일로 하는 것이 좋다.

 

 

2. 다대다


관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.

그래서 보통 다대다 관계를 일대다 + 다대일 관계로 풀어내는 연결 테이블로 사용하는데, 아래 그림은 실제로 필자가 설계한 ERD이다.

 

 

만들고자하는 서비스에서는 유저와 그룹이 있고, 유저는 여러 그룹에 들어갈 수 있으며 한 그룹은 여러 유저를 포함할 수 있게 만들고 싶었다. 글로 적다보니 이는 다대다 관계였고, 강의에서 다대다는 절대 쓰지 말라고 하시던게 떠올라서 중간에 연결 테이블을 배치해두었다.

 

이렇게되면 유저는 여러 그룹에 들어갈 수 있으며, 한 그룹은 여러 유저를 포함할 수 있다. 이 두 관계는 다시 두 가지 관계로 나눌 수 있다.

유저는 자신이 속한 여러 개의 그룹 목록을 가지고 있고, 그룹은 그룹에 속한 여러 개의 유저 목록을 가진다.

따라서 이 둘의 관계를 다대일 x 2 로 표현하면 모두 행복해지는 결과를 낳는다.

 

하지만 객체는 테이블과 다르게 객체 2개로 다대다 관계를 만들 수 있다. 두 객체 모두 컬렉션을 사용해 서로를 참조하면 되고, JPA에서도  @ManyToMany 어노테이션을 지원한다.

 

@ManyToMany를 사용하면 연결 테이블도 자동으로 처리해주므로 도메인 모델이 단순해지고 여러 가지고 편리하다.

 

하지만 이걸 우리 서비스에서 적용하자니 다음과 같은 문제점이 발생하게 된다.

 

어떤 유저 A가 그룹 X에 가입한 정확한 시간과, 그 시점에서의 경험치 정보를 저장해야 하는데, 이런 추가 컬럼들을 테이블에 매핑할 수 있을까?

 

불가능하다. 실제로 테이블 간 @ManyToMany 관계를 설정하면, 중간 테이블이 자동으로 생성되긴 한다.

하지만 이 테이블은 우리가 컨트롤할 수 있는 영역 밖에 있기 때문에, 이 테이블 안에 우리가 원하는 컬럼들을 매핑할 수 없다.

 

대체 방안으로는 둘 중 하나의 테이블에 해당 컬럼들을 미리 null 값으로 매핑해두고, 연결이 되는 즉시 값을 바꾼다는 것인데,, 이럴 바엔 차라리 테이블을 하나 더 만드는게 더 좋아 보인다.

 

 

이렇게 오늘은 일대다, 다대다 관계를 알아보았다. 다시 한 번 밑에 두 관계의 단점을 정리해두고 끝내겠다. 오늘은 여기까지~

 

  • 일대다 : 참조를 가지고 있는 객체와 외래 키를 가지고 있는 테이블이 달라 추가적인 쿼리가 발생된다.
  • 다대다 : 연결 테이블에 필요한 추가적인 컬럼이 발생한다. 따라서 연결 테이블을 매핑하는 연결 엔티티가 필요해지므로 중간에 둬서 다대일, 일대다 관계로 풀어야 한다.