다양한 연관관계 매핑

D A S H B O A R D
D E V E L O P
S E C U R I T Y
 다양한 연관관계 매핑
 다대일 - N:1
 일대다 - 1:N
 일대일 - 1:1
 다대다 - N:M
Reference

 다양한 연관관계 매핑

객체 간의 연관 관계를 매핑하는 방법을 다양하게 제공합니다. 관계형 데이터베이스에서는 외래 키를 사용하여 연결을 구성하지만, 객체는 참조를 사용하여 연결합니다. 이러한 참조를 매핑하는 방법에는 일대일, 일대다, 다대일, 다대다 등이 있습니다. JPA에서는 다양한 연관관계 매핑을 지원하여 객체 간의 복잡한 연결을 쉽게 구성할 수 있습니다.
연관관계 매핑 시 주의할 점
전 글인 “연관관계 매핑”에서 말한바와 같이 아래 3가지를 주의하면서 매핑하여야 한다.
1.
다중성
다대일 : @ManyToOne
일대다 : @OneToMany
일대일 : @OneToOne
다대다 : @ManyToMany
2.
단방향, 양방향
테이블
외래키 하나로 양쪽 조인 가능
사실 방향이라는 개념은 X
객체
참조용 필드가 있는 쪽으로만 참조 가능
한쪽만 참조하면 단방향
양쪽이 서로 참조하면 양방향(단 방향이 두개 있는 것)
3.
연관관계 주인
테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음
객체 양방향 관계는 A → B, B → A처럼 참조가 2군데
객체 양방향 관계는 참조가 2군데 있음, 둘 중 테이블의 외래키를 관리할 곳일 정해야 함
연관관계의 주인 : 외래키를 관리하는 참조
주인의 반대편 : 외래 키에 영향을 주지 않음, 단순 조회만 가능

 다대일 - N:1

단방향

전 글인 “연관관계 매핑에서 사용했던 예제”이기 때문에 간단히만 하고 넘어가겠다. - 코드는 옆에 하이퍼링크로 가서 확인!!
DB 입장에서 생각해 보았을 때 Table의 연관관계은 Team이 ‘일’, Member가 ‘다’가 된다.
이럴 경우 DB 설계상 ‘다’(N)에 외래키가 가야한다.
또한 테이블에서 외래키가 있는 쪽에다가 연관관계 주인을 해주는 것이 보통이다.
가장 많이 사용하는 연관관계이다.

양방향

양방향일 경우 Team 객체에서 Member로 단방향 연관관계를 추가해주면된다.
여기서 연관관계의 주인은 Member이기 때문에 Team 객체에 어떠한 수정을 해주어도 DB에는 아무 영향이 없다.
하지만 객체로는 서로 참조할 수 있도록 개발

 일대다 - 1:N

단방향

@OneToMany @JoinColumn(name = "TEAM_ID") private List<Member> members = new ArrayList<>();
Java
복사
일대다 모델은 권장하지 않는다.
실무에서는 거의! 웬만하면! 사용할 일이 없지만, 표준 스팩이니 넣어둠
다대일에서와 다르게 Team과 Member의 위치를 뒤집고 Team을 연관관계 주인으로써 Team 객체를 중심으로 무언가를 하겠다는 의미
팀 입장에서는 Member를 알고 싶고, Member 입장에서는 Team을 알고 싶지 않을 경우
객체로만 보면 이런 설계가 자주 나올 수 있지만, DB 입장에서는 무조건 ‘다’(N) 쪽에 외래키가 들어가야한다.
이러한 설계를 할 경우, Team이라는 객체가 변경이 되었을 때 DB에서는 Team이 아닌 Member라는 Table을 Update해주어야 한다.
이렇게 되면은 Team에서 Member를update하는 경우이기 때문에 Team과 관련된 쿼리 외의 Member 테이블로의 update 쿼리가 한번더 나가게 된다.
성능에 큰 차이는 없지만, 손해는 손해다. 또한 유지보수할 경우 코드를 보고 쿼리를 추적해보면 아무리 JPA를 잘 알더라도 헷갈리게 되어있다… 그렇기 때문에 차라리 다대일 관계로 처리하고 양방향 관계를 사용하는 것을 권장한다.
정리 - 결론은 그냥 다대일 연관관계를 이용해 양방향 매핑을 해 사용하자
일대다에서는 일이 연관관계 주인이 된다.
테이블 일대다 관계는 항상 다(N)쪽에 외래키가 있다.
객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조
@JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용하여 중간에 테이블을 하나 더 추가한다.
단점
엔티티가 관리하는 외래 키가 다른 테이블에 있음
연관관계 관리르 위해 추가로 Update SQL 실행

양방향

억지성 있는 연결!!
스펙상 지원하는 기능 X
억지로 끼워 맞춰서 만든 연관관계
⇒ 곱게 다대일 양방향 사용하자ㅎㅎㅎㅎ

Team.class

@OneToMany @JoinColumn(name = "TEAM_ID") private List<Member> members = new ArrayList<>();
Java
복사

Member.class

@ManyToOne @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) private Team team;
Java
복사
Member 객체에서의 연관관계 생성도 연관관계 주인을 생성할 때 처럼 @JoinColumn을 이용
하지만 몇가지 속성을 부여해 읽기 전용으로 만듦

 일대일 - 1:1

주테이블 vs 대상 테이블
접근을 더 많이하는 테이블이 주테이블!!!

단방향 (주 테이블에 외래 키)

