外观
使用Flask-SQLAlchemy管理数据库
扩展Flask-SQLAlchemy集成了SQLAlchemy,它简化了连接数据库服务器、管理数据库操作会话等各类操作,让Flask中的数据处理变得更轻松。
连接数据库服务器
DBMS通常以服务器形式运行在操作系统中,要连接数据库服务器,首先要为程序指定数据库URI。一些数据库管理系统的URI格式如下表所示:
| 数据库引擎 | URI |
|---|---|
| MySQL | mysql://username:password@hostname/database |
| Postagres | postgresql://username:password@hostname/database |
| SQLite(Unix) | sqlite:////absolute/path/database |
| SQLite(Windows) | sqlite:///c:/absolute/path/database |
在这些URI中,hostname表示MySQL所在MySQL服务器所在的主机,可以是本地主机也可以是远程主机;数据服务器上可以托管多个数据库,因此database表示使用的数据库名,username和password分别代表用户名和密码。
如使用SQLAlchemy连接本地MySQL的URI如下:
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost/db?charset=utf8mb4'URI的各部分意义如下:
- 数据库:MySQL
- 数据库驱动:pymysql
- 数据库名:root
- 数据库密码:root
- 数据库名称:db
- 字符集:utf8mb4
定义数据模型
在连接数据库模式之后,创建数据表之前,要先创建数据模型。处理数据表中字段和面向对象语言中对象属性之间关系的模式叫做ORM。在Flask-SQLAlchemy中,一个数据表对应一个类,一个字段代表类的一个属性。
以用户和权限模型为例,代码如下:
class Role(db.Model):
__tablename__ = 'tb_role'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
def __repr__(self):
return f'<Role {self.name}'
class User(db.Model):
__tablename__ = 'tb_user'
id = db.Column(db_Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
def __repr__(self):
return f'User {self.username}'类变量tablename定义在数据库中使用的表名,其余的类变量都是该模型的属性,被定义为db.Column类的实例。db.Column类构造函数的第一个参数为数据库列和模型属性的类型,最常用的列类型以及在模型中使用Python类型如下表所示:
| 类型名 | 对应的Python类型 | 说明 |
|---|---|---|
| Integer | int | 普通整数,一般是32位 |
| SmallInteger | int | 取值范围最少的整数,一般是16位 |
| BigInteger | int或long | 不限制精度的整数 |
| Float | float | 浮点数 |
| Numbric | decimal.Decimal | 浮点数 |
| String | str | 变长字符串 |
| Text | str | 变长字符串,对较长或不限长度的字符串做了优化 |
| Unicode | unicode | 变长Unicode字符串 |
| UnicodeText | unicode | 变长Unicode字符串,对较长或不限长度的做了优化 |
| Boolean | bool | 布尔值 |
| Date | datetime.date | 日期 |
| Time | datetime.time | 时间 |
| DateTime | datetime.datetime | 时间和日期 |
| Interval | datetime.datedeta | 时间间隔 |
| Enum | str | 一组字符串 |
| PickleType | 任何Python对象 | 自动化使用Pickle序列化 |
| LargeBinary | str | 二进制文件 |
最常用的SQLAlchemy列选项如下表所示:
| 选项名 | 说明 |
|---|---|
| primary_key | 设为True,表示该列为表的主键 |
| unique | 设为True,表示该列不允许出现重复 |
| index | 设为True,表示为该列创建索引 |
| nullable | 设为True,表示该列不能为空值 |
| default | 为该列定义默认值 |
定义关系
在关系数据库中,数据模型间的关系包含一对一、一对多、多对多关系。常用的SQLAlchemy关系如下表:
| 选项名 | 说明 |
|---|---|
| backref | 在关系的另一个模型中添加反向引用 |
| primaryjoin | 明确指定两个模型之间的反向引用,只需要在模棱两可的关系间指定 |
| lazy | 指定如何加载相关记录,可选值有select,表示首次访问时按需加载;immediate,表示源对象加载后加载;joined,表示加载记录,但使用连结;subquery,表示立即加载,但使用子查询;noload,表示永不加载;dynamic,表示不加载记录,但提供加载记录的查询 |
| uselist | 若设为False,不使用列表,而使用标量值 |
| order_by | 指定关系中记录的排序方式 |
| secondary | 指定多对多关系中表的名字 |
| secordaryjoin | SQLAlchemy无法自行决定时,指定多对多关系中的二级连结条件 |
在上面用户和角色的例子中,一个user只能分配一个role,一个role可以分给多个user,所以是一对多的关系,在user表中设置外键引用role表中的主键。示例代码如下:
class Role(db.Model):
__tablename__ = 'tb_role'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
# 新增:
users = db.relitionship('User', backref=True, lazy='dynamic')
def __repr__(self):
return f'<Role {self.name}'
class User(db.Model):
__tablename__ = 'tb_user'
id = db.Column(db_Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
# 新增:
role_id = db.Column(db.Integer, db.ForeignKey('tb_role.id'))
def __repr__(self):
return f'User {self.username}'除一对多关系外,还有其他的关系类型。一对一关系可以用前面的一对多关系表示,但调用db.relitionship()时要将uselist设为False,把“多”变成“一”。多对一关系也可以使用一对多关系表示,对调两张表即可,或者把外键和db.relationship()都放在“多”那一侧。最复杂的关系是多对多,需要建立第三个表,即关系表。
以用户收藏课程为例,一个user可以收藏多个course,而一个course可以被多个user收藏,所以它们是多对多的关系。定义一个多对多关系模型,示例代码如下:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
username = db.Column(db.String(64), nullable=False)
# secondary:在多对多关系,指定关联表的名称
favorites = db.relationship('Course', secondary='collections', backref=db.backref('user', lazy='dynamic'), lazy='dynamic')
class Course(db.Model):
course_id = db.Column(db.BigInteger, nullable=False, primary_key=True)
# 创建一个收藏的中间表
collections = db.Table('collections', db.Column('user_id', db.Integer, db.Foreign_key('user_id')), db.Column('course_id', db.BigInteger, db.ForeignKey('course.course.id')))上述代码中,在User表中定义了favorites属性,并在relationship()方法中添加了两个属性:
- secondary='collections':指定中间表为collections。
- backref=db.backref('user',lazy='dynamic'):设置backref,添加反向引用,所以在Course表中不需要在使用relationship设置外键。
在中间表collections中需要设置user_id和course_id列属性,并且都设置成外键。
数据库操作
数据库最常见的操作就是增删改查,下面介绍这四个操作
创建数据
在查询内容之前,必须先插入数据。将数据插入数据库的过程分为如下三步:
- 创建Python对象,
- 将其添加到会话中,
- 提交会话。
这里的会话不是Flask会话,而是Flask-SQLAlchemy,其本质上是数据库事务的增强版本。下面介绍如何新增一个用户,示例代码如下:
from models import User
me = User('admin', 'admin@admin.com')
db.session.add(me)
db.session.commit()在将对象添加至会话之前,SQLAlchemy基本上不打算将其添加到事务中。此时,仍然可以放弃更改。add()方法可以将用户对象添加到会话中,但是不会提交到数据库。在使用commit()方法之后,会话才能被提交至数据库。
读取数据
添加完数据后,就可以从数据库中查询数据了。使用模型类提供的query属性,然后调用各种过滤方法及查询方法,即可从数据库中查询数据。
通常,一个完整的查询代码如下:
<模型类>.query.<过滤方法>.<查询方法>例如,查询User表中用户名为qi的用户信息,示例代码如下:
User.query.filter(username='qi').get()上面的示例中,filter()是过滤方法,get()是查询方法。在Flask-SQLAlchemy中,常用的过滤方法如下:
| 过滤器 | 说明 |
|---|---|
| filter() | 把过滤器添加到原查询上,返回一个新查询 |
| filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
| limit() | 使用指定的值显示原查询返回的查询结果数量,返回一个新查询 |
| offset() | 偏移原查询返回的结果,返回一个新查询 |
| order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
| group_by() | 根据指定查询条件对原查询进行分组,返回一个新查询 |
常用的查询如下:
| 方法 | 说明 |
|---|---|
| all() | 以列表形式返回查询的所有结果 |
| first() | 返回查询的第一个结果,若无结果则返回None |
| first_or_404() | 返回查询的第一个结果,若无结果则终止请求同时返回404响应 |
| get() | 返回指定主键对应的行,若无结果则返回None |
| get_or_404() | 返回指定主键对应的行,若无结果则终止请求并返回404响应 |
| count() | 返回查询结果的数量 |
| paginate() | 返回一个Paginate对象,包含指定范围内的结果 |
在实际的开发过程中,使用的数据查询方式比较多,下面介绍一些常用的查询方式:
根据主键查询。在get()方法中传递主键:
User.query.get(1)精确查询。使用filter_by()方法设置查询条件:
User.query.filter_by(username='qi').first()使用filter()方法设置查询条件:
User.query.filter(User.username='qi').first()模糊查询:
users = User.query.filter(User.email.endsWith('@example.com')).all()逻辑非查询:
users = User.query.filter(User.username != 'qi').all()使用not_执行逻辑非查询:
from sqlalchemy import not_ users = User.query.filter(not_(User.username == 'qi')).all()使用and_执行逻辑与查询
from sqlalchemy import and_ users = User.query.filter(and_(User.username == 'qi', User.email.endsWith('@example.com'))).all()使用or_执行逻辑或查询
from sqlalchemy import or_ users = User.query.filter(or_(User.username == 'qi', User.username == 'Qi')).all()查询结果排序:
User.query.order_by(User.username)限制返回数目:
User.query.limit(10).all()
更新数据
更新数据非常简单,直接赋值给模型的字段属性即可,然后调用commit()方法。
例:
user = User.query.first()
user.username = 'guest'
db.session.commit()删除数据
删除数据也非常简单,只需要将插入数据的add()换成delete()即可。例:
user = User.query.fitst()
db.session.delete()
db.session.commit()