A. 데이터베이스 상으로는 차이가 없다.
예제 코드는 DRF CRUD 예제 프로젝트를 활용했다.
데이터베이스 설정만 MySQL로 변경했다. 변경한 예는 아래와 같다.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'test',
'USER': 'root',
'PASSWORD': 'password',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}
환경은
장고는 3.2.12
파이썬은 3.8 버전이다.
ForeignKey
그냥 ForeignKey 모델이다.
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "category"
class Beverage(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
price = models.IntegerField()
is_available = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "beverage"
아래 명령어로 확인해보자.
$ python manage.py makemigrtaions
$ python manage.py migrate
각 테스트 때마다 기존 마이그레이션 파일을 지우고 다시 makemigrations로 생성했고,
기존 데이터베이스를 지우고 새로 migrate했다.
생성되는 마이그레이션 파일은 다음과 같다.
# Generated by Django 3.2.12 on 2022-07-05 13:05
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'category',
},
),
migrations.CreateModel(
name='Beverage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('price', models.IntegerField()),
('is_available', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.category')),
],
options={
'db_table': 'beverage',
},
),
]
데이터베이스 beverage 테이블을 살펴보자.
장고가 이름을 알아서 생성해서 인덱스를 추가한 것을 볼 수 있다.
ForeignKey에 unique=True 추가
이번에는 원래 모델과 동일하고 unique=True만 추가했다.
...
category = models.ForeignKey(Category, on_delete=models.CASCADE, unique=True)
...
makemigrations로 만든 마이그레이션 파일은 다음과 같다.
...
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.category', unique=True)),
...
데이터베이스 beverage 테이블을 살펴보자.
Non_unique가 0으로 바뀌었고, Key_name이 category_id로 깔끔하게 변경된 것을 확인할 수 있다.
OneToOneField
이번에는 OneToOneField로 해보자.
...
category = models.OneToOneField(Category, on_delete=models.CASCADE)
...
makemigrations로 만든 마이그레이션 파일은 다음과 같다.
...
('category', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='app.category')),
...
마이그레이션 파일에도 OneToOneField라고 적혀 있다.
데이터베이스 beverage 테이블을 살펴보자. 똑같다. ForeignKey에 unique=True를 한 결과와 같다.
ForeignKey나 OneToOneField나 따로 인덱스를 추가하는 제약조건을 추가하지 않아도 자동으로 추가되는 것을 확인할 수 있다.
같은 이름으로 제약조건을 추가하면 어떻게 될까?
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "category"
class Beverage(models.Model):
name = models.CharField(max_length=200)
category = models.OneToOneField(Category, on_delete=models.CASCADE)
price = models.IntegerField()
is_available = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "beverage"
constraints = [
models.UniqueConstraint(fields=['category'], name='category_id'),
]
makemigrations로 마이그레이션 파일을 생성하면 다음과 같다.
# Generated by Django 3.2.12 on 2022-07-05 13:19
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'category',
},
),
migrations.CreateModel(
name='Beverage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('price', models.IntegerField()),
('is_available', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('category', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='app.category')),
],
options={
'db_table': 'beverage',
},
),
migrations.AddConstraint(
model_name='beverage',
constraint=models.UniqueConstraint(fields=('category',), name='category_id'),
),
]
맨 마지막 줄에 Unique제약조건이 생성된 것을 확인할 수 있다.
migrate을 하면 에러가 발생한다.
에러는 중복 에러! category_id란 키 이름이 이미 있다는 거다.
....
File "/Users/taptorestart/workspace/playground/drf_crud/venv/lib/python3.8/site-packages/MySQLdb/cursors.py", line 319, in _query
db.query(q)
File "/Users/taptorestart/workspace/playground/drf_crud/venv/lib/python3.8/site-packages/MySQLdb/connections.py", line 254, in query
_mysql.connection.query(self, query)
django.db.utils.OperationalError: (1061, "Duplicate key name 'category_id'")
왜냐하면 자동으로 생성되는 키 이름이 바로 category_id니까.
에러는 났지만 그 전까지는 작업이 되어서 테이블을 살펴보면 다음과 같다.
이름을 다르게 하면 어떻게 될까?
unique_index_category_id로 이름을 바꿔 보았다.
...
class Meta:
db_table = "beverage"
constraints = [
models.UniqueConstraint(fields=['category'], name='unique_index_category_id'),
]
마이그레이션 파일은 다음과 같다.
# Generated by Django 3.2.12 on 2022-07-05 13:23
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'category',
},
),
migrations.CreateModel(
name='Beverage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('price', models.IntegerField()),
('is_available', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('category', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='app.category')),
],
options={
'db_table': 'beverage',
},
),
migrations.AddConstraint(
model_name='beverage',
constraint=models.UniqueConstraint(fields=('category',), name='unique_index_category_id'),
),
]
마이그레이트를 실행하면 에러 없이 잘 된다.
같은 category_id 칼럼으로 만든 두개의 인덱스가 생겼다. 같은 인덱스 두개는 필요 없으니 사실 굳이 제약조건을 추가할 이유는 없다.
내가 원하는 인덱스unique_index_category_id만 남기고 기본 인덱스category_id는 없앨 수 없나?
unique=False, db_index=False, db_constraint=False
따로 따로 적용해봤을 때 기본 인덱스category_id와 unique_index_category_id 둘 다 나왔다.
3가지 모두 적어도 둘 다 나왔다.
기본 인덱스를 없애고 내가 원하는 이름으로 추가할 방법은 못 찾았다. 없는 거 같다.
공식 문서를 보면 아래처럼 설명이 나온다.
"A one-to-one relationship. Conceptually, this is similar to a ForeignKey with unique=True, but the “reverse” side of the relation will directly return a single object."
OneToOneField는 ForeignKey with unique=True 한 것과 비슷하다는 거다. 그래서 OneToOneField에 unique=False를 하고 마이그레이션 파일 살펴보면 아무 변화가 없다.