flask-sqlalchemy扩展

1、基本使用

1.1、认识flask-sqlalchemy


sql-logo

  • 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

mysqlclientpymysql 都是用于mysql访问的依赖包, 前者由C语言实现的, 而后者由python实现, 前者的执行效率比后者更高, 但前者在windows系统中兼容性较差, 工作中建议优先前者。

文档

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
View Code

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)
View Code

方式二:

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
View Code

方式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)
View Code

注意点:

  • 模型类必须继承 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)
View Code

注意点:

  • 给模型对象设置数据 可以通过 初始化参数 或者 赋值属性 两种方式
  • 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)
View Code

查询语法

有两套查询语法可以使用:

  • 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 和 数据库查询缓存)

posted on 2020-11-01 18:55  yycnblog  阅读(313)  评论(0编辑  收藏  举报

导航