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增

步骤:

  1. 创建python模型实例
  2. 使用db.session.add()将该实例对象添加到session对话中
  3. 使用db.session.commit()提交对话

eg:

$ u = User(name='test1')
$ db.session.add(u)
$ db.session.commit()

2. Update改

步骤:

  1. 使用查询语句得到目标对象
  2. 修改目标对象
  3. 使用db.session.commit()提交对话

eg:

$ u = User,query.first()
$ u.name = 'new_data'
$ db.session.commit()

3. Delete删

步骤:

  1. 使用查询语句得到目标对象
  2. 使用db.session.delete()将需删除的目标对象放到对话中
  3. 使用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 :在第一次访问当前数据表内容时会使用joinSQL命令一并把相关的关联表内容一次性加载,可理解为一次对所有涉及的数据表均执行了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"
    )
  • primaryjoinsecondaryjoin建立了与关联表的联系,指明了followersfollowing是关联了表中的哪一列。
  • primaryjoinsecondaryjoin中会看到.c.,是Mapper.columnscolumns缩写。一定要有!!!
  • 每次的变更都需要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命令(降级),它会撤销最后一次迁移在数据库中的改动。
    • 新增后的数据库或表中字段没有对应数据,值为空。如果需要为旧的数据添加默认的字段值,可以手动操作。

常见错误

  1. ERROR [root] Error: Target database is not up to date.

    解决方法:

    After creating a migration, either manually or as --autogenerate, you must apply it with alembic upgrade head. If you used db.create_all() from a shell, you can use alembic 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

    Hello, 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

    It 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