再識ORM
Peewee
是一個輕量級Python``ORM
庫。對于我捐下,對ORM
的經(jīng)歷了反反復(fù)復(fù)的愛恨交織過程到腥。最早對其神奇之處的十分感嘆朵逝;而后又鄙視其生成sql
方式;如今乡范,適時適當的在項目中使用ORM
配名。
歸根結(jié)底,對ORM
態(tài)度的轉(zhuǎn)變篓足,源于對其認識的不足段誊。最早接觸ORM
源自Django
自帶的ORM
,用起來很爽栈拖。當時對sql
并不熟悉连舍,ORM
了卻了心頭一大痛點。后來使用flask
,沒有自帶的ORM
索赏,接觸了sqlalchemy
盼玄,也是一個很優(yōu)秀的ORM
。當時潜腻,我對于ORM
的認識還是很淺薄埃儿,大概就認為ORM
一個是數(shù)據(jù)庫對象的map
,其次就是能夠生成sql
查詢融涣。至于如何map
童番,如何生成,如何執(zhí)行等威鹿,基本上處于認知上的蠻荒時代剃斧。
直到為了學習Python
的元類,并嘗試寫自己的ORM
忽你,才算是對ORM
有一個完整的認識幼东。以前的認識可以稱之為對其原理和使用方式上的糾結(jié)。不明其根本原理科雳,使用也一知半解「罚現(xiàn)在的認識更多源于工程哲學上方面。使用程序人生的一篇文章軟件隨想錄:代碼與數(shù)據(jù)對ORM工程上的總結(jié)如下:
我們用
ORM
糟秘,或者LINQ
简逮,而盡量減少SQL
的使用,是因為我們通過把SQL
這種嵌在代碼里的字符串數(shù)據(jù)蚌堵,轉(zhuǎn)化成了代碼买决,也就意味著我們擁有了編譯時或者運行時的檢查,乃至編輯時的檢查 —— 各種linter
很難檢測出字符串中的SQL
語法或者語義錯誤吼畏,但可以在你撰寫ORM
代碼時便提示你其中蘊含的語法/語義錯誤督赤。這是非常偉大的一個進步 —— 對于軟件工程來說,越早發(fā)現(xiàn)錯誤泻蚊,消弭錯誤所花費的時間就越短躲舌。
誠然,ORM
是數(shù)據(jù)層中的更高的抽象性雄,在工程上没卸,抽象程度越高,對于擴展和維護將會更有利秒旋。當然约计,ORM
常被人詬病的一大理由就是程式生成的SQL
沒有原生的高效。的確迁筛,對于復(fù)雜的SQL
語句煤蚌,使用ORM
的組合可能比原生的sql
更不可讀或者不可維護。此時,我們就需要在項目中繼續(xù)使用raw sql
的形式尉桩。
因此筒占,在項目中使用數(shù)據(jù)層的抽象,ORM
最好具備以下幾個方面特性:
-
ORM
和數(shù)據(jù)庫可以相互獨立存在蜘犁,ORM
可以選擇是否引入關(guān)系約束翰苫。即數(shù)據(jù)庫的字段可以大于ORM
中定義的map
,有的orm
數(shù)據(jù)表字段和orm
中的class
必須一直这橙,這就在已經(jīng)在的表中引入orm
會有點棘手奏窑。 -
ORM
具備提供raw sql
的功能。這樣在orm
無法方便提供查詢的時候可以使用原生的sql
解決問題屈扎。 -
ORM
提供生成數(shù)據(jù)表和根據(jù)數(shù)據(jù)表生成model
的特性良哲。這樣就在數(shù)據(jù)的merge
時候方便的操作。
基于上述幾點助隧,發(fā)現(xiàn)Python
的peewee
基本滿足我的要求。下面就peewee的簡單使用方式做一個說明滑沧。例子主要取自官網(wǎng)的Quickstart并村。
分為兩個部分,第一部分為基本使用滓技,第二部分為整合到項目(以tornado
)為例哩牍。
Peewee 快速開始
快速開始部分根據(jù)官網(wǎng)的文檔為例子。
定義數(shù)據(jù)模型
顧名思義令漂,ORM
為數(shù)據(jù)對象關(guān)系的映射膝昆。首先我們需要定義數(shù)據(jù)模型(model
)。新建一個文件learn_peewee.py
叠必。
import logging
from peewee import MySQLDatabase, Model, CharField, DateField, BooleanField, IntegerField
logger = logging.getLogger('peewee')
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())
db = MySQLDatabase('test', host='127.0.0.1', user='root', passwd='', charset='utf8', port=3306)
class BaseModel(Model):
class Meta:
database = db
class Person(BaseModel):
name = CharField(verbose_name='姓名', max_length=10, null=False, index=True)
passwd = CharField(verbose_name='密碼', max_length=20, null=False, default='123456')
email = CharField(verbose_name='郵件', max_length=50, null=True, unique=True)
gender = IntegerField(verbose_name='姓別', null=False, default=1)
birthday = DateField(verbose_name='生日', null=True, default=None)
is_admin = BooleanField(verbose_name='是否是管理員', default=True)
為了更好的測試學習荚孵,把log
打開。首先纬朝,使用MySQLDatabase
指定了所連接的數(shù)據(jù)庫test
收叶。
然后定義了Person
這個表的數(shù)據(jù)字段,如果不指定主鍵共苛,peewee
會自動幫我們創(chuàng)建一個id的字段作為主鍵判没。每一個Field
都有幾個參數(shù)可以配置,大概就是長度的大小隅茎,是否為空(null
)和默認值(default
)澄峰,索引(index
)和唯一索引(unique
)幾個常見的數(shù)據(jù)庫選項。
創(chuàng)建數(shù)據(jù)表
定義好數(shù)據(jù)模型之后辟犀,下一步就是根據(jù)模型創(chuàng)建數(shù)據(jù)表了俏竞。
In [1]: from learn_peewee import *
In [2]: db
Out[2]: <peewee.MySQLDatabase at 0x1114620d0>
In [3]: db.is_closed()
Out[3]: True
In [4]: db.connect()
In [6]: db.is_closed()
Out[6]: False
導(dǎo)入定義的數(shù)據(jù)庫配置和數(shù)據(jù)模型。然后通過db.is_closed
函數(shù)查看數(shù)據(jù)庫的連接狀態(tài),使用db.connect
函數(shù)創(chuàng)建連接胞此。
? peewee-orm netstat -ant | grep -i 3306
tcp4 0 0 127.0.0.1.3306 127.0.0.1.61925 ESTABLISHED
tcp4 0 0 127.0.0.1.61925 127.0.0.1.3306 ESTABLISHED
tcp46 0 0 *.3306 *.* LISTEN
此時確實也能看見mysql的連接創(chuàng)建了臣咖。接下來就能創(chuàng)建數(shù)據(jù)表啦。使用模型的create_table方法漱牵。
In [5]: Person.sqlall()
Out[5]:
('CREATE TABLE `person` (`id` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` VARCHAR(10) NOT NULL, `passwd` VARCHAR(20) NOT NULL, `email` VARCHAR(50), `gender` INTEGER NOT NULL, `birthday` DATE, `is_admin` BOOL NOT NULL)', [])
('CREATE INDEX `person_name` ON `person` (`name`)', [])
('CREATE UNIQUE INDEX `person_email` ON `person` (`email`)', [])
In [6]: Person.create_table()
('CREATE TABLE `person` (`id` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` VARCHAR(10) NOT NULL, `passwd` VARCHAR(20) NOT NULL, `email` VARCHAR(50), `gender` INTEGER NOT NULL, `birthday` DATE, `is_admin` BOOL NOT NULL)', [])
('CREATE INDEX `person_name` ON `person` (`name`)', [])
('CREATE UNIQUE INDEX `person_email` ON `person` (`email`)', [])
通過sqlall
方法可以看見peewee為我們生成的創(chuàng)建表的sql語句夺蛇。執(zhí)行的時候,log也顯示了將要執(zhí)行的sql酣胀。
如果數(shù)據(jù)表已經(jīng)存在刁赦,執(zhí)行
create_table
的時候,將會拋出異常闻镶。
default的含義
通過查看上面創(chuàng)建數(shù)據(jù)表的sql可以發(fā)現(xiàn)甚脉,default
的行為很古怪。查看數(shù)據(jù)庫的schema
铆农,所謂的數(shù)據(jù)表的defalut并不是我們在class中指定的牺氨。對于可以為null的字段。生成的schema中的字段的默認值為null
墩剖,對于非null
的字段猴凹,生成的默認值為其初始值(零值),即CharField(varchar)為空字串岭皂。InterFiled(int)則為0郊霎。
那么指定default的含義有什么用呢?default用于使用orm插入數(shù)據(jù)的時候爷绘,如果沒有顯示的給字段賦值书劝,那么就會采用default指定的值。使用 create方法插入記錄土至,使用save方法更新記錄购对。
In [2]: p = Person.create(name='master')
('INSERT INTO `person` (`name`, `passwd`, `gender`, `is_admin`) VALUES (%s, %s, %s, %s)', [u'master', u'123456', 1, True])
In [3]: p.save()
('UPDATE `person` SET `name` = %s, `passwd` = %s, `gender` = %s, `is_admin` = %s WHERE (`person`.`id` = %s)', [u'master', u'123456', 1, True, 3])
Out[3]: 0L
可以看到,我們只指定了name
字段的值毙籽,因為passwd
和gender
還有is_admin
字段都指定default
洞斯,此時插入的時候,就使用了默認的值坑赡。調(diào)用save
方法的時候烙如,執(zhí)行是update
語句,因此也同樣執(zhí)行了默認值毅否。而沒有指定默認值的字段亚铁,peewee則直接忽略。
In [4]: p.email = 'master@g.com'
In [5]: p.save()
('UPDATE `person` SET `name` = %s, `passwd` = %s, `email` = %s, `gender` = %s, `is_admin` = %s WHERE (`person`.`id` = %s)', [u'master', u'123456', u'master@g.com', 1, True, 3])
Out[5]: 1L
In [8]: p.passwd = '11'
In [9]: p.save()
('UPDATE `person` SET `name` = %s, `passwd` = %s, `email` = %s, `gender` = %s, `is_admin` = %s WHERE (`person`.`id` = %s)', [None, u'11', u'master@g.com', 1, True, 3])
Out[9]: 1L
In [8]: p = Person.get(id=1)
('SELECT `t1`.`id`, `t1`.`name`, `t1`.`passwd`, `t1`.`email`, `t1`.`gender`, `t1`.`birthday`, `t1`.`is_admin` FROM `person` AS t1 WHERE (`t1`.`id` = %s) LIMIT 1 OFFSET 0', [1])
In [9]: p.save()
('UPDATE `person` SET `name` = %s, `passwd` = %s, `email` = %s, `gender` = %s, `birthday` = %s, `is_admin` = %s WHERE (`person`.`id` = %s)', [u'master', u'11', u'master@g.com', 1, None, True, 1])
Out[9]: 0L
有默認值的字段螟加,如果指定了值徘溢,就使用所賦的值吞琐。新讀的數(shù)據(jù),就不用考慮默認值了然爆,如果讀取的值沒有變站粟,更新的時候就依然采用這個值,與default值沒有關(guān)系曾雕。
總結(jié)
-
default
的設(shè)置與創(chuàng)建數(shù)據(jù)表的schema
沒有關(guān)系奴烙。僅與插入的時候有關(guān)系。 - 插入的時候剖张,如果字段設(shè)置了
default
值切诀,則會按照default
指定的值插入,如果沒有指定搔弄,同時字段可以為null
幅虑,則數(shù)據(jù)庫自動初始化值為null
,如果字段不能為null
顾犹,則數(shù)據(jù)庫自動初始化為其零值倒庵。 - 最佳實踐,如果字段為非
Null
炫刷,最好設(shè)置default
值哄芜,同時數(shù)據(jù)庫schema
也設(shè)置其default值,如果字段為可以為null
柬唯,那么初始值就設(shè)置為null
即可。
快速認識了Peewee
圃庭,接下來討論peewee
更強大的功能锄奢,例如定義關(guān)系和增刪改查。