TIL19. M2M 관계 (중간테이블 유무)

Byoungju Park·2021년 7월 22일
1
post-thumbnail
post-custom-banner

배우와 영화는 M2M 관계이다. 모델링을 할 때 2가지 방법으로 작성할 수 있다.
1. 중간테이블을 사용한 관계
2. ManyToManyFiled를 사용한 관계 (중간테이블 없음)

1. 중간테이블

1-1 모델

from django.db import models

class Actor(models.Model):
    first_name = models.CharField(max_length=45)
    last_name = models.CharField(max_length=45)
    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"

배우와 영화 테이블, 중간테이블을 생성한다. 중간테이블의 ForeignKey로 둘의 관계를 연결해준다. 모델 메소드를 사용해서 데이터를 생성한다. 이 때 중간테이블에 actor_idmovie_id의 값을 직접 연결시켜줘야 한다.

1-2 뷰

import json

from django.http import JsonResponse
from django.views import View

from movies2.models import Actor, Movie

class ActorView(View):
    def get(self, request):
        actors = Actor.objects.all()        
        results = [
            {
                "first_name" : actor.first_name, 
                "last_name" : actor.last_name, 
                "title" : [actor_movie.movie.title for actor_movie in actor.actormovie_set.all()]
            } 
            for actor in actors
        ]

        return JsonResponse({"actors" : results}, status=200)

class MovieView(View):
    def get(self, request):
        movies = Movie.objects.all()
        results=[
            {
                "title" : movie.title,
                "release_date" : movie.release_date,
                "actor" : [movie_actor.actor.first_name for movie_actor in movie.actormovie_set.all()]
            }
            for movie in movies
        ]

        return JsonResponse({"movies":results}, status=200)

배우 테이블에서 조회할 데이터는 1. 배우의 이름2. 출연한 영화 제목이다. 배우 테이블에서 영화를 어떻게 연결시키는 것일지 살펴보자.

테이블의 관계를 살펴보면 배우 테이블과 영화 테이블은 중간테이블을 역참조한다. 중간테이블은 두 테이블을 각각 정참조한다.

배우 <- 중간테이블
영화 <- 중간테이블

정참조 : 참조하는 테이블.참조되는 테이블.필드명
역참조 : 참조되는 테이블.참조하는 테이블_set.all()

먼저 배우와 중간테이블의 관계에 대해 아래 예제를 보며 살펴보자.

  • 중간테이블 id=1인 배우 id 조회하기
    ActorMovie.objects.get(id=1).actor.id 로 바로 연결할 수 있다.
  • 배우테이블 id=1인 중간테이블 id들 조회하기
    actormovies = Actor.objects.get(id=1).actormovie_set.all() : 배우 id=1에 연결된 중간테이블 row는 여러 개이다. all 메소드로 모두 불러오는데 쿼리셋 형식으로 반환하기 때문에, json이 이해할 수 있는 리스트로 바꿔준다.
    [actormovie.id for actormovie in actormovies] : 중간테이블 id들을 하나씩 리스트에 넣어준다.

이제 중간테이블을 거쳐 출연한 영화를 연결지어보자.
중간테이블은 영화를 정참조하므로 중간테이블.영화테이블.영화필드명이 된다.

배우-중간테이블-영화를 합쳐서 표현해보자.
actor_movies = actor.actormovie_set.all() : 한 배우가 역참조하는 중간테이블 모든 row를 가져온다.
[actor_movie.movie.title for actor_movie in actor_movies] : 중간테이블 각각의 row가 정참조하는 영화의 제목을 가져온다.

2. ManyToManyField

2-1 모델

from django.db import models

class Actor(models.Model):
    first_name = models.CharField(max_length=45)
    last_name = models.CharField(max_length=45)
    birth = models.DateField()

    class Meta:
        db_table = "actor"

class Movie(models.Model):
    title = models.CharField(max_length=45)
    release_date = models.DateField()
    run_time = models.IntegerField()
    actor = models.ManyToManyField('Actor')

    class Meta:
        db_table = "movie"

배우와 영화 테이블 한 쪽에 ManyToManyField를 사용하고 관계되는 테이블 이름을 써준다. 중간테이블을 생성하지 않았지만 MySQL에 접속하면 자동으로 생성되어 있다.

영화 테이블에서 ManyToManyField를 넣어주었다. 영화는 배우를 정참조하고, 배우는 영화를 역참조하는 관계이다.

MnayToManyField를 사용하면서 중간테이블을 생성해주어도 괜찮다. 중간테이블이 확장할 가능성이 있다면 직접 만들어서 관리해주는 것도 좋다.

2-2 뷰

import json

from django.http import JsonResponse
from django.views import View

from movies.models import Actor, Movie

class ActorView(View):
    def get(self, request):
        actors = Actor.objects.all()
        results = []

        for actor in actors:
            movies = actor.movie_set.all()
            results.append(
                {
                    "first_name" : actor.first_name,
                    "last_name" : actor.last_name,
                    "title" : [movie.title for movie in movies]
                }
            )
        return JsonResponse({"result":results}, status=200)

class MovieView(View):
    def get(self,request):
        movies = Movie.objects.all()
        results = []

        for movie in movies:
            actors = movie.actor.all()
            results.append(
                {
                    "title" : movie.title,
                    "release_date" : movie.release_date,
                    "run_time" : movie.run_time,
                    "actor" : [actor.first_name for actor in actors]
                }
            )
        return JsonResponse({"result":results}, status=200)

배우가 출연한 영화들을 조회해보자.
배우는 영화를 역참조하는 관계이므로 actor.movie_set.all()로 작성한다.

영화에 나온 배우들을 조회해보자.
영화는 배우를 정참조하는 관계이므로 _set을 생략하고 movie.actor.all()로 작성한다.

2-3 ManyToManyField 의 장점

  • 메소드에서 중간테이블의 이름을 넣지 않아도 되기 때문에, 둘의 관계가 좀더 선명하게 보인다. 코드가 간결해지고 가독성을 높인다.
profile
wanna be good programmer
post-custom-banner

0개의 댓글