Django之ORM
本人理解:ORM就是使用Django框架的时候操作数据库的工具,封装了许多功能,使用的好可以带来很多便利
简介
ORM:Object Relational Mapping(关系对象映射),其中类名对应数据库中的表名;类属性对应数据库里的字段;类实例对应数据库表里的一行数据;obj.id obj.name…..类实例对象的属性
Django ORM优势:Django的ORM本质上会根据对接的数据库引擎,翻译成对应的sql语句;所有使用Django开发的项目无需关心程序底层使用的是MySQL、Oracle、sqlite….,如果数据库迁移,只需要更换Django的数据库引擎即可。
Django连接MySQL
创建数据库
Django自带的ORM是data_first类型的ORM,使用前必须先创建数据库,且需要注意设置数据的字符编码
1 | create database <name> default character set utf8 collate utf8_general_ci; |
修改settings文件中设置
修改项目文件夹中的setting.py,连接MySQL数据库(Django默认使用的是sqllite数据库)
1 | DATABASES = { |
修改Django默认连接MySQL的方式
修改项目文件夹中的__init__.py,使Django默认连接MySQL(需先安装环境npm install pymysql)
1 | import pymysql |
settings文件注册APP
新建app可以使用Django指令python manage.py startapp <app_name>
随后在settings中注册这一app,
1 | INSTALLED_APPS = [ |
models中创建表
下一部分大部分中会具体讲述app文件夹中models.py怎么写,下面给出一个简化版的例子
1 | from django.db import models |
数据迁移
在models.py中定义完模型后,需要将这些 Python 类转换为数据库中的表结构。这个过程称为数据迁移。以下是进行数据迁移的具体步骤和命令:
1 | python manage.py makemigrations #用于生成迁移文件,这些文件记录了模型的变化 |
MySQL可视化
我选择的是jetbrains家的DataGrip,具体教程可看使用DataGrip连接MySQL数据库
models.py创建表
ORM字段介绍
- 字符串类
以下在数据库中本质都是字符串数据类型,此类字段只是在Django自带的admin中生效
models.CharField
更多点击查看
EmailField(CharField)IPAddressField(Field)
URLField(CharField)
SlugField(CharField)
UUIDField(Field)
FilePathField(Field)
FileField(Field)
ImageField(FileField)
CommaSeparatedIntegerField(CharField)
- models.CharField 对应的是MySQL的varchar数据类型
- char 和 varchar的区别 :
- char和varchar的共同点是存储数据的长度,不能超过max_length限制,
- 不同点是varchar根据数据实际长度存储,char按指定max_length()存储数据;所有前者更节省硬盘空间
- 时间字段
1 | data = models.DateTimeField(null=True) |
- 数字字段
1 | num = models.IntegerField() |
- 枚举字段
1 | choice=( |
在数据库存储枚举类型,比外键有什么优势?
- 无需连表查询性能低,省硬盘空间(选项不固定时用外键)
- 在model文件里不能动态增加(选项一成不变用Django的choice)
- 关系字段
1 | author = models.ForeignKey(Author, on_delete=models.CASCADE) |
字段参数介绍
- 数据库级别生效
点击查看,内容有些多
AutoField(Field)- int自增列,必须填入参数 primary_key=True
BigAutoField(AutoField)
- bigint自增列,必须填入参数 primary_key=True
注:当model中如果没有自增列,则自动会创建一个列名为id的列
SmallIntegerField(IntegerField):
- 小整数 -32768 ~ 32767
PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正小整数 0 ~ 32767
IntegerField(Field)
- 整数列(有符号的) -2147483648 ~ 2147483647
PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正整数 0 ~ 2147483647
BigIntegerField(IntegerField):
- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
自定义无符号整数字段
class UnsignedIntegerField(models.IntegerField):
def db_type(self, connection):
return 'integer UNSIGNED'
PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',
BooleanField(Field)
- 布尔值类型
NullBooleanField(Field):
- 可以为空的布尔值
CharField(Field)
- 字符类型
- 必须提供max_length参数,max_length表示字符长度
TextField(Field)
- 文本类型
EmailField(CharField):
- 字符串类型,Django Admin以及ModelForm中提供验证机制
IPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制
GenericIPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
- 参数:
protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both"
URLField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证 URL
SlugField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)
CommaSeparatedIntegerField(CharField)
- 字符串类型,格式必须为逗号分割的数字
UUIDField(Field)
- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证
FilePathField(Field)
- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
- 参数:
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
FileField(Field)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
ImageField(FileField)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
width_field=None, 上传图片的高度保存的数据库字段名(字符串)
height_field=None 上传图片的宽度保存的数据库字段名(字符串)
DateTimeField(DateField)
- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DateField(DateTimeCheckMixin, Field)
- 日期格式 YYYY-MM-DD
TimeField(DateTimeCheckMixin, Field)
- 时间格式 HH:MM[:ss[.uuuuuu]]
DurationField(Field)
- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
FloatField(Field)
- 浮点型
DecimalField(Field)
- 10进制小数
- 参数:
max_digits,小数总长度
decimal_places,小数位长度
BinaryField(Field)
- 二进制类型
- Django admin级别生效
点击查看
blank (是否为空)editable=False 是否允许编辑
help_text="提示信息"提示信息
choices=choice 提供下拉框
error_messages="错误信息" 错误信息
validators 自定义错误验证(列表类型),从而定制想要的验证规则
ORM单表操作
QuerySet数据类型
QuerySet特点:可迭代的、可切片、惰性计算和缓存机制
点击查看
books=models.Book.objects.all()[:10] #切片book= models.Book.objects.all()[6] #索引
for obj in books: #迭代
print(obj.title)
books=models.Book.objects.all()
# 惰性机制--->等于一个生成器,不应用books不会执行任何SQL操作
# query_set缓存机制1次数据库查询结果query_set都会对应一块缓存,再次使用该query_set时,不会发生新的SQL操作
# 这样减小了频繁操作数据库给数据库带来的压力
models.Publish.objects.all().iterator()
# 但是有时候取出来的数据量太大会撑爆缓存,可以使用迭代器解决这个问题
增删改查
- 增
表.objects.create()或obj=类(属性=XX) obj.save(),多对多关系处理外键时有add()、clear()、remove()
- 删
obj.delete(),同时还有级联删除的情况on_delete=models.CASCADE
- 改
update()、save()
- 查
objects.all()、objects.filter(id=2, price=100),这两个都会返回QuerySet对象集合 [对象1、对象2、…. ]
objects.get(id=2) ,这个返回单个对象,没有找到或有多个都会报错
后续还会有values方法与跨表查询
双下划线操作
属性后面搭配双下划线与特定的字段可以方便查询
内容较多,点击查看
获取个数- models.book.objects.filter(price=17).count()
大于,小于
- objects.filter(id__gt=1) # 获取id大于1的值
- objects.filter(id__gte=1) # 获取id大于等于1的值
- objects.filter(id__lt=10) # 获取id小于10的值
- objects.filter(id__lte=10) # 获取id小于等于10的值
- objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值
in
- objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据
- objects.exclude(id__in=[11, 22, 33]) # not in
isnull
- objects.filter(pub_date__isnull=True)
contains
- objects.filter(name__contains="ven")
- objects.filter(name__icontains="ven") # icontains大小写不敏感
- objects.exclude(name__icontains="ven")
range
- objects.filter(id__range=[1, 2])
其他类似
- startswith,istartswith, endswith, iendswith
order by
- objects.filter(name='seven').order_by('id') # 生序
- objects.filter(name='seven').order_by('-id') # 降序
正则匹配
- objects.get(title__regex=r'^(An?|The) +')
- objects.get(title__iregex=r'^(an?|the) +') # 不区分大小写
date
- objects.filter(pub_date__date=datetime.date(2005, 1, 1))
- objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))
year
- objects.filter(pub_date__year=2005)
- objects.filter(pub_date__year__gte=2005)
month
- objects.filter(pub_date__month=12)
- objects.filter(pub_date__month__gte=6)
day
- objects.filter(pub_date__day=3)
- objects.filter(pub_date__day__gte=3)
week_day
- objects.filter(pub_date__week_day=2)
- objects.filter(pub_date__week_day__gte=2)
hour
- objects.filter(timestamp__hour=23)
- objects.filter(time__hour=5)
- objects.filter(timestamp__hour__gte=12)
minute
- objects.filter(timestamp__minute=29)
- objects.filter(time__minute=46)
- objects.filter(timestamp__minute__gte=29)
second
- objects.filter(timestamp__second=31)
- objects.filter(time__second=2)
- objects.filter(timestamp__second__gte=31)
values作用
values() 用于查询特定字段数据并返回字典格式,适合在需要对查询结果进行进一步处理或在不需要整个对象时使用。优点是优化查询性能,简化对结果的处理。
- 提取特定字段
1 | books = Book.objects.values('title', 'price') |
- 结合annotate()使用
1 | from django.db.models import Count |
- 跨表查询(连表查询)
使用values()时,可以通过双下划线 (__) 访问相关联表的字段
1 | books = Book.objects.values('title', 'author__name') |
- values_list() 的扩展
返回元组列表
1 | titles = Book.objects.values_list('title', flat=True) |
ORM连表操作
连表操作通常涉及三种关系,可以将这些操作分为正向查找和反向查找两种方式
- 一对多:
models.ForeignKey(其他表) - 多对多:
models.ManyToManyField(其他表) - 一对一:
models.OneToOneField(其他表)
正向查找
从包含外键的模型出发,查询关联的其他模型数据
ForeignKey 字段在哪个表,哪个表就可以通过 外键字段__关联表字段 进行正向连表操作
1 | # 一对多正向查找 |
反向查找
从没有外键的模型出发,查询关联的模型数据。反向查找依赖于模型中自动生成的反向管理器
反向查找可以通过 小写表名_set(例如 userinfo_set)或者通过自定义的 related_name 进行查找,跨表查询的时候可应用小写表名_字段
1 | # 一对多反向查找 |
跨表查询
使用 values() 或 values_list() 结合外键字段进行跨表查询
1 | # 正向跨表查询 |
多对多关系操作
多对多关系通常涉及第三张关系表,在Django中,使用 ManyToManyField 可以自动处理这个关系表,同时也可以自定义关系表,通过through字段表明
下面是一个案例:假设我们有两个模型 Boy 和 Girl,以及一个表示他们关系的多对多关系。
1 | from django.db import models |
下面是查询操作
1 | # 正向查找:通过 Boy 查询他所有的 Girl 朋友 |
反向查找的自定义名称
使用 related_name 和 related_query_name 自定义反向查找的名称,避免使用默认的 小写表名_set
1 | class UserInfo(models.Model): |
ORM查询性能
普通查询
普通查询存在N+1次查询问题,即在查询一组对象时,先执行一次主查询,然后在后续的循环中对每个对象的外键字段再次执行单独的查询
1 | obj_list = models.Love.objects.all() |
- 这段代码中,
Love表的所有记录首先被查询出来。 - 在遍历
obj_list时,每访问一个对象的b外键字段(例如row.b.name),都会触发一次查询操作,导致多次数据库查询。
select_related 优化
select_related 适用于一对一和一对多的外键关系。它通过在查询时执行 SQL JOIN 操作,将相关表的数据一次性查询出来,从而避免了后续的多次查询。
1 | obj_list = models.Love.objects.all().select_related('b') |
select_related('b')会在查询时通过 SQLJOIN操作将Love表和b表的数据一起查询出来。- 在遍历
obj_list时,外键字段b的数据已经包含在查询结果中,无需再次查询数据库。 - 适用场景:数据量较小,且关联查询的表不多的情况下,
select_related可以显著减少查询次数,提升性能。
prefetch_related 优化
prefetch_related 适用于多对多和一对多的外键关系。它通过先分别查询主表和外键表的数据,再在 Python 层面进行数据组合,从而避免了数据库的复杂 JOIN 操作。
1 | obj_list = models.Love.objects.all().prefetch_related('b') |
prefetch_related('b')会分别查询Love表和b表的数据,然后在 Python 层面将它们组合在一起。- 虽然
prefetch_related也会多次查询数据库,但它避免了 SQLJOIN带来的性能开销,因此在处理大数据量和复杂关联关系时表现更好。 - 适用场景:数据量大,外键关系复杂的情况下,
prefetch_related更高效。
update和 save性能比较
直接给结论:update() 的性能优于 save()。update() 直接生成并执行 SQL 语句,只更新指定的字段,而 save() 会查询整个对象,并在更新时提交所有字段。因此,在需要更新单个或少量字段时,update() 更为高效。
分组和聚合查询
annotate() 分组函数
1 | # 查看每一位作者出过的书中最贵的一本(按作者名分组values(),然后annotate分别取每人出过的书价格最高的) |
aggregate() 聚合函数
通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。
1 | from django.db.models import Avg,Sum,Max,Min |
F查询、Q查询
F查询
F()可以获取对象中的字段的属性(列),并对其进行操作
1 | from django.db.models import F,Q |
Q查询
如果多个查询条件涉及到逻辑使用 fifter并用,隔开可以表示与,但没法表示或非的关系
Q()可以使ORM的fifter()方法支持多个查询条件,使用逻辑关系(&、|、~)包含、组合到一起进行多条件查询
1 | from django.db.models import F,Q |
ContentType表
ContentType 是 Django 内置的一个表,用于记录项目中所有已注册的模型。每个模型都有一个对应的 ContentType 实例,其中包括模型所在的应用名称和模型名称。
在 INSTALLED_APPS 中,django.contrib.contenttypes 是 Django 自动创建和维护 ContentType 表的关键应用。
1 | INSTALLED_APPS = [ |
动态获取模型类
通过 ContentType,可以动态获取某个模型类,这在需要对不同模型进行通用处理时非常有用。
1 | from django.contrib.contenttypes.models import ContentType |
复杂的外键关系处理
有时候,一个模型可能需要关联到不同的模型,例如,一个优惠券模型可能需要关联到课程模型中的某一门课程或学位课程中的某一门课程。在这种情况下,ContentType 和通用外键 (GenericForeignKey) 可以帮助我们实现这种灵活的关联。
如下示例:优惠券与课程的关联
1 | from django.db import models |
Coupon模型中的content_type字段记录了关联模型的ContentType,object_id记录了具体对象的 ID。content_object是一个通用外键,允许你通过content_type和object_id访问关联的对象。
GenericRelation 的使用
GenericRelation 用于在关联模型上定义反向关系,使得你可以通过主模型查询到所有关联的对象。
延续上面示例:查询当前课程的所有优惠券
1 | from django.contrib.contenttypes.fields import GenericRelation |
创建关联对象
创建一个与特定课程关联的优惠券
1 | # 方法1:手动指定 ContentType 和 object_id |
总结
ContentType表:用于动态记录和获取 Django 中的模型类。- **
GenericForeignKey**:允许一个模型与多个模型建立通用的外键关系。 - **
GenericRelation**:在主模型中定义反向关系,方便查询关联对象。 - 应用场景:非常适合处理复杂的外键关系,尤其是在模型间需要动态关联或模型数量较多的情况下。
补充
order_by() 排序查询
1 | # 按 id 降序排序 |
exclude() 排除查询
1 | # 排除 course_type 为 2 的课程 |
按时间查询
Django ORM 支持通过时间字段进行数据查询,前提是模型中的时间字段是 DateTimeField 或 DateField,且存储的数据类型为 Python 的 datetime 对象。
注意
- 如果查询时间时出现警告,可以在
settings.py中设置USE_TZ = True。- 如果时间格式显示为英文,可以在
settings.py中设置LANGUAGE_CODE = 'zh-Hans'。
比如大于某个时间点的数据可以用双下划线的__gt,更多操作可以看前面双下划线部分
1 | import datetime |
- 标题: Django之ORM
- 作者: rainbowYao
- 创建于 : 2024-08-17 01:27:30
- 更新于 : 2024-09-18 09:22:03
- 链接: https://redefine.ohevan.com/2024/08/17/Django之ORM/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。