Loading... # Flask 数据库相关 ## 数据库类型 SQL 和 NoSQL。 SQL 数据库高效、紧凑方式储存结构化数据,需要花费大量精力保证数据一致性。 NoSQL 数据库放宽对一致性要求,从而获得性能优势。 ## ORM 数据库引擎和数据库抽象层。 * 对象关系映射 Object-Relational Mapper, ORM * 对象文档映射 Object-Document Mapper, ODM 1. **在用户不知觉情况下把高层的面相对象操作的指令转换成低层数据库指令** 2. 对数据库业务有一定性能损耗,单生产率大幅提升,选取抽象层的关键: 1. 可移植性, 支持哪种数据库引擎 1. SQLAlchemy ORM 支持 Mysql, Postgres, SQlite 2. Flask 集成度 ## Flask-SQlAlchemy ``` pip install flask-sqlalchemy # 是一个 Flask 扩展 ``` ### 配置 1. 数据库 URL: SQLALCHEMY_DATABASE_URL 2. 请求结束后自动提交数据库变动: 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 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True # Deal FSADeprecation Warning # 获得数据库类实例 db = SQLAlchemy(app) ``` #### 定义模型 **模型** 这个术语表示程序使用的持久化实体。在 orm 中,模型一般是一个 Python 类,**类的属性对应数据库表的类。** [*当这个类的属性发生更改时,数据库也要迁移* ] ##### 常用的SQLAlchemy字段类型 | 类型名 | python中类型 | 说明 | | -------------- | -------------------- | ----------------------------------------------------- | | Integer | int | 普通整数,一般是32位 | | SmallInteger | int | 取值范围小的整数,一般是16位 | | BigInteger | int或long | 不限制精度的整数 | | Float | float | 浮点数 | | Numeric | decimal.Decimal | 普通整数,一般是32位 | | String | str | 变长字符串 | | Text | str | 变长字符串,对较长或不限长度的字符串做了优化 | | Unicode | unicode | 变长Unicode字符串 | | UnicodeText | unicode | 变长Unicode字符串,对较长或不限长度的字符串做了优化 | | Boolean | bool | 布尔值 | | Date | datetime.date | 日期 | | Time | datetime.time | 时间 | | DateTime | datetime.datetime | 日期和时间 | | Internal | datetime.timedelta | 时间间隔 | | Enum | str | 一组字符串 | | PickleType | 任何 python 对象 | 自动使用 Pickle 序列化 | | LargeBinary | str | 二进制文件 | ##### 常用的SQLAlchemy列选项 | 选项名 | 说明 | | ------------- | --------------------------------------------------- | | primary_key | 如果为True,代表表的主键 | | unique | 如果为True,代表这列不允许出现重复的值 | | index | 如果为True,为这列创建索引,提高查询效率 | | nullable | 如果为True,允许有空值,如果为False,不允许有空值 | | default | 为这列定义默认值 | ##### 常用的SQLAlchemy关系选项 关系型数据库使用关系把不同表中的行联系起来。一个角色可以属于多个用户,每个用户只能一种角色。 | 选项名 | 说明 | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | backref | 在关系的另一模型中添加反向引用 | | primary join | 明确指定两个模型之间使用的联结条件,只在模凌两可的关系中需要制定 | | uselist | 如果为False,不使用列表,而使用标量值 | | order_by | 指定关系中记录的排序方式 | | secondary | 指定**多对多** 中记录的排序方式 | | secondary join | 在SQLAlchemy中无法自行决定时,指定**多对多** 关系中的二级联结条件 | | lazy | 指定如何家在相关记录,可选值有select(首次访问时按需加载)、immediate(源对象就绪后加载)、joined(加载记录,但使用联结)、subquery(立即加载,但使用子查询)、noload(用不加载)、dynamic(不加载记录,但提供加载记录的查询) | 以下展示常见的一种**一对多** 关系在模型类中的定义。 ``` class Role(db.Model): # 定义表名 __tablename__ = 'roles' # 定义字段 id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(64), unique=True) users = db.relationship('User', backref='role') # 反推与role关联的多个User模型对象 class User(db.Model): # 定义表名 __tablename__ = 'users' # 定义字段 id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(64), unique=True, index=True) email = db.Column(db.String(64), unique=True) pswd = db.Column(db.String(64)) # 设置外键,外键建立关系,传给 db.ForeignKEy() 的参数 'roles.id' 表明,这列的值是 roles 表中 id 值 role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) ``` 以下是代码的一些说明: 1. 添加到Role模型中的users属性代表这个关系的面相对象视角。对于一个Role类的实例,其users属性将返回与角色相关联的用户组成的列表。 db.relationship() 第一个参数表示这个关系的另一端所指模型,如果模型尚未定义,可以用字符串指定。 2. db.relationship() 中 backref 参数向User模型添加了一个role属性,并且指定为反向关系。这一属性可以替代role_id访问Role模型,此时获取的是模型对象,而不是外键的值。 3. 除了一对多以外,还有其他关系类型。 **一对一** 、**多对一** 、**多对多** 。 1. 一对一 ``` db.Relationship('User',backref='role',uselist=False) ``` 2. 多对一 本质上和一对多没有差别,可以对调两个表 3. 多对多 建立一个关系表,建立复杂映射(这个坑先放在这,以后慢慢填hhh ### 数据库操作 #### 创建表 上面已经定义了程序所需要的模型,但数据库目前只是初始化实例,没有真正对其进行读写(也就是创建 下面介绍两个相关的函数: 1. db.create_all() 2. db.drop_all() 顾名思义,就是创建表和丢弃表的意思,上面已经提到,当修改模型的时候需要对模型进行迁移,这是因为如果数据库表已经存在于数据库中,create_all() 函数便不会创建或更新函数,现在我们可以用drop_all()函数进行丢弃后重建,但这又造成数据库原有的函数丢失。这个问题我们先留存,稍后介绍一个更好的方式实现数据库迁移。 ``` db.drop_all() db.create_all() ``` #### 插入行 上面已经定义了用户和用户角色,现在我们真正来创建一些角色。 新建一个 demo.py,我们来做一些有意义的操作吧: ``` import os from flask import Flask from flask_sqlalchemy import SQLAlchemy from app import Role, User # 从刚刚定义的文件导入身份类 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 # Deal FSADeprecation Warning app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True # 获得数据库类实例 db = SQLAlchemy(app) # 创造用户身份,现在这些变量只存在于内存,并没有真正写入数据库 admin_role = Role(name='admin') user_role = Role(name='user') # 因为没有写入数据库,所以用户身份的id都没有赋值 print(admin_role.id, user_role.id) # 通过数据库会话管理改动,先添加再提交(和Git一样哦 db.session.add(admin_role) db.session.add(user_role) db.session.commit() # 现在可以看到相关的输出了 :) print(admin_role.id, user_role.id) ``` 数据库会话能保证数据库的一致性,也就是说保证数据库在任何时候被任意线程调用的时候数据一致。(现在好像还没有介绍线程、进程的概念,逃…… 不过很快就会引入相关概念啦 如果再写入会话的时候发生错误,整个会话也就失效了,应该始终把相关改动在会话内提交,避免因为数据库部分更新导致数据库不一致。 数据库会话提交错误的话也可以回滚,回滚的英文为 rollback,非常的形象不是吗(往回滚hhh ``` db.session.rollback() ``` #### 修改行 ``` # 通过add方法更新模型 print(admin_role.name) admin_role.name = 'administrator' db.session.add(admin_role) db.session.commit() print(admin_role.name) ``` #### 删除行 ``` # delete方法删除角色 db.session.delete(user_role) db.session.commit() ``` #### 查询行 最基本的当然是查询所有记录(笑,但是我们也可以通过过滤器进行更精确的查找 ``` Role.query.all() # 所有查找 Role.query.filter_by(role=user_role).all() # 查找用户角色 ``` 可以通过转换成str查看sql查询语句。 ##### 常用的SQLAlchemy查询过滤器 | 过滤器 | 说明 | | ------------- | -------------------------------------------------- | | filter() | 把过滤器添加到原查询上,返回一个新查询 | | filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 | | limit | 使用指定的值限定原查询返回的结果 | | offset() | 偏移原查询返回的结果,返回一个新查询 | | order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 | | group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 | ##### 常用的SQLAlchemy查询执行器 | 方法 | 说明 | | ---------------- | ---------------------------------------------- | | all() | 以列表形式返回查询的所有结果 | | first() | 返回查询的第一个结果,如果未查到,返回None | | first_or_404() | 返回查询的第一个结果,如果未查到,返回404 | | get() | 返回指定主键对应的行,如不存在,返回None | | get_or_404() | 返回指定主键对应的行,如不存在,返回404 | | count() | 返回查询结果的数量 | | paginate() | 返回一个Paginate对象,它包含指定范围内的结果 | 关系和查询的处理方式类似,但执行某些表达式时,如 user_role.users,隐含的查询回调用all()形成一个用户列表,由于query对象隐藏所以无法指定精确的查询过滤器,可以加入 lazy='dynamic' 禁止自动查询。 ``` users = db.relationship('User', backref='role') # 反推与role关联的多个User模型对象 # 修改为 users = db.relationship('User', backref='role', lazy='dynamic') # 反推与role关联的多个User模型对象 ``` 这样就可以自定义过滤器啦: ``` user_role.users.order_by(User.username).all() ``` 现在是上午两点,已经太晚了(^_^)a,明天再分享怎么集成Python Shell避免重复导入等。 当然有今日份样例代码: * [X][Code](https://code.uniartisan.com/lizhiyuan/Flask_Notebook_Code/commit/5f007ed7d136e5c28bec29e4f391d8ffc55f8455) Last modification:April 16, 2021 © Allow specification reprint Support Appreciate the author AliPayWeChat Like If you think my article is useful to you, please feel free to appreciate