Flask-SQLAlchemy
使用Flask-SQLAlchemy
模块编写Flask应用的持久层,将数据库表结构和关系抽象成类,着重编写业务代码,避免了直接写底层SQL语句,简化开发流程
Flask框架是一个特别轻量的Python Web框架,适合中小型项目开发,框架有很多插件对底层配置和代码做了很大的抽象,所以通过简单的阅读文档就可以编写一个Web应用,并且只需要编写业务逻辑即可
Flask-SQLAlchemy
通过对SQLAlchemy包装,简化了对数据库的增删改查操作
表模型
使用模型类来包装表,继承db.Model
类声明一个模型类
1 | class Address(db.Model): |
表结构
通过定义类变量来声明表结构
使用db.Colum()
方法来生成列
1 | id = db.Column(db.Integer, primary_key=True) |
其中可选类型如下
类型名 | 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 | 二进制文件 |
可以使用原生SQLAlchemy来表示unsigned类型
1 | from sqlalchemy.dialects.mysql import BIGINT |
可选的键如下
选项名 | 说 明 |
---|---|
primary_key | 如果设为 True ,这列就是表的主键 |
unique | 如果设为 True ,这列不允许出现重复的值 |
index | 如果设为 True ,为这列创建索引,提升查询效率 |
nullable | 如果设为 True ,这列允许使用空值;如果设为 False ,这列不允许使用空值 |
default | 为这列定义默认值 |
表关系
一对多关系
如建立两个表,Person
和Address
,一个Person
可以有多个Address
,则Person
就代表一而Address
就代表多,则需要在Address
中设置Person_id
的外键,在Flask-SQLAlchemy
中还可以在Person
中声明和其他模型的关系
person_id = db.Column(db.Integer, db.ForeignKey('person.id'), nullable=False)
addresses = db.relationship('Address', backref='person', lazy=True)
这样设置可以在对象中直接调用,如有一个Person
对象Jack,即可以使用Jack.addresses
来获得他所有的Address
数组,这是通过relationship
中第一个参数模型名来实现的
在地址中调用SomeAddress.person
即可以获得连接的Person
对象,这是通过backref
来实现的
lazy
lazy参数说明将怎么加载连接的数据
方法 | 作用 |
---|---|
select | 就是访问到属性的时候,就会全部加载该属性的数据 |
joined | 对关联的两个表使用联接 |
subquery | 与joined类似,但使用子子查询 |
dynamic | 不加载记录,但提供加载记录的查询,也就是生成query对象 |
注意当使用dynamic
时,返回的是一个query对象,需要在其后使用.all()
或.first()
获得模型对象
多对多关系
关系型数据库多对多关系使用中间表来实现,在SQL-Alchemy中中间表不使用模型创建,直接创建表Table
1 | article_tag = db.Table('article_tag', |
使用外键连接其他表
在article
表中使用relationship
中的secondary
来指出次要表,使用backref
来说明反向引用自己的名称
1 | class Article(db.Model): |
就完成了连接
在访问时可以直接使用artical.tags
访问文章的所有标签,使用tag.articles
来访问标签所有的文章
增删改查
增删使用db.session.add()
和db.session.delete()
传入要增加的或者要删除的实体即可
查找使用表模型的query
对象
Retrieve a user by username:
1 | >>> peter = User.query.filter_by(username='peter').first() |
Same as above but for a non existing username gives None:
1 | >>> missing = User.query.filter_by(username='missing').first() |
Selecting a bunch of users by a more complex expression:
1 | >>> User.query.filter(User.email.endswith('@example.com')).all() |
Ordering users by something:
1 | >>> User.query.order_by(User.username).all() |
Limiting users:
1 | >>> User.query.limit(1).all() |
Getting user by primary key:
1 | >>> User.query.get(1) |
更改使用查询出的数据库中实体,改变其属性即可
1 | p = Person.query.get(1) |
返回JSON
使用Flask作为后端,要向前端发送JSON数据,而Flask-SQLAlchemy我还没有找到可以返回字典类型的结果的办法,所以目前找到的办法一共有两种
使用dataclasses
库
Python3.7以上的版本可以使用dataclasses
库来直接将实体模型类返回成JSON数据的HTTP响应
1 | from dataclasses import dataclass |
在使用时可以直接使用Flask中的jsonify
1 |
|
结果为
1 | [ |
1 | [ |
可以发现,当使用dataclass
时,可以将基础类型的数据转换成JSON,还可以将可以将已经声明过dataclass
类型的变量转换成JSON
但是可能发现,如果想实现在Address的结果中加入Person好像有些困难,即显示Address是哪个Person的Person信息,经过我分析尝试后,可以声明一个空变量来实现
1 |
|
其中Person变量的名称要和addresses = db.relationship('Address', backref='p', lazy=True, passive_deletes=True)
中relationship
的backref
参数相同,即可以返回出Person的信息,但要注意删除之前的addresses: Address
,因为这样会互相引用导致无限循环超出JSON嵌套的范围而报错
1 | [ |
没有翻出源码,但是根据观察和relationship
的注释
:param backref:
Indicates the string name of a property to be placed on the related mapper’s class that will handle this relationship in the other direction. The other property will be created automatically when the mappers are configured. Can also be passed as a :func:
.backref
object to control the configuration of the new relationship.
可以知道配置了backref
映射后,会自动生成变量
在查询出的Address对象中可以使用Address.p
来访问其Person对象,实现的方式应该是通过relationship
对Address注入了backref
的变量,但因为变量是在运行时所注入,而在程序启动时没有p变量,因此不可以直接在类下声明p变量的类型,所以先定义一个空对象,其目的在于告诉classdata
这个类存在这个对象,其次在定义p的类型,而在查询获得这个Address实体时,已经被注入,所以进而会生成p变量的JSON字符串
使用查询结果拼接成字典
这个方法好理解并且简单,就是通过访问模型实体中的数据,拼接成JSON字符串如
1 |
|
或者在模型类中加入方法as_dict()
1 | def as_dict(self): |
调用ps[0].as_dict()
方法即可返回字典类型数据,本质还是拼接
优点和缺点
- 第一种方法简单方便,在返回成字符串后不需要使用更多的代码,可以说是直接生成HTTP响应一步到位,看起来也更简约,满足了强迫症,但是我试了好多方法,其中的数据改变是很困难的,包括更改删除和增加,唯一可以实现的途径就是更改查询后的实体模型中的变量,但事后要注意要回滚,否则很容易更改源数据库中的数据,并且所有可以生成的变量全部在代码中写死,
jsonify
返回的更是Response
类型的对象,更改其中数据变得十分困难和麻烦 - 第二种方法要自己一个个拼接字典生成JSON,操作麻烦但是可变型高,可以根据需求酌情选择数据,并且通过模型实体中的关系可以很好的找到其连接的表的数据
在实际中应根据情况选择两者使用的方式和场景,目前只找到这两种方式,其实如果可以直接返回字典类型的数据,根据情况删减是最好的,不知道SQLAlchemy是否支持,也没有找到类似的配置