[Database] 스타벅스 음료 페이지를 이용한 DB 모델링 연습 - 2

ybear90·2020년 2월 21일
0

Database

목록 보기
2/2

실제 DB모델에 데이터 넣기

만든 DB모델을 바탕으로 django orm을 이용해서 데이터를 넣어보고 실제 mysql 상에서 확인하는 작업까지 진행해 보려고 한다.

크게,

  • Category 생성(음료, 푸드)
  • DetailCategory 생성(콜드 브루, 브랜드 커피, 베이커리)
  • Size 생성(Short, Tall, Grande, Venti)
  • Allergic 생성(우유, 대두, 밀)

Category 생성

from django.db import models

# Create your models here.

class Drink(models.Model):
    category            = models.ForeignKey('Category', models.SET_NULL, blank=True, null=True)
    datail_category     = models.ForeignKey('DetailCategory', models.SET_NULL, blank=True, null=True)
    name                = models.CharField(max_length=45)
    name_eng            = models.CharField(max_length=45)
    introduction        = models.CharField(max_length=500)
    size                = models.ManyToManyField('Size')
    nutrition           = models.ForeignKey('Nutrition', models.SET_NULL, blank=True, null=True)
    allergic            = models.ManyToManyField('Allergic', through='DrinkAllergic')
    img_link            = models.URLField(max_length=500)
    desc                = models.CharField(max_length=500)

    class Meta:
        db_table = 'drink'

class Category(models.Model):
    name = models.CharField(max_length=50)

    class Meta:
        db_table = 'category'

class DetailCategory(models.Model):
    name        = models.CharField(max_length=50)
    category    = models.ForeignKey('Category', models.SET_NULL, blank=True, null=True)

    class Meta:
        db_table = 'detail_category'

class Size(models.Model):
    name = models.CharField(max_length=50)

    class Meta:
        db_table = 'size'

class Nutrition(models.Model):
    kcal_per_one    = models.DecimalField(max_digits=5, decimal_places=1)
    sodium          = models.DecimalField(max_digits=5, decimal_places=1)
    saturated_fat   = models.DecimalField(max_digits=5, decimal_places=1)
    sugars          = models.DecimalField(max_digits=5, decimal_places=1)
    protein         = models.DecimalField(max_digits=5, decimal_places=1)
    caffein         = models.DecimalField(max_digits=5, decimal_places=1)

    class Meta:
        db_table = 'nutrition'

class DrinkAllergic(models.Model):
    drink    = models.ForeignKey('Drink', on_delete=models.SET_NULL, null=True)
    allergic = models.ForeignKey('Allergic', on_delete=models.SET_NULL, null=True)

    class Meta:
        db_table = 'drink_allergic'


class Allergic(models.Model):
    desc = models.CharField(max_length=50)

    class Meta:
        db_table = 'allergic'

여기서 하나 짚고 넘어가고 싶은 부분은 ForeignKey객체에 첫번째 parameter에 class name을 ''안에 넣어두었는데 그 이유는 class 이름을 선언적 의미로 명시함으로서 모델 class가 만들어지는 순서에 상관이 없이 호출할 수 있다. 만약 이렇게 처리 하지 않는다면 해당 ForeignKey 호출하는 클래스 상위에 class가 존재하고 있어야 한다.

또한 ForeignKey를 호출할 때 _id를 붙이지 않는 이유는 django상에서 migration을 진행할 때 자동으로 붙여주기 때문에 _id가 두번 삽입 되는 번거로움을 피하기 위해서이다.

해당 모델을 바탕으로 아래와 같이 ORM 테스트를 진행해 보았다.

>>> from drink.models import *
>>> Category.objects.create(name='음료')
<Category: Category object (1)>
mysql> select * from detail_category;
Empty set (0.00 sec)

mysql> select * from category
    -> ;
+----+--------+
| id | name   |
+----+--------+
|  1 | 음료    |
+----+--------+
1 row in set (0.00 sec)

django의 db환경을 mysql로 설정해 주었고 해당 결과는 mysql 상에서도 잘 드러나는 것을 확인했다.

