Django ORM 详解
在 Web 开发中,数据库操作是核心环节之一。传统的数据库操作需要编写复杂的 SQL 语句,不仅效率低,还容易出错。而 Django 的 ORM(Object-Relational Mapping,对象关系映射)技术,让开发者可以用面向对象的方式操作数据库,彻底摆脱 SQL 的束缚。本文将详细介绍 Django ORM 的核心概念、使用方法及高级技巧。
一、ORM 是什么?核心思想是什么?
ORM(Object-Relational Mapping)即对象关系映射,是连接数据库与面向对象编程的桥梁。它的核心思想是将数据库中的元素与面向对象模型中的元素建立对应关系,让开发者可以通过操作对象来实现数据库的 CRUD(增删改查)操作。
具体对应关系如下:
数据库概念 | 面向对象模型 | 说明 |
---|---|---|
数据库(Database) | 项目(Project) | 一个 Django 项目通常对应一个数据库 |
表(Table) | 类(Class) | 数据库中的表结构通过模型类定义 |
字段(Field) | 类属性(Attribute) | 表中的字段对应类中的属性 |
记录(Record) | 实例(Instance) | 表中的一行数据对应类的一个实例对象 |
二、模型定义:用类描述数据库表
在 Django 中,ORM 的使用始于模型类(Model) 的定义。模型类本质是对数据库表结构的抽象,定义在models.py
文件中,继承自django.db.models.Model
。
基本模型定义示例
# models.py
from django.db import modelsclass User(models.Model):# 自增主键(如果不定义,Django会自动生成id字段作为主键)id = models.AutoField(primary_key=True)# 用户名(字符串类型,最大长度32)name = models.CharField(max_length=32, verbose_name="用户名")# 密码(整数类型,实际项目中通常用CharField存储哈希值)pwd = models.IntegerField(verbose_name="密码")# 注册时间(自动添加创建时间)register_time = models.DateTimeField(auto_now_add=True, verbose_name="注册时间")class Meta:# 定义表名(不指定则默认用"应用名_类名"作为表名)db_table = "user"# 模型的中文显示名verbose_name = "用户"verbose_name_plural = verbose_name
常用字段类型
Django 提供了丰富的字段类型,覆盖大部分数据库字段需求:
AutoField
:自增整数,通常作为主键CharField
:字符串(需指定max_length
)IntegerField
:整数TextField
:长文本(无长度限制)DateTimeField
:日期时间(auto_now_add=True
表示创建时自动记录时间,auto_now=True
表示更新时自动刷新)BooleanField
:布尔值(True/False)ForeignKey
:外键(用于多表关联,如models.ForeignKey(OtherModel, on_delete=models.CASCADE)
)ManyToManyField
:多对多关系
三、数据库迁移:让模型生效
定义好模型后,需要通过迁移命令将模型转换为数据库中的实际表结构。Django 的迁移机制会记录模型的每一次变更,确保数据库结构与模型保持一致。
核心迁移命令
记录模型变更(生成迁移文件)将模型中与数据库相关的操作(新增表、修改字段等)记录到
migrations
文件夹中:
python manage.py makemigrations
2.执行迁移(同步到数据库)将迁移文件中的操作真正应用到数据库,生成或修改表结构:
python manage.py migrate
注意事项
- 每次修改
models.py
中与数据库相关的代码(如新增字段、修改字段类型),都必须重新执行上述两个命令。 - 迁移文件是项目的一部分,需纳入版本控制(如 Git),确保团队协作时数据库结构一致。
- 如需查看迁移计划(不实际执行),可使用:
python manage.py sqlmigrate 应用名 迁移文件编号
四、ORM 核心操作:CRUD 实战
Django ORM 的核心是通过模型管理器(objects) 操作数据库。objects
是模型类的默认管理器,封装了所有数据库交互方法,支持链式调用。
1. 查询操作(Read)
查询是数据库操作中最常用的场景,Django ORM 提供了丰富的查询方法。
基本查询
from myapp.models import User# 1. 获取所有记录(返回QuerySet对象,可迭代)
all_users = User.objects.all()# 2. 根据条件获取单个记录(条件不存在会抛DoesNotExist异常)
user = User.objects.get(id=1) # 按主键查询
user = User.objects.get(name="张三") # 按其他字段查询# 3. 按条件过滤记录(返回符合条件的QuerySet,支持多条件)
active_users = User.objects.filter(name="张三", pwd=123456) # 多条件"且"
users_contain_li = User.objects.filter(name__contains="李") # 模糊查询(name包含"李")# 4. 排除符合条件的记录
inactive_users = User.objects.exclude(name="张三") # 排除name为"张三"的记录
高级查询
# 1. 链式查询(多个条件组合)
users = User.objects.filter(name__startswith="张").exclude(pwd=0).order_by("register_time")# 2. 排序(默认升序,字段前加"-"表示降序)
users_by_time_asc = User.objects.order_by("register_time") # 按注册时间升序
users_by_time_desc = User.objects.order_by("-register_time") # 按注册时间降序# 3. 判断记录是否存在(返回布尔值,比get()更安全)
has_zhang = User.objects.filter(name="张三").exists()# 4. 统计数量
total_users = User.objects.count() # 总记录数
zhang_count = User.objects.filter(name__startswith="张").count() # 符合条件的数量# 5. 只获取部分字段(返回字典列表)
user_names = User.objects.values("id", "name") # [{"id":1, "name":"张三"}, ...]# 6. 获取单个字段列表(返回元组列表)
user_ids = User.objects.values_list("id", flat=True) # [1, 2, 3, ...](flat=True返回一维列表)
2. 创建操作(Create)
创建操作用于向数据库插入新记录,Django 提供了多种创建方式。
单个对象创建
# 方法1:先实例化对象,再调用save()保存(适合需要先处理对象属性的场景)
user = User(name="李四", pwd=654321)
user.register_time = "2023-10-01 12:00:00" # 可选:手动设置其他字段
user.save() # 保存到数据库# 方法2:直接通过create()创建(一步到位,返回创建的对象)
user = User.objects.create(name="王五", pwd=111222)# 方法3:get_or_create(查询存在则返回,不存在则创建,避免重复)
user, created = User.objects.get_or_create(name="赵六", # 查询条件defaults={"pwd": 333444} # 若不存在,创建时的其他字段
)
# created为布尔值:True表示新创建,False表示查询到已存在
批量创建
批量创建比循环单个创建效率更高(减少数据库交互次数):
# 批量创建3个用户
users = [User(name="小明", pwd=123),User(name="小红", pwd=456),User(name="小刚", pwd=789)
]
User.objects.bulk_create(users) # 批量插入
3. 更新操作(Update)
更新操作用于修改数据库中已有的记录。
单个对象更新
# 方法1:先查询对象,修改属性后save()(会更新所有字段,适合少量字段修改)
user = User.objects.get(id=1)
user.name = "张三_new" # 修改字段
user.save() # 保存更新# 方法2:通过filter()直接更新(只更新指定字段,效率更高)
User.objects.filter(id=1).update(name="张三_new2", pwd=999999)# 方法3:update_or_create(存在则更新,不存在则创建)
user, created = User.objects.update_or_create(id=1, # 查询条件(按id查找)defaults={"name": "张三_updated", "pwd": 888888} # 若存在则更新的字段
)
批量更新
批量更新符合条件的所有记录:
# 批量更新所有name为"张三"的用户密码
User.objects.filter(name="张三").update(pwd=000000)# 更新所有用户的注册时间为当前时间(需导入timezone)
from django.utils import timezone
User.objects.all().update(register_time=timezone.now())
4. 删除操作(Delete)
删除操作用于移除数据库中的记录,需谨慎使用(删除后不可恢复)。
单个对象删除
# 方法1:先查询对象,再调用delete()
user = User.objects.get(id=1)
user.delete()# 方法2:直接通过filter()删除(更简洁)
User.objects.filter(id=1).delete()
批量删除
# 删除所有name包含"测试"的用户
User.objects.filter(name__contains="测试").delete()# 删除所有记录(谨慎!会清空表)
User.objects.all().delete()
注意:如果模型有外键关联(如ForeignKey
),删除时需注意on_delete
参数的配置(如CASCADE
表示级联删除,关联的子表记录也会被删除)。
五、高级查询技巧
除了基础 CRUD,Django ORM 还支持复杂查询场景,满足业务需求。
1. Q 对象:实现 "或" 条件查询
filter()
默认是 "且" 条件,如需 "或" 条件,需使用Q
对象:
from django.db.models import Q# 查询name为"张三"或pwd为123的用户
users = User.objects.filter(Q(name="张三") | Q(pwd=123))# 组合"且"和"或"(Q对象优先级高于普通条件)
users = User.objects.filter(Q(name="张三") | Q(pwd=123),register_time__gt="2023-01-01" # 同时满足注册时间在2023年之后
)
2. F 对象:同表字段比较
F()
用于引用模型中的字段,实现同一条记录的字段间比较:
from django.db.models import F# 查询pwd大于id的用户(假设业务中有此需求)
users = User.objects.filter(pwd__gt=F("id"))# 批量更新:将所有用户的pwd增加100
User.objects.all().update(pwd=F("pwd") + 100)
3. 聚合查询(aggregate)
用于对查询结果进行统计(如求和、平均值等):
from django.db.models import Sum, Avg, Max, Min# 统计所有用户pwd的总和、平均值、最大值、最小值
result = User.objects.aggregate(total_pwd=Sum("pwd"),avg_pwd=Avg("pwd"),max_pwd=Max("pwd"),min_pwd=Min("pwd")
)
# result格式:{"total_pwd": 12345, "avg_pwd": 1234.5, ...}
4. 分组查询(annotate)
结合聚合函数,按字段分组统计
from django.db.models import Count# 按name分组,统计每个name的用户数量
name_counts = User.objects.values("name").annotate(count=Count("id"))
# 结果:[{"name": "张三", "count": 2}, {"name": "李四", "count": 1}, ...]
六、ORM 查询优化
在数据量大的场景下,不合理的查询会导致性能问题,需注意优化。
1. 避免 N+1 查询问题
当查询关联表数据时(如查询用户及其关联的订单),直接循环查询会产生 N+1 次数据库请求(1 次查用户,N 次查每个用户的订单)。
解决方法:使用select_related
(一对一 / 多对一关系)或prefetch_related
(多对多 / 反向多对一关系)预加载关联数据。
# 假设User有一个外键关联到Order(多对一)
from myapp.models import Order# 优化前:查询用户及其订单(N+1问题)
users = User.objects.all()
for user in users:orders = user.order_set.all() # 每个user都会触发一次查询# 优化后:用select_related预加载关联数据(1次查询)
users = User.objects.select_related("order").all() # 假设外键字段是order
2. 只查询需要的字段
使用only()
或defer()
减少查询字段,降低数据传输量:
# 只查询id和name字段(其他字段不加载)
users = User.objects.only("id", "name")# 排除pwd字段(加载其他所有字段)
users = User.objects.defer("pwd")
七、总结
Django ORM 是 Django 框架的核心优势之一,它通过面向对象的方式封装了数据库操作,让开发者无需编写 SQL 即可完成 CRUD 及复杂查询。本文介绍了 ORM 的核心概念、模型定义、迁移命令、CRUD 操作及高级技巧,掌握这些内容可以极大提升数据库操作的效率。
在实际开发中,建议结合 Django 官方文档深入学习 ORM 的细节(如多表关联、事务处理等),并注意查询优化,避免性能问题。
希望本文对你理解 Django ORM 有所帮助,祝你的 Django 开发之旅更加顺畅!