flask-sqlalchemy扩展
1、基本使用
1.1、认识flask-sqlalchemy
Flask-SQLAlchemy 是一个为 Flask 应用增加
SQLAlchemy
支持的扩展。它致力于简化在 Flask 中SQLAlchemy
的使用。SQLAlchemy
是目前python中最强大的 ORM框架, 功能全面, 使用简单。
ORM优缺点
- 优点
- 有语法提示, 省去自己拼写SQL,保证SQL语法的正确性
- orm提供方言功能(dialect, 可以转换为多种数据库的语法), 减少学习成本
- 面向对象, 可读性强, 开发效率高
- 防止sql注入攻击
- 搭配数据迁移, 更新数据库方便
- 缺点
- 需要语法转换, 效率比原生sql低
- 复杂的查询往往语法比较复杂 (可以使用原生sql替换)
安装flask-sqlalchemy
pip install flask-sqlalchemy
flask-sqlalchemy
在安装/使用过程中, 如果出现ModuleNotFoundError: No module named 'MySQLdb'
错误, 则表示缺少mysql依赖包, 可依次尝试下列两个方案后重试:方案1: 安装
mysqlclient
依赖包 (如果失败再尝试方案2)
pip install mysqlclient
- 方案2: 安装
pymysql
依赖包
pip install pymysql
mysqlclient
和pymysql
都是用于mysql访问的依赖包, 前者由C语言实现的, 而后者由python实现, 前者的执行效率比后者更高, 但前者在windows系统中兼容性较差, 工作中建议优先前者。
文档
- 官方文档 https://flask-sqlalchemy.palletsprojects.com/en/2.x/
- 中文翻译 http://www.pythondoc.com/flask-sqlalchemy/index.html
1.2、组件初始化
1.2.1、基本配置
flask-sqlalchemy
的相关配置也封装到了flask
的配置项中, 可以通过app.config
属性 或 配置加载方案 (如config.from_object
) 进行设置- 主要配置
配置项 | 说明 |
SQLALCHEMY_DATABASE_URI | 设置数据库的连接地址 |
SQLALCHEMY_BINDS | 访问多个数据库时, 用于设置数据库的连接地址 |
SQLALCHEMY_ECHO | 是否打印底层执行的SQL语句 |
SQLALCHEMY_RECORD_QUERIES | 是否记录执行的查询语句, 用于慢查询分析, 调试模式下自动启动 |
SQLALCHEMY_TRACK_MODIFICATIONS | 是否追踪数据库变化(触发钩子函数), 会消耗额外的内存 |
SQLALCHEMY_ENGINE_OPTIONS | 设置针对 sqlalchemy 本体的配置项 |
数据库URI(连接地址)格式: 协议名://用户名:密码@数据库IP:端口号/数据库名
, 如:
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test31'
注意点:
- 如果数据库驱动使用的是 pymysql, 则协议名需要修改为
mysql+pymysql://xxxxxxx
sqlalchemy
支持多种关系型数据库, 其他数据库的URI可以查阅 官方文档
代码示例:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 设置数据库连接地址 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test31' # 是否追踪数据库修改(开启后会触发一些钩子函数) 一般不开启, 会影响性能 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 是否显示底层执行的SQL语句 app.config['SQLALCHEMY_ECHO'] = True
1.2.2、两种初始化方式
flask-sqlalchemy
支持两种组件初始化方式:
- 方式1: 创建组件时, 直接关联Flask应用
- 方式2: 先创建组件, 延后关联Flass应用
方式一:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 应用配置 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test31' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_ECHO'] = True # 方式1: 初始化组件对象, 直接关联Flask应用 db = SQLAlchemy(app)
方式二:
from flask import Flask from flask_sqlalchemy import SQLAlchemy # 方式2: 初始化组件对象, 延后关联Flask应用 db = SQLAlchemy() def create_app(config_type): """工厂函数""" # 创建应用 flask_app = Flask(__name__) # 加载配置 config_class = config_dict[config_type] flask_app.config.from_object(config_class) # 关联flask应用 db.init_app(app) return flask_app
方式2主要针对的是 动态创建应用 的场景
1.3、构建模型类
flask-sqlalchemy
的关系映射和 Django-orm
类似:
- 类 对应 表
- 类属性 对应 字段
- 实例对象 对应 记录
代码示例:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 相关配置 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test31' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_ECHO'] = True # 创建组件对象 db = SQLAlchemy(app) # 构建模型类 类->表 类属性->字段 实例对象->记录 class User(db.Model): __tablename__ = 't_user' # 设置表名, 表名默认为类名小写 id = db.Column(db.Integer, primary_key=True) # 设置主键, 默认自增 name = db.Column('username', db.String(20), unique=True) # 设置字段名 和 唯一约束 age = db.Column(db.Integer, default=10, index=True) # 设置默认值约束 和 索引 if __name__ == '__main__': # 删除所有继承自db.Model的表 db.drop_all() # 创建所有继承自db.Model的表 db.create_all() app.run(debug=True)
注意点:
- 模型类必须继承 db.Model, 其中 db 指对应的组件对象
- 表名默认为类名小写, 可以通过
__tablename__
类属性 进行修改 - 类属性对应字段, 必须是通过 db.Column() 创建的对象
- 可以通过
create_all()
和drop_all()
方法 来创建和删除所有模型类对应的表
常用的字段类型
类型名 | python接收类型 | mysql生成类型 | 说明 |
Integer | int | int | 整型 |
Float |
float |
float |
浮点型 |
Boolean | bool | tinyint | 整型,只占一个字节 |
Text | str | text | 文本类型, 最大64KB |
LongText | str | longtext | 文本类型, 最大4GB |
String | str | varchar | 变长字符串, 必须限定长度 |
Date |
datetime.date |
date |
日期 |
DateTime | datetime.datetime | datetime | 日期和时间 |
Time |
datetime.time |
time |
时间 |
常用的字段选项
选项名 | 说明 |
primary_key | 如果为True,表示该字段为表的主键, 默认自增 |
unique | 如果为True,代表这列设置唯一约束 |
nullable | 如果为False,代表这列设置非空约束 |
default | 为这列设置默认值默认 |
index |
如果为True,为这列创建索引,提高查询效率 |
注意点: 如果没有给对应字段的类属性设置default
参数, 且添加数据时也没有给该字段赋值, 则 sqlalchemy
会给该字段设置默认值 None
2、数据操作
2.1、增加操作
增加数据主要需要三步操作
- 创建模型对象
模型对象 = 模型类(字段名=字段值)
- 将模型对象添加到会话中
组件对象.session.add(模型对象)
- 提交会话
组件对象.session.commit()
代码示例:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 相关配置 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test31' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_ECHO'] = True # 创建组件对象 db = SQLAlchemy(app) # 构建模型类 class User(db.Model): __tablename__ = 't_user' id = db.Column(db.Integer, primary_key=True) name = db.Column('username', db.String(20), unique=True) age = db.Column(db.Integer, index=True) @app.route('/') def index(): """增加数据""" # 1.创建模型对象 user1 = User(name='zs', age=20) # user1.name = 'zs' # user1.age = 20 # 2.将模型对象添加到会话中 db.session.add(user1) # 添加多条记录 # db.session.add_all([user1, user2, user3]) # 3.提交会话 (会提交事务) # sqlalchemy会自动创建隐式事务 # 事务失败会自动回滚 db.session.commit() return "index" if __name__ == '__main__': db.drop_all() db.create_all() app.run(debug=True)
注意点:
- 给模型对象设置数据 可以通过 初始化参数 或者 赋值属性 两种方式
session.add(模型对象)
添加单条数据到会话中,session.add_all(列表)
添加多条数据到会话中- 这里的 会话 并不是 状态保持机制中的
session
,而是sqlalchemy
的会话。它被设计为 数据操作的执行者, 从SQL角度则可以理解为是一个 加强版的数据库事务 sqlalchemy
会 自动创建事务, 并将数据操作包含在事务中, 提交会话时就会提交事务- 事务提交失败会自动回滚
2.2、查询数据
sqlalachemy
的查询语法较多, 接下来通过一个案例来进行综合演练- 案例说明
- 案例中包含一个模型类
User
, 对应users
表, 包含四个字段:id
(主键),name
,email
,age
- 首先运行案例代码, 生成测试数据
- 程序启动后会重置
users
表, 并向其中添加10条用户数据
- 程序启动后会重置
- 为了方便展示查询结果, 建议使用 交互模式 测试查询语句
- 推荐使用
ipython
包, 相比 python自带的交互模式 有语法提示 - 安装包
pip install ipython
- 推荐使用
- 关于输出结果
- 内置方法
__repr__()
是__str__()
方法 的升级版, 可以修改 print(对象) 和 交互模式下对象 的输出结果 - 案例中将 模型对象的输出结果 修改为 输出模型对象的所有属性值 (记录的数据), 以便验证查询结果
- 内置方法
- 案例中包含一个模型类
示例代码:
# hm_03_数据查询.py from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 相关配置 app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:mysql@127.0.0.1:3306/test31" app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False app.config["SQLALCHEMY_ECHO"] = False db = SQLAlchemy(app) # 自定义类 继承db.Model 对应 表 class User(db.Model): __tablename__ = "users" # 表名 默认使用类名的小写 # 定义类属性 记录字段 id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64)) email = db.Column(db.String(64)) age = db.Column(db.Integer) def __repr__(self): # 自定义 交互模式 & print() 的对象打印 return "(%s, %s, %s, %s)" % (self.id, self.name, self.email, self.age) @app.route('/') def index(): # 查询所有用户数据 User.query.all() 返回列表, 元素为模型对象 # 查询有多少个用户 User.query.count() # 查询第1个用户 User.query.first() 返回模型对象/None # 查询id为4的用户[3种方式] # 方式1: 根据id查询 返回模型对象/None User.query.get(4) # 方式2: 等值过滤器 关键字实参设置字段值 返回BaseQuery对象 # BaseQuery对象可以续接其他过滤器/执行器 如 all/count/first等 User.query.filter_by(id=4).all() # 方式3: 复杂过滤器 参数为比较运算/函数引用等 返回BaseQuery对象 User.query.filter(User.id == 4).first() # 查询名字结尾字符为g的所有用户[开始 / 包含] User.query.filter(User.name.endswith("g")).all() User.query.filter(User.name.startswith("w")).all() User.query.filter(User.name.contains("n")).all() User.query.filter(User.name.like("w%n%g")).all() # 模糊查询 # 查询名字和邮箱都以li开头的所有用户[2种方式] User.query.filter(User.name.startswith('li'), User.email.startswith('li')).all() from sqlalchemy import and_ User.query.filter(and_(User.name.startswith('li'), User.email.startswith('li'))).all() # 查询age是25 或者 `email`以`itheima.com`结尾的所有用户 from sqlalchemy import or_ User.query.filter(or_(User.age==25, User.email.endswith("itheima.com"))).all() # 查询名字不等于wang的所有用户[2种方式] from sqlalchemy import not_ User.query.filter(not_(User.name == 'wang')).all() User.query.filter(User.name != 'wang').all() # 查询id为[1, 3, 5, 7, 9]的用户 User.query.filter(User.id.in_([1, 3, 5, 7, 9])).all() # 所有用户先按年龄从小到大, 再按id从大到小排序, 取前5个 User.query.order_by(User.age, User.id.desc()).limit(5).all() # 查询年龄从小到大第2-5位的数据 2 3 4 5 User.query.order_by(User.age).offset(1).limit(4).all() # 分页查询, 每页3个, 查询第2页的数据 paginate(页码, 每页条数) pn = User.query.paginate(2, 3) pn.pages 总页数 pn.page 当前页码 pn.items 当前页的数据 pn.total 总条数 # 查询每个年龄的人数 select age, count(name) from t_user group by age 分组聚合 from sqlalchemy import func data = db.session.query(User.age, func.count(User.id).label("count")).group_by(User.age).all() for item in data: # print(item[0], item[1]) print(item.age, item.count) # 建议通过label()方法给字段起别名, 以属性方式获取数据 # 只查询所有人的姓名和邮箱 优化查询 User.query.all() # 相当于select * from sqlalchemy.orm import load_only data = User.query.options(load_only(User.name, User.email)).all() # flask-sqlalchem的语法 for item in data: print(item.name, item.email) data = db.session.query(User.name, User.email).all() # sqlalchemy本体的语法 for item in data: print(item.name, item.email) return 'index' if __name__ == '__main__': # 删除所有表 db.drop_all() # 创建所有表 db.create_all() # 添加测试数据 user1 = User(name='wang', email='wang@163.com', age=20) user2 = User(name='zhang', email='zhang@189.com', age=33) user3 = User(name='chen', email='chen@126.com', age=23) user4 = User(name='zhou', email='zhou@163.com', age=29) user5 = User(name='tang', email='tang@itheima.com', age=25) user6 = User(name='wu', email='wu@gmail.com', age=25) user7 = User(name='qian', email='qian@gmail.com', age=23) user8 = User(name='liu', email='liu@itheima.com', age=30) user9 = User(name='li', email='li@163.com', age=28) user10 = User(name='sun', email='sun@163.com', age=26) # 一次添加多条数据 db.session.add_all([user1, user2, user3, user4, user5, user6, user7, user8, user9, user10]) db.session.commit() app.run(debug=True)
查询语法
有两套查询语法可以使用:
flask-sqlalchemy
扩展 封装的语法为查询结果 = 模型类.query[.查询过滤器].查询执行器
, 返回的查询结果中数据单元为对应的 模型对象sqlalchemy
本体 提供的语法为组件对象.session.query(字段)[.查询过滤器].查询执行器
, 返回的数据单元为 类元组对象, 该类型支持 索引、属性名 以及 别名 三种取值方式- 查询过滤器非必须, 查询执行器必须设置
- 除了特殊查询语句(联表/聚合等)需要使用
sqlalchemy
本体的语法, 一般使用flask-sqlalchemy
扩展 封装的语法即可
常用的SQLAlchemy查询执行器
执行器的特点:
- 将整个查询语句转换为SQL语句并 执行查询
- 在查询语句的末尾设置, 每条查询语句 只能设置一个执行器
方法 | 说明 |
all() | 返回列表, 元素为所有符合查询的模型对象 |
count() | 返回查询结果的数量 |
first() | 返回符合查询的第一个模型对象,如果未查到,返回None |
first_or_404() | 返回符合查询的第一个模型对象,如果未查到,返回404 |
get(主键) | 返回主键对应的模型对象,如不存在,返回None |
get_or_404(主键) | 返回指定主键对应的模型对象,如不存在,返回404 |
paginate(页码, 每页条数) | 返回一个Paginate对象,它包含分页查询的结果 |
常用的SQLAlchemy查询过滤器
- 过滤器的特点:
- 只负责设置过滤条件, 不会执行查询(查询由执行器来完成)
- 允许续接其他过滤器 或 执行器
过滤器 | 说明 |
filter_by(字段名=值) | 把等值过滤器添加到原查询上,返回BaseQuery对象 |
filter(函数引用/比较运算) | 把过滤器添加到原查询上,返回BaseQuery对象 |
limit(限定条数) | 使用指定的值限定原查询返回的结果,返回BaseQuery对象 |
offset(偏移条数) | 根据指定的值按照原查询进行偏移查询,返回BaseQuery对象 |
order_by(排序字段) | 根据指定条件对原查询结果进行排序,返回BaseQuery对象 |
group_by(分组字段) | 根据指定条件对原查询结果进行分组,返回BaseQuery对象 |
options() | 针对原查询限定查询的字段,返回BaseQuery对象 |
注意点:
- 查询过滤器返回的都是
BaseQuery
类型对象, 该对象支持链式调用, 即可以续接其他过滤器 或 执行器 - 如果考虑到性能优化, 应该避免
select *
, 只查询需求字段 (select * 会导致数据库服务器去获取&解析&处理目标数据的每个字段, 对服务器资源造成浪费, 并且不必要的数据也会占用更多的 网络IO 和 数据库查询缓存)