Content:
基本概念:
ORM:Object Relational Mapping对象关系映射。即使用某种方式令A可以关联代表B。
- 这里指python和数据库的映射关系。用python的类映射数据库的表。使用Python实现的ORM有SQLAlchemy、Peewee、PonyORM等。
SQLAlchemy:是Python的SQL工具套件(只能用于关系型数据库)。支持多种数据库后台。SQLAlchemy提供了高层ORM,也提供了使用数据库原生SQL的低层功能。
Flask-SQLAlchemy:是SQLAlchemy的Flask扩展。
- 以下是Flask-SQLAlchemy的学习笔记
一. 配置数据库引擎
数据库引擎通过配置变量
SQLALCHEMY_DATABASE_URI
设置,默认为SQLite内存型数据库(sqlite:///:memory:)。
格式:
dialect+driver://username:password@host:port/database
各数据库URI表:
数据库引擎 | 配置方式 |
---|---|
MySQL | mysql://username:password@hostname/database |
SQLite(UNIX) | sqlite:////absolute/path/to/database |
SQLite(Windows) | sqlite:///absolute\path\to\database |
Postgres | postgresql://username:password@hostname/database |
Oracle | oracle://username:password@hostname:port/sidname |
SQLite(内存型) | sqlite:/// 或 sqlite:///:memory: |
- SQLite是基于文件的DBMS,不需要设置数据库服务器,只需要指定数据库文件的绝对路径。在开发测试时经常使用。
- 如果使用MySQL数据引擎,需要先创建好数据库,否则会报错。
- 数据库URL包含敏感信息,所以优先从环境变量DATABASE_URL获取。例如
os.getenv(参数1,参数2)
参数1是首选地址,参数2是备选地址。 - SQLite数据库文件名不限定后缀,常用的命名方式有xxx.sqlite,xxx.db
- SQLite的数据库URI在Linux或macOS系统下的斜线数量是4个;在Windows系统下的URI中的斜线数量为3个。内存型数据库的斜线固定为3个。
例子
import os
...
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///' + os.path.join(app.root_path, 'data.db'))
二. python中配置数据库
有两种方式创建实例
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
……
#1
app = Flask(__name__)
db = SQLAlchemy(app)
#2
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
db.init_app(app)
return app
- 第二种要上下文存在的情况下才能工作。
三. 模型建立
数据库表映射到Python的类。一个数据库模型类对应数据库中的一个表。所有的模型类都需要继承Flask-SQLAlchemy提供的db.Model基类。
特殊属性
模型有两个特殊特性:
__bind_key__
:此数据表绑定至其他数据库引擎,即在绑定的数据库中创建生成。绑定前需要先通过
SQLALCHEMY_BINDS
变量设置其他数据库引擎。如不绑定,则默认为所连接的数据库。
例子:
SQLALCHEMY_DATABASE_URI = 'postgres://localhost/main' SQLALCHEMY_BINDS = { 'users': 'mysqldb://localhost/users', 'appmeta': 'sqlite:////path/to/appmeta.db' }
__tablename__
:在SQLAlchemy中是必填项,但在flask-Sqlalchemy
中如果没有设置的话,默认以类名为表名。命名规则:
- 如果是把类名作为表名,会将类名“CamelCase”转成“camel_case”;
eg:
class UserDefault1
,则该数据表表名为user_default1
。- 置属性,则会把所有的大写转成小写(如有下划线会保留)
eg:
__tablename__ = 'User_Default2'
,则该数据表表名为user_default2
。
定义模型
例子
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return '<User %r>' % self.username
表的字段(列)由db.Column类的实例映射。列名默认是实例名(或是说变量名)。如果想自定义其他名字,则需要把名字放在创建实例时的首位。eg:
id = db.Column('desired column name', db.Integer, primary_key=True)
第一个参数是列的数据类型(表1)。如果需要传入数据类型的相关参数,可以添加括号。譬如String字符串限定长度,eg:
db.String(20)
- 其他参数是字段的配置选项(表2)
数据类型(表1)
数据类型 | Python类型 | 说明 |
---|---|---|
Integer | int | 普通整数 |
String | str | 字符串(自定义长度) |
Float | float | 浮点数 |
Text | str | 变长字符串,对较长或不限长度的字符串做了优化 |
Boolean | bool | 布尔值 |
DateTime | datetime.datetime | 日期和时间 |
PickleType | 任何Python对象 | 自动使用Pickle序列化 |
LargeBinary | str | 二进制文件 |
数据库列的配置选项(表2)
选项 | 定义 |
---|---|
primary_key | 若为True,表的主键 |
unique | 若为True,不允许出现重复的值 |
index | 若为True,为这列创建索引,提升查询效率 |
nullable | 若为True,允许使用空值;若为False,不允许使用空值 |
default | 定义默认值 |
- 主键一般放在最前,经常以id作为命名。
四. 数据表使用
1. 创建数据表
db.create_all()
- ==执行创建数据表命令前,需要先手动创建数据库,也就是建库。==因为框架的功能并不包含创建数据库,只是会连接指定的数据库,并在该数据库中创建数据表。
- 在创建flask实例时,如需要执行创建数据表的命令,则需要事先导入模型,不然无法创建成数据表。
- 数据库表一旦创建,之后对模型的改动不会自动作用到实际的表中。即使
db.create_all()
亦不会重新创建或者更新这个表。所以当更新数据表时,如不在意数据丢失的情况下,最简单的方式是调用db.dropall()方法删除数据库和表,然后再调用db.createall()方法重新创建数据表。但一般建议使用数据迁移方式操作更新。
2. 删除数据表
db.drop_all()
五. 数据增查改删(CRUD)
增、改、删会涉及到以下命令:
db.session.add(a)
/ db.session.add_all([a,b,c,……])
db.session.delete(a)
db.session.commit()
- 所有命令需输入
db.session.commit()
才执行操作并生效; - 如提交失败或发生错误等须数据回滚
db.session.rollback()
; - “改”和“删”都要先建立在“查”的基础上
1. Create增
步骤:
- 创建python模型实例
- 使用
db.session.add()
将该实例对象添加到session对话中 - 使用
db.session.commit()
提交对话
eg:
$ u = User(name='test1')
$ db.session.add(u)
$ db.session.commit()
2. Update改
步骤:
- 使用查询语句得到目标对象
- 修改目标对象
- 使用
db.session.commit()
提交对话
eg:
$ u = User,query.first()
$ u.name = 'new_data'
$ db.session.commit()
3. Delete删
步骤:
- 使用查询语句得到目标对象
- 使用
db.session.delete()
将需删除的目标对象放到对话中 - 使用
db.session.commit()
提交对话
eg:
$ u = User,query.first()
$ db.session.delete(u)
$ db.session.commit()
4. Read查
过滤器中可多个查询条件一起调用
常用的SQLAlchemy查询过滤器
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上(以关键字表达式的形式),返回一个新查询 |
limit() | 使用指定的值限制原查询返回的结果数量,返回一个新查询 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
常用的SQLAlchemy查询执行函数
方法 | 说明 |
---|---|
all() | 以列表形式返回查询的所有结果 |
first() | 返回查询的第一个结果,如果没有结果,则返回None |
firstor404() | 返回查询的第一个结果,如果没有结果,则终止请求,返回404错误响应 |
get(id) | 返回指定主键对应的行,如果没有对应的行,则返回None |
getor404(id) | 返回指定主键对应的行,如果没找到指定的主键,则终止请求,返回404错误响应 |
count() | 返回查询结果的数量 |
paginate() | 返回一个Paginate对象,它包含指定范围内的结果 |
with_parent(instance) | 传入模型类型实例作为参数,返回和这个实例相关联的对象 |
六. 模型关系
1. 一对多关系模型
关系型数据库就是使用关系把不同表中的行联系起来,即只要有外键,就会存在关系。其中一对多的示例代码如下:
class Role(db.Model):
__tablename__ = 'roles'
...
users = db.relationship('User', backref='role')
class User(db.Model):
__tablename__ = 'users'
...
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
分析:
db.relationship()
与db.ForeignKey()
是成对出现的。前者在一对多关系的“一”中体现;后者在一对多关系的“多”中体现,即外键。- 针对
Role
模型的说明:
users
属性:在数据表中不会作为字段(列)出现。==属性名定义没有固定规则==,建议是关系数据表名字的复数。这个属性并没有使用Column类声明为列,而是使用了db.relationship()
关系函数定义为关系属性。这个关系属性返回多个记录,称之为集合关系属性。即Role.users
返回的是一个列表,是存在关系的若干个User
模型的实例对象。db.relationship(arg1,arg2,...)
是一个关系定义函数,定义和其他关联表的操作,关系,排序等等。函数中第一个参数一定是关联数据表的名字,另外,函数一般还会包含backref
参数,==该参数是给关联表提供反向引用关系,如果不需要反向引用可不用。==backref
参数:给db.relationship()
中所关联的数据表B提供一个反向的关系属性,让数据表B的实例对象可通过该属性名字访问到数据表A中所关系的实例对象。==定义的名称没有固定规则,建议是数据表A的名字的单数==,方便记忆和调用。
- 针对
User
模型的说明:
role_id
属性:因为使用了db.Column()
创建实例,所以是其数据表中的一列。==属性名定义没有固定规则==,建议是绑定的外键属性名称。该属性所获取的值完全取决于ForeignKey
中的定义。db.ForeignKey
参数:与其他一般的db.Column()
创建实例不同,外键需要设置db.ForeignKey
这个参数。传入关系另一侧的表名+字段名,写成“表名.唯一性属性”,一般是绑定关系数据表中的primary
键即id
。
- 从上面两个模型可知,除了
db.ForeignKey
需要真实有效的字段外,其他3个定义均可按照自己的编程习惯来命名。
上述例子的体现
>>> admin = Role.query.first() #为了简化操作,数据表roles只有这条数据
<Role 1>
>>> user1 = User.query.first() #为了简化操作,数据表users只有这条数据
<User 1>
>>> admin.users #结果是一个列表集合
[<User 1>]
>>> user1.role #该外键绑定了关联数据表中的哪个实例对象
<Role 1>
>>> user1.role_id #所绑定关联数据表的实例对象的id
1
关联数据的加载方式
在多对多模型时,加载方式是双向的。
在db.relationship()
关系定义函数还有一个很重要的参数就是lazy
。它是决定关联数据如何加载。有以下几个选项:
'select'
/True
:默认值。在访问当前数据表内容时,不会去联动关联表。当访问关联表内容时,会执行关联表的SQL命令一次性加载并返回结果(无法对其查询进行过滤)。'joined'
/False
:在第一次访问当前数据表内容时会使用join
SQL命令一并把相关的关联表内容一次性加载,可理解为一次对所有涉及的数据表均执行了SQL命令。该模式下,因为要查询当前数据表和关联数据表的内容,第一次加载速度最慢。但由于会将第一次查询的数据存放到缓存中,之后的查询均不再需要执行SQL命令,后续的查询速度是最快的。'subquery'
:同joined
效果一样,不过将使用子查询。'dynamic'
:与select
效果一样。只是在访问关联表内容时,返回的是query对象,需要使用执行函数(譬如all()
)才会执行SQL命令返回结果。在游标状态时,如有需要可结合查询过滤器等使用。
- dynamic选项仅用于集合关系属性,不可用于多对一、一对一或是在关系函数中将uselist参数设为False的情况。
==关系函数relationship()的参数==
参数名 | 说明 |
---|---|
back_populates | 定义反向引用,用于建立双向关系,在关系的另一侧也必须显示定义关系属性 |
backref | 添加方向引用,自动在另一侧建立关系属性,是back_populates的简化版 |
lazy | 指定如何加载相关记录 |
uselist | 指定是否使用列表的形式加载记录,设为False则使用标量(scalar) |
cascade | 设置级联操作 |
order_by | 指定加载相关记录时的排序方式 |
secondary | 在多对多关系中指定关联表 |
primaryjoin | 指定多对多关系中的一级联结条件 |
secondaryjoin | 指定多对多关系中的二级联结条件 |
2. 多对一关系模型
从“一对多”的相反方向看就是“多对一”关系模型
3. 一对一关系模型
限制“多”的一侧最多只能有一个记录,就是“一对一”关系模型
class Country(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), unique=True)
capital = db.relationship('Capital', backref=country, uselist=False)
class Capital(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), unique=True)
country_id = db.Column(db.Integer, db.ForeignKey('country.id'))
- 在定义集合属性的关系函数中将uselist参数设为False,这时一对多关系将被转换为一对一关系。
4. 多对多关系模型
两个模型多对多关联时,需要添加一个额外的表来表明两个模型间的关系,这个表称为关联表。多对多关系可以分解成原表和关联表之间的两个一对多关系。
tags = db.Table('tags',
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True),
db.Column('page_id', db.Integer, db.ForeignKey('page.id'), primary_key=True)
)
class Page(db.Model):
id = db.Column(db.Integer, primary_key=True)
tags = db.relationship('Tag', secondary=tags, lazy='subquery',
backref=db.backref('pages', lazy=True))
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
- 关联表就是一个简单的表,不是模型。SQLAlchemy会自动接管这个表。
- 当添加关系时,对关系属性使用append()方法。eg:
page1.tags.append(tag2)
- 当想要解除关系时,对关系属性使用remove()方法。eg:
tag1.pages.remove(page3)
。 - 在多对多关系中我们只需要在关系的一侧操作关系即可。
5. 进阶版多对多关系模型1
由于关联表只是一个简单的表,只用来记录关系,不能存储数据,所以当需要两个模型之间的关联操作(eg:收藏、关注时间戳)等数据,就需要将关联表升级为关联模型。
class Association(db.Model):
__tablename__ = 'association'
parent_id = db.Column(db.Integer,db.ForeignKey('parent.id'), primary_key=True)
child_id = db.Column(db.Integer, db.ForeignKey('child.id'), primary_key=True)
extra_data = db.Column(db.String(50))
child = db.relationship("Child", backref="parent_associations")
parent = db.relationship("Parent", backref="child_associations")
class Parent(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
children = db.relationship("Child", secondary="association")
class Child(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
parents = db.relationship("Parent", secondary="association")
- 通过Association模型来存储另外两个模型的关系
- 当不需要填写
extra_data
时,可直接通过模型1实例.关系属性.append(模型2实例)
的方式来新增两个模型间的关系。eg:parent1.children.append(child1)
- 每次的变更都需要
db.session.commit()
操作才会生效。
5. 进阶版多对多关系模型2
如果两者关系都是在同一模型中,这种关系被称为自引用关系(Self-Referential Many-to-Many Relationship)。譬如关注的动作,发生和接收者都是在同一User模型里。
class Subscription(db.Model):
id = db.Column(db.Integer, primary_key=True)
main_id = db.Column(db.Integer, db.ForeignKey('user.id'))
fans_id = db.Column(db.Integer, db.ForeignKey('user.id'))
timestamp = db.Column(db.DateTime, default=datetime.datetime.now())
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True, nullable=True)
followers = db.relationship("User",
secondary="subscription",
primaryjoin="User.id==subscription.c.main_id",
secondaryjoin="User.id==subscription.c.fans_id",
backref="following"
)
primaryjoin
和secondaryjoin
建立了与关联表的联系,指明了followers
和following
是关联了表中的哪一列。- 在
primaryjoin
和secondaryjoin
中会看到.c.
,是Mapper.columns
的columns
缩写。一定要有!!! - 每次的变更都需要
db.session.commit()
操作才会生效。
七. Flask-Migrate数据库迁移
当添加了新的模型类,或是在模型类中添加了新的字段,甚至是修改了字段的名称或类型,都需要更新表。但由于简单的创、删数据库命令会让数据库里的数据都被删除,所以使用数据库迁移工具来完成这个工作。数据库迁移工具可以在不破坏数据的情况下更新数据库表的结构。
安装
pip install Flask-Migrate
项目配置
在db对象创建后调用,实例化Migrate类
from flask_migrate import Migrate
db = SQLAlchemy(app)
migrate = Migrate(app, db)
- migrate对象第二个参数传入实例化后的SQLAlchemy类创建的db对象
创建迁移环境
在项目根目录下创建一个migrations文件夹,其中包含了自动生成的配置文件和迁移版本文件夹。
$ flask db init
- 迁移环境只需要创建一次。
生成迁移脚本
$ flask db migrate -m "description"
- 因为每一次迁移都会生成新的迁移脚本,而且Alembic为每一次迁移都生成了修订版本(revision)ID,所以数据库可以恢复到修改历史中的任一点。
- 有些复杂的操作无法实现自动迁移,这时可以使用revision命令手动创建迁移脚本。具体可以访问Alembic官方文档查看。
- 如成功执行该命令,在migrations\versions文件夹中会显示显示。如执行失败,建议完全删除数据库文件,然后从第2步开始。
更新数据库
$ flask db upgrade
如果还没有创建数据库和表,这个命令会自动创建;如果已经创建,则会在不损坏数据的前提下执行更新。
- 如果你想回滚迁移,那么可以使用downgrade命令(降级),它会撤销最后一次迁移在数据库中的改动。
- 新增后的数据库或表中字段没有对应数据,值为空。如果需要为旧的数据添加默认的字段值,可以手动操作。
常见错误
ERROR [root] Error: Target database is not up to date.
解决方法:
After creating a migration, either manually or as
--autogenerate
, you must apply it withalembic upgrade head
. If you useddb.create_all()
from a shell, you can usealembic stamp head
to indicate that the current state of the database represents the application of all migrations.即
$ flask db stamp head $ flask db migrate $ flask db upgrade
There are 2 comments
Rex
2021-06-01T22:11:26ZHello, It is with sad regret to inform you that BestLocalData.com is shutting down. We have made all our databases for sale for a once-off price. Visit our website to get the best bargain of your life. BestLocalData.com Regards, Rex
Rhea
2021-02-02T12:12:12ZIt is with sad regret to inform you StarDataGroup.com is shutting down. Fire sale till the 7th of Feb. Any group of databases listed below is $49 or $149 for all 16 databases in this one time offer. You can purchase it at www.StarDataGroup.com and view samples. - LinkedIn Database 43,535,433 LinkedIn Records - USA B2B Companies Database 28,147,835 Companies - Forex Forex South Africa 113,550 Forex Traders Forex Australia 135,696 Forex Traders Forex UK 779,674 Forex Traders - UK Companies Database 521,303 Companies - German Databases German Companies Database: 2,209,191 Companies German Executives Database: 985,048 Executives - Australian Companies Database 1,806,596 Companies - UAE Companies Database 950,652 Companies - Affiliate Marketers Database 494,909 records - South African Databases B2B Companies Database: 1,462,227 Companies Directors Database: 758,834 Directors Healthcare Database: 376,599 Medical Professionals Wholesalers Database: 106,932 Wholesalers Real Estate Agent Database: 257,980 Estate Agents Forex South Africa: 113,550 Forex Traders Visit www.stardatagroup.com or contact us with any queries. Kind Regards, StarDataGroup.com