한개의 라커는 한명의 멤버에게만 배정 가능
한명의 멤버는 하나의 라커만 가져야 함
@OneToOne @JoinColumn(name = "LOCKER_ID") private Locker locker;
Java
복사
다대일 단방향 매핑과 유사
주 테이블이나 대상 테이블 중에 외래 키 선택 가능
주 테이블에 외래 키
대상 테이블에 외래 키
외래 키에 데이터베이스 유니크(UNI) 제약 조건 추가
유니크 제약 조건 없이 만들 수 있지만, 관리가 말도 안되게 힘들 것….

양방향 (주 테이블에 외래 키)

가장 간단한 일대일 연관관계 매핑
외래키가 있는 곳이 연관관계의 주인
반대편은 mappedBy 적용

Member.class

@OneToOne @JoinColumn(name = "LOCKER_ID") private Locker locker;
Java
복사

Locker.class

@OneToOne(mappedBy = "locker") private Locker locker;
Java
복사

단방향 (대상 테이블에 외래 키)

만약 Member라는 객체를 연관관계 주인으로 두고
DB에서 외래키가 존재하는 테이블이 Locker라고 하면
이는 연관관계 매핑이 성립되지 않는다.

양방향 (대상 테이블에 외래 키)

양방향일 경우 그냥 Locker를 연관관계 주인으로 해서 매핑해주면 된다.
정리하자면 내가 내것만 관리할 수 있다.
잠깐 생각!!
그렇다면 일대일 관계에서 외래키는 어디에 있는 것이 더 좋을까?(DB입장에서 생각) → 물론 정답은 없다.
만약 나중에 비즈니스 로직이 변경되어서, 한명의 멤버가 여러개의 Locker를 가질 수 있게 된다면?
이 때는 Locker에 외래키를 두고 유니크 제약조건만 Alter를 이용해 빼주고
JPA에서는 1:N 관계로 변경해주기만 하면 된다.
그렇다면 개발자 입장에서는?? → 얘도 정답은 없다.
Member에 Locker가 있는게 성능도 그렇고 여러 곳에서 유용하다.
만약 Locker가 있으면 값이 있고 Locker가 없다면 Null을 넣어주면 된다.
만약 멤버가 Locker가 있다면/없다면이라는 조건문이 있을 때, 멤버는 웬만하면 조회를 해와야 하는 부분이기 때문에 쿼리 한번으로 결과를 알 수 있다.
정리
주 테이블에 외래키
주 객체가 대상 객체의 참조를 가지는 것 처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
객체지향 개발자 선호
JPA 매핑 편리
장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
단점: 값이 없으면 외래 키에 null 허용
대상 테이블에 외래키
대상 테이블에 외래 키가 존재
전통적인 데이터베이스 개발자 선호
장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨(프록시는 뒤에서 설명) → 위 양방향 매핑을 보면 Member를 조회한 후 Locker에 대해서 Locker를 또 뒤진 후 Locker가 있을 경우에 값을 넣어주고 아니면 Null을 넣어주기 때문에, 어짜피 쿼리를 날려야 하기 때문에, 지연로딩을 하지 않음

 다대다 - N:M

RDB는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없음
연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야 함
객체는 컬렉션을 이용해 객체 2개로 다대다 관계 가능
@ManyToMany 사용
@JoinTable로 연결 테이블 지정
다대다 매핑: 단방향, 양방향 가능

단방향

@ManyToMany @JoinTable(name = "MEMBER_PRODUCT") private List<Product> products = new ArrayList<>();
Java
복사
이렇게 @JoinTable을 이용하면 외래키가 PK이면서 FK가 되는 그런 테이블이 생성된다.

양방향

Product.class

@ManyToMany(mappedBy = "products") private List<Member> members = new ArrayList<>();
Java
복사

Member.class

@ManyToMany @JoinTable(name = "MEMBER_PRODUCT") private List<Product> products = new ArrayList<>();
Java
복사
실무에서는 쓰면 안된다고 본다.
연결 테이블이 단순히 연결만 하고 끝나지 않는다.
주문시간, 수량 같은 여러 뎅이터가 추가될 수 있는데, 이렇게 되면 다른 데이터들을 Table에 넣을 수 없게된다. → 아래와 같은 DB를 만들 수 없음
어플리케이션은 개발 후 끝나는 것이 아니라 지속적으로 유지 보수하고 추가 개발을 진행한다.
이때 PK는 어떠한 것의 종속적이면 안되는 값을 이용해야하는데,
위와 같은 테이블은 다른 테이블에 있는 값들에 종속되어 버리기 때문에 유지 보수하기 굉장히 어려워진다.

다대다 한계 극복

연결 테이블용 엔티티 추가(연결 테이블을 엔티티로 승격)
@ManyToMany@OneToMany , @ManyToOne

MemberProduct.class

@ManyToOne @JoinTable(name = "MEMBER_ID") private Member memeber; @ManyYToOne @JoinTable(name = "PRODUCT_ID") private Product product;
Java
복사

Member.class

@OneToMany(mappedBy = "memeber") private List<MemberProduct> memberProducts = new ArrayList<>();
Java
복사

Product.class

@OneToMany(mappedBy = "product") private List<MemberProduct> memberProducts = new ArrayList<>();
Java
복사
PK가 종족적이지 않은 의미 없는 값을 주면서 위 한계점을 전부 없애주었다.
그렇기 때문에 다대다는 일대다-다대일로 바꾸자!!