JPA에서 사실상 가장 중요한것은 연관관계 매핑이다. 테이블과 컬럼은 결국 엔티티와 필드와 그대로 매핑하기 때문에 매핑방법만 알고 있다면 어렵지 않지만, DB와 JPA는 테이블간의 관계를 표현하는 패러다임에서 큰 차이가 있기 때문에 나 역시 JPA를 처음 접하고 이 부분이 굉장히 헷갈렸다.
물론 지금도 헷갈려서 겸사겸사 정리해두고자 한다.
먼저 예제 시나리오를 살펴보자
- 회원과 팀이 있다.
- 회원은 하나의 팀에만 소속될 수 있다.
- 회원과 팀은 다대일 관계이다.
먼저 JPA를 고려하지 않고 DB 중심으로 설계한다면? 당연히 테이블 구조는
DB는 외래키를 이용해 테이블간의 관계를 표현하므로 위처럼 표현할 수 있다.
만약 MyBatis와 같은 SQL Mapper 방식을 이용하고 있다면 위 테이블 구조를 그대로 클래스로 옮길것이다. 아래 사진처럼!
그렇다면 이런 설계의 단점이 무엇일까? 아래의 코드를 살펴보자
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeamId(team.getId());
em.persist(member);
Team과 Member 객체를 하나씩 만들고 이를 영속성 컨텍스트에 저장하는 코드이다.
최종적으로 DB에는 아래와 같이 저장된다.
잘못됐다고 할 순 없지만 객체지향적인 측면에서 코드를 살펴보면 어색한 부분이 있다.
member.setTeamId(team.getId());
바로 위 코드인데.. 처음에 작성했던 시나리오를 다시 살펴보자. 회원은 하나의 팀에 소속된다.
우리가 객체를 테이블의 맞춰 작성했기 때문에 Meber 객체가 teamId라는 속성을 갖고 있지만, 만약 DB와 상관없이 객체를 설계하라고 한다면?
물론 개인마다 다르겠지만 아마도 Meber 객체는 teamId라는 속성보다는 Team 객체의 참조값을 속성으로 갖고 있도록 설계하지 않았을까? 조회하는 예제를 하나 더 살펴보도록 하자
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, findTeamId);
Member 객체를 조회해온 뒤 그 member가 속한 Team의 정보를 알고싶다면? Member 객체가 가지고 있는 teamId 값을 이용하여 Team 객체를 가져온다. 이 역시 뭔가 번거롭고 객체지향스럽지 못한것 같다. 보통 객체는 참조값을 이용하여 연관된 객체를 찾기 때문이다.
그렇다면 조금 더 객체지향스럽게 객체를 다시 설계해보자
기본자료형이였던 teamId를 제거하고 Team 객체의 참조값을 필드로 추가하였다.
Member와 Team의 관계는 N:1의 관계이므로 @ManyToOne 어노테이션을 사용하였고,
@JoinColumn 역시 이름 그대로 어떤 컬럼과 조인할것인지를 쓰면 된다.
이렇게 설계한다면 어색했다고 언급한 코드는 아래처럼 변경될 것이다.
member.setTeam(team);
조회를 한다면?
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
아까처럼 teamId를 이용하여 한번 더 Team을 조회하는것이 아닌 Member 객체로부터 곧바로 객체가 속한 Team 객체를 가져올 수 있다. 어떠한가.. 자바 입장에서 보면 변경 된 코드들이 더 자연스럽지 않은가?
JPA는 이처럼 테이블 중심의 설계를 객체지향스러운 설계로 변경할 수 있도록 도와준다.
이번 글에선 가장 간단한 단방향 연관관계의 예를 하나 살펴보았다. 개인적으로 예제는 간단하지만 JPA의 본질에 대해 잘 알 수 있는 부분이라고 생각한다. 끝!