개요
현재, 개인 프로젝트에서 JPA를 사용해 entity 간의 연관 관계를 설정한 상태다. 그런데 프로젝트를 계속 진행하면서 문득, 내가 올바르게 연관 관계를 설정한 것인지 의문이 들기 시작했고, JPA 연관 관계에 대해 다시 한번 정리하면서 이 연관 관계를 수정해야 하는지 판단하기로 했다.
JPA 연관 관계 매핑
JPA와 같은 ORM 기술을 사용할 때 중요하게 생각해야 할 사항 중 하나는 "객체(Object) 와 관계형 데이터베이스 (Relational DB) 테이블이 어떻게 매핑 (Mapping) 되는지를 이해하는 것" 이라고 생각한다. 왜냐하면 ORM 기술의 목적은 객체 지향 프로그래밍과 데이터베이스 사이의 패러다임 불일치를 해결하는 것과 직접적으로 연관되어 있기 때문이다.
그래서 entity 객체에 JPA를 적용시킬 때, 기존 DB의 테이블 관계를 잘 생각하며 entity 객체의 연관 관계를 설정해야 한다.
또한, 연관 관계를 매핑할 때 고려해야 할 것들이 여러가지가 있는데, 그 중에서도 아래의 3가지가 가장 중요한 사항들이라고 할 수 있다.
- 방향 - 단방향, 양방향
- 다중성 - 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)
- 연관 관계의 주인 - 양방향일 때, 연관 관계에서 관리 주체
방향 (Direction)
단방향
단방향 연관 관계에서 객체와 DB의 테이블은 각각 다른 연관 관계를 가진다.
- 객체 : 참조를 통해 연관된 객체를 찾는다. A가 B를 참조할 때 B -> A 는 불가능하다.
- 테이블 : 외래 키(Foreign Key) 로 Join 해 연관된 테이블을 조회한다. 양방향으로 A -> B, B -> A 모두 가능하다.
이러한 차이가 있고 이러한 차이를 극복하기 위해 매핑이 필요한 것이다. 아래와 같이 JPA를 사용하여 객체 중심적인 모델링을 통해 이런 차이를 극복할 수 있다.
@Entity
public class Image {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
@ManyToOne
private User owner; // 연관된 entity 객체
}
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
}
양방향
위에서 설명했듯이, 객체는 단방향 관계만 설정할 수 있다. 하지만, JPA를 이용하여 데이터베이스의 테이블과 비슷하게 객체도 양방향 연관 관계를 맺을 수 있다.
@Entity
public class Image {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
@ManyToOne
private User owner;
}
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
@OneToMany(mappedBy = "owner")
private List<Image> images = new ArrayList<>();
}
다중성 (Multiplicity)
관계형 데이터베이스에 대해 알고 있는 사람이라면, 일대다, 다대일 관계 등, 여러 종류의 테이블 관계에 대해서 들어본 적이 있을 것이다. JPA에서도 이런 다중성 설정이 가능하다.
위에서 나온 Image 와 User 객체를 예로 들어보자.
- 여러 장의 image는 하나의 user에게 소유될 수 있으므로 image와 user의 관계는 다대일 (N:1) 관계이다. 반대로, 하나의 user는 여러 장의 image를 소유할 수 있기 때문에 user : image 는 일대다 (1:N) 관계이다.
위에서 방향성에 대해 설명할 때 사용했던 예제 코드를 다시 살펴보면, User.images 에는 @OneToMany를, Image.owner에는 @ManyToOne 을 설정한 것을 확인할 수 있다.
그 외에도, JPA에서는 @OneToOne 을 사용하는 일대일 관계, @ManyToMany 와 연결 테이블을 이용한 다대다 관계도 지원한다.
연관 관계의 주인
객체에는 양방향 연관 관계는 없다. 그저, 서로 다른 단방향 연관 관계 2개를 어플리케이션 로직으로 양방향인 것처럼 보이게 할 뿐이다.
그런데, 객체를 양방향 연관 관계로 설정하면 객체의 참조는 둘인데 외래 키는 하나만 존재하는 상황이 생기게 된다.
- 따라서, 두 객체 연관 관계 중 하나를 정해서 테이블의 외래 키를 관리해야 하는데, 이 외래 키를 관리하는 객체를 연관 관계의 주인(Owner)이라 한다.
규칙
- 두 연관 관계 중 하나를 주인으로 설정한다.
- 연관 관계의 주인만이 데이터베이스 연관 관계와 매핑되고 외래 키를 관리 (등록, 수정, 삭제) 할 수 있다.
- 반면에, 주인이 아닌 쪽은 읽기만 할 수 있다.
- 주인은 mappedBy 속성을 사용하지 않는다.
- 연관 관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것.
다시 한 번 Image 와 User를 살펴보자.
데이터베이스 테이블의 N:1, 1:N 관계에서는 항상 N 쪽이 외래 키를 가진다. 그러므로 Image 가 외래 키를 가지고 있다는 것을 알 수 있다.
Image 가 외래 키를 갖고 있으므로 Image.owner 가 주인이 된다. 주인이 아닌 User.images 는 아래와 같이 mappedBy = "owner" 로 주인이 아님을 표현한다.
@OneToMany(mappedBy = "owner")
private List<Image> images = new ArrayList<>();
- 여기서 mappedBy 값으로 사용된 owner 는 연관관계의 주인인 Image entity 의 owner 필드를 뜻한다.
- N:1 에서 N 쪽인 @ManyToOne 은 항상 연관 관계의 주인이 되므로, mappedBy를 설정할 수 없다.
적용
위의 설명에서도 알 수 있듯이, 현재 개인 프로젝트에 사용되고 있는 Image 와 User entity는 다대일 양방향 매핑이 되어있다.
@Entity
public class Image {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
@ManyToOne
private User owner;
}
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
@OneToMany(mappedBy = "owner")
private List<Image> images = new ArrayList<>();
}
그런데, 개요에서도 언급했듯이 이렇게 연관 관계를 생성하면서 하나 의문점이 생겼다.
꼭 양방향으로 연관 관계를 맺어야 할까?
지금까지 쭉 내용을 다시 정리해본 결과, 나는 여기에 NO 라는 답을 내렸다.
사실, 잘 생각해 보면 단방향 매핑만으로도 이미 연관관계 매핑은 완료된 상태이다. 그래서 꼭 User.images 를 사용해야 하는 상황이 아니라면, 양방향 매핑을 단방향으로 바꿔도 기능의 차이는 없을 것이라고 생각했다.
그래서 아래와 같이, User.images 를 지워서 단방향 매핑으로 다시 설계했다.
@Entity
public class Image {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
@ManyToOne
private User owner;
}
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
}
하지만, 단방향과 양방향 선택은 상황에 따라 다르게 선택할 수 있으니, 현재 프로젝트의 상황에 알맞게 선택하여 적용할 필요가 있다.
'JPA' 카테고리의 다른 글
[Spring / JPA] Soft Delete를 JPA에서 적용하는 방법 (0) | 2024.05.21 |
---|---|
[Spring /JPA] Spring JPA Delete Query 작성 시 주의할 점 (0) | 2023.12.26 |
[JPA] JPA Auditing (0) | 2023.12.24 |
[JPA] 지연 로딩과 즉시 로딩 (0) | 2023.12.24 |
[JPA] Hibernate entity에 Lombok 사용 시 주의사항 (0) | 2023.12.24 |