ORM 개요
MVC 또는 MTV 프레임워크에서 ORM(Object-Relational Mapping)은 핵심 구성 요소로, 데이터 모델과 데이터베이스 간의 결합도를 낮추어 특정 데이터베이스에 의존하지 않고 모델을 설계할 수 있게 합니다. 간단한 설정만으로도 데이터베이스를 쉽게 교체할 수 있어 개발 효율성을 크게 향상시킵니다.
# SQL 테이블 생성
CREATE TABLE staff(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
gender BIT DEFAULT 1,
birth_date DATE,
dept VARCHAR(20),
salary DECIMAL(8,2) UNSIGNED
);
# SQL 레코드 조작
INSERT INTO staff (name,gender,birth_date,salary,dept)
VALUES ("john",1,"1990-05-15",7500,"청소팀");
SELECT * FROM staff WHERE age=25;
UPDATE staff SET birth_date="1992-08-20" WHERE id=1;
DELETE FROM staff WHERE name="john";
# Django ORM 방식
class Staff(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
gender = models.BooleanField()
birth_date = models.DateField()
dept = models.CharField(max_length=32)
salary = models.DecimalField(max_digits=8, decimal_places=2)
# ORM 객체 조작
staff = Staff(name="john", gender=True, birth_date="1990-05-15", dept="청소팀")
staff.save()
Staff.objects.filter(age=25)
Staff.objects.filter(id=1).update(birth_date="1992-08-20")
Staff.objects.filter(name="john").delete()
단일 테이블 조작
모델 정의
from django.db import models
class Publication(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=32)
status = models.BooleanField()
publish_date = models.DateField()
cost = models.DecimalField(max_digits=8, decimal_places=2)
publisher = models.CharField(max_length=32)
필드 타입 및 파라미터
- CharField: 문자열 필드, max_length 필수
- IntegerField: 정수형 필드
- FloatField: 부동소수점, max_digits와 decimal_places 지정
- AutoField: 자동 증가 정수형 기본키
- BooleanField: 불리언 값
- TextField: 대용량 텍스트
- EmailField: 이메일 형식 검증 문자열
- DateField: 날짜 필드, auto_now와 auto_now_add 옵션
- DateTimeField: 날짜시간 필드
- TimeField: 시간 필드
- ImageField/FileField: 파일 업로드 필드, upload_to 필수
- URLField: URL 형식 검증
- NullBooleanField: NULL 허용 불리언
- SlugField: URL 친화적 문자열
- IPAddressField: IP 주소 형식
- CommaSeparatedIntegerField: 쉼표 구분 정수
- SmallIntegerField/PositiveIntegerField: 다양한 정수 범위
- BigIntegerField: 장정수형
- UUIDField: UUID 형식
모델 필드 옵션
class UserProfile(models.Model):
GENDER_CHOICES = ((1, '남성'), (2, '여성'), (0, '미지정'))
user_id = models.AutoField(primary_key=True)
username = models.CharField(max_length=32, unique=True)
gender = models.IntegerField(default=1, choices=GENDER_CHOICES)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "user_profiles"
unique_together = (("username", "gender"),)
indexes = [("created_at", "updated_at")]
verbose_name = "사용자 프로필"
verbose_name_plural = "사용자 프로필들"
쿼리셋 메서드
| 메서드 | 기능 | 반환값 | 예제 |
|---|---|---|---|
| all() | 전체 조회 | QuerySet | Publication.objects.all() |
| filter(**kwargs) | 조건 필터링 | QuerySet | Publication.objects.filter(cost=100) |
| get(**kwargs) | 단일 객체 반환 | Model 인스턴스 | Publication.objects.get(title="Django 가이드") |
| exclude(**kwargs) | 조건 제외 필터 | QuerySet | Publication.objects.exclude(status=False) |
| order_by(*field) | 정렬 | QuerySet | Publication.objects.order_by("-id", "cost") |
| count() | 개수 반환 | int | Publication.objects.filter(status=True).count() |
| first()/last() | 첫/마지막 레코드 | Model 인스턴스 | Publication.objects.first() |
| exists() | 데이터 존재 여부 | bool | Publication.objects.exists() |
| values(*field) | 딕셔너리 형태 반환 | QuerySet | Publication.objects.values("title", "cost") |
| values_list(*field) | 튜플 형태 반환 | QuerySet | Publication.objects.values_list("cost", flat=True) |
| distinct() | 중복 제거 | QuerySet | Publication.objects.values("publisher").distinct() |
다중 데이터베이스 운영
데이터베이스 설정
DATABASES = {
"primary": {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'main_db',
'USER': 'root',
'PASSWORD': 'password123',
'HOST': '192.168.1.10',
'PORT': 3306,
},
"replica": {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'backup_db',
'USER': 'root',
'PASSWORD': 'password123',
'HOST': '192.168.1.11',
'PORT': 3306,
}
}
라우터 클래스 구현
class DatabaseRouter:
def db_for_read(self, model, **hints):
if model._meta.app_label == 'analytics':
return 'replica'
return 'primary'
def db_for_write(self, model, **hints):
return 'primary'
def allow_migrate(self, db, app_label, model_name=None, **hints):
if db == 'replica':
return False
return True
설정 등록
DATABASE_ROUTERS = ['myproject.routers.DatabaseRouter']
수동 데이터베이스 선택
# 읽기 작업
data = MyModel.objects.using('replica').all()
# 쓰기 작업
obj = MyModel(title="새 항목")
obj.save(using='primary')
마이그레이션 명령
python manage.py migrate --database=primary
python manage.py migrate --database=replica
앱별 데이터베이스 분리 예제
# 라우터 확장
class AppDatabaseRouter:
route_app_labels = {'auth', 'contenttypes'}
def db_for_read(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'auth_db'
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
if app_label in self.route_app_labels:
return db == 'auth_db'
elif db == 'auth_db':
return False
return None
주의사항
- 다중 데이터베이스 간 외래키 관계는 지원되지 않음
- 관련 테이블은 동일한 데이터베이스에 배치 권장
- 트랜잭션 처리 시 데이터베이스 일관성 고려 필요