基於關係模型的數據庫,稱爲SQL數據庫。文檔數據庫和鍵值對數據庫合稱NoSQL數據庫。
5.1 SQL數據庫
關係數據庫把數據存在表中。表的列數固定,行數可變。列定義實體的數據屬性,行定義各列的真實數據。特殊的列,叫主鍵,其值爲表中各行的唯一標識符。外鍵,引用同一個表或不同表中某行的主鍵。行之間的聯繫稱爲關係。
5.2 NoSQL數據庫
NoSQL使用集合代替表,使用文檔代替記錄。每個用戶存儲一份完整的自己的數據庫數據。
5.3 使用SQL還是NoSQL?
看情況
5.4 Python數據庫框架
如果市面上常用數據庫包無法滿足需求,還有數據庫抽象層代碼包,例如SQLAlchemy和MongoEngine。可以使用抽象包直接處理高等級的Python對象,而不用處理表、文檔或查詢語言此類的數據庫實體。
抽象層,也叫對象關係映射(Object-Relational Mapper, ORM)或對象文檔映射(Object-Document Mapper,ODM),在用戶不知覺的情況下把高層的面向對象操作轉換爲低層的數據庫指令。
5.5 Flask-SQLAlchemy管理數據庫
SQLAlchemy提供了高層ORM,也提供了使用數據庫原生SQL的低層功能。
pip install flask-sqlalchemy
在Flask-SQLAlchemy中,數據庫使用URL指定。
數據庫引擎 URL
MySQL mysql://username:password@hostname/database
Postgres postgresql:// username:password@hostname/database
SQLite(Unix) sqlite:////absolute/path/to/database
SQLite(Windows) sqlite:///c:/absolute/path/to/database
其中:
hostname表示MySQL服務所在的主機(本地localhost或遠程服務器)
database表示數據庫名
username和password表示數據庫用戶密令
程序使用的數據庫URL保存到Flask配置對象的SQLALCHEMY_DATABASE_URI鍵中。選項SQLALCHEMY_COMMIT_TEARDOWN鍵設爲True,每次請求結束後自動提交數據庫變動。
from flask_sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = \
'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
5.6 定義模型
在ORM中,模型一般是一個Python類,類中的屬性對應數據庫表中的列。
Flask-SQLAlchemy創建的數據庫實例爲模型提供了一個基類以及一系列輔助類和輔助函數,用於定義模型的結構。
hello.py:定義Role和User模型
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
def __repr__(self):
return 'User %r' % self.username
__tablename__定義數據庫中使用的表名;其餘類變量爲模型的屬性,被定義爲db.Column類的實例。常用的SQLAlchemy列類型自行查閱。SQLAlchemy列選項:primary_key主鍵、unique不允許出現重複的值、index爲此列創建索引、nullable允許使用空值、default默認值。__repr()__方法,返回一個具有可讀性的字符串表示模型,可在調試和測試使用。
注意:Flask-SQLAlchemy要求每個模型都要定義主鍵,這一列經常命名爲id。
5.7 關係
關係型數據庫把不同表的行聯繫起來。
class Role(db.Model):
#...
users = db.relationship('User', backref='role')
class User(db.Model):
#...
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
添加到User模型中的role_id列被定義爲外鍵。傳給db.ForeignKey()的參數roles.id表明列的值是roles表中行的id值。
添加到Role模型中的users屬性代表關係的面向對象視角。users屬性返回與角色相關聯的用戶組成的列表。db.relationship()的第一個參數表明關係的另一端是User模型,第二個參數的backref向User模型添加一個role屬性,從而定義反向關係;這一屬性可替代role_id訪問Role模型。
SQLAlchemy關係選項
backref:在關係的另一個模型中添加反向引用
primaryjoin:指定兩個模型之間使用的聯結條件
lazy:指定如何加載相關記錄,select首次訪問按需加載,immediate源對象加載後加載,joined加載記錄但使用聯結,subquery立即加載但使用子查詢,noload永不加載,dynamic不加載記錄但提供加載記錄的查詢
uselist:使用列表還是標量值
order_by:排序方式
secondary:多對多關係中關係表的名字
secondaryjoin:指定多對多關係的二級聯結條件
5.8 數據庫操作
5.8.1 創建表
python
>>>from hello import db
>>>db.create_all()
此時創建了名爲data.sqlite文件
更新現有數據庫表的粗暴方式是先刪表再創建
>>>db.drop_all()
>>>db.create_all()
5.8.2 插入行
在Python中添加角色和用戶
>>> 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)
模型的構造函數,參數使用關鍵字參數指定的模型屬性初始值。此時還沒有寫入數據庫。
通過數據庫會話管理數據庫改動,在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
提交方式使用原子方式,如果寫入會話的過程發生錯誤,整個會話失效。數據庫會話也叫回滾,調用db.session.rollback()後,添加到數據庫會話中的所有對象都會還原到在數據庫時的狀態。
5.8.3 修改行
>>> admin_role.name='Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()
此處將Admin角色重命名爲Administrator
5.8.4 刪除行
>>> db.session.delete(mod_role)
>>> db.session.commit()
此處將Moderator角色從數據庫刪除
5.8.5 查詢行
5.8.5.1 Flask-SQLAlchemy爲每個模型類都提供了query對象。
>>> Role.query.all()
[<Role u'Administrator'>, <Role u'User'>]
>>> User.query.all()
[<User u'john'>, <User u'susan'>, <User u'david'>]
5.8.5.2 過濾器可以配置query對象進行更精確的查詢
>>> User.query.filter_by(role=user_role).all()
[<User u'susan'>, <User u'david'>]
5.8.5.3 可以將query對象去掉all(),轉換爲字符串顯示原生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 \nFROM users \nWHERE ? = users.role_id'
5.8.5.4
如果退出shell,則Python對象的形式消失,需要發起查詢來加載數據
>>> user_role = Role.query.filter_by(name='User').first()
5.8.5.5 常用查詢過濾器
filter():過濾器添加到原查詢,返回一個新查詢
filter_by():等值過濾器添加到原查詢,返回一個新查詢
limit():限制原查詢返回的結果數量,返回一個新查詢
offset():偏移原查詢返回的結果,返回一個新查詢
order_by():對原查詢結果排序,返回一個新查詢
group_by():對原查詢結果分組,返回一個新查詢
5.8.5.6 觸發查詢執行
all():返回所有結果
first():返回第一個結果
first_or_404:返回第一個結果或者終止請求返回404
get():返回指定主鍵對應的行
get_or_404():返回指定主鍵對應的行,或者終止請求返回404
count():返回查詢結果的數量
paginate:返回一個Paginate對象,包含指定範圍內的結果
5.8.5.7 關係查詢
>>> users = user_role.users
>>> users
[<User u'susan'>, <User u'david'>]
>>> users[0].role
<Role u'User'>
問題:user_role.users是隱含all()是自動查詢,所以無法使用過濾器。
改進:
class Role(db.Model):
#...
users = db.relationship('User', backref='role', lazy='dynamic')
禁止自動執行之後就可以添加過濾器了。
5.9 在視圖函數中操作數據庫
hello.py
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.name.data).first()
if user is None:
user = User(username=form.name.data)
db.session.add(user)
db.session.commit()
session['known'] = False
else:
session['known'] = True
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'),
known=session.get('known', False))
index.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
{% if not known %}
<p>Pleased to meet you!</p>
{% else %}
<p>Happy to see you again!</p>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
5.10 集成Python shell
讓Flask-Script的shell命令自動導入特定的對象。爲shell命令註冊一個make_context回調函數。
@app.shell_context_processor
def make_shell_context():
return dict(db=db, User=User, Role=Role)
manager = Manager(app)
manager.add_command("shell", Shell(make_context=make_shell_context))
使用命令直接將數據庫文件裏的數據導入Python
python hello.py shell
測試:
>>> db
<SQLAlchemy engine=sqlite:////yingshe/learning/data.sqlite>
5.11 使用Flask-Migrate實現數據庫遷移
更新表的更好的方式是使用數據庫遷移框架。Alembic集成到Flask-Script.
5.11.1 創建遷移倉庫
pip install flask-migrate
hello.py
migrate = Migrate(app, db)
使用db命令
python hello.py db init,創建migrations文件夾
5.11.2 創建遷移腳本
數據庫遷移用遷移腳本表示,upgrade()和downgrate()。
手工創建:revision命令加Operations對象指令
自動創建:migrate子命令
python hello.py db migrate -m "initial migration"
注意:自動遷移可能會漏掉內容,需要檢查
5.11.3 更新數據庫
檢查並修正好遷移腳本後,使用db upgrade命令遷移
python hello.py db upgrade