写点什么

Django 模型关系:从一对多到多对多全解析

  • 2025-07-31
    福建
  • 本文字数:3233 字

    阅读完需:约 11 分钟

一、一对多关系: ForeignKey


一对多是最常见的模型关系,例如 "作者 - 书籍" 场景:假设一个作者可以写多本书,但每本书只能属于一个作者。

定义关系

核心参数说明:

  • on_delete=models.CASCADE:当作者被删除时,关联的书籍也会被自动删除

  • related_name='books':定义反向查询名称,可通过author.books.all()获取作者的所有书籍

from django.db import models
class Author(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100)
def __str__(self): return f"{self.first_name} {self.last_name}"
class Book(models.Model): title = models.CharField(max_length=100) publication_date = models.DateField() # 外键关联Author,级联删除,反向查询名为books author = models.ForeignKey( Author, on_delete=models.CASCADE, related_name='books' )
def __str__(self): return self.title
复制代码


数据操作示例

创建数据

# 创建作者author1 = Author.objects.create(first_name='J.K.', last_name='Rowling')author2 = Author.objects.create(first_name='George', last_name='Orwell')
# 创建书籍并关联作者book1 = Book.objects.create( title='Harry Potter', publication_date='1997-06-26', author=author1)book2 = Book.objects.create( title='1984', publication_date='1949-06-08', author=author2)
复制代码


查询操作

# 正向查询:通过书籍找作者book = Book.objects.get(title='1984')print(book.author)  # 输出: George Orwell
# 反向查询:通过作者找书籍author = Author.objects.get(last_name='Rowling')for book in author.books.all(): print(book.title) # 输出: Harry Potter
复制代码


高级配置

禁用外键约束:当需要灵活管理关联关系(如允许删除存在关联数据的主表记录)时,可关闭数据库级约束

author = models.ForeignKey(    Author,     on_delete=models.SET_NULL,    related_name='books',    db_constraint=False,  # 不创建数据库外键约束    null=True)
复制代码


自定义数据库列名:默认会生成<ClassName>_id列,可通过db_column修改

dept_id = models.ForeignKey(    "SystemDept",    on_delete=models.SET_NULL,    db_column="dept_id",  # 显式指定数据库列名    null=True)
复制代码


二、多对多关系: ManyToManyField


多对多关系适用于 "作者 - 书籍" 的另一种场景:假设一个作者可以写多本书,一本书也可以有多个作者。

定义关系

Django 会自动创建中间表(默认名为appname_book_authors)存储关联关系,无需手动定义。

from django.db import models
class Author(models.Model): name = models.CharField(max_length=100) email = models.EmailField()
def __str__(self): return self.name
class Book(models.Model): title = models.CharField(max_length=200) publication_date = models.DateField() # 多对多关联Author authors = models.ManyToManyField(Author, related_name='books')
def __str__(self): return self.title
复制代码


数据操作示例

添加 / 移除关联

# 创建实例author1 = Author.objects.create(name='Alice', email='alice@example.com')author2 = Author.objects.create(name='Bob', email='bob@example.com')book = Book.objects.create(title='Example Book', publication_date='2023-01-01')
# 添加关联book.authors.add(author1, author2)
# 移除关联book.authors.remove(author1)
复制代码


查询操作

# 正向查询:书籍的所有作者book = Book.objects.get(title='Example Book')for author in book.authors.all():    print(author.name)
# 反向查询:作者的所有书籍author = Author.objects.get(name='Bob')for book in author.books.all(): # related_name='books' print(book.title)
复制代码


自定义中间表

当需要存储关联关系的额外信息(如邀请原因、加入时间)时,可自定义中间表

class Membership(models.Model):    group = models.ForeignKey(Group, on_delete=models.CASCADE)    person = models.ForeignKey(Person, on_delete=models.CASCADE)    inviter = models.ForeignKey(Person, related_name="invites", on_delete=models.CASCADE)    invite_reason = models.CharField(max_length=64)  # 额外信息
class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField( Person, through="Membership", # 指定中间表 through_fields=("group", "person"), # 关联字段 )
复制代码


三、性能优化技巧

select_related:用于一对多关系,提前加载关联对象,减少数据库查询

# 普通查询(N+1问题)entries = Entry.objects.all()for entry in entries:    print(entry.blog.name)  # 每次循环都会触发新查询
# 优化后(仅1次查询)entries = Entry.objects.select_related('blog').all()for entry in entries: print(entry.blog.name) # 使用缓存数据
复制代码


批量操作:利用 update()进行批量更新,避免循环操作

# 批量标记站内信为已读SystemNotifyMessage.objects.filter(    id__in=ids.split(",")).update(    read_status=True,     read_time=timezone.now())
复制代码


四、关于是否使用外键约束


在实际项目中,是否使用数据库外键约束需要权衡利弊

使用外键的优势

  • 数据完整性:数据库级别的约束保证关联数据一致性

  • 开发效率:ORM 自动处理关联查询和级联操作

  • 查询便捷:支持select_related等优化方法,简化多表查询


禁用外键的场景

  • 高并发系统:外键会增加数据库锁竞争,影响写入性能

  • 分布式架构:分库分表环境下,跨库外键无法生效

  • 复杂迁移:避免循环依赖导致的迁移失败问题


折中方案:使用db_constraint=False 参数

  • 数据库层面:无外键约束,数据库不会强制校验关联数据的存在性

  • Django ORM 层面:保留逻辑关联,ORM 仍将字段视为外键关系(逻辑关联),支持 ORM 查询、操作语法



五、多对多关系实战


实战场景:在一个后台管理系统中,用户与角色往往是多对多关系。一个用户可以分配多个角色,一个角色也可以属于多个用户。



模型定义:点击查看完整代码

class SystemUsers(BaseModel, AbstractBaseUser):    id = models.BigAutoField(primary_key=True, db_comment="用户ID", help_text="用户ID")    username = models.CharField(        max_length=30, unique=True, db_comment="用户账号", help_text="用户账号"    )    # ...    # 与角色多对多关系    roles = models.ManyToManyField(        "SystemRole",        through="SystemUserRole",        through_fields=("user_id", "role_id"),        related_name="users",    )    # ...        class SystemUserRole(BaseModel):    """用户和角色关联中间表"""    id = models.BigAutoField(primary_key=True, db_comment="id")    user_id = models.ForeignKey(        "SystemUsers",        on_delete=models.CASCADE,        db_constraint=False,        db_column="user_id",        db_comment="用户ID",    )    role_id = models.ForeignKey(        "SystemRole",        on_delete=models.CASCADE,        db_constraint=False,        db_column="role_id",        db_comment="角色ID",    )
class Meta: managed = True db_table = "system_user_role" db_table_comment = "用户和角色关联表" ordering = ["-id"]
复制代码


system_user_role数据库生成的中间表



文章转载自:小王子1024

原文链接:https://www.cnblogs.com/xwz1024/p/19013425

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2025-04-01 加入

还未添加个人简介

评论

发布
暂无评论
Django模型关系:从一对多到多对多全解析_数据库_电子尖叫食人鱼_InfoQ写作社区