관계형 데이터베이스(RDB)에서 Many to Many(N:N) 관계를 정규화하기 위해서는 서로간의 관계를 나타내는 중간의 관계테이블이 필요하다.
movies와 actors는 하나의 영화가 여러 배우를 포함하고, 하나의 배우가 여러 영화에 출연할 수 있어 N:N관계를 가진다.
ERD는 아래와 같다
django에서 이를 구현하기 위해서는 2가지 방법이 있다.
1. 중간테이블 actors_movies
클래스를 생성
2. Django에서 제공하는 ManyToManyField
이용하여 2개의 table을 연결
actor
와 movie
를 attribute로 갖는 ActorMovie
클래스를 별도로 생성하였다.
아래는 models.py
와 그에 따른 mysql table이다.
# models.py
class Actor(models.Model):
first_name = models.CharField(max_length=45)
last_name = models.CharField(max_length=45)
date_of_birth = models.DateField()
class Meta:
db_table = 'actors'
class Movie(models.Model):
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
class Meta:
db_table = 'movies'
class ActorMovie(models.Model):
actor = models.ForeignKey('Actor', on_delete=models.CASCADE)
movie = models.ForeignKey('Movie', on_delete=models.CASCADE)
class Meta:
db_table = 'actors_movies'
특정 actor instance와 연결된 모든 movie를 찾기 위해서는 역참조를 통해 중간테이블 actors_movies
의 QuerySet를 가져와, movie를 찾아야 한다.
actor = Actor.objects.get(id=1) # actor: Instance
actormovies = actor.actormovie_set.all() # actormovie: QuerySet
# 역참조
movies = []
for actormovie in actormovies:
movies.append(actormovie.movie)
# 해당 actor가 출연한 movie 인스턴스들을 담은 list
Actor
클래스 생성시, movies
라는 attribute를 아래와 같이 할당함.
movies = models.ManyToManyField('Movie')
migration시, 자동으로 actors_movies
라는 database 중간table이 생성된다.
class Actor(models.Model):
first_name = models.CharField(max_length=45)
last_name = models.CharField(max_length=45)
date_of_birth = models.DateField()
movies = models.ManyToManyField('Movie')
class Meta:
db_table = 'actors'
class Movie(models.Model):
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
class Meta:
db_table = 'movies'
ActorMovie
클래스를 따로 생성했을때와 동일한 table이 생성된 것을 확인할 수 있다.
Movie
클래스에서는actors = ManyToManyField('Actor')
라는 attribute를 할당하지 않았다.- 만약 할당하게 되면,
movies_actors
라는 다른 이름의 관계테이블이 중복으로 생기기 때문에, 주의해야 한다.
특정 actor가 출연한 movie정보를 구하기 위해서는 기존에 actor
클래스에 할당한 movies
attribute를 활용한다.
이때의 movies
는 Django의 ManyRelatedManager
objects를 반환한다.
actor = Actor.objects.get(id=1) # actor: Instance
movies = actor.movie.all() # actormovie: QuerySet
# ManyRelatedManger 활용
# 해당 actor가 출연한 movie 인스턴스들을 담은 QuerySet
특정 movie에서 출연한 actors 정보를 구하기 위해서는 ManyToManyField
를 통해 자동으로 생성된 ManyRelatedManager인 movie.actor_set
을 활용한다.
movie = Movie.objects.get(id=1) # movie: Instance
actors = movie.actor_set.all() # actormovie: QuerySet
# 역참조
# 해당 movie에 출연한 actor 인스턴스들을 담은 QuerySet
ManyToManyField
를 쓰는 것의 장점은
1. 관계테이블을 따로 정의하지 않아도, Django에서 만들어주기 때문에 편하다.
2. 중간테이블의related_name
을 메소드로 사용하지 않고, 바로 N:N 테이블로 접근할 수 있어 코드가 직관적이다.
- 그러나, 관계테이블에 대한 접근이 많이 필요한 데이터 구조의 경우, 관계테이블에 대한 클래스를 따로 정의하고,
ManyToManyField
의 argument로through
를 넘겨줘야하기 때문에, 1번의 장점은 사라진다.through
를 사용하면, 관계테이블에 데이터를 넣는 방법인add
메소드는 사용할 수 없다.
class Actor(models.Model):
first_name = models.CharField(max_length=45)
last_name = models.CharField(max_length=45)
date_of_birth = models.DateField()
movies = models.ManyToManyField('Movie', through="ActorMovie")
class Meta:
db_table = 'actors'
class Movie(models.Model):
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
class Meta:
db_table = 'movies'
class ActorMovie(models.Model):
actor = models.ForeignKey(Actor, on_delete=models.CASCADE)
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
class Meta:
db_table = 'actors_movies'