>>> DetailCategory.objects.create(name='콜드브루', category=Category.objects.get(id=1))
<DetailCategory: DetailCategory object (1)>
>>> DetailCategory.objects.values()
<QuerySet [{'id': 1, 'name': '콜드브루', 'category_id': 1}]>
>>> DetailCategory.objects.create(name='브루드 커피', category=Category.objects.get(id=1))
<DetailCategory: DetailCategory object (2)>
>>> DetailCategory.objects.values()
<QuerySet [{'id': 1, 'name': '콜드브루', 'category_id': 1}, {'id': 2, 'name': '브루드 커피', 'category_id': 1}]>

실제로 DB모델링을 할 때 구성했던 대로 외래키를 통한 참조관계가 잘 이뤄졌는지(1(Category):n(DetailCategory)) 확인하고자 위와 같은 orm queryset 메소드를 실행해봤다.

결과를 잘 갖고 오는 것을 확인했으며 category 객체 파라미터에 실제 넣어줄 외래키에 해당하는 Category 데이터 객체를 더해 넣어 주었다. category_id를 이용하여 정수를 넣어도 상관 없으며 filter()를 통해서 나중에 데이터를 가져올 때도 ForeignKey 객체나 실제 FK로 쓰이는 데이터를 통해서 filter를 진행할 수 있다.

mysql> select * from detail_category;
+----+------------------+-------------+
| id | name             | category_id |
+----+------------------+-------------+
|  1 | 콜드브루           |           1 |
|  2 | 브루드 커피      	|           1 |
+----+------------------+-------------+
2 rows in set (0.00 sec)

mysql에서도 해당 결과는 잘 출력이 되었다.

Size, Allergic 생성

같은 방법으로 Size, Allergic 테이블에도 데이터를 넣어 주었다.

>>> Size.objects.create(name='Short')
<Size: Size object (1)>
>>> Size.objects.create(name='Tall')
<Size: Size object (2)>
>>> Size.objects.create(name='Grande')
<Size: Size object (3)>
>>> Size.objects.create(name='Venti')
<Size: Size object (4)>
>>> Size.objects.values()
<QuerySet [{'id': 1, 'name': 'Short'}, {'id': 2, 'name': 'Tall'}, {'id': 3, 'name': 'Grande'}, {'id': 4, 'name': 'Venti'}]>
mysql> select * from size;
+----+--------+
| id | name   |
+----+--------+
|  1 | Short  |
|  2 | Tall   |
|  3 | Grande |
|  4 | Venti  |
+----+--------+
4 rows in set (0.00 sec)
>>> Allergic.objects.create(desc='우유')
<Allergic: Allergic object (1)>
>>> Allergic.objects.create(desc='대두')
<Allergic: Allergic object (2)>
>>> Allergic.objects.create(desc='밀')
<Allergic: Allergic object (3)>
>>> Allergic.objects.values()
<QuerySet [{'id': 1, 'desc': '우유'}, {'id': 2, 'desc': '대두'}, {'id': 3, 'desc': '밀'}]>
mysql> select * from allergic
    -> ;
+----+--------+
| id | desc   |
+----+--------+
|  1 | 우유    |
|  2 | 대두    |
|  3 ||
+----+--------+
3 rows in set (0.00 sec)

Nutrition 생성

>>> Nutrition.objects.create(kcal_per_one=5, sodium=11, saturated_fat=0, sugars=0, protein=0, caffein=150)
<Nutrition: Nutrition object (1)>
>>> Nutrition.objects.create(kcal_per_one=5, sodium=10, saturated_fat=0, sugars=0, protein=0, caffein=140)
<Nutrition: Nutrition object (2)>
>>> Nutrition.objects.values()
mysql> select * from nutrition
    -> ;
+----+--------------+--------+---------------+--------+---------+---------+
| id | kcal_per_one | sodium | saturated_fat | sugars | protein | caffein |
+----+--------------+--------+---------------+--------+---------+---------+
|  1 |          5.0 |   11.0 |           0.0 |    0.0 |     0.0 |   150.0 |
|  2 |          5.0 |   10.0 |           0.0 |    0.0 |     0.0 |   140.0 |
+----+--------------+--------+---------------+--------+---------+---------+
2 rows in set (0.00 sec)

여러개의 데이터에 한꺼번에 특정 컬럼 값을 넣는 식으로 영양정보를 여러개의 drink 데이터에 넣었다(test)

