使用Python
的
Django
模型的話,一般都會用它自帶的
ORM
(
Object-relational mapping
)模型。這個
ORM
模型的設(shè)計比較簡單,學(xué)起來不會特別花時間。不過,
Django
的
ORM
模型有自己的一套語法,有時候會覺得別扭。這里聊一下我自己的體會,希望對大家
學(xué)習(xí)django有所幫助
。
模型設(shè)計
這一部分算處理得比較好的部分。Django
的數(shù)據(jù)模型的建立過程很簡單,就是繼承
django.db.models
中的
Model
類,然后給它增加屬性。每一個屬性可以對應(yīng)關(guān)系數(shù)據(jù)庫中的一個字段。比如在一個叫
myapp
的
Django App
下,創(chuàng)建
models.py
文件:
from django.db
import models
class Person(
models.Model):
name = models.CharField(
max_length=10)
通過manage.py
的
makemigrations
和
migrate
命令,就可以執(zhí)行數(shù)據(jù)庫的遷移。上面的
name
屬性,就對應(yīng)了生成的
myapp_person
表中名為
"name"
的一列。這里的
max_length=10
對應(yīng)了限制條件:
VARCHAR(10)
?。ㄔ?/span>MySQL V4
中,代表了
10
個字節(jié);在
MySQL V5
中,代表了
10
個字符。)
除了上面的字符類型,其他常見的字段類型,在Django
都有對應(yīng)的
*Field
來表達,比如
TextField
、
DateField
、
DateTimeField
、
IntegerField
、
DecimalField
。此外,還有一些常見的限制條件,除了上面的
max_length
,還有
default
、
unique
、
null
、
primary_key
等等。數(shù)字類型的限制條件有
max
、
min
、
max_digits
、
decimal_places
。這些限制條件都通過參數(shù)的形式傳給屬性。有一些限制條件是
Django
提供的,并沒有數(shù)據(jù)庫層面的對應(yīng)物,比如
blank
。
(
當(dāng)
blank
參數(shù)為真時,對應(yīng)字段可以為留為空白。
)
在基本的模型設(shè)計上,Django ORM
沒有留什么坑。
關(guān)系
Django
中的一對一、多對一、多對多關(guān)系可以通過下面方式表達:
from django.db
import models
class Company(
models.Model):
name = models.CharField(
max_length=10)
class Group(
models.Model):
name = models.CharField(
max_length=10)
class Person(
models.Model):
name = models.CharField(
max_length=10)
class Customer(
models.Model):
name = models.CharField(
max_length=10)
person = models.OneToOneField(Person)
company = models.ForeignKey(Company,
on_delete=
models.CASCADE)
groups = models.ManyToManyField(Group)
Customer
的定義中,用到一對一、多對一、多對多關(guān)系。它們分別通過
OneToOneField
、
ForeignKey
和
ManyToManyField
來實現(xiàn)。
需要注意的是,在Django ORM
中,只能通過
ForeignKey
來定義多對一關(guān)系,不能顯示地定義一對多關(guān)系。但你可以使用模型對象的
*_set
語法來反向調(diào)用多對一關(guān)系。比如說:
company.customer_set #company
是一個
Company
的實例
就可以根據(jù)一對多關(guān)系,調(diào)到該公司下的所有客戶。此外,多對多關(guān)系也可以用類似的方式反向調(diào)用,比如:
group.customer_set
此外,你還可以在模型中加入related_name
參數(shù),從而在反省調(diào)用時,改用
"*_set"
之外的其他名稱,比如:
class
Customer(models.Model):
person = models.OneToOneField(Person)
address = models.CharField(max_length=100)
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="customers")
如果兩個模型之間有多個關(guān)系時,related_name
可以防止
*_set
重名。
總的來說,上面的解決方案可以實現(xiàn)功能,并不影響使用。但我總是覺得這個解決方案有些丑陋。由于不能顯式地表達兩個模型之間的關(guān)系,模型之間的關(guān)系看起來不夠明了。特別是讀代碼時,第一個類定義完全沒法提示一對多的關(guān)系。我必須要看到了第二個類定義,才能搞明白兩個模型之間的關(guān)系。真希望有一種顯式說明關(guān)系的辦法,降低讀代碼時的認知負擔(dān)。
查詢
Django ORM
可以通過一些方法來實現(xiàn)。其中的很多方法返回的是
Django
自定義的
QuerySet
類的迭代器。
Python
看到迭代器時會懶惰求值,所以這些方法返回時并不會真正進行數(shù)據(jù)庫操作。這樣,多個方法串聯(lián)操作時,就避免了重復(fù)操作數(shù)據(jù)庫。返回
QuerySet
的常見方法包括:
all()
filter()
exclude()
annotate()
order_by()
reverse()
distinct()
...
對于依賴具體數(shù)據(jù)的操作,QuerySet
會求值。比如遍歷
QuerySet
時,就會先執(zhí)行數(shù)據(jù)庫操作。用
len()
獲得
QuerySet
長度時,也會造成
QuerySet
估值。此外
QuerySet
一些方法,比
get()
、
count()
、
earlist()
、
exists()
等,都會對
QuerySet
進行求值。因此,在寫程序時,要注意
QuerySet
求值的時間點,避免重復(fù)的數(shù)據(jù)庫操作。
SQL
的
WHERE
條件可以通過參數(shù)的形式來傳給方法。這些參數(shù)一般是
"[
字段
]__[
運算符
]"
的命名方式,比如:
Customer.objects.filter(name__contains="abc")
除了contains
,還有
in
、
gt
、
lt
、
startswith
、
date
、
range
等等操作符,能實現(xiàn)的
WHERE
條件確實夠全的了。
不過,這又是一個有點別扭的地方,即通過命名方式來控制查詢行為。我看過有的ORM
是用
lambda
的形式來表達
WHERE
條件,還有的會做一個類似于
contains()
的方法,都要比
Django ORM
的方式好看。如果是跨表查詢,
Django
的方式就更丑了:
Customer.objects.filter(company__name__contains="xxx")
無限的雙下劃線啊……
聚合
Django
實現(xiàn)聚合的方式簡直是噩夢。貌似
ORM
對表達
GROUP BY
很無力,源代碼里的注釋就認輸了:
聚合的aggregate()
和
annotate()
方法可以實現(xiàn)基本的功能,但稍微復(fù)雜一點,代碼就變得魔幻了:
看到一大串values()
、
annotate()
變來變?nèi)?,有沒有覺得頭暈?我覺得這種情況下,可以直接上原始的
SQL
查詢語句了,沒必要再自己折騰自己。
F表達式和Q表達式
F
表達式指代了一列,對于
update
操作時引用列的值有用。
Q
表達式代表了
WHERE
的一個條件,可以用于多個
WHERE
條件的連接。這些都是
Django ORM
用來彌補缺陷的。就拿
Q
表達式來說。查詢方法中跟多個參數(shù)的話,相當(dāng)于多個
WHERE
條件。這些條件會默認為
AND
關(guān)系。為了表達
OR
和
NOT
關(guān)系,
Django ORM
就造了個
Q
表達式,比如:
filter(
Q(
name__contains="abc")|Q(
name__startswith("xxx")))
為了彌補缺陷,Django ORM
又增加了一種語法風(fēng)格。于是,學(xué)習(xí)路上又多了一個坑
……
總結(jié)
總的來說,Django ORM
在實現(xiàn)基礎(chǔ)的數(shù)據(jù)庫操作方面沒問題。但如果需要構(gòu)建復(fù)雜的
SQL
語句,與其在
Django ORM
里繞來繞去,還不如直接用原始的
SQL
語句。這個是我最強烈的一個感受。
來源:
博客園