무조건 양방향 연관관계는 좋은가?

maketheworldwise·2022년 6월 11일
0


이 글의 목적?

프로젝트를 진행하면서 양방향 매핑을 할지 안할지 고민해야하는 상황이 발생했다. 회사에서는 무조건 양방향 매핑으로 진행했었는데, 과연 양방향 매핑이 좋은가? 라는 의문에서 시작하여 글을 작성해보고자 한다. (양방향, 단방향에 대한 내용은 생략)

양방향 매핑이 복잡해진다

내가 참고한 레퍼런스에 따르면, 양방향 매핑을 했을 때 객체 입장에서는 더 복잡해질 수 있다고 한다. 가장 대표적으로는 User 엔티티를 생각하면 될 것 같다. 실제로 내가 회사에서 양방향 매핑을 적용하면서 User 엔티티의 복잡성이 크게 증가하는 것을 확인할 수 있었다.

따라서 레퍼런스에서는 무조건적으로 양방향 매핑을 하는 것이 아닌, 기본적으로 단방향 매핑으로 적용하고 나중에 역방향으로 객체 탐색이 꼭 필요하다고 느낄 때 추가하는 방향을 권장했다.

연관 관계의 주인 지정

연관 관계에서는 주인을 선택하는 것이 중요하다. 주인을 선택하는 방법은 주인이 수행해야하는 기능을 기준으로 잡으면 된다. 연관 관계의 주인은 CRUD 모두 가능하지만, 주인이 아닌 경우 조회만 가능하다는 점을 이용하는 것이다. 판단하기 어렵다면 가장 쉬운 방법으로 외래키가 있는 곳을 연관 관계의 주인으로 정하면 된다.

내가 참고한 레퍼런스에서는 게시판(Board)과 게시글(Post) 객체의 관계를 예시로 들어 설명했는데, 레퍼런스와 다르게 연관 관계를 설명할 때 자주 등장하는 Member와 Team의 관계로 정리해보자.

철수와 영희(Member)는 한화(Team)의 팬이라고 해보자. 근데 철수와 영희가 싸우면서 영희가 기아로 넘어간다고 해보자. 그럼 Member 객체에서 setTeam() 메서드로 수정하는게 맞을까? 아니면 Team 객체에서 setMember() 메서드로 수정하는게 맞을까?

Member와 Team 입장에서는 모두 사용가능한 방법이지만, JPA 입장에서는 Member에서 Team을 수정할 때 FK를 수정할지, Team에서 Member를 수정할 때 FK를 수정할지 모른다. 결국, 우리는 JPA에게 어떤 FK를 수정해야할지를 지정해야 한다는 의미다.

💡 외래키를 구성하는 것이 과연 좋을까?

최근에 RealMySQL을 읽으면서 공부하고 있는데 성능 향상을 위해서 외래키를 사용하지 않는 경우도 있다고 한다. 그 이유가 InnoDB에서는 인덱스를 기준으로 잠금을 하는데, 외래키 인덱스를 사용함으로써 잠금이 전파되기 때문이다.

예를 들어 A와 B가 외래키로 연결되어있을 때, A를 읽으려할 경우 외래키 잠금 전파로 인해 B가 잠금이 된다는 것이다. 즉, A를 읽을 때 B도 잠금이 일어나 성능 저하가 일어날 수 있다는 것이다.

따라서 프로젝트에서 중요시하는 부분에 따라 외래키를 설정하는 경우도 안하는 경우도 있다. 데이터 정합성이 매순간 필요한 경우 외래키를 사용하고, 정합성의 중요도가 떨어지면 Eventually Consistent(?)와 같이 한순간에 정합성이 맞아지게 만들거나 남아있는 로그로 추후에 백업해주는 방식으로 진행한다고 한다.

연관 관계의 주인 제어

레퍼런스에서는 연관 관계의 주인만을 제어하는게 맞는가에 대해서도 다루었는데, 데이터베이스만을 생각했을 때는 주인만 변경하는 것이 맞다고 한다. 하지만 객체의 입장에서는 주인인 곳과 주인이 아닌 곳 모두 변경해주는 것이 좋다고 했다. 그 이유는 두 참조를 사용하는 순수한 두 객체가 데이터 동기화가 되어야하기 때문이라고 한다.

흠...? 🫠 검색을 통해 알아본 또 다른 내용을 가져와보자.

양쪽 연관 관계가 모두 업데이트한다고 가정한다. 그럼 setMember(), setTeam()을 사용할 것이다. 그럼 Update 쿼리가 두 번 실행될 가능성이 있고, 이를 최적화하기 위해 추가적인 연산이 생길 수도 있다. 즉, setMember(), setTeam()를 모두 허용할 경우 영속성 컨텍스트가 복잡해질 수 있다. 또한 양쪽 연관 관계가 모순되는 경우에 처리해야하는 규약도 복잡해질 수도 있다.

(여러 레퍼런스를 보고 생각했을 때는 본래의 목적과 맞게 연관 관계의 주인만이 제어를 하도록 구성하는 편이 더 맞는 것 같다.)

양방향 매핑 없이 Cascade

레퍼런스를 따라 객체의 관계를 단방향으로 먼저 구성했다고 해보자. 그리고 데이터를 삭제한다고 생각해보자. 데이터베이스에서는 Cascade 옵션이 없을 경우 자식이 있는 부모 객체를 삭제할 수 없다.

보통 JPA에서 Cascade 옵션은 양방향 매핑에서 사용하기 때문에, 이 옵션을 주기 위해서 양방향 매핑을 처리해주는 것은 옳지 않다. 그럼 양방향 매핑 없이 Cascade 옵션을 사용해야하면 어떻게 해야할까?

우선 내가 찾은 바에 따르면 하단의 어노테이션을 설정해주면 된다.

@OnDelete(Action = OnDeleteAction.CASCADE)

하지만 이 어노테이션은 임시방편인 것 같다. 차라리 데이터베이스에 ON DELETE, UPDATE CASCADE 옵션을 지정해주는 편이 더 좋지 않을까 싶다.

이 글의 레퍼런스

profile
세상을 현명하게 이끌어갈 나의 성장 일기 📓

0개의 댓글