기본적으로 관계형 데이터베이스는 논리적으로 관계가 있는 테이블끼리 연관관계를 설정할 수 있는데 객체 지향 설계에서도 자율적인 객체들간에 협력 관계를 만들기 위해 두 객체 사이에 연관관계를 정의해주어야 한다. 하지만 객체의 연관관계와 테이블 간 연관관계에는 큰 차이가 있다.
테이블은 외래 키로 JOIN을 해서 쉽게 테이블의 연관관계를 만들 수 있지만 객체는 테이블에 맞추어 모델링을 하면 객체 지향적인 협력 관계를 만들어내기가 어렵다. 예를 들면 멤버 객체에서 팀 객체의 PK값인 teamId를 외래 키(FK)로 저장하려면 먼저 팀 객체에서 Id를 조회해서 가져와야하고 나중에 팀 객체에 접근하려면 다시 teamId를 통해 찾아야 하는데 이렇게 식별자로 다시 조회하는 것은 객체 지향적이지 않다.
반면 객체의 참조를 사용해서 테이블의 외래 키를 매핑하면 멤버 객체는 팀 객체의 참조를 저장하고 객체 그래프 탐색으로 바로 연관관계를 조회할 수 있다. 멤버 객체에서 팀 객체의 참조를 통해 접근하기 때문에 멤버 -> 팀의 단방향 연관관계라고 한다.
JPA에서 외래 키를 매핑할 때는 @JoinColumn을 사용하며 name 기본값은 "필드명_참조 테이블 기본 키 컬럼명", 그 외 unique, nullable, updatable 등이 있다.
양방향 연관관계의 경우 테이블은 외래 키 하나로 JOIN을 하면 두 테이블이 자연스럽게 양방향 관계가 되지만 객체의 양방향 연관관계는 객체가 서로 양쪽을 참조하는 것으로 서로 다른 단방향 연관관계 2개를 만드는 것과 같다.
객체의 양방향 매핑 규칙
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정하여 연관관계의 주인만 외래 키를 등록, 수정할 수 있다.
- 주인이 아닌 쪽은 읽기만 가능하며 mappedBy로 지정을 해줘야 한다.
연관관계의 주인만 외래 키를 등록, 수정 가능하기 때문에 당연히 외래 키가 있는 쪽(N)을 주인으로 정하는 것이 좋다. (반대의 경우에는 외래 키를 수정하려면 반대쪽 객체에 접근해서 수정을 해야 함)
연관관계 주인이 아닌 쪽(mappedBy)는 읽기만 가능하기 때문에 값을 설정 안 해도 되지만 순수 객체 상태를 고려하여 양쪽에 값을 설정하는 것이 좋다.
기본적으로 단방향 매핑으로 설계를 하며 양방향은 필요할 때 추가하는 것이 좋다. 연관관계 주인이라고 해서 비즈니스 로직을 기준으로 생각하면 안 되고 (멤버가 팀에 속해 있으니 팀이 주인이겠군 -> X), 외래 키를 보유한 쪽을 기준으로 정하며 외래 키는 항상 많은 쪽에 있다.
연관관계를 매핑할 때는 위에서의 단방향, 양방향과 연관관계의 주인, 그리고 마지막으로 다중성에 대해 고려해야 한다.
1. 다대일 (@ManyToOne)
- 글로벌 페치 전략 fetch = FetchType.EAGER(기본값)
- 영속성 전이 기능 cascade
다대일 단방향은 가장 많이 사용하는 연관관계로, 항상 많은 쪽(N)이 참조 필드(외래 키)를 가지고 있기 때문에 외래 키를 가진 쪽에서 참조로 접근을 하는 단방향으로 설계를 한다. 단방향 연관관계는 참조 필드에 @JoinColumns를 붙여서 정의할 수 있다.
2. 일대다 (@OneToMany)
- 양방향 시 mappedBy = "연관관계의 주인 필드"
- 글로벌 페치 전략 fetch = FetchType.LAZY(기본값)
- 영속성 전이 기능 cascade
일대다 단방향은 외래 키가 다른 테이블에 있고 연관관계 관리를 위해 추가 SQL이 실행되기 때문에 사용하지 않는 것이 좋다. (사용하려면 @JoinColumn 필수, 없으면 Join Table 방식으로 중간에 테이블을 하나 생성함)
3. 일대일 (@OneToOne)
일대일 관계는 주 테이블과 대상 테이블 중에 외래 키를 선택할 수 있으며 외래 키에 유니크 제약 조건을 추가한다.
주 테이블에 외래 키를 설정하면 주 테이블에서 참조를 통해 대상 테이블에 접근하기 때문에 객체 지향적이며 JPA 매핑이 편리해지고 주 테이블만 조회하면 대상 테이블에 데이터가 있는지 확인이 가능하다. 단점은 값이 없으면 외래 키에 null 을 허용해야 한다.
대상 테이블에 외래 키는 주 테이블과 대상 테이블의 관계가 일대일에서 일대다로 변경될 때(많은 쪽이 외래 키) 테이블 구조가 유지되기 때문에 데이터베이스 관점에서 좋지만 프록시 적용이 안 되어 지연 로딩이 되지 않고 항상 즉시 로딩만 되는 단점이 있다. (프록시와 지연 로딩은 뒤에서 다룬다.) 대상 테이블에 외래 키의 경우 단방향은 JPA에서 지원하지 않는다.
4. 다대다 (@ManyToMany)
다대다 관계의 경우 @ManyToMany, @JoinTable로 연결 테이블을 지정하고 컬렉션을 사용하면 객체 2개로 연결 테이블을 나타낼 수 있지만 실제로는 연결 테이블이 키값만 있지 않기 때문에 연결 테이블을 엔티티로 만들고 @OneToMany, @ManyToOne으로 연결하는 것이 좋다.
[참고] 인프런 김영한님 강의를 공부한 내용입니다.
'JPA' 카테고리의 다른 글
[JPA] JPQL (0) | 2022.07.05 |
---|---|
[JPA] 프록시를 이용한 지연 로딩(FetchType.LAZY)과 연관관계 옵션(영속성 전이, 고아 객체) (0) | 2022.06.30 |
[JPA] 상속관계 매핑 (0) | 2022.06.30 |
[JPA] 엔티티(Entity) 매핑 (0) | 2022.06.29 |
[JPA] JPA 기본 개념과 영속성 컨텍스트 (0) | 2022.06.24 |