Flask SQLAlchemy 关系映射详解:一对多、一对一与多对多

Theo
Theo
发布时间:2026-10-16| 6人浏览过
原创

本文深入解析 Flask SQLAlchemy 中的四种核心数据关系:一对多、多对一、一对一及多对多。通过作者与文章、国家与首都、学生与教师等具体案例,详细演示外键定义、relationship 函数使用、backref 与 back_populates 参数配置,以及关联表的创建方法。内容涵盖关系属性的双向构建、数据插入与查询操作,帮助开发者掌握 SQLAlchemy 中复杂关系映射的实际应用。

本文以作者与文章的关联为例,阐述一对多关系的核心概念:即单个作者可以发布多篇文章,而每篇文章仅归属于一个特定的作者。

配置项

首先,需要完成数据库连接的config.py配置。

username = 'xxxx'
password = 'xxxx'
database = 'school'
hostname = 'localhost'
port = '3306'
uri = f'mysql+pymysql://{username}:{password}@{hostname}:{port}/{database}'
SQLALCHEMY_DATABASE_URI  = uri
SQLALCHEMY_TRACK_MODIFICATIONS = False

随后在 app.py 文件中导入上述配置模块。

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config
app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)

接下来创建模型类,实现 Python 类与数据库表之间的映射。

定义外键

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(100),nullable=False)
class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(200),nullable=False)
    content = db.Column(db.Text,nullable=False)
    user_id = db.Column(db.Integer,db.ForeignKey('user.id')) 

鉴于一个作者对应多篇文章的逻辑,外键必须设置在Article类中。这样,每篇文章的user_id字段仅存储单一值,确保每篇文章只关联一个作者。

若假设在User类中设置外键字段article_id,则意味着一个作者的所有文章需集中存储于该字段。由于外键仅能存储单一数据(标量),因此外键必须定义在“多”这一侧。

定义关系属性

引入关系属性的具体原因尚不明确,但从查询便捷性角度考虑,这能显著提升操作效率。

定义关系属性需借助关系函数。该属性应定义在关系的“出发侧”,即一对多关系中的“一”一侧。例如,在 User 模型中定义名为 articles 的关系属性,用于表示每个作者所关联的多篇文章。

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(100),nullable=False)
    articles = db.relationship('Article',backref=db.backref('user'))

articles 字段通过db.relationship()关系函数被定义为关系属性,该属性返回多条记录。

  • relationship() 函数的第一个参数指定关系另一侧的模型名称,即 Python 类的名称,而非数据表名。
  • 第二个参数用于添加反向引用,它会自动在另一侧(即 Article 模型)创建一个名为 user 的关系属性,通过该字段可查询到文章所属的用户。

此时 user 表中虽出现 articles 字段,但这并非数据库层面的物理字段,实际表中不存在该列,它仅作为一个查询接口存在。

随后创建数据表并插入初始数据。

 u1 = User(name='zs')
 a1 = Article(title='西游记',content='西游记是四大名著')
 a1.author = u1
 db.session.add(a1)
 db.session.commit()

创建了 user 表和 article 表的记录,如何建立二者联系?需使用关系属性字段进行关联。

文章唯一指向一个作者。

a1.author = u1

将作者实例对象 u1 直接赋值给文章实例对象 a1 的 author 字段,即可建立关系,此时文章表中的 user_id 字段将指向该作者。

article 表

user 表

假设用户 zs 拥有两篇文章,现在尝试利用关系属性进行查询。

u1 = User.query.filter_by(name='zs').first()
print(u1.articles)

输出 user 表的 articles 字段结果。

[<Article 1>, <Article 2>]

结果显示该字段包含两条记录,即 article 表中的两条数据。由于这两篇文章均属于 zs,通过此关系属性字段即可获取该作者对应的所有文章。

反之,关于反向引用backref=db.backref('user'),它会在 Article 模型中也建立一个名为 user 的关系属性,通过该字段可获取文章对应的作者。

例如:

#先找到文章
article1 = Article.query.filter_by(title='西游记').first()
#使用反向引用的字段,user,获取到这个文章对应的作者
print(article1.user)

输出:

<User 1>

双向的关系属性

上述在建立关系属性时,仅在 User 类中使用了关系属性定义。

articles = db.relationship('Article',backref=db.backref('user'))

这种方式会隐式地在 Article 类中建立一个名为 user 的关系属性。我们可以使用back_populates参数显式地建立双向关系属性。

仍以作者 author 和文章 article 为例,一个作者可有多篇文章,一篇文章仅属一个作者,现建立显式的双向关系属性。

class User(db.Model):
    __tablename__ = 'author'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(100),nullable=False)
    #关系属性
    articles = db.relationship('Article',back_populates='authors')
class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(200),nullable=False)
    content = db.Column(db.Text,nullable=False)
    auth_id = db.Column(db.Integer, db.ForeignKey('author.id'))
    #关系属性
    authors = db.relationship('User',back_populates='articles')

在“一”侧的 User 类中,建立了名为 articles 的关系属性,用于获取一个作者对应的多个文章记录。

在“多”侧的 Article 类中,建立了名为 authors 的关系属性,用于获取每篇文章对应的一个作者记录。

使用back_populates参数连接对方,参数值需设置为关系另一侧的关系属性名。

使用关系属性添加数据

u1 = User(name='zs')
a1 = Article(title='西游记', content='西游记是四大名著')
a2 = Article(title='红楼梦', content='红楼梦是四大名著')
a1.authors = u1
a2.authors = u1
db.session.add_all([a1,a2])
db.session.commit()

实例化 User 类对象 u1,以及 Article 类对象 a1、a2。

然后使用 Article 类的关系属性字段 authors。

将 User 类对象 u1 赋值给 Article 类对象的关系属性 authors。

