Flask Web Development —— 數據庫(中)

七、關係

關係數據庫經過使用關係在不一樣的表中創建鏈接。圖像5-1的關係圖表達了用戶和用戶角色之間的簡單關係。這個角色和用戶是一對多關係,由於一個角色能夠從屬於多個用戶,而一個用戶只能擁有一個角色。python

示例5-3的模型類展現了圖像5-1中表達的一對多關係。git

示例5-3. hello.py:關係github

class Role(db.Model): 
    # ...
    users = db.relationship('User', backref='role')

class User(db.Model): 
    # ...
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

就像圖像5-1中看到的那樣,關係經過使用外鍵來鏈接兩行。添加給User模型的role_id列被定義爲外鍵,且創建關係。db.ForeignKey()的參數roles.id指定的列應該理解爲在roles表的行中持有id值的列。sql

添加到Role模型的users屬性表現了關係的面向對象的觀點。給定Role類的實例,users屬性會返回一組鏈接到該角色的用戶。指定給db.relationship()的第一個參數代表模型中關係的另外一邊。若是類還未定義,這個模型能夠做爲字符串提供。shell

注意:以前在segmentdefault中遇到的問題,後來粗略閱讀了SQLAlchemy的源碼。ForeignKey類的column接收三種類型的參數,一種是「模型名.屬性名」;一種是「表名.列名」,最後一種沒看明白,下次試着用一下。數據庫

db.relationship()backref參數經過給User模型增長role屬性來定義反向關係。這個屬性能夠替代role_id訪問Role模型,是做爲對象而不是外鍵。flask

大多數狀況下db.relationship()能夠定位本身的外鍵關係,可是有時候不能肯定哪一個列被用做外鍵。例如,若是User模型有兩個或更多列被定義爲Role的外鍵,SQLAlchemy將不知道使用兩個中的哪個。每當外鍵配置模棱兩可的時候,就必須使用額外參數db.relationship()。表格5-4列出一些經常使用配置選項用於定義關係。session

表格5-4. 經常使用SQLAlchemy關係選項app

建議:若是你有克隆在GitHub上的應用程序,你如今能夠運行git checkout 5a來切換到這個版本的應用程序。函數

除了一對多關係還有其餘種類關係。一對一關係能夠表述爲前面描述的一對多關係,只要將db.relationship()中的uselist選項設置爲False,「多」就變爲「一」了。多對一關係也可表示爲將表反轉後的一對多關係,或表示爲外鍵和db.relationship()定義在「多」那邊。最複雜的關係類型,多對多,須要一個被稱做關聯表的額外表。你將在第十二章學習多對多關係。

八、數據庫操做

根據圖像5-1的數據庫圖,模型已經徹底配置完且準備好使用。學習怎樣使用模型的最好方式就是使用Python shell。如下部分將介紹最多見的數據庫操做。

8.一、建立表

首先要作的第一件事情就是指示Flask-SQLAlchemy基於模型類建立數據庫。db.create_all()函數會完成這些:

(venv) $ python hello.py shell 
>>> from hello import db
>>> db.create_all()

若是你檢查應用程序目錄,你會發現名爲data.sqlite的新文件,SQLite數據庫名在配置中給出。若是數據庫已存在db.create_all()函數不會從新建立或更新數據庫表。這會很是的不方便當模型被修改且更改須要應用到現有的數據庫時。更新現有的數據庫表的蠻力解決方案是先刪除舊的表:

>>> db.drop_all()
>>> db.create_all()

不幸的是,這種方法有個不受歡迎的反作用就是摧毀舊的數據庫中的全部數據。更新數據庫問題的解決方案會在這章快結束的時候介紹。

8.二、插入行

下面的示例會建立新的角色和用戶:

>>> from hello import Role, User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> user_role = Role(name='User')
>>> user_john = User(username='john', role=admin_role) 
>>> user_susan = User(username='susan', role=user_role) 
>>> user_david = User(username='david', role=user_role)

模型的構造函數接受模型屬性的初始值做爲關鍵字參數。注意,甚至可使用role屬性,即便它不是一個真正的數據庫列,而是一對多關係的高級表示。這些新對象的id屬性沒有顯式設置:主鍵由Flask-SQLAlchemy來管理。到目前爲止對象只存於Python中,他們尚未被寫入數據庫。由於他們的id值還沒有分配:

>>> print(admin_role.id) None
>>> print(mod_role.id) None
>>> print(user_role.id) None

修改數據庫的操做由Flask-SQLAlchemy提供的db.session數據庫會話來管理。準備寫入到數據庫中的對象必須添加到會話中:

