Django ORM 入门笔记
前置知识 后端入门 Django 项目基础配置
本笔记默认读者已经对数据库有一定的认识
最少需要理解如何在数据库中建立一对一、一对多、多对多关系
本笔记建立在已经知晓并配置完数据库连接的前提下
本笔记中使用的数据库是 MySQL 8.0
使用pymysql
模块连接数据库时,manage.py
在执行数据库相关指令时要求计算机shell中可以运行mysql
指令
ORM 基础概念
简单了解 ORM
Object Relational Mapping 对象关系映射,简称ORM。
ORM通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。ORM在业务逻辑层和数据库层之间充当了桥梁的作用。
简单来说,以往我们操作数据库时使用的是 SQL 语句,但有了 ORM 后,我们操作数据库只需要操作在 ORM 框架下建立的数据模型对象,然后 ORM 替我们执行背后的 SQL 语句。符合我们面向对象编程的习惯,也有效防止了 SQL 注入攻击的发生。
ORM 对象和数据库结构的对应关系
这里以 Django 为例。
以下是我们大创数据库中的一个表的模型与设计。
class SiteAllPeriod(models.Model):
periodid = models.AutoField(db_column='periodID', primary_key=True)
week = models.IntegerField()
siteperiodid = models.IntegerField(db_column='sitePeriodID')
siteid = models.ForeignKey(Site, models.DO_NOTHING, db_column='siteID', related_name='%(class)s_siteid')
sitename = models.ForeignKey(Site, models.DO_NOTHING, db_column='siteName', related_name='%(class)s_sitename')
date = models.DateField()
childsiteid = models.ForeignKey('SiteChild', models.DO_NOTHING, db_column='childSiteID', blank=True, null=True, related_name='%(class)s_childsiteid')
childsitename = models.ForeignKey('SiteChild', models.DO_NOTHING, db_column='childSiteName', blank=True, null=True, related_name='%(class)s_childsitename')
starttime = models.TimeField(db_column='startTime', blank=True, null=True)
endtime = models.TimeField(db_column='endTime', blank=True, null=True)
stateid = models.ForeignKey('SitePeriodState', models.DO_NOTHING, db_column='stateID', blank=True, null=True, related_name='%(class)s_stateid')
prompt = models.CharField(max_length=50, blank=True, null=True)
price = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
studentprice = models.DecimalField(db_column='studentPrice', max_digits=10, decimal_places=2, blank=True, null=True)
staffprice = models.DecimalField(db_column='staffPrice', max_digits=10, decimal_places=2, blank=True, null=True)lowercase.
orderusername = models.CharField(db_column='orderUserName', max_length=30, blank=True, null=True)
orderuserpaymethod = models.CharField(db_column='orderUserPayMethod', max_length=30, blank=True, null=True)
needsubcard = models.IntegerField(db_column='needSubcard', blank=True, null=True)
teachingperiodname = models.CharField(db_column='teachingPeriodName', max_length=30, blank=True, null=True)
teacher = models.CharField(max_length=30, blank=True, null=True)
class Meta:
managed = False
db_table = 'site_all_period'
可以看出来,Django 的 ORM 的模型中:
- 类对应某个特定的表
- 类的实例对应着一条记录
- 类的属性对应着表内的字段
- 类属性的类型对应着表内字段的类型
- 类属性类型构造时的参数与字段的参数一一对应
- 外键拥有特殊的类型
- 外键类型直接指向另一个 ORM 模型(如果另一个模型在本模型下方,需要用单引号包裹)
可见 ORM 的模型既能和数据库形成良好的对应关系、能体现出数据库不同表之间的对应关系。
【知识链接】👇
Django ORM 特性 — 反向查询
在上面的代码中可以发现,字段类型构造的参数中,有一个名为related_name
的参数,它的作用是为 ORM 模型提供反向查询支持。
什么是反向查询
在关系型数据库中,我们使用常外键(Foreign Key)来实现关系,反向查询是使用外键寻找另一表中记录的反过程。
拿到一个记录后,反向查询可以获得使用外键指向该记录的其他表中的记录。
related_name 需要独一无二?
如果你将两个related_name
设为相同值,你会发现框架无法正常启动。
这是因为,知道外键,你可以知道唯一的记录,但是可能有不同表的多个外键指向同一记录。如果存在相同的related_name
,程序就无法知晓应该去哪一个表寻找外键了,就无法反向获得那些特定的记录。
在 Django 中配置 ORM 模型
创建一个 ORM 模型
为了偷懒容易理解,这里引入一个来自 Django 官方文档的样例
这个样例定义了一个 Person
模型,拥有 first_name
和 last_name
:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
first_name
和 last_name
是模型的 字段。每个字段都被指定为一个类属性,并且每个属性映射为一个数据库列。
上面的 Person
模型会创建一个如下的数据库表:
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
若想详细了解各种字段,请移步Django 官方文档 – ORM 字段选项
使用新创建的模型
假设上面的模型位于myapp
项目的models.py
模块内
一旦你定义了你的模型,你需要告诉 Django 你准备 使用 这些模型。你需要修改设置文件中的 INSTALLED_APPS
,在这个设置中添加包含 models.py
文件的模块名称。
例如,若模型位于项目中的 myapp.models
模块( 此包结构由 manage.py startapp
命令创建), INSTALLED_APPS
应设置如下:
INSTALLED_APPS = [
#...
'myapp',
#...
]
当你向 INSTALLED_APPS
添加新的应用后。
运行 manage.py migrate
,你的模型会被自动映射到数据库中(新创建一个符合模型的表)
这时,数据库中就会出现一个名为 myapp_person 的表
使用 Meta 信息指定命名表
如果我想让上面的模型对应的表拥有自定义的表名呢?
答案是:定义 Meta 子类
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class Meta:
db_table = 'person'
添加了该 Meta 子类后,使用manage.py migrate
迁移完成,数据库中出现的表就名为 person 了。
元数据还有其他很多参数可选,具体请移步 模型可选参数参考
使用已有数据库自动建立 ORM 模型
- Django ORM 的另一强大之处在于它可以双向构造映射
- 创建了模型,可以生成表;同样的,设计了表,可以生成模型
首先,在settings.py
配置数据库
完成后,运行指令
python manage.py dbshell
如果执行后,你进入了数据库的命令行,就说明配置成功了
python manage.py inspectdb > models.py
静待两分钟,没有任何输出,运行结束,则说明models.py
生成成功
去根目录寻找models.py
文件,放到喜欢的地方(?),就可以开始使用了。
在 Django 中使用 ORM 完成数据库操作
如果时间充裕,建议先去温习一下SQL,Django 的 ORM 使用了很多高级的 SQL 语句,拥有相关知识有助于理解 ORM 的运行否则就是玄学
下面会展示一些常用 SQL 语句的 ORM 对应
继续以笔记开头的第一个模型为例,为了方便观察,这里再贴一个拷贝
class SiteAllPeriod(models.Model):
periodid = models.AutoField(db_column='periodID', primary_key=True)
week = models.IntegerField()
siteperiodid = models.IntegerField(db_column='sitePeriodID')
siteid = models.ForeignKey(Site, models.DO_NOTHING, db_column='siteID', related_name='%(class)s_siteid')
sitename = models.ForeignKey(Site, models.DO_NOTHING, db_column='siteName', related_name='%(class)s_sitename')
date = models.DateField()
childsiteid = models.ForeignKey('SiteChild', models.DO_NOTHING, db_column='childSiteID', blank=True, null=True, related_name='%(class)s_childsiteid')
childsitename = models.ForeignKey('SiteChild', models.DO_NOTHING, db_column='childSiteName', blank=True, null=True, related_name='%(class)s_childsitename')
starttime = models.TimeField(db_column='startTime', blank=True, null=True)
endtime = models.TimeField(db_column='endTime', blank=True, null=True)
stateid = models.ForeignKey('SitePeriodState', models.DO_NOTHING, db_column='stateID', blank=True, null=True, related_name='%(class)s_stateid')
prompt = models.CharField(max_length=50, blank=True, null=True)
price = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
studentprice = models.DecimalField(db_column='studentPrice', max_digits=10, decimal_places=2, blank=True, null=True)
staffprice = models.DecimalField(db_column='staffPrice', max_digits=10, decimal_places=2, blank=True, null=True)lowercase.
orderusername = models.CharField(db_column='orderUserName', max_length=30, blank=True, null=True)
orderuserpaymethod = models.CharField(db_column='orderUserPayMethod', max_length=30, blank=True, null=True)
needsubcard = models.IntegerField(db_column='needSubcard', blank=True, null=True)
teachingperiodname = models.CharField(db_column='teachingPeriodName', max_length=30, blank=True, null=True)
teacher = models.CharField(max_length=30, blank=True, null=True)
class Meta:
managed = False
db_table = 'site_all_period'
1. SELECT * FROM site_all_period
认真看本条!包含了一些讲解!
选择 site_all_period 中全部的记录
ORM中,使用以下语句获取记录
querySet = SiteAllPeriod.objects.all()
SQL 语句查询返回的是一个记录列表,而 ORM 查询的返回值是QuerySet
,顾名思义,这是一个储存查询结果的集合。
了解 QuerySet
QuerySet
和 Python 标准的 list 类型有些相似,使用时很多时候可以把它当作一个 list 来处理,比如可以使用for ... in ...
遍历选择到的记录,或者使用下标访问index
位置的记录(如果记录有序,这个的用处就比较大了)。
- 仅仅创建
QuerySet
不会触发数据库操作,只有在QuerySet
被使用(读取具体内容)时,才会执行对应的 SQL 语句。 -
QuerySet
是可以迭代的,比如你可以使用records = SiteAllPeriod.objects.all().all().all().all().all()
(逃)请牢记它可以被迭代,它的迭代特性可以用来组装一个复杂的 SQL 语句。看文档应该是会合并成单 SQL 语句,有待验证,好奇可以QQ联络我让我验证。
-
QuerySet
具有缓存功能,如果事先创建了QuerySet
对象,那么对它进行遍历不会反复进行数据库操作,而是会对已有的QuerySet
对象进行遍历。具体缓存机制请参阅 Django 官方文档 – ORM QuerySet 缓存机制
若想更全面的了解QuerySet
,请阅读 Django 官方文档 – ORM 中的 QuerySet
2. SELECT * FROM site_all_period WHERE week=1
选择 site_all_period 中week=1的全部记录
ORM中,使用以下语句获取记录
querySet = SiteAllPeriod.objects.filter(week=1) # 使用参数名确定对象属性(字段)
TIP
假设你想要像2中选择week=1
的记录那样,选择siteid=1
的记录。为此你使用了如下语句,会怎么样?
querySet = SiteAllPeriod.objects.filter(siteid=1)
答案是无法正常运行,因为siteid
是一个外键,你将它作为参数名称,ORM 会尝试去寻找外键对应的对象。
那如果确实想要获得siteid=1
的记录呢?
请在该属性名称后方加上_id
,使用如下语句:
querySet = SiteAllPeriod.objects.filter(siteid_id=1) # 外键属性+_id才能获取到字段的值
3. SELECT JOIN 连接查询/跨表查询
如果要实现对关系的处理,以往我们需要使用 JOIN SQL 语句来进行连接查询,Django 的 ORM 同样提供了极其简便的方式,即“双下划线”
题外话:这个笔记只能帮助读者对 ORM 建立一定的理解,鉴于这东西的复杂性,只能举一些例子,不可能完整呈现 ORM 的神奇功能,还是需要读者完整阅读文档。
比如上面的SiteAllPeriod
,里面有一些外键,我们可以利用这些外键实现跨表查询
比如外键siteid
,Site类型里面有个courtid
字段,如果我们想选择SiteAllPeriod
中(此处断句)所有外键siteid
指向的Site对象的courtid
字段为1的记录
这时需要使用语句
querySet = SiteAllPeriod.objects.filter(siteid__courtid_id=1)
这行语句的背后就是 SQL 的 JOIN 连接查询,Django 会帮你完成该语句的构造和执行。
4. 模型对象之间的访问
Django 的 ORM 模型还有个不错的地方就是:
siteAllPeriod_record = SiteAllPeriod.objects.filter(siteid_id=1).first() # 选择 siteid = 1 的记录,并拿取第一个(当然这种情况也只有一个)
courtid_of_this_period = siteAllPeriod_record.siteid.courtid_id
"""
siteid 是外键属性,访问该属性会直接返回对应的对象
这里可以直接获得 siteAllPeriod 归属的 site 对象的 courtid (site-siteAllPeriod 是个一对多关系)
注意,这样写看起来很美好,但是访问另一个表的记录对象,是会执行一次 SQL 语句的,性能损耗很大!
"""
5. 记录的保存
这个很容易,拿到对象,直接改属性,改完调用这个对象的save()
方法就保存了
当然,对QuerySet
也可以像上面那样干(python的对象默认都是引用,所以懂的都懂)
6. 记录的删除
和5一模一样,就是把save()
换成delete()
结语
上面只提供了部分例子,也是我认为的入门最需要理解的例子。
如果想查看详细的样例与解释,请参阅 Django 官方文档 – ORM 执行查询
后端道阻且长,希望大家一起加油呀!