5.1 SQL数据库
表中有个特殊的列,称为主键,其值是表中各行的唯一标识符,表中还可以有称为外键的列,引用同一个表或不同表中某行的主键,行之间的这种联系称为关系,这是SQL数据库的基础。
5.2 NoSQL数据库
所有不遵循上节所述关系模型的数据库统称为NoSQL数据库。NoSQL数据库一般使用集合Collection代替表,使用文档Document代替记录(行)。
NoSQL数据库可以减少表的数量,但却增加了数据重复量,但数据重复又可以提高查询速度。
5.3 使用SQL还是NoSQL
视实际情况所需。
5.4 Python数据库框架
数据库包:MySQL、Postgres、SQLite、Redis、MongoDB、CouchDB
数据库抽象层代码包(ORM或ODM):SQLAlchemy、MongoEngine
一般情况下,ORM和ODM对生产率的提升远远超过“把对象业务转换成数据库业务”而带来的性能降低。
5.5 使用Flask-SQLAlchemy管理数据库
在Flask-SQLAlchemy中,使用哪种数据库,要通过URL指定。常用的数据库引擎采用的数据库URL格式如下:
表5-1 Flask-SQLAlchemy数据库URL
数据库引擎 | URL |
---|---|
MySQL | mysql://username:password@hostname/database |
Postgres | postgres://username:password@hostname/database |
SQLite(Unix) | sqlite:////absolute/path/to/database |
SQLite(Windows) | sqlite:///c:/absolute/path/to/database |
hostname
可以是本地主机(localhost
),也可以是原创服务器。database
表示要使用的数据库名称。
程序使用的数据库URL必须保存到Flask配置对象的SQLALCHEMY_DATABASE_URI
键中。
配置对象中有个SQLALCHEMY_COMMIT_ON_TEARDOWN
键,将其设为True
时,每次请求结束后都会自动提交数据库中的变动(相当于自动commit
)。
初始化及配置SQLite如下:
db
对象是SQLAlchemy类的实例,表示程序所使用的数据库。
5.6 定义模型
在ORM中,模型(相当于数据库表table)一般是个Python类,类中的属性对应数据库表中的列。
在hello.py中定义Role模型和User模型:
类变量__tablename__
定义在数据库中使用的表名,如果没有定义,Flask-SQLAlchemy会使用一个默认名字,但该名字没有遵守使用复数形式进行命名的约定。
db.Column
类构造函数的第一个参数是数据库列的类型(如Integer、String等),对应模型(即Python类)的对象类型(如int、str等)
表5-2 最常用的SQLAlchemy列类型
类型名称 | 对应Python类型 | 说明 |
---|---|---|
Integer | int | 普通整数,一般时32位 |
Float | int | 浮点数 |
String | str | 变长字符串 |
Text | str | 变成字符串,对较长或不限长度的字符串做了优化 |
Unicode | unicode | 变长Unicode字符串 |
UnicodeText | unicode | 变长Unicode字符串,对较长货不限长度的字符串做了优化 |
Date | datetime.data | 日期 |
Time | datetime.time | 时间 |
DateTime | datetime.datetime | 日期和时间 |
db.Column
中其余的参数可对列中的数据做一些配置或设置
表5-3 最常用的SQLAlchemy列选项
选项名 | 说明 |
---|---|
primary_key | 如果设为True,这列是主键 |
unique | 如果设为True,这列不允许出现重复值 |
index | 如果设为True,为这列创建索引,提高查询效率 |
nullable | 如果设为True,这列允许使用空值;设为False,则不允许为空 |
default | 为这列设置默认值 |
注意:Flask-SQLAlchemy要求每个模型都要定义主键。
5.7 关系
定义一对多关系(Role对User):
添加到Role模型中的类变量users
代表这个关系(Role与User的关系)的面向对象视角,它将返回与具体Role实例相关联的用户(具体User实例)组成的列表。(如Role实例role_admin
返回的users为[richard, john]
,其中richard
和john
都是User实例,即role_admin.users = [richard,john]
体现了面向对象视角的一对多关系)
db.relationship()
的第一个参数是这个关系的另一端对应的模型。如果模型尚未定义,可使用字符串形式指定。backref
参数为'role'
表明:在User模型中添加一个属性(或称为类变量)role
(可理解成在User表中添加一列名为role
的列。但实际查看数据库表时是看不到的),从而定义反向关系。
注意:一般情况下,db.relationship()
都能自行找到关系中的外键,有在某些情况下无法决定把哪一列作为外键。如User模型中有两列或以上的列定义为Role模型的外键,那么SQLAlchemy就不知道该使用哪列了。此时,你就要为db.relationship()
提供额外参数,从而确定使用哪列外键。常用配置如表5-4。
表5-4 db.relationship()
常用的SQLAlchemy关系选项
选项名 | 说明 |
---|---|
backref | 在关系的另一端模型中添加反向作用 |
primaryjoin | 明确指定两个模型之间使用的联结条件。只在模凌两可的关系中需要指定 |
lazy | 指定如何加载相关记录。可选值有:select(首次访问时按需加载),immediate(源对象加载后加载),joined(加载记录,但使用联结),subquery(立即加载,但使用子查询),noload(永不加载),dynamic(不加载记录,单提供加载记录的查询) |
userlist | 如果设为Fales,不适用列表,而使用标量值 |
order_by | 指定关系中记录的排序方式 |
secondary | 指定多对多关系中关系表的名字 |
secondaryjoin | SQLAlchemy无法自行决定时,指定多对多关系中的二级联结条件 |
一对一关系:可以用前面介绍的一对多关系表示,但调用db.relationship()
时要把userlist
设为False
。
多对一关系:可以用一对多关系表示,只是两个表对调。或者把外键
和db.relationship()
都放在“多”这一侧。
多对多关系:需要用到第三张表(关系表)。
5.8 数据库操作
需要在Python Shell中进行操作。
5.8.1 创建表
|
|
如果修改模型(如增加了一列)后要把修改的地方应用到现在的数据库中,那么更新现有数据库表的粗暴方式是先删除旧表,在重新创建表:
但是这种方法会把数据库中原有的数据都删除掉。在5.11 使用Flask-Migrate实现数据库迁移中会介绍更新数据库更好的方式。
5.8.2 插入行
|
|
现在这些对象只存在于Python中,还没有写入数据库。因此id尚未赋值,所以print(role_admin.id)
的结果为None
。
要把对象写入数据库要分两步:
- 要把对象添加到会话(
db.session
)中:12>>> db.session.add(role_admin)>>> db.session.add(user_richard)
或者简写成:
- 调用
commit()
提交会话1>>> db.session.commit()
此时对象已经写入数据库,再print(role_admin.id)
时,它的结果时1
。
调用db.session.rollback()
后,可实现事务回滚。添加到数据库会话中的所有对象都会还原为它们在数据库时的状态。
注意:如果在写入会话过程中发生了错误,那么整个会话都会实效。这样就保证了数据库的一致性。因为可以防止只更新正确部分,而发生错误的部分没有更新。
5.8.3 更新行
在数据库会话上调用add()
方法可以更新数据。如:
5.8.4 删除行
在数据库会话上调用delete()
方法删除数据。如:
注意:插入、更新、删除,只有commit()
后才会真正写入数据库。
5.8.5 查询行
Flask-SQLAlechemy为每个模型(注意不是具体的模型实例)都提供了query
对象,在query
对象上调用相应方法可进行查询。如查询所有记录:
在query
对象上可使用过滤器进行更精确的查询,如:
表5-5 常用的SQLAlchemy查询过滤器
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit() | 使用指定的值限制原查询返回的结果数量,返回一个新查询 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
注意:在查询上应用指定过滤器后,需通过一些执行函数执行查询。
表5-6 最常用的SQLAlchemy查询执行函数
方法 | 说明 |
---|---|
all() | 以列表的形式返回查询的所有结果 |
fitst() | 返回查询的第一个结果,如果没有,则返回None |
first_or_404() | 返回查询的第一个结果,如果没有,则终止请求,返回404错误响应 |
get() | 返回指定主键对应的行,如果没有,则返回None |
get_or_404() | 返回指定主键对应的行,如果没有,则返回None |
count() | 返回查询结果的数量 |
paginate() | 返回一个Paginate对象,它包含指定范围的结果 |
再看一个从关系的两端查询Role和User之间一对多的关系的例子:
执行role_visitor.users
时,隐含的查询会调用all()
方法返回一个列表,query
对象时隐藏的,因此很难做更进一步的查询。此时可在db.relationship()
中添加lazy='dynamic
参数,从而禁止自动执行查询(添加后需调用all()
等方法才能执行查询,当然也可在执行前调用过滤器)。
5.9 在视图函数中操作数据库
新版的hello.py
当输入新用户名时,会把新用户名写入数据库,并显示Pleased to meet you!
信息;当输入的是旧用户名(数据库中已有的名字)时,会显示Happy to see you again!
hello.py
如下:
index.html
如下:
5.10 集成Python shell
每次启动shell会话都要将数据库模型和实例一个一个地import进去很麻烦,对此我们可以为shell命令注册一个make_context
回调函数,把想import的对象导入列表。
对hello.py
修改如下:
这样启动shell时就会将对象直接导入shell中:
5.11 使用Flask-Migrate实现数据库迁移
在5.8.1 创建数据库表中我们说到,更新表的方法之一是删除旧表再重新创建表,但是这会丢失原有的数据。现在介绍方法二:使用数据库迁移框架。
数据库迁移框架:能够跟踪数据库模式的变化,然后增量式地把变化应用到已有数据库中。(其功能类似与Git,能够跟踪数据库模式的变化)
可从理解Git的角度理解数据库迁移框架。
5.11.1 创建迁移仓库
hello.py
如下:
Flask-Migrate提供了一个MigrateCommand
类,它可以附加到Flask-Script的manager
对象上,从而导出数据库迁移命令。该例中,MigrateCommand类使用db
命令附加(类似于5.10中,将Shell类使用shell
命令附加)。
注意:在维护数据库迁移之前,首先要使用init
子命令创建迁移仓库:
这个命令会创建一个migrations
的文件夹,所有迁移脚本都在里面。
5.11.2 创建迁移脚本
在Alembic中,数据库迁移用迁移脚本表示。脚本中有两个函数:upgrade()
函数:把迁移中的改动应用到数据库中。downgrade()
函数:将改动删除。
我们可以用revision
命令手动创建Alembic迁移(upgrade()
和downgrade()
都是空的,需使用Alembic提供的Operations
对象指令实现具体操作),
也可以用migrate
命令自动创建Alembic迁移(会根据模型定义和数据库当前的状态之前的差异生成upgrade()
和downgrade()
函数的内容)。
使用migrate
命令自动创建迁移脚本:
5.11.3 更新数据库
检查并修正好迁移脚本后,可以使用db upgrade
命令把迁移应用到数据库中。如:
对于第一个迁移来说,其作用和调用db.create_all()
方法一样,但在后续的迁移中,upgrade
命令能把改动应用到数据库中,而且不影响其中保存的数据。