for i in Drink.objects.all():
    i.nutrition_id = 1
    i.save()

Drink(음료) 정보 입력

>>> Drink.objects.create(name='콜드 브루', name_eng='cold brew', introduction='바리스타의 정성' img_link='coldbrew_link', desc='차가운 전용 원두')

위와 같은 orm을 사용하여 데이터를 넣었는데 미처 category, detail_category에 데이터를 넣지 못해 아래와 같이 중간 삽입 과정도 거쳤다.

>>> models = Drink.objects.filter(id=1)
>>> models.category_id = 1
>>> models.save()

위와 같은 방식은 단일 데이터를 수정할 때 사용하는 방식이고 아래와 같이 update()를 활용한 방식은 여러개의 데이터를 한꺼번에 수정할 때 사용된다.

>>> Drink.objects.filter(id=1).update(category_id=1, detail_category_id=1)

최종적으로 아래와 같이 DB에 데이터를 넣어두었다.

mysql> select * from drink;
+----+------------------+-------------+------------------------+-----------------+---------------------------+-------------+--------------------+--------------+
| id | name             | name_eng    | introduction           | img_link        | desc                      | category_id | datail_category_id | nutrition_id |
+----+------------------+-------------+------------------------+-----------------+---------------------------+-------------+--------------------+--------------+
|  1 | 콜드 브루        | cold brew   | 바리스타의 정성        | coldbrew_link   | 차가운 전용 원두          |           1 |                  1 |            1 |
|  2 | 아이스 커피      | iced coffee | 차가운 커피            | ice_coffee_link | 케냐 하우스블랜드         |           1 |                  2 |            1 |
+----+------------------+-------------+------------------------+-----------------+---------------------------+-------------+--------------------+--------------+
2 rows in set (0.00 sec)

ManyToMany(m:n) 관계를 위한 중간테이블(drink_allergic) 데이터 삽입

위와 같이 DB모델링을 정의한 상태에서 Drink 모델 class에 ManyToManyField 객체인 allergic를 이용하여 drink_allergic테이블에 아래와 같이 데이터를 넣어주었다.

>>> drink1.allergic.add(Allergic.objects.get(id=1), Allergic.objects.get(id=2), Allergic.objects.get(id=3))
mysql> select * from drink_allergic
    -> ;
+----+-------------+----------+
| id | allergic_id | drink_id |
+----+-------------+----------+
|  1 |           1 |        1 |
|  2 |           2 |        1 |
|  3 |           3 |        1 |
+----+-------------+----------+

실제로 중간테이블에 위와 같이 데이터가 들어가게 되어 drink와 allergic 테이블에 참조를 더 효율적으로 할 수 있게 하였다.

같은 방식으로 Size 테이블과의 ManyToMany관계에 데이터를 추가적으로 더하고 아래와 같이 정리하였다.

>>> drink1.size.add(Size.objects.get(id=1), Size.objects.get(id=2), Size.objects.get(id=3))
mysql> select * from drink_size;
+----+----------+---------+
| id | drink_id | size_id |
+----+----------+---------+
|  1 |        1 |       1 |
|  2 |        1 |       2 |
|  3 |        1 |       3 |
|  4 |        2 |       1 |
|  5 |        2 |       2 |
|  6 |        2 |       3 |
+----+----------+---------+
6 rows in set (0.00 sec)

python shell상에서는 따로 ManyToMany를 위한 model class를 따로 만들지 않았기 때문에 mysql상에서 확인을 하였다.

위 사실로 알수 있는 것은 다음과 같다.

ManyToMany 클래스는 class 이름through를 받게되는데 through에 넣어주는 중간모델클래스(중간DB테이블)를 따로 정의해주지 않아도 ManyToMany로 연결해 주었다면 자동적으로 생성해 준다는 점이다.

정리

지금까지 해서 실제로 DB모델링(ERD)를 만들어 보고 django framework상에서 모델을 구현하고 DB와의 연동을 확인하는 작업까지 진행해 보았다. 같은 방식으로 데이터를 더 추가해보고 추가한 데이터들을 토대로 select_related()prefetch_related() 그리고 field lookup까지 해보고 DB모델링 및 사용하는 내용을 정리해 보고자 한다.

profile
wanna be good developer

0개의 댓글