- Published on
django many to many 관계와 중간테이블 구조
- Authors
- Name
- hongreat
- ✉️hongreat95@gmail.com
저번 글에서 ManyToManyField
를 통해 여러 개의 관계를 가질 수 있는 다대다(Many-to-Many) 관계를 쉽게 설정하는 메서드를 알아봤습니다.
이번 글에서는 ManyToManyField
의 동작 방식과 중간 테이블의 개념을 설명하고, 왜 해당 모델의 ManyToManyField
에서 옵션으로 null=True
가 의미없는지 기록합니다.
1. ManyToMany 기본 모델
여기서 Course
모델의 students
필드는 여러 학생과의 다대다 관계를 설정합니다.
(한 코스에서 여러학생이 들을 수 있고, 한 학생이 여러 코스의 수업을 들을 수 있는 개념입니다.)
class Student(models.Model):
name = models.CharField(max_length=100)
class Course(models.Model):
name = models.CharField(max_length=100)
students = models.ManyToManyField(Student)
2. 중간 테이블의 개념
Django는 ManyToManyField
를 사용하여 두 모델 간의 관계를 설정할 때 자동으로 중간 테이블을 생성합니다.
중간 테이블은 두 모델(Student, Course) 간의 관계를 관리하는 실제 데이터베이스 테이블 입니다.
따로 생성하지 않아도, 내부적으로 course_students
라는 이름의 중간 테이블을 생성합니다.
이 중간 테이블은 두 모델의 Primary Key
값만을 저장합니다.
해당 테이블은 아래 SQL 문을 통해 생성되는 구조입니다.
CREATE TABLE course_students (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
course_id INTEGER NOT NULL,
student_id INTEGER NOT NULL,
FOREIGN KEY (course_id) REFERENCES course(id),
FOREIGN KEY (student_id) REFERENCES student(id)
);
course_id
:Course
모델의 기본 키(Primary Key
).student_id
:Student
모델의 기본 키(Primary Key
).- 이 두 외래 키가
ManyToMany
관계를 관리하는 방식입니다. - 참고)PostgreSQL 10 이상에서는
GENERATED BY DEFAULT AS IDENTITY
를 통해 PK(ID)를 지정할 수 있고, 이전에는SERIAL
가 사용되었다고 합니다.
이 중간 테이블에서 서로 어떤 PK 가 어떤 PK 와 관계 되어있는지 알기 때문에, 연결(묶음)이 되는 것 입니다.
3. 왜 null=True 가 의미가 없는가?
일반적으로 Django 모델 필드에 null=True
옵션을 설정하면 해당 필드에 NULL
값을 허용합니다.
some_field = models.OneToOneField(…생략…, null=True)
하지만 ManyToManyField
는 중간 테이블을 사용하여 관계를 저장하기 때문에 필드에 NULL
값을 허용할 수 없습니다.
중간 테이블의 외래 키는 항상 두 모델 간의 유효한 관계를 가져야 하기 때문에, NULL
값은 해당 관계가 성립하지 않음을 의미하게 됩니다.
따라서 null=True
는 ManyToManyField
에서 아무런 의미가 없으며, Django는 이 옵션을 허용하지 않습니다.
3.1 [System Warning] null has no effect on ManyToManyField
null=True
를 ManyToManyField
에 설정하고 서버를 run 하게 되면, system에서 모델에 대한 validation을 해주고 아래처럼 [Warning]System check identified some issues을 통해 알려줍니다.
참고로 model migration 을 해도 문제 없습니다. 하지만 의미가 없으니 하지 않는게 맞겠습니다.
System check identified some issues:
WARNINGS:
some_app.SomeModel.work_plans: (fields.W340) null has no effect on ManyToManyField.
3.2 blank=True 의 역할
반면, blank=True
는 폼 레벨에서의 유효성 검사와 관련이 있습니다.
이는 해당 필드를 폼에서 비워둘 수 있는지를 결정합니다.
즉, ManyToManyField
가 있는 폼에서 필드를 비워도 관계 설정이 이루어지지 않을 뿐, 데이터베이스 상에서 문제가 발생하지는 않습니다.
class Course(models.Model):
name = models.CharField(max_length=100)
students = models.ManyToManyField(Student, blank=True)
위 코드에서 blank=True
를 설정함으로써, 폼에서 students
필드를 비워두는 것이 허용됩니다.
4. 중간 테이블 커스텀 하기
때로는 중간 테이블에 추가 정보를 저장하고 싶을 수 있습니다. 이 경우 Django에서 직접 중간 테이블을 정의할 수 있습니다.
예를 들어, 학생이 강의를 언제 수강했는지 기록하기를 원하는 상황이라면, 아래처럼 중간 테이블을 명시적으로 정의할 수 있습니다.
class Enrollment(models.Model): # 등록(기록)
student = models.ForeignKey(Student, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
enrollment_date = models.DateField()
class Course(models.Model):
name = models.CharField(max_length=100)
students = models.ManyToManyField(Student, through='Enrollment')
이 방식에서는 중간 테이블인 Enrollment
모델이 추가 정보(enrollment_date
)를 담고 있으며, 두 모델 간의 관계를 직접적으로 관리하게 됩니다.