一、一对多关系: 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
- )
复制代码 自定义数据库列名:默认会生成_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 查询、操作语法
特性db_constraint=True (默认)db_constraint=False数据库外键约束创建,强制数据一致性不创建级联操作数据库自动处理仅由 Django 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数据库生成的中间表
您正在阅读的是《Django从入门到实战》专栏!关注不迷路~
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |