5.django-模型ORM
- 2022 年 11 月 17 日
- 筆記
- python全棧開發-django學習
Django中內嵌了ORM框架,不需要直接編寫SQL語句進行資料庫的操作,通過定義模型類來完成對資料庫中表的操作
O:Object,也就是類對象的意思
R:Relation,關係資料庫中表的意思
M:Mapping:映射
模型類:映射的是sql語句中的table表
類對象:映射表中的某一行數據
類成員:映射表的欄位
ORM的優點:
-
定義模型類,更加容易維護
-
不必編寫複雜的SQL語句,開發效率高
-
兼容多種資料庫,可以自由切換資料庫
ORM的缺點:
-
ORM不是輕量級工具,需要花費較大的精力學習
-
性能相對原生的SQL差一些
ORM的使用主要以下四步
1. 配置資料庫的連接
2. 在model.py中定義模型類
3. 生成資料庫遷移文件並執行遷移文件
4. 通過模型類對象提供的方法操作資料庫
1.配置資料庫連接
1.1 配置單個mysql資料庫
django中默認配置的資料庫是sqlite,如果想切換為mysql,操作如下
- 安裝pymysql模組
pip install pymysql
from pymysql import install_as_MySQLdb install_as_MySQLdb() # 讓pymysql以MySQLDB的運行模式和Django的ORM對接運行
- 修改setting.py中的配置如下
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 指定mysql引擎 'HOST': '127.0.0.1', # 資料庫主機 'PORT': 3306, # 資料庫埠 'USER': 'root', # 資料庫用戶名 'PASSWORD': '123', # 資料庫用戶密碼 'NAME': 'student' # 資料庫名字 } }
- 在mysql中創建對應的資料庫
ORM 無法主動創建資料庫,我們需要在連接的資料庫中先創建資料庫create database student default charset=utf8mb4; # mysql8.0之前的版本
- 如果想列印orm轉換過程中的sql,需要在settings.py中進行如下配置
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
1.2 配置多個mysql資料庫
jango支援連接多個mysql資料庫
主要在配置文件中添加如下(在上面的基礎上)
DATABASES = { "default": { 'ENGINE': 'dj_db_conn_pool.backends.mysql', 'NAME': 'day05db', # 資料庫名字 'USER': 'root', 'PASSWORD': 'root123', 'HOST': '127.0.0.1', # ip 'PORT': 3306, 'POOL_OPTIONS': { 'POOL_SIZE': 10, # 最小 'MAX_OVERFLOW': 10, # 在最小的基礎上,還可以增加10個,即:最大20個。 'RECYCLE': 24 * 60 * 60, # 連接可以被重複用多久,超過會重新創建,-1表示永久。 'TIMEOUT': 30, # 池中沒有連接最多等待的時間。 } }, "bak": { 'ENGINE': 'dj_db_conn_pool.backends.mysql', 'NAME': 'day05bak', # 資料庫名字 'USER': 'root', 'PASSWORD': 'root123', 'HOST': '127.0.0.1', # ip 'PORT': 3306, 'POOL_OPTIONS': { 'POOL_SIZE': 10, # 最小 'MAX_OVERFLOW': 10, # 在最小的基礎上,還可以增加10個,即:最大20個。 'RECYCLE': 24 * 60 * 60, # 連接可以被重複用多久,超過會重新創建,-1表示永久。 'TIMEOUT': 30, # 池中沒有連接最多等待的時間。 } }, }
2.定義模型類
模型類的定義
模型類一般定義在子項目的models.py
模型類必須直接或者間接的繼承django.db.midels.Model類
2.1 單張表的模型類創建
2.1.1 表名設置
模型類如果未指明表名db_table,Django默認以 小寫app應用名_小寫模型類名 為資料庫表名。
可通過db_table 指明資料庫表名。
2.1.2 關於主鍵
django會為表創建自動增長的主鍵列,每個模型只能有一個主鍵列。
如果使用選項設置某個欄位的約束屬性為主鍵列(primary_key)後,django不會再創建自動增長的主鍵列。
一般情況下我們不需要主動創建,默認創建的主鍵屬性明為id,或者pk
class Student(models.Model): # django會自動在創建數據表的時候生成id主鍵/還設置了一個調用別名 pk id = models.AutoField(primary_key=True, null=False, verbose_name="主鍵") # 設置主鍵
2.1.3 屬性命名規範
-
-
不允許使用兩個連續的下劃線,這是由django的查詢方式決定的,__是關鍵字
-
屬性名 = models.欄位類型(約束選項, verbose_name="注釋")
2.1.4 欄位類型
說明 | |
---|---|
AutoField | 自動增長的IntegerField,通常不用指定,不指定時Django會自動創建屬性名為id的自動增長屬性 |
BooleanField | 布爾欄位,值為True或False |
NullBooleanField | 支援Null、True、False三種值 |
CharField | 字元串,參數max_length表示最大字元個數,對應mysql中的varchar |
TextField | 大文本欄位,一般大段文本(超過4000個字元)才使用。 |
IntegerField | 整數 |
DecimalField | 十進位浮點數, 參數max_digits表示總位數, 參數decimal_places表示小數位數,常用於表示分數和價格 Decimal(max_digits=7, decimal_places=2) ==> 99999.99~ 0.00 |
FloatField | 浮點數 |
DateField | 日期 參數auto_now表示每次保存對象時,自動設置該欄位為當前時間。 參數auto_now_add表示當對象第一次被創建時自動設置當前。 參數auto_now_add和auto_now是相互排斥的,一起使用會發生錯誤。 |
TimeField | 時間,參數同DateField |
DateTimeField | 日期時間,參數同DateField |
FileField | 上傳文件欄位,django在文件欄位中內置了文件上傳保存類, django可以通過模型的欄位存儲自動保存上傳文件, 但是, 在資料庫中本質上保存的僅僅是文件在項目中的存儲路徑!! |
ImageField |
2.1.5 約束選項
如果為True,表示允許為空,默認值是False。相當於python的None | |
---|---|
blank | 如果為True,則該欄位允許為空白,默認值是False。 相當於python的空字元串,「」 |
db_column | 欄位的名稱,如果未指定,則使用屬性的名稱。 |
db_index | 若值為True, 則在表中會為此欄位創建索引,默認值是False。 相當於SQL語句中的key |
default | 默認值,當不填寫數據時,使用該選項的值作為數據的默認值。 |
primary_key | 如果為True,則該欄位會成為模型的主鍵,默認值是False,一般不用設置,系統默認設置。 |
unique | 如果為True,則該欄位在表中必須有唯一值,默認值是False。相當於SQL語句中的unique |
max_length | 欄位的最大長度 |
verbose_name |
2.2 關聯表的模型類創建
關聯模型類的關係有以下三種
-
一對一
-
一對多
-
多對多
2.2.1 一對一
兩張表是一對一的關係,也就是一張表的一條數據只能對應另外一張表的一條數據
OneToOneField()
如:學生和學生詳細資訊
class Student(models.Model): name = models.CharField(max_length=32, verbose_name='學生姓名') class StudentDetail(models.Model): telephone = models.CharField(max_length=16, verbose_name='學生電話') address = models.CharField(max_length=16, verbose_name="學生住址") # 一對一 student = models.OneToOneField(to=Student, verbose_name='學生ID', on_delete=models.CASCADE)
2.2.2 一對多
兩張表是一對多的關係,也就是一張表的一條數據對應另外一張表的多條數據
ForeignKey
如:學生表和班級表,一個班級可以有很多學生
class Cls(models.Model): title = models.CharField(max_length=32, verbose_name='班級名稱') class Student(models.Model): name = models.CharField(max_length=32, verbose_name='學生姓名') # 一對多 cls = models.ForeignKey(to=Cls, verbose_name='班級名稱', on_delete=models.CASCADE)
注意:在一對多中,必須指定on_delete刪除模式,就比如刪除了班級表中的數據,學生表中相關的數據該做何處理,常見的處理如下
- CASCADE, # 級聯刪除,即關聯的表刪除某一項數據,此表關聯的數據都會被刪除
- DO_NOTHING, # 刪除關聯數據,什麼也不做
- SET_NULL, # 刪除關聯數據,與之關聯的值設置為null(前提FK欄位需要設置為可空)
-
SET(值), # 刪除關聯數據, 與之關聯的值設置為指定值
2.2.3 多對多
多對多就是一張表的一行數據可以對應另一張表的多行數據,反之亦然
ManyToManyField
或者自定義第三個類
如學生表和選修課程表,這需要通過第三張表的引入,來關聯二者的關係
方式一(推薦)
class Course(models.Model): title = models.CharField(max_length=32, verbose_name='課程名稱') class Student(models.Model): name = models.CharField(max_length=32, verbose_name='學生姓名')# 多對多 course = models.ManyToManyField(to=Course, verbose_name='課程')
方式二
class Course(models.Model): title = models.CharField(max_length=32, verbose_name='課程名稱') class Student(models.Model): name = models.CharField(max_length=32, verbose_name='學生姓名')class Student_Course(models.Model): student = models.ForeignKey(to=Student, on_delete=models.CASCADE) course = models.ForeignKey(to=Course, on_delete=models.CASCADE)
3.數據遷移
在django中數據遷移分為兩步
- 生成遷移文件
python manage.py makemigrations
- 同步到資料庫
python manage.py migrate
4.操作資料庫
4.1 單張表的資料庫基本操作
4.1.1 添加記錄
- save()方法
通過創建模型類對象,執行對象的save()方法保存到資料庫中role = Role(title='銷售顧問') role.save()
- create()方法
過模型類.objects.create()保存,返回生成的模型類對象obj = Role.objects.create(title='總經理')
4.1.2 修改記錄
- 使用save更新數據【不建議】
student = Student.objects.filter(name='劉德華').first() student.age = 19 student.classmate = "303" student.save()
- update更新(推薦)
# update是全局更新,只要符合更新的條件,則全部更新,因此強烈建議加上條件!!! student = Student.objects.filter(name="趙華",age=22).update(name="劉芙蓉",sex=True)
4.1.3 刪除記錄
-
student = Student.objects.get(id=13) student.delete()
Student.objects.filter(id=14).delete()
4.1.4 基礎查詢
ORM中針對查詢結果的限制,提供了一個查詢集[QuerySet].這個QuerySet,是ORM中針對查詢結果進行保存數據的一個類型,我們可以通過了解這個QuerySet進行使用,達到查詢優化,或者限制查詢結果數量的作用。
- all()
查詢所有對象,返回一個queryset對象,所有對象的集合queryset = Role.objects.all() # <QuerySet [<Role: Role object (1)>, <Role: Role object (2)>]>
- filter()
篩選條件相匹配的對象,返回queryset對象。queryset = Role.objects.filter(title='總經理') # <QuerySet [<Role: Role object (1)>]>
- get()
返回與所給篩選條件相匹配的對象,返回結果有且只有一個, 如果符合篩選條件的對象超過一個或者沒有都會拋出錯誤。try: student = Student.objects.get(name="kunmzhao") print(student) print(student.description) except Student.MultipleObjectsReturned: print("查詢得到多個結果!") except Student.DoesNotExist: print("查詢結果不存在!")
- first()/last()
分別為查詢集的第一條記錄和最後一條記錄,返回一個對象stu01 = Student.objects.first() stu02 = Student.objects.last()
- exclude()
篩選條件不匹配的對象,返回queryset對象。# 查詢張三以外的所有的學生 students = Student.objects.exclude(name="張三")
- order_by()
對查詢結果排序,返回一個queryset對象# order_by("欄位") # 按指定欄位正序顯示,相當於 asc 從小到大 # order_by("-欄位") # 按欄位倒序排列,相當於 desc 從大到小 # order_by("第一排序","第二排序",...) # 查詢所有的男學生按年齡從高到低展示 # students = Student.objects.all().order_by("-age","-id") students = Student.objects.filter(sex=1).order_by("-age", "-id")
- count()
查詢集中對象的個數# 查詢所有男生的個數 count = Student.objects.filter(sex=1).count() print(count)
- exists()
判斷查詢集中是否有數據,如果有則返回True,沒有則返回False# 查詢Student表中是否存在學生 Student.objects.exists()
- values()/values_list()
-
-
values_list()
: 把結果集中的模型對象轉換成列表
# values 把查詢結果中模型對象轉換成字典 student_list = student_list.order_by("-age") ret1 = student_list.values() # 默認把所有欄位全部轉換並返回 ret2 = student_list.values("id","name","age") # 可以通過參數設置要轉換的欄位並返回 ret3 = student_list.values_list() # 默認把所有欄位全部轉換並返回 ret4 = student_list.values_list("id","name","age") # 可以通過參數設置要轉換的欄位並返回
-
- distinct()
從返回結果中剔除重複紀錄。返回queryset。# 查詢所有學生出現過的年齡 print(Student.objects.values("age").distinct())
4.1.5 模糊查詢
基於雙下劃線查詢
- contains
例:查詢姓名包含’華’的學生。tudent.objects.filter(name__contains='華')
- startswith、endswith
查詢姓名以’文’結尾的學生Student.objects.filter(name__endswith='文')
- isnull
查詢個性簽名不為空的學生student_list = Student.objects.filter(description__isnull=True)
- in
查詢編號為1或3或5的學生Student.objects.filter(id__in=[1, 3, 5])
-
-
gte 大於等於 (greater then equal)
-
lt 小於 (less then)
-
lte
比較查詢
查詢編號大於3的學生
Student.objects.filter(id__gt=3)
- 日期查詢
year、month、day、week_day、hour、minute、second:對日期時間類型的屬性進行運算。
查詢2010年被添加到數據中的學生。Student.objects.filter(born_date__year=1980)
例:查詢2016年6月20日後,2017年6月21號之前添加的學生資訊
from django.utils import timezone as datetime Student.objects.filter(created_time__gte=datetime.datetime(2016,6,20),created_time__lt=datetime.datetime(2017,6,21)).all()
4.1.6 進階查詢
4.1.6.1 F查詢
之前的查詢都是對象的屬性與常量值比較,兩個屬性怎麼比較呢? 答:使用F對象,被定義在django.db.models中。
語法如下:
"""F對象:2個欄位的值比較""" # 獲取從添加數據以後被改動過數據的學生 from django.db.models import F # SQL: select * from db_student where created_time=updated_time; student_list = Student.objects.exclude(created_time=F("updated_time")) print(student_list)
6.1.6.2 Q 查詢
多個過濾器逐個調用表示邏輯與關係,同sql語句中where部分的and關鍵字。
例:查詢年齡大於20,並且編號小於30的學生
Student.objects.filter(age__gt=20,id__lt=30)
或
Student.filter(age__gt=20).filter(id__lt=30)
Q(屬性名__運算符=值)
Q(屬性名__運算符=值) | Q(屬性名__運算符=值)
例:查詢年齡小於19或者大於20的學生,使用Q對象如下。
from django.db.models import Q student_list = Student.objects.filter( Q(age__lt=19) | Q(age__gt=20) ).all()
6.1.6.3 聚合查詢
使用aggregate()過濾器調用聚合函數。聚合函數包括:**Avg** 平均,**Count** 數量,**Max** 最大,**Min** 最小,**Sum** 求和,被定義在django.db.models中。
例:查詢學生的平均年齡。
from django.db.models import Sum,Count,Avg,Max,Min Student.objects.aggregate(Avg('age'))
注意:aggregate的返回值是一個字典類型,格式如下
{'屬性名__聚合類小寫':值}
使用count時一般不使用aggregate()過濾器。
例:查詢學生總數。
Student.objects.count() # count函數的返回值是一個數字。
6.1.6.4 分組查詢
QuerySet對象.annotate() # annotate() 進行分組統計,按前面select 的欄位進行 group by # annotate() 返回值依然是 queryset對象,增加了分組統計後的鍵值對 模型對象.objects.values("id").annotate(course=Count('course__sid')).values('id','course') # 查詢指定模型, 按id分組 , 將course下的sid欄位計數,返回結果是 name欄位 和 course計數結果 # SQL原生語句中分組之後可以使用having過濾,在django中並沒有提供having對應的方法,但是可以使用filter對分組結果進行過濾 # 所以filter在annotate之前,表示where,在annotate之後代表having # 同理,values在annotate之前,代表分組的欄位,在annotate之後代表數據查詢結果返回的欄位
6.1.6.5 原生查詢
執行原生SQL語句,也可以直接跳過模型,才通用原生pymysql.
ret = Student.objects.raw("SELECT id,name,age FROM db_student") # student 可以是任意一個模型 # 這樣執行獲取的結果無法通過QuerySet進行操作讀取,只能循環提取 print(ret,type(ret)) for item in ret: print(item,type(item))
4.2 關聯查詢
基於雙下劃線查詢(join查詢)
ret = Student.objects.filter(name="張三").values("age") # (1) 查詢年齡大於22的學生的姓名以及所在名稱班級 # 方式1 : Student作為基表 ret = Student.objects.filter(age__gt=22).values("name","clas__name") # 方式2 :Clas表作為基表 ret = Clas.objects.filter(student_list__age__gt=22).values("student_list__name","name") # (2) 查詢電腦科學與技術2班有哪些學生 ret = Clas.objects.filter(name="電腦科學與技術2班").values("student_list__name") # (3) 查詢張三所報課程的名稱 ret = Student.objects.filter(name="張三").values("courses__title") print(ret) # <QuerySet [{'courses__title': '近代史'}, {'courses__title': '籃球'}]> # (4) 查詢選修了近代史這門課程學生的姓名和年齡 ret = Course.objects.filter(title="近代史").values("students__name","students__age") # (5) 查詢李四的手機號 ret = Student.objects.filter(name='李四').values("stu_detail__tel") # (6) 查詢手機號是110的學生的姓名和所在班級名稱 # 方式1 ret = StudentDetail.objects.filter(tel="110").values("stu__name","stu__clas__name") print(ret) # <QuerySet [{'stu__name': '張三', 'stu__clas__name': '電腦科學與技術2班'}]> # 方式2: ret = Student.objects.filter(stu_detail__tel="110").values("name","clas__name") print(ret) # <QuerySet [{'name': '張三', 'clas__name': '電腦科學與技術2班'}]>
4.3 關聯添加
- 一對多與一對一
stu = Student.objects.create(name="王五", clas_id=9, stu_detail_id=6)
- 多對多
# 添加多對多方式1 c1 = Course.objects.get(title="思修") c2 = Course.objects.get(title="邏輯學") stu.courses.add(c1,c2) # 添加多對多方式2 stu = Student.objects.get(name="張三") stu.courses.add(5,7) # 添加多對多方式3 stu = Student.objects.get(name="李四") stu.courses.add(*[6,7])
4.4 關聯刪除
# 刪除多對多記錄 stu = Student.objects.get(name="李四") stu.courses.remove(7) # 清空多對多記錄:clear方法 stu = Student.objects.get(name="rain") stu.courses.clear() #重置多對多記錄:set方法 stu = Student.objects.get(name="李四") stu.courses.set([5,8])