[JPA] 연관관계 매핑
객체와 테이블은 연관관계를 맺을 때 큰 차이가 있다.
객체의 경우 참조를 이용하지만, 테이블은 외래키를 사용하여 연관관계를 매핑한다.
그렇다면 JPA는 객체지향 프로그래밍과, 데이터베이스 사이의 패러다임 불일치를 어떻게 해결하였을까?
연관관계를 매핑할 때 크게 3가지를 고려해야 한다.
- 방향 (단방향 - 두 객체 중 한 객체만 참조를 가짐, 양방향 - 두 객체 모두가 서로에 대한 참조를 가짐)
- 다중성 ( 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) )
- 연관관계의 주인 : 연관관계가 양방향으로 맺어져 있을 때 관리 주체
단방향, 양방향 매핑
- 데이터베이스의 경우
- 데이터베이스는 외래키를 가지고 연관관계를 알 수 있다. (두 테이블을 조인하는 방식)
- 즉 외래키 하나만 가지고 양쪽 테이블의 원하는 값을 조회할 수 있다. (양방향)
- 객체의 경우
- 참조를 가지고 있는 객체만 다른 객체를 참조하는 것이 가능하다. (단방향, 양방향)
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "team_id")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
객체를 테이블에 맞춰서 데이터 중심 모델링을 한다면 객체 간 협력 관계를 만들 수 없다.
예를 들어, team.addMember(member) 메서드를 호출하면 새로운 멤버를 데이터베이스에 추가하거나 해당 멤버의 외래 키를 업데이트한다. 또한 member.setTeam(team)을 호출할 경우 새로운 팀을 추가하거나 멤버의 외래 키를 업데이트 해야한다. 데이터베이스에서는 member 테이블의 team 외래 키만 업데이트하면 되지만, 객체 지향 프로그래밍에서는 team 테이블이 member 테이블에 영향을 미칠 수 있는 구조를 가지게 된다.
따라서 연관 관계의 주인은 다른 테이블에 영향을 줄 수 있는 명령을 어느 엔티티 객체에 한정할 것인지 결정하는 것으로, 양방향 매핑을 사용할 경우 외래 키를 관리할 주체를 명확히 지정해야 한다.
위 예시에선 member 객체에 있는 team으로 외래 키를 관리할 것인지, 아니면 team 객체에 있는 member로 외래 키를 관리할 것인지 선택해야 한다. (mappedBy를 통해 Member.team이 연관관계 주인으로 설정)
연관관계의 주인
위에서 설명했다시피 객체와 테이블 간 패러다임의 차이가 생기게 되므로 두 테이블 중 외래키를 관리할 곳을 지정해야 하는데 이를 연관관계의 주인이라고 한다.
- 연관관계의 주인 : 외래 키를 관리하는 참조를 의미
- 주인의 반대편 : 외래 키에 영향을 주지 않음, 단순 조회만 가능 (mappedBy 속성을 사용)
- mappedBy = “값은 대상이 되는 변수명”
@OneToMany(mappedBy = "team") // 여기서 team은 Member 객체에 있는 변수 team -> Member.team이 연관관계의 주인
private List<Member> members = new ArrayList<>();
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("user1");
member.setTeam(team); // 연관관계 주인에 연관관계 설정
em.persist(member);
// 연관관계의 주인이 아닌 곳에서 연관관계를 설정하면 실제 DB에 외래키 설정이 되지 않음.
team.getMembers().add(member); // 객체 관계를 고려해서 양쪽 모두 값을 설정
즉 위 예시에서 members로 조회는 가능하지만 값을 변경할 때는 Member.team만 참조
데이터베이스에서 외래키는 항상 다(N)에서 가지고 있으므로 객체에서 주로 N쪽에 있는 참조가 연관관계의 주인이 된다.
다중성
- 다대일(N:1)
- 다음과 같은 요구사항이 있다고 하자.
- 하나의 팀에는 여러 멤버가 있을 수 있다.
- 한 사용자는 한 팀에만 소속될 수 있다.
- 이 경우 멤버와 팀은 다대일 관계이다.
- 다음과 같은 요구사항이 있다고 하자.
데이터베이스는 다(N)쪽이 외래키를 가지는데, 위 예시에서 N인 쪽(Member)이 @ManyToOne으로 Team 객체와 연결해주는 경우 다대일 단방향 연관관계라고 한다.
그리고 1(Team)인 쪽 역시 Member를 알고 있다면 이는 다대일 양방향 연관관계라고 하며, 양방향이므로 연관 관계의 주인을 지정한다.
- 일대다(1:N)
- 요구사항은 위와 같지만 여기서 설명할 일대다 관계는 연관관계의 주인을 1에다가 지정하는 경우 (권장X)
@OneToMany
@JoinColumn(name = "TEAM_ID)
private List<Member> members = new ArrayList<>();
데이터베이스는 다(N)쪽이 외래키를 관리한다.
하지만 위 경우는 1쪽에서 N쪽 객체를 관리할 수 있게 된다.
이는 team.getMembers().add(member)와 같이 Team.members에 멤버를 추가하게 되면 팀뿐만 아니라 멤버의 외래키 역시 수정을 해야하므로 Member 테이블에 update 쿼리가 추가로 발생한다.
따라서 성능상 단점이 있고, 팀을 수정했는데 왜 멤버 테이블이 수정이 되는가? 라는 의문이 생겨 유지보수에 악영향을 미칠 수 있다.
+ @JoinColumn을 사용하지 않으면 조인 테이블 방식을 사용한다.
- 일대일(1:1)
- 다음과 같은 요구사항이 있다고 하자.
- 회원은 1개의 사물함을 소지할 수 있고, 사물함 역시 딱 하나의 회원만 가질 수 있다.
- 이 경우 Member - Locker 의 관계는 1:1 관계이다.
- 다음과 같은 요구사항이 있다고 하자.
일대일 관계는 주 테이블, 대상 테이블 중에 어느 테이블에 외래 키를 둘 지 선택할 수 있다. (외래 키에는 데이터베이스 유니크 제약 조건 추가)
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
양방향을 하고 싶다면 다대일과 마찬가지로 반대 쪽에서 mappedBy 지정
만약 대상 테이블에 외래 키가 있고, 이를 단방향으로 연결하는 경우는 JPA에서 지원하지 않음 (양방향은 가능)
주 테이블(주로 많이 조회하는 테이블)에 외래 키가 존재하는 경우
- 객체지향 개발자 선호
- 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인이 가능하지만 값이 없다면 외래 키에 null 허용
대상 테이블에 외래 키가 존재하는 경우
- 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조를 유지하며 유연하게 변경이 가능하지만 지연 로딩으로 설정해도 항상 즉시 로딩된다.
- 다대다(N:M)
- 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 따라서 중간에 연결 테이블을 추가하여 일대다, 다대일 관계로 풀어낸다.
JPA는 @ManyToMany, @JoinTable로 연결 테이블 지정하여 다대다 매핑이 가능하지만 이 경우 매핑 테이블에는 외래 키만 존재한다.
하지만 매핑 테이블에는 단순히 연결만 하고 끝나는 것이 아닌 추가 요구사항이 생길 수 있으며, 매핑 테이블에 의한 복잡한 쿼리가 발생할 수 있으므로 사용을 권장하지 않고, 매핑 테이블을 엔티티로 만들어 일대다, 다대일 관계로 풀어내어 사용하는 것이 변경사항에도 유연하게 대처할 수 있다.
참고
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com