[JPA] 연관 관계의 이해

Big0·2022년 8월 25일
0

"본 게시글은 김영한님의 '자바 ORM 표준 JPA 프로그래밍'을 기반으로 작성된 글입니다."

JPA를 기반으로 한 프로젝트 2개가 끝났다. 프로젝트가 끝날 때까지 '분명 이걸 이렇게 사용하는 건 맞는데..'하며, 강의도 듣고 프로젝트도 끝냈지만 깊은 원리를 모르고 쓰는 것 같았다. 이대로면 나중에 문제를 맞닥뜨렸을 때 헤맬 게 뻔하기 때문에 이를 한번 정리하는 차원에 글을 쓴다.


연관 관계 용어

방향: 단방향과 양방향
다중성: 다대일, 일대다, 일대일, 다대다
연관관계의 주인: 객체 양방향 연관관계는 관리 주인이 필요

예제의 시나리오는 '회원'과 '팀'이 있으며, '회원'은 하나의 '팀'에만 소속될 수 있다. 이 둘은 다대일(N:1) 관계다. (아래 그림 참조)

연관 관계 없이 한다면?

객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.

  • 테이블은 외래키로 조인을 사용해 연관된 테이블을 찾는다.
  • 객체는 참조를 사용해서 연과된 객체를 찾는다.
  • 테이블과 객체 사이에는 이런 큰 간격이 존재한다.

위를 좀 풀어서 코드로 설명해보자. 만약 회원의 팀에 대한 정보를 얻기 위해 조회를 한다면

//팀 생성
Team team = new Team();
team.setName("teamA");

em.persist(team);

//회원 생성
Member member = new Member();
member.setUserName("hello");
//회원의 팀 정보 생성
member.setTeamId(team.getId()); // 이 부분이 좀 객체지향스럽지 않다.. setTeam(team) 이렇게 돼야 할 거 같은데ㅠ
em.persist(member);

//회원의 팀 정보 불러오기
Member findMember = em.find(Member.class, member.getId());
Long teamId = findMember.getTeamId(); //getTeam이 아닌 teamId를 가져와야 한다.
Team findTeam = em.find(Team.class, teamId); //이 부분도 뭔가 이상하다..

객체를 데이터 중심으로 모델링한 결과다. getTeam()으로 바로 조회하는 게 아니라 member의 teamId를 조회하고 그걸로 다시 Team을 조회 해야한다. 매우 번거로운 과정이 생긴다. 연관 관계가 없기 때문이다.

단방향? 양방향?

데이터베이스의 테이블은 아래 그림과 같이 외래키 하나로 양 쪽 테이블 모두 조인이 가능하다. 즉 데이터베이스는 단방향이나 양방향을 나눌 필요가 없다.

하지만 객체는 다른 객체를 필드에 선언(참조용 필드)해야만 다른 객체를 참조하는 것이 가능하다. 아래 그림과 같이 Member에서 Team을 조회한다면, member.getTeam()과 같은 식으로 말이다. 이렇게 한 객체에서 다른 객체를 참조할 때 단방향 관계가 성립되며, 이 관계가 양 쪽에 모두 있다면 양방향 관계가 성립된다. member.getTeam(), team.getMember()

하지만 양방향 연관 관계는 무분별하게 사용하는 것을 지양하는 게 좋다. "아니, 팀에서도 회원을 참조할 수 있고, 회원에서도 팀을 참조할 수 있게 설계하는 게 좋지 않나?"라는 생각이 물론 들 수 있다. 하지만, 회원과 같은 엔티티는 서비스 규모가 커질 수록 훨씬 많은 다른 객체와 관계를 맺기 때문에 회원의 복잡성이 증가할 것이다. 물론 양방향이 꼭 필요하다면 쓰는 것이 맞지만, 설계 초기에는 단방향으로 설정해두고 필요 시에 바꾸는 접근 방법이 좋을 것이다.

연관 관계 주인

양방향 연관 관계를 사용한다면 연관 관계의 주인을 지정해줘야 한다. 연관 관계의 주인을 지정 하는 것은 두 단방향 관계(양방향 = 두 단방향) 중, 제어의 권한(외래 키를 비롯한 테이블 레코드를 저장, 수정, 삭제 처리)을 갖는 실질적인 관계가 어떤 것인지 JPA에게 알려준다. 연관 관계의 주인은 연관 관계를 갖는 두 객체 사이에서 조회, 저장, 수정, 삭제를 할 수 있지만, 연관 관계의 주인이 아니면 조회만 가능하다.

연관 관계의 주인을 설정하는 방법은 @OneToMany(mappedBy = "team")와 같이 다중성 어노테이션에 mappedBy를 통해 가능하다. 이는 연관 관계의 주인이 아닌 곳에서 "'team'이 주인이에요!"하고 알려주는 것이기 때문에 주인이 아닌 객체에서 사용해야 한다.

그러면 memberteam중에 어딜 주인으로 잡아야 하는 것일까? 정답은 외래키가 있는 곳을 주인으로 정해야 한다.(보통 '다' 쪽에 주인을 잡는다.) 외래키가 있는 곳은 member이니 회원을 연관 관계의 주인으로 잡는 게 맞다. 그런데 왜 그럴까?

사실 팀을 주인으로 설정을 할 수는 있다. 하지만 조금 문제가 생기는데, 데이터베이스 테이블의 외래키는 회원에 있는 상태에서 Team엔티티에 있는 List members의 회원 정보를 바꾼다고 하면, 쿼리는 member에서 나가게 된다... 영한님이 말씀하시기로는 기술적으로는 문제가 없지만, '난 분명 팀의 회원을 변경했는데 회원 테이블에서 변경된다..?' 무척 헷갈릴 것이다. 이는 지양하는 것이 좋다.

0개의 댓글