在 python 中,最有名的 ORM 框架就是 SQLAlchemy,它已成为 python web 服务的标配 ORM 选择,它是一个很强大的关系型数据库框架,支持多种数据库后台,如 MySQL、PostgreSQL、SQLite、 Redis、MongoDB、CouchDB 等。
Flask 也不例外,配有集成 SQLAlchemy 库的 Flask-SQLAlchemy。
使用 Flask-SQLAlchemy
Flask-SQLAlchemy 是一个 Flask 扩展,简化了在 Flask 程序中使用 SQLAlchemy 的操作。
安装
和其他大多数扩展一样,Flask-SQLAlchemy 也使用 pip 安装,如下命令:
(venv) $ pip install flask-sqlalchemy
也可以如下:
(venv) $ pip install Flask-SQLAlchemy
笔者下面演示的是 2.5.1 版本,完整命令如下:
(venv) $ pip install flask_sqlalchemy==2.5.1
使用
在 Flask-SQLAlchemy 中,数据库使用 URL 指定。最流行的数据库引擎采用的数据库 URL 如下:
数据库引擎 | URL |
---|---|
MySQL |
mysql://username:password@hostname/database 如果是使用的 pymysql client 库,可以如下示例: mysql+pymysql://username:password@hostname/database |
PostgreSQL |
postgresql://username:password@hostname/database |
SQLite(Unix) |
sqlite:////absolute/path/to/database |
SQLite(Windows) |
sqlite:///c:/absolute/path/to/database |
SQLite 数据库不需要使用服务器,因此不用指定 hostname、username 和 password。URL 中的 database 是硬盘上文件的文件名。
配置数据库
程序使用的数据库 URL 必须保存到 Flask 配置对象的 SQLALCHEMY_DATABASE_URI 键中。配置对象中还有一个很有用的选项,即 SQLALCHEMY_COMMIT_ON_TEARDOWN
键,将其设为 True 时,每次请求结束后都会自动提交数据库中的变动。
import os
from flask import Flask
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)
db 对象是 SQLAlchemy 类的实例,表示程序使用的数据库,同时还获得了 Flask-SQLAlchemy 提供的所有功能。
如果 Flask 程序绑定多个数据库,需要在 app 设置中指定 SQLALCHEMY_BINDS
,示例如下:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 绑定多个数据库
app.config['SQLALCHEMY_BINDS'] = {
'search': 'mysql+pymysql://user_name:password@host_or_ip:port/db_search',
'recommend': 'mysql+pymysql://user_name:password@host_or_ip:port/db_recommend',
}
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
定义模型
模型这个术语表示程序使用的持久化实体。在 ORM 中,模型一般是一个 Python 类,类中的属性对应数据库表中的列。
Flask-SQLAlchemy 创建的数据库实例为模型提供了一个基类以及一系列辅助类和辅助函数,可用于定义模型的结构。
class UserRole(db.Model):
__bind_key__ = 'search'
__tablename__ = 'user_role'
__table_args__ = (
# 联合唯一索引
db.UniqueConstraint('uid', 'rid', name='uniq_uid_rid'),
)
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
uid = db.Column(db.BIGINT)
rid = db.Column(db.Integer)
class User(db.Model):
__bind_key__ = 'recommend'
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
类变量 __bind_key__
指定对应的数据库,__tablename__
定义在数据库中使用的表名。如果没有定义 __tablename__,Flask-SQLAlchemy 会使用一个默认名字,但默认的表名没有遵守使用复数形式进行命名的约定,所以最好由我们自己来指定表名,其它的表相关信息,如索引、引擎、编码等都用 __table_args__
变量里定义。其余的类变量都是该模型的属性,被定义为 db.Column 类的实例。
db.Column 类构造函数的第一个参数是数据库列和模型属性的类型。
类型名 | Python 类型 | 说明 |
---|---|---|
Integer | int | 普通整数,一般是32位 |
SmallInteger | int | 取值范围小的整数,一般是16位 |
BigInteger | int 或 long | 不限制精度的整数 |
Float | float | 浮点数 |
Numeric | 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.timedelta | 时间间隔 |
Enum | str | 一组字符串 |
PickleType | 任何 Python 对象 | 自动使用 Pickle 序列化 |
LargeBinary | str | 二进制文件 |
db.Column 中其余的参数指定属性的配置选项。
选项名 | 说明 |
---|---|
primary_key | 如果设为 True,这列就是表的主键。 |
unique | 如果设为 True,这列不允许出现重复的值。 |
index | 如果设为 True,为这列创建索引,提升查询效率。 |
nullable | 如果设为 True,这列允许使用空值;如果设为 False,这列不允许使用空值。 |
default | 为这列定义默认值。 |
Flask-SQLAlchemy 要求每个模型都要定义主键,这一列经常命名为 id。
数据库操作
创建表
首先,我们要让 Flask-SQLAlchemy 根据模型类创建数据库。方法是使用 db.create_all() 函数:
db.create_all()
插入操作
>>> from db_op 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()
数据库会话能保证数据库的一致性。提交操作使用原子方式把会话中的对象全部写入数据 库。如果在写入会话的过程中发生了错误,整个会话都会失效。如果你始终把相关改动放 在会话中提交,就能避免因部分更新导致的数据库不一致性。
数据库会话也可回滚。调用 db.session.rollback() 后,添加到数据库会话 中的所有对象都会还原到它们在数据库时的状态。
更新操作
在数据库会话上调用 add() 方法也能更新模型。
>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()
删除操作
数据库会话还有个 delete() 方法。下面这个例子把 "Moderator" 角色从数据库中删除:
>>> db.session.delete(mod_role)
>>> db.session.commit()
查询操作
Flask-SQLAlchemy 为每个模型类都提供了 query 对象。最基本的模型查询是取回对应表中的所有记录:
>>> Role.query.all()
使用过滤器可以配置 query 对象进行更精确的数据库查询。
>>> User.query.filter_by(role=user_role).all()
若要查看 SQLAlchemy 为查询生成的原生 SQL 查询语句,只需把 query 对象转换成字符串:
>>> 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'
filter_by() 等过滤器在 query 对象上调用,返回一个更精确的 query 对象。多个过滤器可以一起调用,直到获得所需结果。
过滤器 | 说明 |
---|---|
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 对象,它包含指定范围内的结果 |