티스토리 뷰

728x90
반응형

Django ORM(Object-Relational Mapping)은 개발자가 SQL을 직접 작성하지 않고도 데이터베이스를 조작할 수 있도록 도와주는 강력한 도구입니다. 그러나 ORM을 효율적으로 사용하려면 몇 가지 중요한 개념을 깊이 있게 이해할 필요가 있습니다. 이번 글에서는 Django ORM의 핵심 개념과 특이성을 살펴보고, 실무에서 자주 사용되는 패턴과 최적화 기법에 대해 다뤄보겠습니다.


1. Django ORM의 기본 원리

Django ORM은 모델 클래스(Model)를 사용하여 데이터베이스 테이블을 객체처럼 다룰 수 있게 해줍니다.

1.1 ORM의 핵심 개념

  • 모델(Model): 데이터베이스의 테이블을 나타내는 클래스
  • 쿼리셋(QuerySet): 데이터베이스에서 가져온 객체 목록을 나타내는 객체
  • 매니저(Manager): 데이터 조회를 수행하는 인터페이스 (objects)
  • 마이그레이션(Migration): 모델을 기반으로 데이터베이스 스키마를 자동 생성 및 변경하는 기능

1.2 기본적인 ORM 활용 예시

from django.db import models

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

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    published_year = models.IntegerField()

이 모델을 기반으로 Django ORM을 사용하면 다음과 같이 데이터 조회가 가능합니다:

# 특정 저자가 쓴 책 조회
books = Book.objects.filter(author__name="George Orwell")

이제 ORM의 좀 더 고급 개념으로 넘어가 보겠습니다.


2. ORM의 특이성과 고급 기능

2.1 더블 언더스코어(__)를 활용한 필터링

Django ORM에서는 __(더블 언더스코어)를 사용하여 관계형 데이터베이스의 JOIN과 WHERE 절을 쉽게 다룰 수 있습니다.

# ForeignKey 관계를 이용한 필터링
books = Book.objects.filter(author__name="J.K. Rowling")

이는 SQL로 변환하면 다음과 같습니다:

SELECT * FROM book
INNER JOIN author ON book.author_id = author.id
WHERE author.name = 'J.K. Rowling';

다양한 필터링 연산자

  • __exact : 정확한 값 매칭 (title__exact="Harry Potter")
  • __icontains : 대소문자 구분 없이 포함 (title__icontains="harry")
  • __gte, __lte : 크거나 같음, 작거나 같음 (published_year__gte=2000)
  • __in : 특정 값 목록에 포함 (author__name__in=["J.K. Rowling", "George Orwell"])

2.2 역참조 (_set) 활용하기

ForeignKey가 있는 경우, Django는 자동으로 _set을 생성하여 역참조할 수 있도록 합니다.

author = Author.objects.get(name="George Orwell")
books = author.book_set.all()

SQL로 변환하면 다음과 같습니다:

SELECT * FROM book WHERE author_id = (SELECT id FROM author WHERE name = 'George Orwell');

related_name 사용하기

_set을 보다 직관적인 이름으로 변경하고 싶다면 related_name을 사용할 수 있습니다.

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")

이제 book_set 대신 books로 접근할 수 있습니다:

author = Author.objects.get(name="George Orwell")
books = author.books.all()

2.3 Select Related & Prefetch Related

ORM을 사용할 때 쿼리 최적화가 중요합니다. Django ORM에서 select_related와 prefetch_related는 N+1 문제를 해결하는 대표적인 방법입니다.

1) select_related (JOIN을 사용한 최적화)

books = Book.objects.select_related("author").all()

SQL 변환:

SELECT book.*, author.* FROM book
INNER JOIN author ON book.author_id = author.id;
  • 장점: ForeignKey를 가진 객체를 한 번의 JOIN으로 가져옴 → 쿼리 실행 횟수 감소
  • 사용 예시: ForeignKey가 자주 사용되는 경우

2) prefetch_related (서브쿼리를 사용한 최적화)

authors = Author.objects.prefetch_related("books")

SQL 변환:

SELECT * FROM author;
SELECT * FROM book WHERE author_id IN (저자 ID 목록);
  • 장점: 두 개의 쿼리를 실행하지만 Python 레벨에서 관계를 최적화하여 관리
  • 사용 예시: ManyToManyField 또는 reverse ForeignKey 관계에서 사용

2.4 Aggregation (집계 함수)

ORM을 사용하여 데이터베이스 집계 함수를 실행할 수도 있습니다.

from django.db.models import Count, Avg

# 저자가 쓴 책 개수 구하기
author_stats = Author.objects.annotate(book_count=Count('books'))

# 평균 출판 연도 구하기
avg_year = Book.objects.aggregate(Avg('published_year'))

SQL 변환:

SELECT author.id, author.name, COUNT(book.id) AS book_count
FROM author
LEFT JOIN book ON author.id = book.author_id
GROUP BY author.id;

SELECT AVG(published_year) FROM book;

2.5 Raw SQL을 활용하는 방법

Django ORM이 강력하지만, 때때로 직접 SQL을 실행해야 할 때도 있습니다.

from django.db import connection

with connection.cursor() as cursor:
    cursor.execute("SELECT * FROM book WHERE published_year > %s", [2000])
    books = cursor.fetchall()

Django의 raw() 메서드를 사용하여 모델을 기반으로 직접 SQL을 실행할 수도 있습니다.

books = Book.objects.raw("SELECT * FROM book WHERE published_year > %s", [2000])

3. 정리하면서...

Django ORM은 강력한 기능을 제공하지만, 효율적으로 사용하지 않으면 성능 문제가 발생할 수 있습니다. 따라서 쿼리 최적화, 역참조 활용, JOIN 및 prefetch 최적화, 집계 함수 활용 등의 개념을 깊이 이해하는 것이 중요합니다. Django ORM을 보다 효과적으로 활용하여 더 빠르고 안정적인 애플리케이션을 개발해 보세요!

728x90
반응형