- 数据库(database)
- 数据库管理系统(DBMS, Database Management Sysment),常见的数据库管理系统如Mysql、PotgrsSQL、SQLite、MongoDB。
数据库分类
SQL
SQL(Structured Query Language)结构化查询语言。常用的SQL DBMS主要包括:SQL Server, Oracle, MySQL, PostgreSQL, SQLite.
- 表(table):储存数据的特定结构
- 模式(schema):定义表的结构信息
- 列/字段(column/field):表中的列,存储一系列特定的数据。
- 行/记录(row/record): 表中的行,代表一条记录。
- 标量(scalar):指单一数据,与之相对的时集合(collection)
NoSQL
NoSQL数据库泛指不使用传统关系型数据库中的表格形式的数据库。
- 相比SQL,NoSQL主要优势在于速度、扩展性、无模式(scema-free)、分布式、水平伸缩(horizontally scalable)。
常用NoSQL数据库
1 文档存储(document Store)
使用类JSON格式表示数据, 常用的文档存储DBMS如MongoDB、Couch DB。 MongoDB长用于存储实时消息。
2 键值对存储(key-value store)
类似python中的字典,通过键存储数据,读取非常快。 通常用来存储临时的内容作为缓存使用。 常见的键值DBMS如Redis、Riak等。 Redis还可以用于缓存后端(cache backend)和消息代理(message broker)。
ORM
在web应用里,可以使用原生的SQL语句操作数据库,但是会出现编写乏味、切换不同DBMS不易。 因此可以使用ORM进行简化。
- ORM可以将Python语言转换为DBMS读懂的SQL指令。 因此只需要编写Python对象即可,如下对应:表-Python类、字段-类属性、记录-类实例。
- 用Python实现的ORM有多个库, 最常用的是SQL-Alchemy。
使用Flask-SQLAlchemy管理数据库
Flask-SQLAlchemy集成了SQLAlchemy,简化了连接数据库服务、管理数据库操作会话等工作。
- 安装: pip install flask-sqlalchemy
- 实例化Flask-SQLAlchemy提供的SQLAlchemy类,传入实例程序完成初始化。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app= Flask(__name__)
db = SQLAlchemy(app) #一般把实例对象命名为db,代表数据库,通过它可以操作Flask-SQLAlchemy提供的所有功能。
连接数据库服务器
- DBMS安装后会提供数据库服务运行在操作系统中。 可通过数据库URI连接数据库服务
- URI(Uniform Resource Identifier统一资源标识符),数据库URI是一串包含各种属性的字符串、其中包括了各种用于连接数据库的信息。
常用DBMS即数据库URI格式
DBMS | URI |
---|---|
PostgreSQL | postgresql://username:password@host/databasename |
MySQL | mysql://username:password@host/databasename |
Oracle | oracle://username:password@host:port/sidname |
SQLite(UNIX) | sqlite:////absolute/path/to/foo.db |
SQLite(Win) | sqlite:///absolute\\path\\to\\foo.db或r’sqlite:///absolute\path\to\foo.db’ |
SQLite(内存型) | sqlite:///或sqlite:///:memory |
- 在Flask-SQLAlchemy中,数据库URI是通过配置变量SQLALCHEMY_DATABASE_URI设置。 默认为SQLite内存型数据库。
- SQLite是基于文件的DBMS,不需要设置数据服务器,只需要指定数据文件的绝对路径。
import os
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///'+os.path.join(app.root_path, 'data.db'))
#从环境变量取数据库URI,如果没有就使用默认sqlite
# sqlites数据库文件名不限制后缀,常用的如foo.db、foo.sqlite、foo.sqlite3
- 程序启动后会有一行警告信息,提示设置SQLALCHEMY_TRACK_MODIFICATIONS配置变量,这个配置决定是否最终对象的修改,用于Flask-SQLALchemy的事件通知系统,默认值为None、可以设置为Flase
app.config[‘SQLALCHEMY_TRACK_MODIFIFATIONS’] = False
定义数据库模型
- 数据库模型(Model):用来映射到数据库表的python类通常被成为数据库模型。 一个数据库模型对应数据库中的一个表。
定义模型即使用Python类定义表模式,并声明映射关系。 所有模型类都需要继承Flask-SQLAlchemy提供的db.Model基类
- 表的字段(列)由db.Column类的实例表示。
- 字段的类型通过Column类构造方法的第一个参数传入。 常用字段类型如下: | 字段 | 说明 | | —- | —- | | Integer | 字符串、可选参数length来设置最大长度 | | Text | 较长的Unicode文本 | | Date | 日期,存储Python的datetime.date对象 | | Time | 时间,存储Python的datetime.time对象 | | Datetime | 时间和日期,存储Python的datetime对象 | | Interval | 时间间隔,存储Python的datetime.timedelta对象 | | Float | 浮点数 | | Boolean | 布尔值 | | PickleType | 存储Pickle列化的Python对象 | | LargeBinary | 存储任意二进制数据 |
字段类型一般直接声明即可,如需要参数可以添加括号传入。
- 默认情况下Flask-SQLAlchemy会根据模型类的名称生产一个表名称(规则:单个单词转换为小写,多个单词转换为小写并使用下划线分割),也可以通过定义tablename属性定义表名。
- 字段名默认为类属性名,可以通过字段构造方法的第一个参数指定或使用关键字nanme指定。
- 字段类构造方法还可以传入一下参数:
参数名 | 说明 |
---|---|
primary_key | 如果设置为True,该字段为主键。主键是每条记录独一无二的标识。也是模型类中必须定义的字段,一般命名id或pk |
unique | 如果设置为True,字段不允许出现重复值。 如用户名 |
index | 如果设置True,为该字段创建索引,以提高查询效率。 |
unllable | 字段是否可为空,值为True或False,默认为True |
default | 为字段设置默认值 |
class Note(db.Model):
id = db.Column(db.Integer, primary_key=True)
body=db.Column(db.Text)
创建数据库和表
- 数据库: 存储数据的仓库
- 表: 仓库中的一个文件夹
创建模型类后,需要手动创建数据库和对应的表。可以通过对db对象调用careate_all()方法实现。
# 在创建表之前导入相应模型类
db.create_all()
- 数据可和表一旦创建,之后对模型的修改不会直接作用的实际的表中。再次调用create_all()也不会更新。
- 如果要是改动生效
- 1 调用db.drop_all()删除数据库和表。然后再调用db.create_all()生成。
- 2 pass
可以定义一个flask命令完成表创建, 执行时在命令行输入flask initdb
import click
@app.cli.command()
def inintdb():
db.cerate_all()
click.echo('初始化数据库完成')
数据库操作
SQLAlchemy使用数据库会话(也称事物transaction)来管理数据库操作。 Flask-SQLAlchemy会自动帮我们创建会话,可以通过db.session属性获取。
- 数据库会话:
- 数据库中的会话代表一个临时存储区,对数据库做出的改动都会存放在这里。
- 可以调用add()方法将新创建的对象添加到数据库会话中或是对会话中的对象进行更新。
- 对数据库会话对象调用commit()方法时,改动才被提交到数据库。
- 数据库会话也支持回滚操作,可通过rollback()将会话中且未提交的改动撤回。
CRUD操作方法
数据库操作主要是CRUD:即Create(创建)、Read(读)、Update(更新)、Delete(删除)。
默认情况下,Flask-SQLAlchemy会自动为模型生成一个repr()方法, 在Python shell中,当调用模型对象时,repr()方法会返回一条类似<模型名称 主键值>的字符串。 为测试方便,可以在模型类中重新定义repr()方法以返回更有用的信息
class Note(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text)
def __repr__():
return '<Note %r>' % self.body
Create
添加一条新记录到数据库分三步
- 实例化数据模型,作为一条记录
- 添加新创建的记录到数据库会话
- 提交数据库会话
note1 = Note(body='hahahahahaha') #使用关键字参数传入字段数据,完成模型实例创建。 ps:模型类继承自db.Model,db.Model会为模型类提供一个构造函数接收匹配类属性名称的参数值并赋值给对应的类属性。
db.session.add(note1) #调用add将传入记录对象添加到数据库会话中, 也可以使用db.session.add_all()加入所有创建记录对象。/
db.session.commit()
Read
- 使用模型类提供的query属性附加调用各种过滤方法及查询方法完成数据查询:<模型类>.query.<过滤方法>.<查询方法>。 可以没有过滤方法
- 查询模式:从某个模型出发,通过在query属性对应的Query对象山附加的过滤方法和查询函数对模型类对应的表中的记录进行各种筛选和调整,最终返回包含对应数据库记录数据的模型类实例,对返回的实例调用属性即可获取对应的字段数据。
常用查询方法:
查询方法 | 说明 |
---|---|
all() | 返回包含所有查询记录的列表 |
first() | 返回查询记录的第一条,如果未找到则返回None |
one() | 返回第一条记录,且仅允许有一条记录,如果记录数量不等于1则抛出错误 |
get(ident) | 传入主键值作为参数,返回指定主键值的记录,如果未找到则返回None |
count() | 返回查询结果的数量 |
one_or_none() | 类似one(),结果数量不为1返回None |
first_or_404() | 返回查询结果的第一条记录,如果未找到则返回404错误 |
get_or_404(ident) | 传入主键值作为参数,返回指定主键值的记录,如果未找到则返回404 |
paginate() | 返回一个pagination对象,可以对记录进行分页处理 |
with_parent(instance) | 传入模型类实例作为参数,返回和这个实例相关联的对象 |
常用的过滤方法
使用过滤可以获得更精准的查询, 对模型类的query属性存储的Query调用过滤方法会返回一个更加精准的Query对象。 因为每个过滤器都会返回一个新的查询对象,所以过滤器可以叠加使用。
过滤方法 | 说明 |
---|---|
filter() | 使用指定的规则过滤记录,返回新产生的查询对象。 filter中传入表达式时可以传入如==、!=、LIKE、IN、NOT IN、AND、 OR等 |
filter_by() | 使用指定规则过滤记录(以关键字表达式的形式),返回新产生的查询对象 filter_by()比filter()更易使用,在fliter_by()中可以使用关键字表达式来指定规则,且可以直接使用字段名称。 |
order_by() | 根据指定条件对记录进行排序,返回新产生的查询对象 |
group_by() | 根据指定条件对记录进行分组,返回新产生的查询对象 |
offset(offset) | 使用指定的值偏移原查询的结果,返回新产生的查询对象。 |
>>> Note.query.filter(Note.body=='shasha').first()
>>> Note.query.filter(Note.body.like('%haha%'))
>>> Note.query.filter(Note.body.in_('foo','haha', 'ss'))
>>> Note.query.filter(~Note.body.in_('foo','haha', 'ss'))
#and
#1使用and
from sqlalchemy import and_
Note.query.filter(Note.body=='haha' and Note.title=='hhe')
#2 或在filter中加入多个表达式,逗号隔开
Note.query.filter(Note.body=='haha', Note.title=='hhe')
#3 或调用多个filter()/filter_by()
Note.query.filter(Note.body=='haha').filter(Note.title=='hhe')
#filter_by()
Note.query.filter_by(body=='haha').first()
UPdate
更新数据:直接赋值给模型类的字段属性就可以改变字段值,然后调用commit()方法提交会话。
note=Note.query.get(1)
note.body='qadsfsdf'
db.session.commit()
Delete
会话中delete数据实例,然后调用commit方法提交修改。
note=Note.query.get(1)
db.session.delete(note)
db.session.commit()
在视图函数里操作数据库
创建
1 新建表单
from flask_wtf import FlaskForm
from wtforms import TextAreaField, SubmitField
from wtforms.validators impor DataRequired
calss NewNoteForm(FlaskForm):
body = TextAreaField('文章内容', validators=[DataRequired()])
submit =Submit('Save')
2 new_note视图函数
@app.route('/new', methods=['GET', 'POST'])
def new_note():
form = NewNoteForm()
if validate_on_submit():
body = form.body.data
note= Note(body=body)
db.session.add(note)
db.session.commit()
flash("新建成功")
return redrict(url_for('index'))
return render_template('new-note.html', form=form)
3 now-nete,html模板
<form method="POST">
{{ form.csrf_token }}
{{ form_field(form.body, rows=5,cols=50) }}
</form>
index.html模板
<a href="{{ url_for('new_note') }}">新建</a>
读
1 修改index视图
@app.route('/index')
def index():
notes = Note.query.all()
return render_template('index', notes=notes)
2 修改index.htm
<a href="{{ url_for('new_note') }}">新建</a>
{{ notes|length }}
{% for note in notes %}
<div>{{ note.body }}</div>
{% endfor %}
更新
1 + 更新笔记表单
class EditNoteForm(FlaskForm):
body = TextAreaField('文章内容', validators=[DataRequired()])
submit =Submit('Update')
#或继承新建表单
class EditNoteForm(NewNoteForm):
submit =Submit('Update')
2 +更新笔记视图
@app.route('/edit/<int:note_id>', methods=['GET', 'POST'])
def edit_note(note_id):
form = EditNoteForm()
note = Note.query.get(note_id)
if validate_no_submit():
note.body = form.body.data
db.session.add(note)
db.session.commit()
flash('更新成功')
return redrict(url_for('edit_note',note_id=note.id))
form.body.data=note.body
return render_template('edit.html', form=form)
- 使用WTForms在渲染模板表单字段时,如果表单字段的data属性不为空,WTForms会自动把data属性的值添加到表单字段的values属性中,作为表达值填充进去。
删除
1 创建一个删除表单
class DeleteForm(FlaskForm):
submit= Submit('Delete')
2 删除笔记处理视图
@app.route('/delete/<int:note_id>', methods=['POST'])
def delete_note(note_id):
form = DeleteForm()
if form.validate_on_submit():
note = Note.query.get(note_id)
db.session.delete(note)
db.session.commit()
flash('删除成功')
else:
abort(400)
return redrict(url_for('index'))
3 修改index.html,加入删除
<a href="{{ url_for('new_note') }}">新建</a>
{{ notes|length }}
{% for note in notes %}
<div>{{ note.body }}</div>
<form methods="post" action="{{ url_for('delete_note', note_id=note.id) }}">
{{ from.csrf_token }}
{{ form.submit(class='btn') }}
</form>
{% endfor %}
定义关系
在关系型数据库中,通过关系让不同表之间建立联系。
一般定义关系分两步:
- 创建外键
- 定义关系属性
在复杂的多对多关系中,还需要定义关联表来管理关系
一对多关系
使用作者Author和文章Article来演示一对多关系
class Author(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), unique=True)
info = db.Column(db.String(200))
articles = db.Relationshio('Article') #2 定义关系属性
class Article(db.Model):
id = db.Column(db.Integer, primary_key=True)
title =db.Column(db.String(50), index=True)
body = db.Column(db.Text)
author_id = db.Column(db.Integer, db.ForeignKey('author_id')) #1 定义外键
建立一对多关系的目的是在表示1的类(如Author)中添加一个关系属性(如articles)作为集合属性(collection),当我们对特定的1类的对象(author1)调用这个关系属性时会返回所有相关的多类的对象(article1、article2、…)
定义一对多关系
1 定义外键(foreign key)
- 外键是用来在A表(表示多的类如Article)存储B表(表示1的类Author)的主键值以便和B表建立联系的关系字段。
- 这个字段使用db.ForeignKey类定义为外键,传入关系另一侧的”表名.主键字段名”(如author.id)。 将表示多的类(如Article)的外键字段(如author_id)值限制为表示1的类(如Author)列的id。
- 外键字段名没有限制,为便于区分通常取“对应对象表名_主键字段名”(author_id)。
2 定义关系属性
关系属性使用关系函数定义,关系属性定义在出发侧即一对多的“一”这一侧。
- 关系属性使用db.relationshop()关系函数定义为关系属性。relationship()函数的第一个参数为关系另一侧的模型名称
- 当这个属性被调用,SQLAlchemy会找到关系另一侧(article表)的外键字段(author_id),然后查询article表中所有author_id为当前主键值的记录,然后包含这些记录的列表(某个作者的多篇文章纪录)。 因此关系属性又称为集合关系属性。
- 关系属性名称没有限制。相当于一个快捷查询、不会作为字段写入数据库中。
关系函数中,又很多参数用来设置挂你属性进行查询时的具体行为:
参数名 | 说明 |
---|---|
back_polulates | 定义反向引用,用于建立双向关系。在关系的另一侧也必须显示定义关系属性。 |
backref | 建立反向引用,自动在另一侧建立关系属性,是back_populates的简化版 |
lazy | 指定如何加载相关基类,可传入参数可选值,见后续表 |
uselist | 指定是否使用列表的形式加载记录,设为False则使用标量(scalar) |
cascade | 设置级联操作 |
order_by | 指定加载相关记录时的排序方式 |
secondary | 在多对多关系中指定关联表 |
primaryjoin | 指定多对多关系中的一级联结条件 |
secondaryjoin | 指定多对多关系中的二级联结条件 |
当关系函数被调用时会加载相应的记录,不同lazy参数控制加载方式
关系加载方式 | 说明 |
---|---|
lazy=’select’ | 一次性加载记录、返回包含记录的列表。 等同于lazy=True |
lazy=’joined’ | 和父查询一样加载记录,但使用联结,等同于lazy=False |
lazy=’immediate’ | 一旦父查询加载就加载 |
lazy=’subquery’ | 类似于joined,不过将使用子查询 |
dynamic | 不直接加载记录,而是返回一个包含相关记录的query对象,以便再继续附加查询函数对结果进行过滤。 仅用于集合关系属性,不可用于多对一、一对一或在关系函数中将uselist设为True的情况。 使用dynamic加载方式意味着每次操作关系都会进行一次SQL查询,会造成潜在性能问题,因此建议只有在调用关系属性会返回大量记录且总是需要对关系属性返回的结果对象附加额外查询时使用。 |
3 建立关系
3.1 方式1:为外键字段赋值
article2.author_id=1
3.2 方式2:操作关系属性,将关系属性赋值给实际的对象即可建立关系,可以向列表一样使用append、remove操作。
author1.articles.append(article1)
author1.articles.remove(article1)
author1.articles.pop() #删除最后一个记录
# 修改后需要commit会话,才会把改动写入到数据库
4 双向关系
- 集合关系属性:用于获取多条记录的关系属性, 如author中用于获取多个文章的articles属性
- 标量关系属性:用于获取一条记录的关系属性, 如article中用于获取所属哪一个作者的author属性
- 双向关系:两边都添加关系属性的用于获取对方记录的关系。
- 双向关系需要在两边都定义关系函数,关系函数中,使用back_populates参数连接对方,值设为关系另一侧的关系属性名。
class Author(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), unique=True)
info = db.Column(db.String(200))
articles = db.Relationship('Article',back_populates='author') #2 定义双向关系属性
class Article(db.Model):
id = db.Column(db.Integer, primary_key=True)
title =db.Column(db.String(50), index=True)
body = db.Column(db.Text)
author_id = db.Column(db.Integer, db.ForeignKey('author.id')) #1 定义外键
author = db.Relationship('Author',back_populates='atricles') #2 定义双向关系属性
使用backref简化关系定义
在关系函数中的backref参数可以简化双向关系的定义。
- 在一对多中, backref可以自动为关系另一侧添加关系属性,作为反向引用(back reference),赋予的值会作为关系另一侧的关系属性名称。
class Singer(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), unique=True)
songs = db.relationship('Song', backref='singer') #使用backref,定义关系属性并自动为另一侧添加关系属性,另一侧关系属性为singer
class Song(db.Model):
id = db.Column(db.Integer, primary_key=True)
title =db.Column(db.String(50), index=True)
body = db.Column(db.Text)
singer_id = db.Column(db.Indeger, db.ForeignKey('singer.id')) #1 定义外键
- 使用backref仅允许我们在关系一侧定义另一侧的关系属性,但是如果我们希望对关系另一侧的关系属性进行设置,这时需要使用backref()函数,函数第一个参数在关系另一侧的关系属性名,其他关键字参数作为关系另一侧关系函数的参数传入。
class Singer(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), unique=True)
songs = db.relationship('Song', backref=backref('singer', uselist=False))
class Song(db.Model):
id = db.Column(db.Integer, primary_key=True)
title =db.Column(db.String(50), index=True)
body = db.Column(db.Text)
singer_id = db.Column(db.Indeger, db.ForeignKey('singer.id')) #1 定义外键
多对一
如多个市民居住在一个城市, 从市民侧出发查询所在城市。
- 关系属性定义在出发侧,即多的一侧
- 如果建立双向关系, 如果不适应backref,那么多对一和一对多是同一种关系模型。
一对一
一对一关系实际上是通过建立双向关系的一对多,然后设置两侧的关系属性都是标量属性,即都只返回单个值。
- 通过在定义集合属性的关系函数中将uselist设置为false,实现一对多转换成一对一。
- 一对一为标量数量后不可再使用列表语义操作对象。
class Country(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True)
captial = db.Relationship('Captial', uselist=False) #设置uselist
class Capital(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True)
country_id = db.Column(db.Integer, db.ForeignKey('country.id'))
counrty = db.Relationship('Country')
多对多
- 在多对多关系中,关系两侧的模型都需要存储一组外键; 另外还需要创建一个关联表(association table)
- 关系表不存储数据,只用来存储关系两侧模型的外键对应关系
- 关系表用db.Table类定义,传入的第一个参数是关联表的名称,然后是需要存储的外键字段
- 在多对多定义关系函数中,除了第一个参数是另一侧模型名此外,还需要添加secondary参数,设置其值为关联表名称。
- 调用模型关系属性时,SQLAlchemy会直接返回多对多另一侧的对象集合,而不是关联表记录。 和其他集合关系一样,可当列表操作。
- 关联表由SQLAlchemy接管,我们只需要操作关系属性建立或解除关系,SQLAlchemy会自动在关联表创建或删除数据而不用手动操作关联表。
association_table = db.Table('association', db.Column('student_id', db.Integer, db.ForeignKey('student.id')), db.Column('teacher_id', db.Integer, db.ForeignKey('student.id')))
class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True)
teachers = db.Relationship('Teacher', secondary=association_table, back_populates='students')
class Teacher(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True)
students = db.Relationship('Student', secondary=association_table, back_populates='teachers')
邻接列表关系
邻接列表关系(Adjacency List Relationship):在同一个模型内一对多关系
- 需要在模型内添加一个外键指向它自身。
- 两侧的关系属性都定义在这个模型内。
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
author = db.Column(db.String(20))
email = db.Column(db.String(200))
content = db.Column(db.Text)
from_admin = db.Column(db.Boolean, default=False)
reviewed = db.Column(db.Boolean, default=False)
created_time = db.Column(db.DateTime, default=datetime.utcnow, index=True)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
post = db.relationship('Post', back_populates='comments')
replied_id = db.Column(db.Integer, db.ForeignKey('comment.id'))
replied = db.relationship('Comment', back_populates='replies', remote_side=[id]) # replied表示被回复评论的标量关系属性。 通过remote_side=[id]将id字段设置为远程侧, replied_id就变为了本地侧。
replies = db.relationship('Comment', back_populates='replied', cascade='all')
更新数据库表
模型新增或更新后,数据库表不会自动更新,都需要手动触发更新数据库表。下面几种方式
重新生成表
如果数据库中数据不重要,可以删除后重新生成表,使用db.drop_all()方法。如果时SQlite,可以直接删除数据库文件即可。
@app.cli.command()
@click.option('--drop', is_flag=True, help='drop db') #使用click提供的option装饰器为命令添加一个--drop选项并通过is_flag将其声明为布尔类型。 添加选项后--drop选项的值True作为参数传入命令函数。
def initdb(drop):
if drop:
click.comfirm('是否确认删除数据', abort=True)
db.drop_all()
click.echo('删除成功')
db.create_all()
click.echo('创建数据库成功')
#执行时使用
$ flask initdb --drop
使用Flask-Migrate迁移数据库
- 数据库迁移工具Alembic:可以在不破坏数据结构的情况下更新数据库表结构。
- Flask-Migrate集成了Alembic,提供了一些flask命令来简化迁移工作。
安装:pip install flask-migrate
1 初始化
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db=SQLAlchemy(app)
migrate = Migrate(app, db)
2 创建迁移环境
$ flask db init
- 数据迁移前需要使用上述命令创建迁移环境,迁移环境只需要创建一次,命令运行后会在项目根目录下创建一个migrations文件夹,包含了自动生成的配置文件和迁移版本文件夹
3 生成迁移脚本
使用flask db migrate命令生成迁移脚本
$ flask db migrate -m"增加id字段" #m内容用来添加迁移备注。
- 执行命令后会检测模型变化并生成迁移脚本
- 每次执行迁移命令都会生成新的迁移脚本,而且Alembic为每次迁移都生成了修订本吧,因此可以恢复到修改历史的任意点。
4 更新数据库
使用flask db upgrade命令更新数据库
$ flask db upgrade
- 如果会创建数据库和表,会自动创建; 如果已经创建会更新数据库表和模型一致。
- 当执行upgrade后发现不对,可以使用flask db downgrade命令进行回滚; 删除对应的迁移脚本、重新生成迁移脚本后再进行更新。
数据库进阶实践
级联操作
级联操作(cascade)就是再操作一个对象的同时,对相关的对象也执行一些操作。
- 级联行为通过关系函数relationship()的cascade参数设置。设置了cascsde的一侧视为父对象、相关的对象则被是为子对象。
- cascsde通常使用多个值组合,级联值之间使用逗号分隔。常用的配置组合
常用的配置组合 | 说明 |
---|---|
save-update、merge | 默认值。 即没有设置cascsde参数时的设置 |
save-update、merge、delete | |
all | |
all、delete-orphan |
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(50), unique=True)
body = db.Column(db.Text)
comments = db.relationship('Comment', cascade='save-update, merge, delete', back_populates='post')
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
post = db.relationship('Post', back_populates='comments')
save-update
父对象添加到会话时,子对象也会自动被添加到会话。
delete
- 默认情况下post删除后,会自动取消和comment的关联,comment中外键字段的值会被清空。
- 如果设置了cascade=’delete’, 当post对象被删除后关联的comment对象也会被删除。
- 需要设置delete级联时一般设置为all或 save-update、merge、delete
delete-orphan
- 一般delete-orphan和delete一起使用, 设置delete-orphan一般设置为all、delete-orphan。
- delete-orphan包含delete,当父对象被删除时子对象也被删除。 此外父对象和子对象解除关联,子对象也被删除。