"본 게시글은 김영한님의 '자바 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'이 주인이에요!"하고 알려주는 것이기 때문에 주인이 아닌 객체에서 사용해야 한다.
그러면 member
와 team
중에 어딜 주인으로 잡아야 하는 것일까? 정답은 외래키가 있는 곳을 주인으로 정해야 한다.(보통 '다' 쪽에 주인을 잡는다.) 외래키가 있는 곳은 member
이니 회원을 연관 관계의 주인으로 잡는 게 맞다. 그런데 왜 그럴까?
사실 팀을 주인으로 설정을 할 수는 있다. 하지만 조금 문제가 생기는데, 데이터베이스 테이블의 외래키는 회원에 있는 상태에서 Team
엔티티에 있는 List members
의 회원 정보를 바꾼다고 하면, 쿼리는 member
에서 나가게 된다... 영한님이 말씀하시기로는 기술적으로는 문제가 없지만, '난 분명 팀의 회원을 변경했는데 회원 테이블에서 변경된다..?' 무척 헷갈릴 것이다. 이는 지양하는 것이 좋다.