CH05 연관관계 매핑 기초
4장에서 엔티티의 매핑을 알아봤다면 이번 장에서는 Relational 에서 가지는 연관관계들을 Object 에서 어떻게 표현하는가에 대한 내용이다. 그래서 '연관관개 매핑' 이라는 용어가 사용되고 있다. 책의 목차 순서와는 조금 다르게 예전에 정리한 순서대로 정리를 다시 하면서 약간의 내용을 추가했다.
연관관계
복잡한 비즈니스 상에서는 여러 엔티티가 설계되고 엔티티간에 상관관계가 존재할 수 있다. 이를 테이블의 세계에서는 외래키라는 개념으로 연관관계를 설정한다. ORM 에서 이 외래키 및 연관관계의 개념을 어떻게 설정하고 사용하는지에 관한 것이 ‘연관관계’ 이다.
연관관계 설정시 아래 세 가지 개념이 핵심이다.
방향(direction)
Relational 의 세계에서는 외래키 하나만 있으면 두 쪽 다 조회가 가능하다.
테이블의 세계에서는 외래키 라는 개념으로 테이블간의 연관관계를 설계한다. 그리고 테이블에서는 하나의 외래키를 이용해서 연관관계가 있는 두 테이블 모두 조회가 가능하다.
예를 들어서 team 과 member가 있고 member에 외래키가 존재하는 경우 특정 teamId를 통해서 memeber들을 조회할 수 있으며, 반대로 특정 member들의 team 을 조회할 수 있다. 즉, 두 테이블 모두 하나의 외래키로 조회가 가능하다.
Object 의 세계에서는 참조를 하려면 반드시 필드로 해당 객체를 가지고 있어야 한다.
하지만 객체의 경우 해당 필드를 가지고 있어야만 조회가 가능하다. 즉, memeber 에서 team 을 조회하려면 member 는 team 을 필드로 갖고 있어야하며, 반대로 team 에서 member를 조회하고 싶은 경우 member 들을 필드로 갖고 있어야 한다. 그래야 참조를 할 수 있다.
이렇게 테이블의 세계에서는 외래키 하나로 member -> team, team -> member 모두 조회가 가능하지만 객체의 세계에서는 ‘객체 그래프 탐색’의 방향이 제한된다. 한쪽만 가능한 경우를 두고 ‘단방향’이라고 하며 양쪽 모두에서 서로 참조가 가능한 경우를 ‘양방향’ 이라고 한다.
양방향은 결국 단방향을 양쪽 모두에 걸어준 것일 뿐이다.
객체 그래프 탐색이 양방향 모두 가능하게 설정해주는 것의 장점은(=단방향과 비교하여 양방향의 장점) 그래프 탐색이 서로 참조가 가능하다는 것 뿐이다. 그리고 사실 양방향이란 단방향을 서로에게 걸어주는 것일 뿐이다.
즉, 단방향 대비 양방향의 장점은 그저 객체 그래프 탐색의 방향이 일방적이지 않고 서로 참조가 가능하다는 것 뿐이다.
다중성(multiplicity)
엔티티간의 관계에서 갖게 되는 수적인 관계에 관한 개념이다. 일대다, 다대다, 다대일, 일대일과 같은 연관성에서 포함되는 수의 개념을 의미한다.
관계의 주인(owner)
궁극적으로 객체 지향 프로그래밍을 통해서 데이터베이스 세계와 소통하는 것이 ORM의 목적인데, 테이블의 세계에서는 외래키 하나만 있으면 된다. 그런데 만약 객체의 세계에서 양방향 설정을 하게 되면 외래키는 하나인데 외래키를 누가 관리해야할 것인가의 이슈가 발생한다.
즉, 관계의 주인이란 양방향 설정시 고려해야할 개념이며 ‘외래키를 관리하는 객체’가 관계의 주인이 된다. 외래키를 관리하는 자가 곧 두 관계를 정의하는 자이고 이것이 관계의 주인이다.
@JoinColumn 자세히 알아보기
너무 기계적으로 써온 것 같아서 이번 기회에 다시 좀 정리를 한다. 일단 기본적으로 @Column 과 역할은 동일한데 속성이 더 확장되었다고 보면 된다.
joining an entity 를 하는 column 을 Specifiy 한댄다. 명확하게 나와있다. 이름 부터가 JoinColumn 이다. 외래키인 컬럼이라는 뜻이다.
더 설명이 필요가 없다. name 속성 자체가 외래키의 이름이다. 위에 썼듯이 @Column 과 유사한데 외래키 컬럼에 사용하는 메타데이터라고 보자.
이 외래키로 참조하는 컬럼의 이름이다. 반드시 일치 해야한다. 당연한 이야기이지만 이 속성, 더 크게는 이 어노테이션 자체가 Object 에 Releational 의 무엇을 참조할지를 알려주는 메타데이터이기 때문이다.
이번 기회에 알게된 사실인데 인텔리제이에 DataSource 를 설정해주면 실제 Relational 까지 살펴보고 해당 속성에 대해서 존재하지 않는 컬럼명을 설정할 경우 경고를 준다.
보통 DataGrip 을 따로 써서 인텔리제이에는 데이터소스 할당을 하지 않는데, 생각보다 좋은 것 같다.
mappedby 자세히 알아보기(ex. @OneToMany(mappedBy = ...))
@JoinColumn 은 컬럼에 붙여주는 어노테이션이고 mappedby 는 연관관계 설정시 사용하는 어노테이션의 속성일 뿐이다. 하지만 @JoinColumn 과 연결된 개념이라 이어서 정리한다.
앞서 정리했듯이 관계의 주인은 외래키를 가지고 있는 엔티티이다. 따라서 mappedby 가 선언되는 엔티티는 관계의 주인이 아니다. 그리고 mappedby 의 값은 주석에 잘 적혀있듯이 Relational 의 값이 아니라 Object 의 값이다.
더 정확히는 관계의 주인 엔티티와 매핑되는 Object(@Entity 로 선언된 클래스) 에서 외래키이자 객체 참조로 활용되고 있는 객체 필드의 '필드명' 이다.
위에 내가 실제로 프로젝트에 사용한 코드 일부를 가져왔는데 Meal 클래스에서 User 를 'user' 라는 필드명으로 선언해서 참조하고 있기 때문에 "user" 를 mappedBy 에 잡아주었다.
관계의 주인의 역할
책에는 다른 목차 이름으로 적혀있는데 내가 일부러 '관계의 주인의 역할' 이라는 이름으로 바꿨다. 상식적으로 외래키를 관리하는 곳이 관계의 주인이기 때문에 결국 실제 Relational 과의 관계를 위한 Object 의 데이터 싱크도 관계의 주인에 잡아줘야 한다.
Member와 Team 간의 관계에서 Member 가 관계의 주인이라면 Member 의 team 필드에 연관된 Team 을 세팅해줘야 한다는 뜻이다. Team 이 주인이 아닌데도 Team 에 Member 를 세팅해줘봤자 소용이 없다. 주인이 아닌 쪽에서는 객체 그래프 탐색을 통한 조회 밖에 할 수 없다.
양방향 연관 관계 주의점
관계의 주인 뿐만이 아니라 반대편에도 데이터를 맞춰주어야 한다
백기선님 강의때도 다뤘던 주제인데 연관관계를 가진 두 객체의 일관성을 유지해줘야 하는 것이 중요하다. 편의 메소드를 통해서 해결하도록 한다.
편의 메소드에 관해서 공식 문서 같은 것에서 서술하고 있는 레퍼런스를 찾고 싶은데 나오질 않아서 링크 첨부가 불가능 하다. 편의 메소드란 연관관계를 가진 두 엔티티의 싱크를 맞춰주는 메소드이다. 편의 메소드 작성시 서로 참조에 의한 무한 루프에 빠지지 않도록 유의해야 한다. 양 쪽에 다 작성할 수 있지만 그냥 한 쪽에만 작성하도록 하자.
예를 들어 Team, Member 사이에서 연관관계의 주인인 Member에 Team 을 세팅해주고 insert 를 하면 테이블에는 데이터가 들어가지만 Team 의 Memeber 에는 해당 Memeber 가 들어가 있지 않을 것이므로 버그 방지를 위해서 Object 에서의 참조관계도 신경써서 처리해줘야 한다는 것이다.
추가 뿐만이 아니라 삭제 등 참조관계의 변경에서도 마찬가지다.
Entity 를 그대로 반환하지 말라
entity 를 그대로 반환하는 경우 두 가지 문제가 생길 수 있다.
첫째, 양방향 참조하는 entity 를 json 으로 변환하는 과정에서 서로 참조하고 있는 객체를 변환하면서 무한 루프에 빠지는 문제가 있다.
둘째, entity 변경이 발생하면 API 스펙이 바뀌어버리게 된다.
따라서 위와 같은 문제점들 때문에 entity 를 dto 로 변환해서 반환하여야 한다.
Last updated