>>> db.session.add(admin_role)
>>> db.session.add(mod_role)
>>> db.session.add(user_role)
>>> db.session.add(user_john)
>>> db.session.add(user_susan)
>>> db.session.add(user_david)

或,更簡潔的:

>>> db.session.add_all([admin_role, mod_role, user_role,
...     user_john, user_susan, user_david])

爲了寫對象到數據庫,須要經過它的commit()方法來提交會話:

>>> db.session.commit()

再次檢查id屬性;這個時候它們都已經被設置好了:

>>> print(admin_role.id) 
1
>>> print(mod_role.id)
2
>>> print(user_role.id) 
3

注:db.session數據庫會話和第四章討論的Flask會話沒有任何聯繫。數據庫會話也叫事務

數據庫會話在數據庫一致性上是很是有用的。提交操做會原子性地將全部添加到會話中的對象寫入數據庫。若是在寫入的過程發生錯誤,會將整個會話丟棄。若是你老是在一個會話提交相關修改,你必須保證避免因部分更新致使的數據庫不一致的狀況。

注:數據庫會話也能夠回滾。若是調用db.session.rollback(),任何添加到數據庫會話中的對象都會恢復到它們曾經在數據庫中的狀態。

8.三、修改行

數據庫會話中的add()方法一樣能夠用於更新模型。繼續在同一shell會話中,下面的示例重命名「Admin」角色爲「Administrator」:

>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()

注意:不過貌似咱們在作更新操做的時候都不使用db.session.add(),而是直接使用db.session.commit()來提交事務。

8.四、刪除行

數據庫會話一樣有delete()方法。下面的示例從數據庫中刪除「Moderator」角色:

>>> db.session.delete(mod_role)
>>> db.session.commit()

注意刪除,和插入更新同樣,都是在數據庫會話提交後執行。

8.五、返回行

Flask-SQLAlchemy爲每一個模型類建立一個query對象。最基本的查詢模型是返回對應的表的所有內容:

>>> Role.query.all()
[<Role u'Administrator'>, <Role u'User'>]
>>> User.query.all()
[<User u'john'>, <User u'susan'>, <User u'david'>]

使用過濾器能夠配置查詢對象去執行更具體的數據庫搜索。下面的例子查找全部被分配「User」角色的用戶:

>>> User.query.filter_by(role=user_role).all()
[<User u'susan'>, <User u'david'>]

對於給定的查詢還能夠檢查SQLAlchemy生成的原生SQL查詢,並將查詢對象轉換爲一個字符串:

>>> str(User.query.filter_by(role=user_role))
'SELECT users.id AS users_id, users.username AS users_username,
users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'

若是你退出shell會話,在前面的示例中建立的對象將不能做爲Python對象而存在,但可繼續做爲行記錄存在各自的數據庫表中。若是你開始一個全新的shell會話,你必須從它們的數據庫行中從新建立Python對象。下面的示例執行查詢來加載名字爲「User」的用戶角色。

>>> user_role = Role.query.filter_by(name='User').first()

過濾器如filter_by()經過query對象來調用,且返回通過提煉後的query。多個過濾器能夠依次調用直到須要的查詢配置結束爲止。

表格5-5展現一些查詢中經常使用的過濾器。完整的列表參閱SQLAlchemy文檔

表格5-5.經常使用SQLAlchemy查詢過濾器

在須要的過濾器已經所有運用於query後,調用all()會觸發query執行並返回一組結果,可是除了all()之外還有其餘方式能夠觸發執行。表格5-6.展現其餘查詢執行方法。

表格5-6.經常使用SQLAlchemy查詢執行器

關係的原理相似於查詢。下面的示例從兩邊查詢角色和用戶之間的一對多關係:

>>> users = user_role.users
>>> users
[<User u'susan'>, <User u'david'>]
>>> users[0].role
<Role u'User'>

此處的user_role.users查詢有點小問題。當user_role.users表達式在內部調用all()時經過隱式查詢執行來返回用戶的列表。由於查詢對象是隱藏的,是不可能經過附加查詢過濾器進一步提取出來。在這個特定的例子中,它多是用於按字母排列順序返回用戶列表。在示例5-4中,被lazy = 'dynamic'參數修改過的關係配置的查詢是不會自動執行的。

示例5-4. app/models.py:動態關係

class Role(db.Model): 
    # ...
    users = db.relationship('User', backref='role', lazy='dynamic') 
    # ...

用這種方式配置關係,user_roles.user查詢尚未執行,因此能夠給它增長過濾器:

>>> user_role.users.order_by(User.username).all()
[<User u'david'>, <User u'susan'>]
>>> user_role.users.count()
2
相關文章
相關標籤/搜索