데이터베이스 관례상 N에서 1을 참조하기 위한 FK를 정의해야 한다. 이때, FK는 1의 PK이다.
예를 들어, 하나의 Album이 여러 Song을 가지는 1:N
의 관계라고 가정하자. Album 클래스는 Song 클래스의 리스트를 멤버로 가지고 있다. 그럼 엔티티와 테이블에 매핑을 고려해서 Album 테이블에도 Song을 참조하기 위한 컬럼이 있어야 할까?
꼭 그렇지는 않다. 만약, Album과 Song이 1:N
이며 단방향 관계라면 다음과 같은 양상을 보인다.
==== << 엔티티 >> ====
@Entity
public class Album {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long albumId;
private String albumName;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "album_id")
private List<Song> songs;
}
@Entity
public class Song {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long songId;
private String songName;
==== << 테이블 >> ====
Album Table:
+-----------+-------------+
| album_id | album_name |
+-----------+-------------+
| 1 | Love |
| 2 | Happiness |
| 3 | Dream |
+-----------+-------------+
Song Table:
+---------+--------------+----------+
| song_id | song_name | album_id |
+---------+--------------+----------+
| 1 | Love Song | 1 |
| 2 | I Love U | 1 |
| 3 | Happiness | 2 |
| 4 | Dreams | 3 |
+---------+--------------+----------+
위 예시를 보면 Album 테이블에 Song과 관련된 컬럼이 없다. 오히려 Song 쪽에 Album의 PK인 album_id에 대한 필드가 있다. 왜 그런걸까?
엔티티와 테이블은 서로 다른 개념이므로 엔티티와 테이블 간에 필드가 반드시 1:1로 매핑되지는 않는다. 따라서, Alubm 엔티티가 Song 엔티티를 참조하고 있다고 해서 반드시 Album 테이블이 Song 테이블 관련 컬럼을 가져야 하는 것은 아니다.
관례상 데이터베이스는 N 테이블에 FK가 존재한다. 이에 반해, 엔티티 관점에서는 1:N
단방향 관계가 성립하려면 1의 엔티티가 N 엔티티의 컬렉션을 필드로 가져야 한다. 바로 이 지점에서 엔티티와 테이블 간 차이가 발생하는 것이다.
1:N
단방향 관계가 가지는 구조적 문제는 쿼리에도 영향을 끼친다. 만약 1 엔티티에서 참조하고 있는 필드인 N 엔티티 값을 수정한다고 해보자.
try {
Song song = new Song();
song.setSongName("new song");
em.persist(song);
Album album = new Album();
album.setAlbumName("new album");
album.getSongs().add(song);
em.persist(song);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
위 코드에는 Album 객체의 필드인 songs를 업데이트 하는 로직이 구현되어 있다. 문제는 데이터베이스에 변경 사항을 반영하기 위해 전송되는 쿼리에서 확인할 수 있다.
insert into Song (songId, songName)
values (null, ?)
insert into Album (albumId, albumName)
values (null, ?)
update Song set ALBUM_ID=? where songId=?
수정된 엔티티는 Album인데 실제 쿼리는 Song 테이블에 전송된다.
단순한 구조에서는 상관 없지만 실무에서는 여러 테이블이 다양한 관계로 엮여서 돌아가는만큼 운영에 어려움이 발생할 수 있다.