转自:一只在当地较为英俊的程序猿
前言
Web程序最常用基于关系模型的数据库,这种数据库也称为SQL数据库,因为它们使用结构化查询语言。关系型数据库把数据存储在表中,表模拟程序中的不同实体。简单来说,关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。
关系模型中常用的概念:
- 关系:可以理解为一张二维表,每个关系都具有一个关系名,就是通常说的表名。
- 元组:可以理解为二维表中的一行,在数据库中经常被称为记录。
- 属性:可以理解为二维表中的一列,在数据库中经常被称为字段。
- 域:属性的取值范围,也就是数据库中某一列的取值限制。
- 关键字:一组可以唯一标识元组的属性,数据库中常称为主键,由一个或多个列组成。
- 关系模式:指对关系的描述。其格式为:关系名(属性1,属性2, … … ,属性N),在数据库中称为表结构。
Python数据库框架
由于我们使用的是Flask框架来搭建我们的Web应用,因此选用集成了Flask的数据库框架对我们来说是最合适,也是最节省时间的。因此,选择Flask-SQLAlchemy扩展来实现,这个扩展包装了SQLAlchemy框架。
Flask-SQLAlchemy框架的配置
下面是Flask-SQLAlchemy中常用的配置值。Flask-SQLAlchemy从Flask主配置中加载这些值。注意其中的一些在引擎创建后不能修改,所以确保尽早配置且不在运行时修改它们。
- SQLALCHEMY_DATABASE_URI:用于数据库的连接,例如sqlite:tmp/test.db
- SQLALCHEMY_TRACK_MODIFICATIONS:如果设置成True(默认情况),Flask-SQLAlchemy将会追踪对象的修改并且发送信号。这需要额外的内存,如果不必要的可以禁用它。
- SQLALCHEMY_COMMIT_ON_TEARDOWN:每次request自动提交db.session.commit()
在这里我们只列出了在Imatation-weibo程序中需要使用的配置键,更多的配置键请参考Flask-SQLAlchemy官方文档。
常见数据库的连接URI格式如下所示:
- Postgres:postgresql://scott:tiger@localhost/mydatabase
- MySQL:mysql://scott:tiger@localhost/mydatabase
- Oracle:oracle://scott:tiger@127.0.0.1:1521/sidname
- SQLite:sqlite:absolute/path/to/foo.db
Flask-SQLAlchemy框架的初始化
常见情况下,对于只有一个Flask应用,我们需要先创建Flask应用,选择加载配置,然后创建SQLAlchemy对象时候把Flask应用传递给它作为参数。一旦创建,这个对象就包含 sqlalchemy 和 sqlalchemy.orm 中的所有函数和助手。此外它还提供一个名为 Model 的类,用于作为声明模型时的 delarative 基类。
创建并初始化Flask-SQLAlchemy,当然由于我们的程序使用了工厂函数,因此,我们在这里也用之前初始化Flask-Bootstrap扩展一样的方法来实现它:
|
声明模型
创建数据库模型
创建数据库模型的方法如下,创建表时必须导入基类:
|
这个模型创建了两个字段,他们是类db.Column的实例,id和username,db.Column 类构造函数的第一个参数是数据库列和模型属性的类型,下面列出了一些常见的列类型以及在模型中使用的Python类型。
- Integer:普通整数,一般是32bit
- String:变长字符串
- Text:变长字符串,对较长或不限长度的字符做了优化
- Boolean:布尔值
- Date:日期
- DateTime:日期和时间
db.Column 中其余的参数指定属性的配置选项。下面列出了一些常用选项:
- primary_key:如果设置为True,这列就是表的主键
- unique:如果设置为True,这列不允许出现重复的值
- index:如果设置为True,为这列创建索引,提升查询效率
- default:为这列定义默认值
一对多关系
最为常见的关系就是一对多关系,因为关系在它们建立之前就已经声明。关系使用relationship()函数表示,外键使用类sqlalchemy.schema.ForeignKey来单独声明。
在一对多关系中,要在”多”这一侧加入一个外键,指向”一”这一侧联接的记录,即relationship()声明出现在代表”少”那个类,而外键声明出现在代表”多”的那个类中。如下代码所示,每一个人(少)可以有多个地址(多):
|
关系使用address表中的外键连接了两行。添加到address模型中person_id列被定义为外键,就是这个外键建立起了联系。传给db.ForeignKey()的参数’person_id’表明,这一列的值是person表中行的id值。
添加到person表中的address属性代表这个关系的面向对象视角。对于一个person实例,其address属性将返回与person相关联的多个地址。db.relationship()的第一个参数表明这个关系的另一端是哪个模型。
db.relationship()中的backref参数向address模型中添加一个person属性,从而定义反向关系。这一属性可替代person_id访问 person模型,此时获取的是模型对象,而不是外键的值。
大多数情况下,db.relationship()都能自行找到关系中的外键,但有时却无法决定把哪一列作为外键。例如如果address模型中有两个或以上的列定义为person模型的外键,
SQLAlchemy就不知道该使用哪列。如果无法决定外键,你就要为db.relationship()提供额外参数,从而确定所用外键,常用的配置选项如下所示:
- backref:在关系的另一个模型中添加反向引用
- primary join:明确指定两个模型之间使用的联结条件。只在模棱两可的关系中需要指定
- lazy:决定了SQLAlchemy什么时候从数据库中加载数据。可选值有 select(首次访问时按需加载)、immediate(源对象加
载后就加载)、 joined(加载记录,但使用联结)、 subquery (立即加载,但使用子查询),
noload(永不加载)和 dynamic(不加载记录,但提供加载记录的查询) - uselist:如果设为Fales,表示一对一关系
- order_by:指定关系中记录的排序方式
- secondary:指定多对多关系中关系表的名字
- secondaryjoin:SQLAlchemy无法自行决定时,指定多对多关系中的二级联结条件
如果想为反向引用(backref)定义惰性(lazy)状态,可以使用backref()函数:
|
多对多关系
一对多关系,一对一关系至少有一侧是单个实体,所以记录之间的联系可以通过外键来实现,让外键指向这个实体。但是两侧都是多的关系,显然不能通过一个简单的外键来实现。解决办法是添加第三张表。
多对多关系一个典型的例子是文章与标签之间的关系,一篇文章可以有多个标签,一个标签也可以对应多篇文章。
我们把tags和posts表之间的多对多关系转换成它们各自与关联表connections之间的两个一对多关系。
查询这个多对多关系分为两步。若想知道某篇文章有多少个标签,首先从posts和connections之间的一对多关系开始,获取这篇文章在connections表中的所有和这篇文章相关的记录,然后再按照多到一的关系在tags表中查找对应的所有标签。
同样,若想查找某个标签所对应的所有文章,首先从tags表和connections表之间的一对多关系开始,获取这个标签在connections表中所有的和这个标签相关的记录,然后再按照多到一的关系在posts表中查找对应的所有文章。
|
多对多关系仍使用定义一对多关系的db.relationship()方法进行定义,但在多对多关系中,必须把secondary参数设为关联表。多对多关系可以在任何一个类中定义,backref参数会处
理好关系的另一侧。关联表connections就是一个简单的表,不是模型,SQLAlchemy会自动接管这个表。
自引用关系
多对多关系在我们的Web应用中可以用来实现用户之间的关注,但是在上面的文章和标签的例子中,关联表连接的是两个明确的实体,而在用户关注其他用户时,都在users表内,只有一个实体。如果关系中的两侧都在同一个表中,这种关系称为自引用关系。在关注中,关系的左侧是用户实体,称为”关注者”;右侧也是用户实体,称为”被关注者”。
这种用户之间关注的关系,我们依然可以使用上面的方法来实现。
高级多对多关系
自引用多对多关系可在数据库中表示用户之间的关注,但却有个限制。使用多对多关系时,往往需要存储所联两个实体之间的额外信息。对用户之间的关注来说,可以存储用户关注另一个用户的日期,这样就能按照时间顺序列出所有关注者。这种信息只能存储在关联表中,但是在之前实现的学生和课程之间的关系中,关联表完全是由SQLAlchemy掌控的内部表。
为了能在关系中处理自定义的数据,我们必须提升关联表的地位,使其变成程序可访问的模型。
|
SQLAlchemy不能直接使用这个关联表,因为如果这么做程序就无法访问其中的自定义字段。相反地,要把这个多对多关系的左右两侧拆分成两个基本的一对多关系,而且要定义成标准的关系。
|
followd和follower都定义为单独的一对多关系,为了消除外键之间的歧义,定义关系时必须选用可选参数foreign_keys指定外键。而且,db.backref()不指定这两个关系之间的引用关系,而是回引Follow模型。
回引中的 lazy 参数指定为 joined 。这个 lazy 模式可以实现立即从联结查询中加载相关对象。例如,如果某个用户关注了 100 个用户,调用user.followed.all()后会返回一个列表,其中包含100个Follow实例,每一个实例的follower和followed回引属性都指向相应的用户。设定为lazy=’joined’模式,就可在一次数据库查询中完成这些操作。如果把lazy设为默认值select,那么首次访问follower和followed属性时才会加载对应的用户,而且每个属性都需要一个单独的查询,这就意味着获取全部被关注用户时需要增加100次额外的数据库查询。
这两个关系中,User一侧设定的lazy参数作用不一样。lazy参数都在“一”这一侧设定,返回的结果是“多”这一侧中的记录。上述代码使用的是dynamic,因此关系属性不会直接返回记录,而是返回查询对象,所以在执行查询之前还可以添加额外的过滤器。
cascade 参数配置在父对象上执行的操作对相关对象的影响。比如,层叠选项可设定为:将用户添加到数据库会话后,要自动把所有关系的对象都添加到会话中。层叠选项的默认值能满足大多数情况的需求,但对这个多对多关系来说却不合用。删除对象时,默认的层叠行为是把对象联接的所有相关对象的外键设为空值。但在关联表中,删除记录后正确的行为应该是把指向该记录的实体也删除,因为这样能有效销毁联接。这就是层叠选项值delete-orphan的作用。
操作数据库
我们可以在shell模式下进行这些操作。
注意:需要完成Chapter5的内容才能来操作这些命令,因为需要在manage.py文件中加入数据库的代码才能在shell中运行db实例。
|
|
使用过滤器可以配置query对象进行更精确的数据库查询。下面列出常用的过滤器,完整的列表请参见SQLAlchemy官方文档:
- filter():把过滤器添加到原查询上,返回一个新查询
- filter_by():把等值过滤器添加到原查询上,返回一个新查询
- limit():使用指定的值限制原查询返回的结果数量,返回一个新查询
- offset():偏移原查询返回的结果,返回一个新查询
- order_by():根据指定条件对原查询结果进行排序,返回一个新查询
- group_by():根据指定条件对原查询结果进行分组,返回一个新查询
在查询上应用指定的过滤器后,通过调用all()执行查询,以列表的形式返回结果。除了all()之外,还有其他方法能触发查询执行。下面列出常用的执行查询方法:
- all():以列表形式返回查询的所有结果
- first():返回查询的第一个结果,如果没有结果,则返回 None
- first_or_404():返回查询的第一个结果,如果没有结果,则终止请求,返回 404 错误响应
- get():返回指定主键对应的行,如果没有对应的行,则返回 None
- get_or_404():返回指定主键对应的行,如果没找到指定的主键,则终止请求,返回 404 错误响应
- count():返回查询结果的数量
- paginate():返回一个 Paginate 对象,它包含指定范围内的结果