在ORM中,一对一,一对多,多对多基本上可以满足所有的关系需求。本文将以一个简单的示例来阐述说明这些关系的定义与用法。

示例说明

下面用一下示例来说明 Flask-SQLArchemy 中各种关系

  • Accoun Profile 账号 档案 一对一关系
  • Project Host 项目 主机 一对多关系
  • Account Project  账号 项目 多对多关系


Account 包含 account_name account_email

Profile 包含 fullname gender 

Project 包含 project_name project_webhook

Host 包含 hostname ip


Model定义

以下代码为Model类定义

model.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class Base(db.Model):
    """基类"""
    # 作为父类被继承,不会被创建成表(抽象)
    __abstract__ = True
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)

    def save(self):
        db.session.add(self)
        db.session.commit()

    def delete(self):
        db.session.delete(self)
        db.session.commit()

    def update(self):
        db.session.commit()


class Account(Base):
    __tablename__ = "accounts"
    account_name = db.Column(db.String(50), unique=True, nullable=False)
    account_email = db.Column(db.String(120), unique=True, nullable=False)
    # 与Profile建立一对一关系
    profile = db.relationship('Profile', backref='account', uselist=False)

    def __init__(self, account_name, account_email):
        self.account_name = account_name
        self.account_email = account_email

    def __repr__(self):
        return '<Account %r %r>' % self.account_name, self.account_email


class Profile(Base):
    __tablename__ = "profiles"
    fullname = db.Column(db.String(100))
    gender = db.Column(db.String(10))
    # 外键
    account_id = db.Column(db.Integer, db.ForeignKey('accounts.id'), unique=True)

    def __init__(self, fullname, gender):
        self.fullname = fullname
        self.gender = gender

    def __repr__(self):
        return '<Profile %r %r>' % self.fullname, self.gender


class Project(Base):
    __tablename__ = "projects"
    project_name = db.Column(db.String(50), unique=True, nullable=False)
    project_webhook = db.Column(db.String(150))
    # 与Host建立一对多关系
    hosts = db.relationship('Host', backref='project', lazy=True)
    # 与Accounts通过中间表建立多对多关系
    accounts = db.relationship('Account', secondary='account_project', backref=db.backref('projects', lazy=True))

    def __init__(self, project_name, project_webhook):
        self.project_name = project_name
        self.project_webhook = project_webhook

    def __repr__(self):
        return '<Project %r %r>' % self.project_name, self.project_webhook


class Host(Base):
    __tablename__ = "hosts"
    hostname = db.Column(db.String(50))
    ip = db.Column(db.String(15))
    project_id = db.Column(db.Integer, db.ForeignKey('projects.id'))

    def __init__(self, hostname, ip):
        self.hostname = hostname
        self.ip = ip

    def __repr__(self):
        return '<Host %r %r>' % self.hostname, self.ip


# 中间表
account_project = db.Table(
    'account_project',
    db.Column('account_id', db.Integer, db.ForeignKey('accounts.id')),
    db.Column('project_id', db.Integer, db.ForeignKey('projects.id'))
)

外键ForeignKey

在本文中每个模型都有id作为主键。而外键建立了两个表之间的关联关系。外键用于定义表与表之间的连接,通常用于建立表与表之间的引用关系,使得一个表中的某列的值可以引用另一个表的主键。这样的关联关系有助于维护数据的一致性和完整性。

在 Flask-SQLAlchemy 中,外键的定义通常是通过在模型类的字段上使用 db.ForeignKey 构造函数来完成的。

例如Profile中定义的外键

account_id = db.Column(db.Integer, db.ForeignKey('accounts.id'), unique=True)

Profile 模型中定义了外键 account_id ,并制定了与外键引用的目标表和目标列,此示例中指定的是accounts表的id列(在定义Account模型是,使用__tablename__指定了表名,如果不指定,表名将为模型类名称的小写)

在三种关系中,外键ForeignKey有着不同的定义方式

  • 一对一,外键可以在任意一侧定义
  • 一对多,外键在多的一侧定义
  • 多对多,外键在关联表上定义

关系relationship

relationship 是 SQLAlchemy 中用于定义模型之间关系的关键部分。它用于描述表与表之间的关联关系,包括一对多、多对一、一对一和多对多关系。

常用参数

  • backref: 在一对多关系中,用于在多的一侧的模型中创建一个属性,以便能够访问到与其关联的一的一侧的模型。
  • back_populates: 在双向关系中,用于同时定义两个模型之间的关联关系。
  • uselist: 在一对一关系中,用于指定关联的模型是一个单一对象还是一个对象列表。
  • lazy: 控制查询的时机,常见的取值有 'select'、'joined'、'subquery'、'dynamic' 等,影响 SQL 查询的性能。
  • secondary: 在多对多关系中,用于指定关联表。

lazy说明

lazy 是 relationship 定义中的一个参数,用于指定关联关系的加载方式。lazy 参数有几种常见的取值,每种取值都影响着 SQLAlchemy 如何加载与关系相关联的对象。以下是一些常见的 lazy 参数取值及其区别:

select

说明: 默认值。等效于True表示在访问关联对象时,使用额外的 SELECT 查询加载。

适用场景: 适用于一对一和多对一关系,以及对性能要求不是非常高的情况。

joined

说明: 在访问关联对象时,通过使用 JOIN 查询来一次性加载所有相关对象。

适用场景: 适用于一对一和多对一关系,可以减少 SELECT 查询的次数,提高性能。

subquery

说明: 在访问关联对象时,通过使用子查询来加载所有相关对象。

适用场景: 适用于一对一和多对一关系,可以在不使用 JOIN 的情况下减少 SELECT 查询的次数,提高性能。

dynamic

说明: 返回一个查询对象,而不是直接返回加载的结果。这允许你在需要时添加额外的查询条件。

适用场景: 适用于一对多和多对多关系,以及需要在关系上执行更复杂查询的情况。

False

说明: 表示禁用延迟加载,即在加载对象时立即执行关联查询。

适用场景: 适用于对性能要求很高,希望尽早加载关联对象的情况。

选择合适的 lazy 参数取值取决于你的具体需求和性能要求。在性能要求高的情况下,可以考虑使用 'joined' 或 'subquery' 以减少查询次数。在需要动态查询的情况下,可以使用 'dynamic' 返回查询对象。

back_populate与backref

backref back_populates 都用于定义 SQLAlchemy 模型之间的双向关系,但它们有一些区别。

backref

功能backref 用于在一对多关系中,通过在多的一侧的模型中创建一个属性,以便能够访问到与其关联的一的一侧的模型。

注意事项: backref 是用于单向关系的,不需要在两个模型之间都设置。它只在一的一侧的模型中创建属性,用于访问多的一侧。

back_populates

功能: back_populates 用于在双向关系中,同时定义两个模型之间的关联关系。它需要在两个模型中分别设置,以指定两个方向上的关系。

注意事项: back_populates 是用于双向关系的,需要在两个模型之间都设置。它可以更明确地指定关联关系,确保两个方向的关系都正确地建立。

是否可以替换使用

在一些情况下,backref 可以实现与 back_populates 类似的效果,但并非所有情况下都可以完全替代。back_populates 更加明确和灵活,适用于需要更精准地控制关联关系的情况。

总体而言,选择使用 backref 还是 back_populates 取决于你的具体需求和个人偏好。在一些简单的情况下,它们可以互换使用,但在需要更精细控制关系的场景下,back_populates 提供了更多的灵活性。


未完待续:

数据模型迁移管理。

数据插入,以及关系关联演示

marshmallow 的使用 序列化与反序列化