꼭 관계 설정이 필요한 건 아니다
- JPA(Java Persistence API)와 같은 ORM(Object Relational Mapping)을 사용할 경우 연관관계 설정은 데이터베이스와 객체지향 프로그래밍 언어 사이의 간극을 메우는 중요한 역할을 한다. 관계를 사용하면 연관된 엔티티의 정보를 쉽게 추가, 변경, 삭제를 간단하게 처리할 수 있다. 하지만 불필요한 관계와 복잡한 관계는 중복 데이터가 생성되거나 데이터 일관성을 유지하기 어려워진다. 또는 결합도가 증가해 유연한 설계가 어려울 수 있으니, 관계는 신중히 설정되어야 한다.
양방향 연관관계와 연관관계의 주인
- 데이터베이스 테이블은 외래키 하나로 두 테이블의 연관관계를 관리(join)하지만, 객체지향 언어에서는 엔티티를 양방향 연관관계로 설정하기 위해 두 엔티티가 서로를 참조한다. 하지만 외래키는 하나이므로 객체 연관관계에서는 하나를 정해 테이블의 외래키를 관리하도록 한다. 여기서 외래키 관리하는 엔티티를 연관관계의 주인이라고 한다.
- 주인이 아니라면 mapped by 속성을 사용해 주인이 아님을 명시한다.
- 양방향은 외래 키가 있는 쪽이 항상 연관관계의 주인이다.
- 양방향 연관관계는 항상 서로를 참조해야 한다. 연관관계 편의 메서드를 작성하면 어느 쪽에서든 연관관계 설정을 할 수 있으므로 유연한 코드 작성이 가능하다.
public void setTeam(Team team) {
this.team = team;
if (!team.getMembers().contains(this)) {
team.getMembers().add(this);
}
}
public void addMember(Member member) {
this.members.add(member);
if (member.getTeam() != this) {
member.setTeam(this);
}
}
연관관계의 종류
단방향 일대일 관계(@OneToOne)
- 공간 예약 서비스에서 하나의 예약에 대한 하나의 후기가 남겨질 수 있다. 일대다, 다대일 관계와 달리 누가 외래키를 가질지 선택해야 한다.
- 예약이 외래키를 가질 경우 예약을 통해서 후기에 접근해야 한다. 예약 없이 리뷰만 필요한 경우가 많을 때는 비효율이 발생한다. 반대도 마찬가지이므로, 엔티티가 자주 변경되거나 업데이트되는 경우 주 테이블로 설정하는 것이 좋다. 해당 예시에서는 예약이 만들어지고 나서 리뷰가 생성되므로 예약을 주 테이블로 설정한다.
public class Reservation {
...
@OneToOne
@JoinColumn(name = "review_id")
private Review review;
}
public class Review {
...
}
양방향 일대일 관계(@OneToOne)
public class Reservation {
...
@OneToOne
@JoinColumn(name = "review_id")
private Review review;
}
public class Review {
...
@OneToOne(mappedBy = review)
private Reservation reservation;
}
- @OneToOne(mappedBy = review)에서 mappedBy 속성의 값 "review"는 Review 클래스 내에서 Reservation 엔티티를 참조하는 필드의 이름을 지정한다.
단방향 일대다 관계(@OneToMany)
- 공간 예약 시스템에서 하나의 부동산은 여러개의 공간을 가질 수 있다.
- 부동산을 통해 공간을 참조할 수 있다.
public class RealEstate {
...
@OneToMany
@JoinColumn(name = "realEstate_id")
private List<Space> spaces = new ArrayList<>();
}
public class Space {
...
}
단방향 다대일 관계(@ManyToOne)
public class RealEstate {
...
}
public class Space {
@ManyToOne
@JoinColumn(name = "realEstate_id")
private RealEstate realEstate;
}
일대다 단방향 매핑은 가급적 사용하지 말자
- 엔티티를 매핑한 테이블이 아닌 다른 테이블의 외래키를 관리해야 한다. 이것은 N+1 성능 문제가 있으며, 외래키 관리도 부담스럽다.
- 연관된 엔티티에 실제로 접근할 때까지 로드를 지연시키는 지연 로딩 전략에 의해 N+1 문제가 발생한다. 이는 초기 쿼리를 실행하고, 연관된 N개 엔티티를 로드시키는 쿼리를 실행한다. 예를 들어, 부동산이 10개가 있고, 각각의 공간이 있다면 초기 부동산을 가져오는 쿼리를 실행(1)하고, 각각의 부동산(N)에 대해 공간 정보를 가져와야 하므로 N+1개의 쿼리가 실행된다.
일대다, 다대일 양방향 관계(@OneToMany)
- 공간을 통해 부동산을, 부동산을 통해 공간을 참조할 수 있다. 일대다, 다대일 관계에서 항상 많은 쪽이 외래키를 가지고 있어 연관관계의 주인이 되는 점을 인지하자.
public class RealEstate {
...
@OneToMany(mappedBy = "realEstate"
private List<Space> spaces = new ArrayList<>();
}
public class Space {
@ManyToOne
@JoinColumn(name = "realEstate_id")
private RealEstate realEstate;
}
다대다 관계(@ManyToMany)
- 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 따라서 일대다, 다대일 관계로 풀어낸다.
참고자료
- 자바 ORM 표준 JPA 프로그래밍(2015)