或者反过来,使用用户的关系属性字段 articles 添加数据。

def insert():
    u1 = User(name='zs')
    a1 = Article(title='西游记',content='西游记是四大名著')
    a2 = Article(title='水浒传',content='水浒传是四大名著')
    u1.articles = [a1,a2]
    db.session.add(u1)
    db.session.commit()

这里的添加方式为:u1.articles = [a1,a2]

接着进行查询,利用关系属性字段即可获取数据。此处仅演示使用back_populates参数显式建立双向关系属性,此前使用的 backref 可简化关系定义,属于隐式的双向关系建立方式。

一对一

此处以国家和首都为例演示一对一关系:每个国家仅有一个首都;反之,一个城市也只能作为某个国家的首都。

一对一关系实际上是基于双向关系的一对多关系转化而来。

class Country(db.Model):
    __tablename__ = 'country'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(30),unique=True)
	capital = db.relationship('Capital',back_populates='country')
class Capital(db.Model):
    __tablename__ = 'capital'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(30),unique=True)
    #外键在哪一个表中设置应该都可以
    country_id = db.Column(db.Integer,db.ForeignKey('country.id'))
    #设置关系属性
    country = db.relationship('Country',back_populates='capital')

首先上述为双向关系属性,此时表关系与之前的一对多无异。现在需给关系属性中加入参数uselist=False。

在”一“一侧加入,即在 Country 类模型中加入相关配置。

capital = db.relationship('Capital',back_populates='country',uselist=False)

加入该参数后,在使用 Country.capital 获取记录时,将限制仅返回一条记录。

由于 Capital 中设置了外键 country_id 以存储单一数据,一条记录的country_id 仅对应 Country 中的一条记录。

至此,一对多关系转化为一对一关系。

测试一对一:

首先插入数据,插入一个国家记录和一条城市记录,然后建立关系。

def insert():
    china = Country(name='中国')
    Beijing = Capital(name='北京')
    Beijing.country = china
    db.session.add(Beijing)
    db.session.commit()

这样,Country 表中就有了相关数据。

capital 表中:

目前二者时一对一关系,假如现在新增一个城市,其 country_id 指向中国。

此时变成了一个国家对应两个城市,即变为一对多,但我们定义的是一对一,这样可行吗?

def f():
    #首先拿到中国的这条记录
    china = Country.query.filter_by(name='中国')
    china = china.first()
    print(china)
    #新增城市,
    Guangzhou = Capital(name='广州')
    #建立广州与中国之间的关系
    Guangzhou.country = china
    db.session.add(Guangzhou)
    db.session.commit()
    print('success')

执行该函数后,数据表会有以下变化:

可见capital表中北京这条记录的country_id值变为 NULL。这正是因为我们建立的关系是一对一,不允许变为一对多,所以会自动取消之前的对应关系。假如删除参数uselist=False,即可建立一对多关系。

多对多

此处使用学生和老师来演示多对多关系:每个学生可有多个老师,每个老师也可教多个学生。

在一对多关系中,可在“多”侧添加外键指向“一”侧,因外键仅存储一条记录。但在多对多关系中,每条记录均可与关系另一侧的多条记录建立关联,关系两侧的模型均需存储一组外键。

在 SQLAlchemy 中,表示多对多关系时,除关系两侧的模型外,还需创建一个关联表(association table)。关联表不存储业务数据,仅用于存储关系两侧模型的外键对应关系。

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config
app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)
#关联表
association_table = db.Table('stu_tea_associ',#关联表的名称
    db.Column('student_id',db.Integer,db.ForeignKey('student.id')),#字段student_id,类型,关联的外键
    db.Column('teacher_id',db.Integer,db.ForeignKey('teacher.id')))#字段teacher_id,类型,关联的外键
#学生表
class Student(db.Model):
    __tablename__ = 'student'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(20))
    #建立关系属性
    teachers = db.relationship('Teacher',secondary=association_table,back_populates='students')
#教师表
class Teacher(db.Model):
    __tablename__ = 'teacher'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(20))
    #建立关系属性
    students = db.relationship('Student', secondary=association_table, back_populates='teachers')

关联表使用 db.Table 类定义,传入的第一个参数是关联表的名称。我们在关联表中定义了两个字段student_id、teacher_id,这两个字段作为外键,分别与student.id、teacher.id两个字段关联起来。

另外,此处建立的关系属性是双向的,需使用参数secondary='关联表名称'。

接着,利用关系属性添加数据。

def insert():
    stu1 = Student(name='小明')
    stu2 = Student(name='小红')
    tea1 = Teacher(name='张三')
    tea2 = Teacher(name='李四')
    #学生1的老师有多个
    #由于是多对多关系,所以会采用列表的形式进行外键的orm赋值
    stu1.teachers = [tea1,tea2]
    #学生2的老师有多个
    stu2.teachers = [tea1,tea2]
    db.session.add(stu1)
    db.session.add(stu2)
    db.session.commit()

调用关系属性赋值时,此处需使用列表形式进行添加。

student:

teacher:

关联表:association_table:

接着,使用关系属性查询数据。

def query():
    #先查询到小明这个同学
    stu1 = Student.query.filter_by(name='小明').first()
    #使用关系属性输出对应的老师
    print(stu1.teachers)
    #反过来,查找到老师,使用关系属性输出老师对应的所有学生
    tea1 = Teacher.query.filter_by(name='张三').first()
    print(tea1.students)

输出记录

[<Teacher 1>, <Teacher 2>]

[<Student 1>, <Student 2>]

到此这篇关于 Flask SQLAlchemy 一对多、多对一、一对一及多对多关系介绍的文章就介绍到这了,更多相关 Flask SQLAlchemy 内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。