03 数据库
Flask中使用数据库
- Web应用中普遍使用的是关系模型的数据库
- 关系型数据库把所有的数据都存储在表中,表用来给应用的实体建模,表的列数是固定的,行数是可变的
- 关系型数据库的列定义了表中表示的实体的数据属性。比如:商品表里有name、price、number等
- Flask本身不限定数据库的选择,你可以选择SQL或NOSQL的任何一种
- 也可以选择更方便的SQLALchemy,类似于Django的ORM
Flask-SQLAlchemy扩展
- SQLALchemy 实际上是对数据库的抽象,让开发者不用直接和 SQL 语句打交道,而是通过 Python 对象来操作数据库,在舍弃一些性能开销的同时,换来的是开发效率的较大提升
- SQLAlchemy是一个关系型数据库框架,它提供了高层的ORM和底层的原生数据库的操作。flask-sqlalchemy是一个简化了SQLAlchemy操作的flask扩展。
Django与Flask操作数据库对比

安装 flask-sqlalchemy
pip install flask-sqlalchemy
如果连接的是mysql数据库,需要安装mysqldb
pip install flask-mysqldb
使用Flask-SQLAlchemy管理数据库
在Flask-SQLAlchemy中,数据库使用URI指定,而且程序使用的数据库必须保存到Flask配置对象的SQLALCHEMY_DATABASE_URI键中。
Flask的数据库设置:
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test'
其他设置:
# 动态追踪修改设置,如未设置只会提示警告 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True #查询时会显示原始SQL语句 app.config['SQLALCHEMY_ECHO'] = True
| 名字 | 备注 |
|---|---|
| SQLALCHEMY_DATABASE_URI | 用于连接的数据库 URI 。例如:sqlite:////tmp/test.dbmysql://username:password@server/db |
| SQLALCHEMY_BINDS | 一个映射 binds 到连接 URI 的字典。更多 binds 的信息见用 Binds 操作多个数据库。 |
| SQLALCHEMY_ECHO | 如果设置为Ture, SQLAlchemy 会记录所有 发给 stderr 的语句,这对调试有用。(打印sql语句) |
| SQLALCHEMY_RECORD_QUERIES | 可以用于显式地禁用或启用查询记录。查询记录 在调试或测试模式自动启用。更多信息见get_debug_queries()。 |
| SQLALCHEMY_NATIVE_UNICODE | 可以用于显式禁用原生 unicode 支持。当使用 不合适的指定无编码的数据库默认值时,这对于 一些数据库适配器是必须的(比如 Ubuntu 上 某些版本的 PostgreSQL )。 |
| SQLALCHEMY_POOL_SIZE | 数据库连接池的大小。默认是引擎默认值(通常 是 5 ) |
| SQLALCHEMY_POOL_TIMEOUT | 设定连接池的连接超时时间。默认是 10 。 |
| SQLALCHEMY_POOL_RECYCLE | 多少秒后自动回收连接。这对 MySQL 是必要的, 它默认移除闲置多于 8 小时的连接。注意如果 使用了 MySQL , Flask-SQLALchemy 自动设定 这个值为 2 小时。 |
连接其他数据库
完整连接 URI 列表请跳转到 SQLAlchemy 下面的文档 (Supported Databases) 。这里给出一些 常见的连接字符串。
- Postgres:
postgresql://scott:tiger@localhost/mydatabase
- MySQL:
mysql://scott:tiger@localhost/mydatabase
- Oracle:
- oracle://scott:tiger@127.0.0.1:1521/sidname
- SQLite (注意开头的四个斜线):
sqlite:////absolute/path/to/foo.db
常用的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.datetime | 日期和时间 |
| 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中无法自行决定时,指定多对多关系中的二级联结条件 |
数据库基本操作
-
在Flask-SQLAlchemy中,插入、修改、删除操作,均由数据库会话管理。
- 会话用db.session表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 commit() 方法提交会话。
-
在Flask-SQLAlchemy中,查询操作是通过query对象操作数据。
- 最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。
在视图函数中定义模型类
# 显式的指定表名,如果不指定,会默认创建以当前类名为名字的表 __tablename__ = "显式的指定表名"
创建test1.py的项目
# coding:utf8 import sys reload(sys) sys.setdefaultencoding('utf-8') 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/test1' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 初始化SQLalchemy对象,并将当前应用程序与其相关联 db = SQLAlchemy(app) # 创建模型类 class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) email = db.Column(db.String(64), default='') @app.route('/') def index(): return 'hello world' if __name__ == '__main__': # 删除所有的表 db.drop_all() # 创建所有的表 db.create_all() app.run(debug=True)
运行项目

出现了警告:
可以在上面的代码中添加配置
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
再次运行,警告就会消失。
查询数据库

如果出现了下面这样的警告:

