TIL

내일배움캠프 본캠프 32일차 - Django의 모델 관계, 다대다 예시

수현조 2025. 1. 9. 21:05

 

Django의 모델 관계(Relationships)는 RDBMS(Relational Database Management System)에서 사용할 수 있도록 설계되어 있습니다. 관계는 데이터 간의 연결을 정의하는 기능으로, RDBMS 기반 데이터베이스(예: SQLite, MySQL, PostgreSQL 등)에서만 효과적으로 작동합니다.


Django 모델에서의 관계

Django에서 사용되는 관계의 주요 유형은 다음과 같습니다:

1. ForeignKey (1:N 관계)

  • 한 개의 레코드가 여러 개의 관련 레코드를 가질 수 있음.
  • 예: 하나의 사용자(User)가 여러 개의 게시글(Post)을 가질 수 있음.
class User(models.Model):
    name = models.CharField(max_length=100)

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)

2. OneToOneField (1:1 관계)

  • 한 개의 레코드가 오직 하나의 관련 레코드와만 연결될 수 있음.
  • 예: 사용자(User)와 사용자 프로필(Profile)이 1:1 관계.
class User(models.Model):
    name = models.CharField(max_length=100)

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField()

3. ManyToManyField (N:M 관계)

  • 여러 레코드가 여러 관련 레코드와 연결될 수 있음.
  • 예: 여러 사용자가 여러 그룹에 속할 수 있음.
class User(models.Model):
    name = models.CharField(max_length=100)

class Group(models.Model):
    name = models.CharField(max_length=100)
    members = models.ManyToManyField(User)

관계형 데이터베이스(RDBMS)에서만 가능한 이유

관계형 데이터베이스는 데이터의 관계를 효율적으로 관리하고 질의(Query)를 처리할 수 있는 **키(Key)**와 **제약 조건(Constraints)**을 제공하기 때문입니다.

  • Primary Key & Foreign Key: RDBMS는 각 레코드의 고유 식별자(Primary Key)를 기반으로 다른 테이블과의 관계(Foreign Key)를 정의합니다.
  • Join 연산: 관계형 데이터베이스는 SQL의 JOIN 연산을 사용해 서로 다른 테이블의 데이터를 결합하여 관계를 표현합니다.

NoSQL에서는 왜 사용할 수 없을까?

NoSQL 데이터베이스(예: MongoDB, DynamoDB)는 스키마리스(Schema-less) 데이터 구조를 가지므로 관계를 정의하거나 사용할 수 없습니다.

  • NoSQL은 **문서(Document)**나 컬렉션(Collection) 기반으로 데이터를 저장하며, 관계보다는 중복 데이터를 허용해 빠른 접근을 제공합니다.
  • 관계가 필요한 경우, NoSQL에서는 데이터를 중첩(Nested) 구조로 저장하거나, 애플리케이션 레벨에서 직접 관계를 관리해야 합니다.

Django에서 관계를 정의할 때의 유의점

  1. 데이터베이스 엔진 선택:
    • Django 기본 데이터베이스인 SQLite에서도 관계를 사용할 수 있지만, 더 큰 프로젝트에서는 MySQL이나 PostgreSQL 같은 완전한 RDBMS를 사용하는 것이 좋습니다.
  2. on_delete 옵션:
    • 외래 키 관계에서 관련된 데이터 삭제 시 동작을 정의해야 합니다. 예:
      author = models.ForeignKey(User, on_delete=models.CASCADE)  # 사용자 삭제 시 관련 게시글 삭제
      
  3. 최적화 필요:
    • 복잡한 관계를 많이 사용하면 데이터베이스 성능에 영향을 미칠 수 있으므로, 쿼리 최적화(select_related, prefetch_related)를 고려해야 합니다.

 

M2M 관계 예시

ManyToManyField(M2M, 다대다 관계)는 하나의 객체가 여러 객체와 연결될 수 있고, 그 반대도 성립하는 관계를 표현할 때 사용합니다.


