본문 바로가기

TIL

[TIL 2022-2-15]JPA 양방향 매핑

양방향 매핑

테이블의 연관관계에는 방향이라는 개념이 필요없다. 하지만 객체에서는 외래키가 아니라 객체의 참조값을 이용하기 때문에 하나의 엔티티를 통해 다른 연관관계의 엔티티에 접근하기 위해선 양방향 매핑을 사용 해야한다.

 

꼭 연관 관계를 통해 양쪽 객체 모두에서 접근할 필요가 없다면 단방향 매핑으로 구현하는 것이 좋다. 하지만 실무에서는 다양한 상황들이 존재하고 양방향 매핑이 이를 손쉽게 해결해 주기 때문에 잘 알고 사용하면 좋다고 한다. (객체지향 관점에서도 두 객체가 서로 참조하고 있는 것은 좋지는 않음)

 

 

연관관계의 주인

객체와 테이블간은 연관관계를  맺는 방식이 다르다. 테이블은 외래키 '하나'로 양뱡향으로 모두 조인할 수 있는 양방향 연관관계를 갖지만, 객체에서는 연관관계에 있어서 한 쪽이 다른 객체의 주소를 참조하고, 다른 쪽 객체가 또 한 쪽의 주소를 참조하는, 두개의 단방향 관계로 양뱡향을  구현한다.

 

이렇게 구현된 엔티티에서 JPA는 외래 키를 관리할 엔티티객체를 연관관계의 주인으로 설정해 관리한다. 양쪽 객체 모두에서 연관된 객체를 관리하면, 개발자의 수정으로 두 객체 사이에 연관관계가 달라졌을 때 JPA는 어떤 객체를 기준으로 외래키를 수정해야하는지 알 수 없을 것이다. 때문에 기준이되는 연관관계 주인 객체를 설정해 놓고 이를 기준으로 데이터베이스에 적용시킨다. 연관관계의 주인 지정은 mappedBy를 통해 이뤄진다.

 

 

mappedBy

mappedBy를 통해 연관관계의 주인 설정

연관관계의 주인이 아닌쪽에 설정해 준다. 연관관계의 주인은 외래 키를 가지고 있는 쪽으로 지정한다. 이는 비즈니스 로직에서 가지는 의미와는 상관없다. mappedBy를 통해 매핑된 객체는 읽기 전용으로 동작한다. 즉 이 객체에서 연관관계를 갖는 객체를 변경해도 데이터베이스에는 적용되지않는다.

team 객체에 meber객체를 추가해주지 않았지만 데이터베이스에는 연관관계가 설정되어 저장된다.

위 코드처럼 연관관계에 주인인 Member 객체에 Team 객체를 추가해주면, Team 객체에는 따로 Member 객체를 세팅하지 않아도 외래키는 잘 설정되어 디비에 저장된다.

 

하지만 아래처럼 연관관계의 주인이 아닌 Team 객체에서는 멤버를 추가하는 변경을 가해도 외래 키는 설정되지 않는다. 

이처럼 양방향 연관관계에 있는 엔티티에서 참조하고 있는 객체를 변경할 때에는 꼭 연관관계의 주인을 통해서 명확히 해야한다. mappedBy를 통해 매핑된 쪽에서는 조회만을 담당한다.  

 

 

하지만 객체지향적인 관점에서 데이터베이스에 외래 키 적용과 무관하다고 해도 양방향 연관관계를 가지는 두 객체에 둘다 데이터를 변경해 동기화 해주는 것이 좋다. 따로 영속성 콘텍스트를 위처럼 flush(), clear()를 통해 초기화 해주지 않는 이상 트랜잭션 안에서는 1차 캐시에서 객체를 조회하기 때문에 객체의 연관관계가 다르면 정상적으로 동작하지 않을 수 있다. 이렇게 두 객체의 연관관계를 맺는 메소드는 따로 만들어 관리하는게 좋다. 이를 '연관관계 편의 메소드'라고 한다. 간단하게 구현하면 아래와 같다.

 

연관관계의 주인인 객체에서 반대 쪽 객체를 파라미터로 받아 양쪽 객체의 관계 맺어주는 코드이다. 이 편의 메소드를 구현하는 것은 연관관계의 주인에 구애받지않고 그때그때 상황에 맞춰 구현한다. 단 이 코드 그대로 사용하면 중복적으로 메소드를 호출했을 때 버그가 발생할 수 있다. 멤버의 팀이 변경되었을 때 이전에 Team에 멤버가 그대로 남아 있게 되는 것이다. 이를 해결하기 위해 아래와 같이 코드를 다시 작성하였다.

 

조건문을 통해 기존에 연관된 팀이 있었다면, 해당 팀에서 멤버(this)를 제거하고 새로운 팀에 추가한다. 이는 동일한 팀을 넘겨도 똑같이 작동한다.

 

 

 

주의할 점

toString(), JSON 생성 라이브러리 등에서 무한루프 조심해야한다. 만약에 연관관계를 갖는 객체 양쪽에 둘다 toString()을 잘못 작성하고 print하면, 끊임없이 서로를 참조해가면서 출력해 결국 스택오버플로우로 터지고 만다. JSON의 경우에도 엔티티를 컨트롤러의 반환값으로 그대로 사용하면, 객체안의 객체, 그 객체안의 객체를 끊임없이 Json 객체로 만드려고 시도하다가 터지고 말 것이다. 때문에 컨트롤러에서 엔티티를 절대 직접 반환해서는 안된다. 또 이 이유가 아니여도 엔티티 변경시 api 스펙 자체가 바뀌어버리는 문제 또한 존재하기 때문에. 컨트롤러의 반환 dto를  꼭따로 만들어서 사용해야 한다.

 

결론

단방향 매핑만으로도 이미 연관관계 매핑은 충분하다. 양방향 매핑은 반대 방향으로 조회 기능이 추가된 것 뿐이다. (JPQL 등에서 역방향으로 탐색할 일이 많음) 단방향 매핑으로 통해 엔티티 객체를 잘 설계해 놓으면 나중에 양방향 관계가 필요할 때 추가해도 된다.(테이블에 영향을 끼치지 않음)

 

연관관계의 주인은 항상 다대일 관계에서 많은 쪽, 외래 키를 갖는 쪽이다. 주인이 아닌 쪽에서 외래 키를 변경할만한 동작을 해도 JPA는 이를 반영하지 않는다. 오로지 연관관계의 주인만이 외래키를 변경할 수 있다.


참고:

https://www.inflearn.com/course/ORM-JPA-Basic/

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

https://joanne.tistory.com/220

 

[Spring Data JPA] 연관관계 편의 메서드

연관관계 편의 메서드 양방향 연관관계를 맺을 때에는, 양쪽 모두 관계를 맺어주어야한다. 사실 JPA의 입장에서 보았을 때에는 외래키 관리자(연관관계의 주인) 쪽에만 관계를 맺어준다면 정상

joanne.tistory.com