点击报错的位置,进入源码,把建议替换的值,替换掉即可
在交互模式下实现增删改查
增加
ipython from test1 import * role = Role(name='admin') db.session.add(role) db.session.commit()
查询数据库

user = User() user.name = 'a' user.email = '127@.com' db.session.add(user) db.session.commit()

删除
db.session.delete(user)
db.session.commit()

修改
role.name='bbb' db.session.commit()

模型之前的关联
一对多
在一的这边给多的添加反向引用
backref='role' 指定关联关系,并添加一个反向引用,就是给 User 这个类添加一个role这个属性
lazy='dynamic' 关联在使用的时候才加载数据
class Role(db.Model): ... #关键代码 us = db.relationship('User', backref='role', lazy='dynamic')
多对一
在多的这边添加一的id
class User(db.Model): ... role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
- 其中realtionship描述了Role和User的关系。在此文中,第一个参数为对应参照的类"User"
- 第二个参数backref为类User申明新属性的方法
- 第三个参数lazy决定了什么时候SQLALchemy从数据库中加载数据
- 如果设置为子查询方式(subquery),则会在加载完User对象后,就立即加载与其关联的对象,这样会让总查询数量减少,但如果返回的条目数量很多,就会比较慢
- 另外,也可以设置为动态方式(dynamic),这样关联对象会在被使用的时候再进行加载,并且在返回前进行过滤,如果返回的对象数很多,或者未来会变得很多,那最好采用这种方式
多对多
registrations = db.Table('registrations', db.Column('student_id', db.Integer, db.ForeignKey('students.id')), db.Column('course_id', db.Integer, db.ForeignKey('courses.id')) ) class Course(db.Model): ... class Student(db.Model): ... classes = db.relationship('Course',secondary=registrations, backref='student', lazy='dynamic')
常用的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对象,它包含指定范围内的结果 |
创建表:
db.create_all()
删除表
db.drop_all()
插入一条数据
ro1 = Role(name='admin') db.session.add(ro1) db.session.commit() #再次插入一条数据 ro2 = Role(name='user') db.session.add(ro2) db.session.commit()
一次插入多条数据
us1 = User(name='wang',email='wang@163.com',password='123456',role_id=ro1.id) us2 = User(name='zhang',email='zhang@189.com',password='201512',role_id=ro2.id) us3 = User(name='chen',email='chen@126.com',password='987654',role_id=ro2.id) us4 = User(name='zhou',email='zhou@163.com',password='456789',role_id=ro1.id) us5 = User(name='tang',email='tang@itheima.com',password='158104',role_id=ro2.id) us6 = User(name='wu',email='wu@gmail.com',password='5623514',role_id=ro2.id) us7 = User(name='qian',email='qian@gmail.com',password='1543567',role_id=ro1.id) us8 = User(name='liu',email='liu@itheima.com',password='867322',role_id=ro1.id) us9 = User(name='li',email='li@163.com',password='4526342',role_id=ro2.id) us10 = User(name='sun',email='sun@163.com',password='235523',role_id=ro2.id) db.session.add_all([us1,us2,us3,us4,us5,us6,us7,us8,us9,us10]) db.session.commit()
创建test2.py的项目
# coding:utf8 import sys reload(sys) sys.setdefaultencoding('utf-8') 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/test1' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 初始化SQLalchemy对象,并将当前应用程序与其相关联 db = SQLAlchemy(app) # 创建模型类 class Role(db.Model): # 显式的指定表名 __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) user = db.relationship('User', backref='role', lazy='dynamic') def __repr__(self): return '<Role %s>' % self.name class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) email = db.Column(db.String(64), default='') password = db.Column(db.String(64), default='') role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) def __repr__(self): return '<User %s %s %d>' % (self.name, self.email, self.role_id) @app.route('/') def index(): return 'hello world' if __name__ == '__main__': # 删除所有的表 db.drop_all() # 创建所有的表 db.create_all() ro1 = Role(name='admin') db.session.add(ro1) db.session.commit() ro2 = Role(name='user') db.session.add(ro2) db.session.commit() us1 = User(name='wang', email='wang@163.com', password='123456', role_id=ro1.id) us2 = User(name='zhang', email='zhang@189.com', password='201512', role_id=ro2.id) us3 = User(name='chen', email='chen@126.com', password='987654', role_id=ro2.id) us4 = User(name='zhou', email='zhou@163.com', password='456789', role_id=ro1.id) us5 = User(name='tang', email='tang@itheima.com', password='158104', role_id=ro2.id) us6 = User(name='wu', email='wu@gmail.com', password='5623514', role_id=ro2.id) us7 = User(name='qian', email='qian@gmail.com', password='1543567', role_id=ro1.id) us8 = User(name='liu', email='liu@itheima.com', password='867322', role_id=ro1.id) us9 = User(name='li', email='li@163.com', password='4526342', role_id=ro2.id) us10 = User(name='sun', email='sun@163.com', password='235523', role_id=ro2.id) db.session.add_all([us1, us2, us3, us4, us5, us6, us7, us8, us9, us10]) db.session.commit() app.run(debug=True)
运行项目
关联查询示例:
在交互模式下
通过多的表查询一(用户属于什么角色)
In [16]: User.query.first().role
<Role admin>
通过一查询多(这个角色共有多少用户)
In [8]: Role.query.first().user.all()
[<User wang wang@163.com 1>, <User zhou zhou@163.com 1>, <User qian qian@gmail.com 1>, <User liu liu@itheima.com 1>]
查询:filter_by精确查询
返回名字等于wang的所有人
User.query.filter_by(name='wang').all()

