다양한 연관관계 매핑
연관관계 매핑을 어떻게 할 지 헷갈릴때는 대칭성을 생각해보면 좋다.
예를 들어 일대다의 반대는 다대일인 것이다.
추가적으로, 다대다는 실무에서 쓰면 안된다! 이유는 뒤에서 설명하겠다.
다대일 관계를 가장 많이 쓴다.
- 다대일: @ManyToOne
- 일대다: @OneToMany
- 일대일: @OneToOne
- 다대다: @ManyToMany
단방향, 양방향
외래키는 한 쪽에만 세팅을 하면 양쪽 조인이 가능하기에 테이블은 방향이라는 개념이 없다.
하지만 객체는 방향 존재한다. 참조용 필드가 있는 쪽으로만 참조가 가능하다.
따라서 한쪽만 참조하면 단방향, 양쪽이 서로 참조하면 양방향이라고 보통 말하는데
사실 양방향이라는 건 존재하지 않는다. 이해하기 쉬우라고 '양방향' 이라는 말이 있는 것이다.
단방향 2개를 양방향처럼 보이게 하는 것이다.
이 또한 뒤에서 자세히 설명하겠다.
연관관계 주인
앞서 말했듯, 테이블은 외래 키 하나로 두 테이블의 연관관계를 맺는다.
(외래 키 하나로 양쪽 조인이 가능하다.)
하지만 객체 양방향 관계는 A→B, B→A처럼 참조가 2군데 이다.
둘 중 외래 키를 관리할 곳을 지정해야 하는데,
이러한 외래 키를 관리하는 쪽을 연관관계의 주인이라고 하고,
그 반대편은 외래 키에 영향을 주지 않는다. 단순 조회만 가능하다.
다대일
가장 많이 사용하는 연관관계이다.
다대일(ManyToOne) 관계에서는 '다(many)' 쪽에 외래키가 위치해야 한다.
외래키가 있는 곳에 참조를 걸고
@ManyToOne
@JoinColumn(name = "TEAM_ID") //join하는 컬럼명
private Team team;
TEAM_ID라는 외래키랑 Team team을 연결할거야 라는 뜻이다.
Team에 아무런 코드도 추가하지 않으면 단방향 다대일
Team에서는 Member를 참조할 아무런 의지가 없는 것이다.
어차피 연관관계 주인이 외래키를 관리하기에 반대쪽(Team)에 필드를 추가해도 테이블은 아무 변화 필요 없다. 어차피 읽기만 가능하기 때문이다.
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
Team에 위와 같은 코드 추가하면 양방향 다대일
mappedBy로 연관관계 주인 명시해야 한다! Member객체의 team변수를 의미한다.
"나는 team에 의해서 매핑이 되어진 애야" 라는 뜻으로 단순한 읽기만 가능하다.
일대다
'일(one)'이 연관관계 주인인 관계이다. 즉, 일 방향에서 외래키를 관리한다.
이 모델은 권장하지 않는다.
객체 입장에선 이런 모델이 나올 확률이 있는데 DB입장에서는 이해되지 않는 부분이 많다.
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
@JoinColumn(name = " ")은 외래키가 들어가야한다. 테이블상 구조를 보면 당연한 것인데,
처음에 Team객체 내의 조인컬럼이니까 Member의 List를 가져오려면 MEMBER_ID로 조인해야 하나?라고 생각했었다.
테이블 조인의 기준은 당연히 외래키이다. 따라서 @JoinColumn의 이름은 외래키인 TEAM_ID이 적절하다.
위 코드의 결과로 로그를 확인하면 update쿼리를 하나 더 보낸 것을 알 수 있다.
이유가 무엇일까?
위 테이블 연관관계를 볼 때 Team 객체를 저장하려면
Team team = new Team();
team.setName("Team A");
이러한 코드로 바로 저장이 가능하다.
하지만 team에 미리 만들어 둔 member 내용을 add하려면 team.getMembers().add(member) 와 같은 코드가 사용된다.
이때 Team엔티티를 저장하는 데
이 부분의 TEAM_ID를 바꿀 수 있는 방법이 없고 옆 테이블을 update하는 수 밖에 없다
≫ 외래키인 TEAM_ID를 저장하기 위해 update쿼리를 하나 더 보낼 수 밖에 없는 것이다.
테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음
• 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하
는 특이한 구조
또한 코드만 보면 Team만 내가 손댄 것 같은데 Member테이블에 보내는 업데이트 쿼리가 로그에 찍힌다. 이렇게 되면 로직상 헷갈리거나 운영이 힘들어질 수 있다.
"다대일 단방향 관계에 필요하면 양방향 추가" 전략을 선택하는 게 낫다고 판단되는 이유이다.
일대일
- 일대일 관계는 그 반대도 일대일 → 어차피 대칭이니까 두 테이블 모두 외래키를 넣는 것이 가능하다.
- 주 테이블에 외래 키
- 대상 테이블에 외래 키
- 외래 키에 데이트베이스 유니크(UNI) 제약조건을 추가한다.
주 테이블은 주로 참조하는 테이블을 의미한다.
또한 당연한 말이지만 일대일 관계라는 비즈니스적 로직이 우선적으로 필요하다.
다대일(@ManyToOne) 단방향 매핑과 유사하다.
- 주 테이블에 외래 키
- 객체지향 개발자가 선호하는 방식: 주 객체가 대상 객체의 참조를 가지는 것과 유사한 로직이다.
- JPA 매핑이 편리하다.
- 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인이 가능하다: 주 테이블이 이미 외래 키를 가지므로 해당 외래 키로 조회 가능
- 외래키 값이 없으면 외래 키에 null이 허용된다.
- 대상 테이블에 외래 키
- 전통적인 데이터베이스 개발자가 선호하는 방식
- 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 (비즈니스에선 여러 상황이 생기기에 당연히 테이블 관계가 변경될 수 있다.) 테이블 구조가 유지된다.
- 지연 로딩으로 설정해도 항상 즉시 로딩된다. (나중에 자세한 설명 예정)
다대다
객체는 컬렉션을 사용해서 다대다 관계가 가능하지만 테이블은 불가능하다.
이를 해결하기 위해서 ORM은 @ManyToMany를 만들어 놓긴 하였다.
하지만 다대다 관계는 실무에서 사용하면 안된다.
새로운 정보를 넣기가 너무 힘들어진다. 거의 불가능하다고 보면 된다.
이렇게 하면 다대다 단방향 매핑이 정상적으로 동작하는 것처럼 보이고
@ManyToMany(mappedBy = "products")
private List<Member> members = new ArrayList<>();
지금까지 해왔던 것처럼
Product객체에 위의 코드를 추가하면 다대다 양방향도 문제 없을 것 같다.
@ManyToMany를 이용한 다대다 매핑의 한계
중간 테이블 관리가 어려움
- @ManyToMany 를 사용하면 JPA가 자동으로 중간 테이블을 생성하지만, 실무에서는 이 중간 테이블이 단순 연결만 하고 끝나지 않는다. 추가적인 속성이 필요한 경우가 많다.
- 예를 들어, 두 엔티티 간의 관계에 생성일, 수정일, 관계 유형 등의 추가 데이터가 계속 생길 수 있지만 데이터 저장이 쉽지 않다.
성능 문제
- 즉시 로딩(eager loading)이 기본값이기 때문에, 예상치 못한 N+1 문제를 유발할 가능성이 크다.
- 연관된 엔티티를 조회할 때 중간 테이블을 거쳐야 하므로, 추가적인 JOIN 연산이 발생하여 성능이 저하될 수 있다.
쿼리 최적화 어려움
- 자동 생성된 중간 테이블을 활용한 복잡한 쿼리를 작성하기 어렵다.
- 숨겨진 중간 테이블로 쿼리를 자동으로 보내면 로그를 확인하는 것도 쉬운 작업이 아니다.
따라서 @ManyToMany 는 편리해보이지만 실무에서 사용 불가
한계 극복
중간 테이블을 엔티티로 승격 한다.
Member와 Product의 중간 테이블명을 ORDER라고 했을 때
ORDER_ID를 새로 만들 지
아님 MEMBER_ID랑 PRODUCT_ID를 각각 PK, FK 로 쓸 지는 생각을 해봐야하지만
모든 테이블에 그냥 Id를 만드는 걸 추천한다.
Id가 다른 테이블에 종속된 상태이면 시스템 유연성이 제한될 수 있기 때문이다.
또한 비즈니스 적으로 의미 없는 값 2개를 굳이 묶을 필요는 없다.
↓
'백엔드 > Spring' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 07. 고급 매핑 (0) | 2025.03.08 |
---|---|
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 05. 연관관계 매핑 기초 (0) | 2025.03.04 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 04. 엔티티 매핑 (0) | 2025.03.03 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 03. 영속성 관리 (0) | 2025.02.28 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 02. JPA 시작 (0) | 2025.02.28 |