예시 1: 학생(Student)과 강의(Course)

  • 한 명의 학생이 여러 강의를 수강할 수 있고, 하나의 강의에 여러 학생이 등록될 수 있습니다.
from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Course(models.Model):
    title = models.CharField(max_length=100)
    students = models.ManyToManyField(Student, related_name="courses")

    def __str__(self):
        return self.title

데이터베이스 구조 : Django는 students_courses라는 중간 테이블을 자동으로 생성하여 다대다 관계를 관리합니다.

  • students_courses 테이블은 두 개의 열(student_id와 course_id)로 구성됩니다.

사용 예시:

# 학생 생성
student1 = Student.objects.create(name="Alice")
student2 = Student.objects.create(name="Bob")

# 강의 생성
course1 = Course.objects.create(title="Mathematics")
course2 = Course.objects.create(title="Science")

# 다대다 관계 추가
course1.students.add(student1, student2)  # 수학 강의에 Alice와 Bob 등록
course2.students.add(student1)  # 과학 강의에 Alice만 등록

# 데이터 조회
print(course1.students.all())  # 수학 강의의 학생: [Alice, Bob]
print(student1.courses.all())  # Alice가 등록한 강의: [Mathematics, Science]

예시 2: 태그(Tag) 시스템

  • 블로그 글(Post)과 태그(Tag)가 다대다 관계를 가질 수 있습니다.
  • 하나의 블로그 글이 여러 태그를 가질 수 있고, 하나의 태그가 여러 블로그 글에 사용될 수 있습니다.
class Tag(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    tags = models.ManyToManyField(Tag, related_name="posts")

    def __str__(self):
        return self.title

사용 예시:

# 태그 생성
tag1 = Tag.objects.create(name="Django")
tag2 = Tag.objects.create(name="Python")

# 블로그 글 생성
post1 = Post.objects.create(title="Learning Django", content="Django is great!")
post2 = Post.objects.create(title="Python Tips", content="Learn some Python tips.")

# 다대다 관계 추가
post1.tags.add(tag1, tag2)  # "Learning Django" 글에 Django와 Python 태그 추가
post2.tags.add(tag2)  # "Python Tips" 글에 Python 태그 추가

# 데이터 조회
print(post1.tags.all())  # [Django, Python]
print(tag2.posts.all())  # [Learning Django, Python Tips]

중간 테이블(Custom Through 모델)

기본적으로 Django는 다대다 관계를 위해 자동으로 중간 테이블을 생성합니다. 하지만, 중간 테이블에 추가 정보를 저장해야 할 경우에는 through 옵션을 사용하여 직접 중간 모델을 정의할 수 있습니다.

예시: 사용자(User)와 이벤트(Event) 간 참여 정보 추가

class Event(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class User(models.Model):
    name = models.CharField(max_length=100)
    events = models.ManyToManyField(Event, through='Participation')

    def __str__(self):
        return self.name


class Participation(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    date_joined = models.DateTimeField(auto_now_add=True)  # 참여 날짜 저장
    is_active = models.BooleanField(default=True)  # 현재 활성화된 참여 여부

사용 예시:

# 사용자와 이벤트 생성
user1 = User.objects.create(name="Alice")
event1 = Event.objects.create(name="Hackathon")

# 중간 테이블에 데이터 추가
Participation.objects.create(user=user1, event=event1, is_active=True)

# 데이터 조회
for participation in Participation.objects.all():
    print(f"{participation.user.name} joined {participation.event.name} on {participation.date_joined}")

M2M의 특징

  • 관계가 복잡해질수록 성능 문제를 고려해야 함.
  • prefetch_related를 사용해 M2M 데이터를 효율적으로 가져올 수 있음:
    posts = Post.objects.prefetch_related('tags')
    for post in posts:
        print(post.tags.all())