first()返回查询到的第一个对象
User.query.first()

all()返回查询到的所有对象

filter模糊查询,返回名字结尾字符为g的所有数据
User.query.filter(User.name.endswith('g')).all()

get():参数为主键,如果主键不存在没有返回内容
User.query.get(1)

逻辑非,返回名字不等于wang的所有数据
User.query.filter(User.name!='wang').all()

not_ 相当于取反
from sqlalchemy import not_ User.query.filter(not_(User.name=='chen')).all()

逻辑与,需要导入and,返回and()条件满足的所有数据
from sqlalchemy import and_
User.query.filter(and_(User.name!='wang',User.email.endswith('163.com'))).all()

逻辑或,需要导入or_
from sqlalchemy import or_ User.query.filter(or_(User.name!='wang',User.email.endswith('163.com'))).all()

查询数据后删除
user = User.query.first()
db.session.delete(user)
db.session.commit()
User.query.all()
更新数据
user = User.query.first() user.name = 'dong' db.session.commit() User.query.first()
数据库迁移
- 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
- 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
- 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
- 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。
首先要在虚拟环境中安装Flask-Migrate。
pip install flask-migrate
创建一个test3.py的项目
# -*- coding:utf-8 -*- from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_script import Manager from flask_migrate import Migrate, MigrateCommand app = Flask(__name__) # 设置数据库连接的相关配置 app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://root:mysql@127.0.0.1:3306/qianyi" # 设置是否追踪修改<待会演示>,如果不设置,会弹出警告。 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 初始化 SQLALCHEMY 对象,并将当前应用 程序 与其相关联 db = SQLAlchemy(app) # 初始化迁移的对象,并且关联当前的应用 migrate = Migrate(app, db) manager = Manager(app) # 添加command,指定名字为db manager.add_command('db', MigrateCommand) # 角色类:管理员,普通用户 class Role(db.Model): # 显式的指定表名,如果不指定,会默认创建以当前类名为名字的表 __tablename__ = "roles" # 主键id id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) # 指定关联关系,并添加一个反向引用,就是给 User 这个类添加一个role这个属性 users = db.relationship('User', backref='role', lazy='dynamic') def __repr__(self): return '<Role %s>' % self.name # 用户类 class User(db.Model): __tablename__ = "users" # 主键id id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) email = db.Column(db.String(64), default='') password = db.Column(db.String(64), default='') # 添加一个字段,用于记录其对应的角色的id role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) def __repr__(self): return '<User %s %s %d>' % (self.name, self.email, self.role_id) @app.route('/') def index(): return 'Hello world' if __name__ == '__main__': manager.run()
创建迁移仓库
在数据库中创建qianyi的数据库
这个命令会创建migrations文件夹,所有迁移文件都放在里面。
这时候数据库中没有数据
python test3.py db init

创建迁移脚本
- 自动创建迁移脚本有两个函数
- upgrade():函数把迁移中的改动应用到数据库中。
- downgrade():函数则将改动删除。
- 自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。
- 对比不一定完全正确,有可能会遗漏一些细节,需要进行检查
python test3.py db migrate -m 'initial migration'

更新数据库
执行下面的语句后,数据库中就会有表了
python test3.py db upgrade
在交互模式下添加一条数据
from test3 import * role=Role(name='admin') db.session.add(role) db.session.commit()

这时我在Role类中添加一个字段
info = db.Column(db.String(64), default='')
创建迁移脚本
python test3.py db migrate -m 'add role_info'
更新数据库
python test3.py db upgrade
这时候会多了一个字段

这时我在User类中添加一个字段
user_info = db.Column(db.String(64), default='')
创建迁移脚本
python test3.py db migrate -m 'add user_info'
更新数据库
python test3.py db upgrade
user也会多了一个字段

返回以前的版本
可以根据history命令找到版本号,然后传给downgrade命令
python test3.py db history

回滚到指定版本
python app.py db downgrade 版本号
恢复到最初的状态,添加到两张表的info多没了
python test3.py db downgrade 4ab5513e5cf5


浙公网安备 33010602011771号