折腾,是对梦想的尊重。
一、ORM简介
MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,在业务逻辑层和数据库层之间起了桥梁的作用。
ORM(Object Relational Mapping)是“对象—关系—映射”的简称。
二、前期准备
1、在settings中配置数据库
首先想将模型转为mysql数据库中的表,要在settings中配置。Django默认使用的是sqlite数据库,这里换成mysql。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db_name', # 要连接的数据库,连接前需要创建好
'HOST': '127.0.0.1', # 连接主机,默认本机
'USER': 'root', # 连接数据库的用户名
'PASSWORD': ' ', # 连接数据库的密码
'PORT': 3306 # 端口 默认3306
}
}
🐷 tips: NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建 USER和PASSWORD分别是数据库的用户名和密码。
设置完后启动项目会报错:no module named MySQLdb。这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb 对于py3有很大问题,所以我们需要的驱动是PyMySQL,接下来去激活我们的mysql
我们只需要找到项目名文件下的init,在里面写入:
import pymysql
pymysql.install_as_MySQLdb()
2、在settings中配置日志Logging
日志在程序开发中是少不了的,通过日志我们可以分析到错误在什么地方,有什么异常。在生产环境下有很大的用途,而且一旦涉及到对数据库的操作,更应该记录下来,数据是很重要的。在Java开发中通常用log4j,logback等第三方组件。那么在django中是怎么处理日志?django利用的就是Python提供的logging模块,但django中要用logging,还得有一定的配置规则,需要在setting中设置。
(1) logging 模块
logging模块为应用程序提供了灵活的手段记录事件、错误、警告和调试信息。对这些信息可以进行收集、筛选、写入文件、发送给系统日志等操作,甚至还可以通过网络发送给远程计算机。
a、 日志记录级别
logging模块的重点在于生成和处理日志消息。每条消息由一些文本和指示其严重性的相关级别组成。级别包含符号名称和数字值。
级别 | 值 | 描述 |
---|---|---|
CRITICAL | 50 | 关键错误/消息 |
ERROR | 40 | 错误 |
WARNING | 30 | 警告消息 |
INFO | 20 | 通知消息 |
DEBUG | 10 | 调试 |
NOTSET | 0 | 无级别 |
b、记录器
记录器负责管理日志消息的默认行为,包括日志记录级别、输出目标位置、消息格式以及其它基本细节。
关键字参数 | 描述 |
---|---|
filename | 将日志消息附加到指定文件名的文件 |
filemode | 指定用于打开文件模式 |
format | 用于生成日志消息的格式字符串 |
datefmt | 用于输出日期和时间的格式字符串 |
level | 设置记录器的级别 |
stream | 提供打开的文件,用于把日志消息发送到文件 |
c、 format日志消息格式
格式 | 描述 |
---|---|
%(name)s | 记录器的名称 |
%(levelno)s | 数字形式的日志记录级别 |
%(levelname)s | 日志记录级别的文本名称 |
%(filename)s | 执行日志记录调用的源文件的文件名称 |
%(pathname)s | 执行日志记录调用的源文件的路径名称 |
%(funcName)s | 执行日志记录调用的函数名称 |
%(module)s | 执行日志记录调用的模块名称 |
%(lineno)s | 执行日志记录调用的行号 |
%(created)s | 执行日志记录的时间 |
%(asctime)s | 日期和时间 |
%(msecs)s | 毫秒部分 |
%(thread)d | 线程ID |
%(threadName)s | 线程名称 |
%(process)d | 进程ID |
%(message)s | 记录的消息 |
d、内置处理器
logging模块提供了一些处理器,可以通过各种方式处理日志消息。使用addHandler()方法将这些处理器添加给Logger对象。另外还可以为每个处理器配置它自己的筛选和级别。
- handlers.DatagramHandler(host,port) 发送日志消息给位于制定host和port上的UDP服务器。
- handlers.FileHandler(filename) 将日志消息写入文件filename。
- handlers.HTTPHandler(host, url) 使用HTTP的GET或POST方法将日志消息上传到一台HTTP 服务器。
- handlers.RotatingFileHandler(filename) 将日志消息写入文件filename。如果文件的大小超出maxBytes制定的值,那么它将被备份为filename1。
ps: 由于内置处理器还有很多,如果想更深入了解,可以查看官方手册。
(2) Django中使用logging记录日志
a、在settings.py中进行配置
- 完整版
🌈 先导入模块
import logging
import django.utils.log
import logging.handlers
LOGGING = {
'version': 1, # 解析配置,目前为止,这是dictConfig 格式唯一的版本
'disable_existing_loggers': True, # 这是一个布尔型值,默认值为True(为了向后兼容)表示禁用已经存在的logger,除非它们或者它们的祖先明确的出现在日志配置中;如果值为False则对已存在的loggers保持启动状态。
'formatters': {
'standard': {
'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'} #日志格式
},
'filters': {
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'include_html': True,
},
'default': {
'level':'DEBUG',
'class':'logging.handlers.RotatingFileHandler',
'filename': '/sourceDns/log/all.log', #日志输出文件
'maxBytes': 1024*1024*5, #文件大小
'backupCount': 5, #备份份数
'formatter':'standard', #使用哪种formatters日志格式
},
'error': {
'level':'ERROR',
'class':'logging.handlers.RotatingFileHandler',
'filename': '/sourceDns/log/error.log',
'maxBytes':1024*1024*5,
'backupCount': 5,
'formatter':'standard',
},
'console':{
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'standard'
},
'request_handler': {
'level':'DEBUG',
'class':'logging.handlers.RotatingFileHandler',
'filename': '/sourceDns/log/script.log',
'maxBytes': 1024*1024*5,
'backupCount': 5,
'formatter':'standard',
},
'scprits_handler': {
'level':'DEBUG',
'class':'logging.handlers.RotatingFileHandler',
'filename':'/sourceDns/log/script.log',
'maxBytes': 1024*1024*5,
'backupCount': 5,
'formatter':'standard',
}
},
'loggers': {
'django': {
'handlers': ['default', 'console'],
'level': 'DEBUG',
'propagate': False
},
'django.request': {
'handlers': ['request_handler'],
'level': 'DEBUG',
'propagate': False,
},
'scripts': {
'handlers': ['scprits_handler'],
'level': 'INFO',
'propagate': False
},
'sourceDns.webdns.views': {
'handlers': ['default', 'error'],
'level': 'DEBUG',
'propagate': True
},
'sourceDns.webdns.util':{
'handlers': ['error'],
'level': 'ERROR',
'propagate': True
}
}
}
🐷tips:
loggers类型为"django"这将处理所有类型日志;
sourceDns.webdns.views 应用的py文件
- 简化版
import logging
import django.utils.log
import logging.handlers
import logging.StreamHandler
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'} #日志格式
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': { # 数据库引擎
'handlers': ['console'],
'propagate': True,
'level': 'DEBUG',
},
}
}
参数解析:
【a】formatters:配置打印日志格式
【b】handlers:用来定义具体处理日志的方式,可以定义多种,"default"就是默认方式,"console"就是打印到控制台方式,"导入StreamHandler",日志信息会输出到指定的stream中,如果stream为空则默认输出到sys.stderr。
【c】loggers:用来配置用那种handlers来处理日志,比如你同时需要输出日志到文件、控制台。
(3) 在settings中注册APP
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'book'
]
🐷 tips:如果出现如下报错
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None
因为MySQLclient目前只支持到python3.4,如果使用的更高版本的python,需要修改如下:
通过查找路径C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql
把这个路径文件中下面这两句话注释掉:
if version < (1, 3, 3):
raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
🐷 tips: 如果想打印orm转换过程中的sql,需要在settings中进行如下配置:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
3、在models中创建表
(1) 创建模型表
from django.db import models
class Book(models.Model):
id=models.AutoField(primary_key=True)
title=models.CharField(max_length=32)
state=models.BooleanField()
pub_date=models.DateField()
price=models.DecimalField(max_digits=8,decimal_places=2)
publish=models.CharField(max_length=32)
(2) ORM字段介绍
Django提供了很多字段类型,比如URL/Email/IP/ 但是mysql数据没有这些类型,这类型存储到数据库上本质是字符串数据类型,其主要目的是为了封装底层SQL语句。
每个字段有一些特有的参数,例如,CharField需要max_length参数来指定VARCHAR数据库字段的大小。还有一些适用于所有字段的通用参数。 这些参数在文档中有详细定义,这里我们只简单介绍一些最常用的:
【常用字段】
-
AutoField
int自增列,必须填入参数 primary_key=True。当model中如果没有自增列,则自动会创建一个列名为id的列( 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model)。
自定义一个主键:my_id=models.AutoField(primary_key=True)
-
CharField
字符串类型,必须提供max_length参数,用于从数据库层和Django校验层限制该字段所允许的最大字符数。 -
IntegerField
整数类型,范围在 -2147483648 to 2147483647 -
FloatField
浮点数类型. 必须提供两个参数:
max_digits 总位数(不包括小数点和符号)
decimal_places 小数位数
举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段:models.FloatField(..., max_digits=5, decimal_places=2)
要保存最大值一百万(小数点后保存10位)的话,你要这样定义:models.FloatField(..., max_digits=19, decimal_places=10)
admin 用一个文本框(<input type="text">)表示该字段保存的数据.
-
DateField
日期字段,日期格式 YYYY-MM-DD 年月日. 共有下列额外的可选参数:
Argument 描述
auto_now 当对象被保存时,自动将该字段的值设置为当前时间,每次操作该数据都会自动更新时间,通常用于表示 "last-modified" 时间戳
auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间,此后不再更新,通常用于表示对象创建时间
🐷(仅仅在admin中有意义...) -
DateTimeField
日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] 年月日时分秒。参数和datefield一样。 -
BooleanField
A true/false field。admin 用 checkbox 来表示此类字段。 -
TextField
一个容量很大的文本字段
admin 用一个 <textarea> (文本区域)表示该字段数据(一个多行编辑框)。 -
EmailField
一个带有检查Email合法性的 CharField,不接受 maxlength 参数。 -
FileField
一个文件上传字段
要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径。这个路径必须包含 strftime ,formatting,该格式将被上载文件的 date/time替换(so that uploaded files don't fill up the given directory)。
admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) 。
🐷 注意:在一个model中使用FileField或ImageField需要以下步骤:
(1) 在你的settings文件中,定义一个完整路径给MEDIA_ROOT以便让Django在此处保存上传文件(出于性能考虑,这些文件并不保存到数据库),定义MEDIA_URL作为该目录的公共URL,要确保该目录对web服务器用户账号是可写的。
(2) 在你的model中添加FileField或ImageField,并确保定义了upload_to选项,以告诉Django使用MEDIA_ROOT的哪个子目录保存上传文件,你的数据库中要保存的只是文件的路径(相对于MEDIA_ROOT),出于习惯你一定很想使用Django提供的get_<fieldname>_url函数,举例来说,如果你的ImageField叫做mug_shot,你就可以在模板中以{{ object.get_mug_shot_url }}这样的方式得到图像的绝对路径。 -
FilePathField
可选项目为某个特定目录下的文件名。 支持三个特殊的参数, 其中第一个是必须提供的。
参数 | 描述
path | 必需参数,一个目录的绝对文件系统路径FilePathField 据此得到可选项目。例如"/home/images"
match | 可选参数,一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名。注意这个正则表达式只会应用到 base filename 而不是路径全名。例如"foo.*\.txt^"
,将匹配文件foo23.txt,却不匹配bar.txt或foo23.gif。
recursive | 可选参数,要么True,要么False,默认是False,是否包括path下面的全部子目录。
match | 仅应用于 base filename, 而不是路径全名。 那么,这个例子:FilePathField(path="/home/images", match="foo.*", recursive=True)...
会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif。 -
ImageField
类似 FileField, 不过要校验上传对象是否是一个合法图片。它有两个可选参数:height_field和width_field,如果提供这两个参数,则图片将按提供的高度和宽度规格保存。 -
URLField
用于保存 URL, 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且没有返回404响应)。
admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框)。 -
NullBooleanField
类似 BooleanField, 不过允许 NULL 作为其中一个选项,推荐使用这个字段而不要用 BooleanField 加 null=True
admin 用一个选择框 <select> (三个可选择的值:"Unknown", "Yes" 和 "No" ) 来表示这种字段数据。 -
SlugField
"Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符。它们通常用于URLs。
若你使用 Django 开发版本,你可以指定 maxlength.。若 maxlength 未指定, Django 会使用默认长度: 50。在以前的 Django 版本,没有任何办法改变50 这个长度。这暗示了 db_index=True。
它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate the slug, via JavaScript,in the object's admin form: models.SlugField (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受DateTimeFields。 -
XMLField
一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema 的文件系统路径。 -
IPAddressField
一个字符串形式的 IP 地址, (i.e. "24.124.1.30") -
CommaSeparatedIntegerField
用于存放逗号分隔的整数值,类似 CharField, 必须要有maxlength参数。
详解
(1) 字符串类(以下在数据库中本质都是字符串数据类型,此类字段只是在Django自带的admin中生效)
🌈models.CharField 对应的是MySQL的varchar数据类型
🐷tips:char 和 varchar的区别 :
char和varchar的共同点是存储数据的长度,不能超过max_length限制;
不同点是varchar根据数据实际长度存储,char按指定max_length( )存储数据,所有前者更节省硬盘空间;
EmailField(CharField):
IPAddressField(Field)
URLField(CharField)
SlugField(CharField)
UUIDField(Field)
FilePathField(Field)
FileField(Field)
ImageField(FileField)
CommaSeparatedIntegerField(CharField)
------- 在模型表中的定义 -------
class Publisher(models.Model):
id = models.AutoField(primary_key=True)
publishName = models.CharField(max_length=32)
address = models.CharField(max_length=32)
tele = models.CharField(max_length=32)
(2) 时间字段
models.DateTimeField(null=True)
date=models.DateField()
(3) 数字字段
(max_digits=30,decimal_places=10) 总长度30,小数位10位
num = models.IntegerField()
num = models.FloatField() 浮点
price=models.DecimalField(max_digits=8,decimal_places=3) 精确浮点
(4) 枚举字段
在数据库存储枚举类型,比外键有什么优势?
无需连表查询性能低,省硬盘空间(选项不固定时用外键);
在modle文件里不能动态增加(选项一成不变用Django的choice)
choice=(
(1,'男人'),
(2,'女人'),
(3,'其他')
)
lover=models.IntegerField(choices=choice) #枚举类型
# 或者
gender = models.CharField(verbose_name='性别', max_length=8, choices=(("male", "男"), ("female", "女")),default='female')
(5) 其他字段
db_index = True 表示设置索引
unique(唯一的意思) = True 设置唯一索引
------- 联合唯一索引 -------
class Meta:
unique_together = (
('email','ctime'),
)
------- 联合索引(不做限制)------
index_together = (
('email','ctime'),
)
------- 多对多操作 -------
ManyToManyField(RelatedField)
(6) 自定义字段
class UnsignedIntegerField(models.IntegerField):
def db_type(self, connection):
return 'integer UNSIGNED'
自定义char类型字段:
class FixedCharField(models.Field):
"""
自定义的char类型的字段类
"""
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(FixedCharField, self).__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
"""
限定生成数据库表的字段类型为char,长度为max_length指定的值
"""
return 'char(%s)' % self.max_length
class Class(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=25)
# 使用自定义的char类型的字段
cname = FixedCharField(max_length=25)
附ORM字段与数据库实际字段的对应关系
'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)',
【常用字段参数】
-
null
表示某个字段可以为空,如果为True,Django将用NULL在数据库中存储空值,默认是False。 -
blank
如果为True,该字段允许不填,默认为False。
🐷tips:这与null不同,null纯粹是数据库范畴的,而blank是数据验证范畴的。如果一个字段的blank=True,表单的验证将允许该字段是空值,如果字段的blank=False,该字段是必填的 - default
字段的默认值,可以是一个值或者可调用对象,如果可调用,每有新对象被创建它都会被调用。 - primary_key
如果为True,那么这个字段就是模型的主键,如果你没有指定任何一个字段的primary_key=True,Django就会自动添加IntegerField字段作为主键,所以除非你想覆盖默认的主键行为,否则没必要设置任何一个字段的primary_key=True。 - unique
如果该值设置为True,则这个数据字段的值在整张表中必须是唯一的。 - required
如果设置该值为True,则表示请求不能为空 - choice
由二元组组成的一个可迭代对象(例如:列表或元组),用来给字段提供选择项,如果设置了choices,默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices中的选项。
class User(models.Model):
name=models.CharField(max_length=32)
password=MyCharField(max_length=32)
choices=((1,'重点本科'),(2,'普通本科'),(3,'专科'),(4,'其他'))
education= models.IntegerField(choices=choices) 通过选择数字来选择相应的属性
user_obj.education 拿到的是数字🌈
uer_obj.get_education_display() 固定用法,获取choice字段对应的注释🌈
- db_index
如果db_index=True,则表示为此字段设置索引。 - only与defer
两者是相反的models.User.objects.only('name')
🐷不走数据库
[ DateField 和 DateTimeField中的参数 ]
- auto_now_add
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。 - auto_now
配置auto_now=True,每次更新数据记录的时候会更新该字段。
【关系字段及参数】
🌈ForeignKey
外键类型在ORM中用来表示外键关联关系,一般把ForeignKey字段设置在‘一对多中多的一方’。
ForeignKey可以和其他表做关联关系同时也可以和自身做关联关系。
- 字段参数
to 设置要关联的表
to_field 设置要关联的表的字段
related_name 反向操作时使用的字段名,用于代替原反向查询时的'表名_set'
举个例子🌰
class Classes(models.Model):
name = models.CharField(max_length=32)
class Student(models.Model):
name = models.CharField(max_length=32)
theclass = models.ForeignKey(to="Classes")
当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写models.Classes.objects.first().student_set.all()
,
当我们在ForeignKey字段中添加了参数 related_name 后
class Student(models.Model):
name = models.CharField(max_length=32)
theclass = models.ForeignKey(to="Classes", related_name="students")
当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写models.Classes.objects.first().students.all()
related_query_name 反向查询操作时,使用的连接前缀,用于替换表名
on_delete 当删除关联表中的数据时,当前表与其关联的行为,即外键的删除
on_delete属性:
1、常见的使用方式(设置为null) >>> on_delete=models.SET_NULL
2、关于别的属性的介绍
models.CASCADE: 这就是默认的选项,级联删除,你无需显性指定它,删除关联数据,与之关联也删除。
models.PROTECT: 保护模式,如果采用该选项,删除关联数据的时候,会抛出ProtectedError错误。
models.SET_NULL: 置空模式,删除关联数据的时候,外键字段被设置为空,前提就是blank=True, null=True,定义该字段的时候,允许为空。
models.SET_DEFAULT: 置默认值,删除关联数据的时候,外键字段设置为默认值,所以定义外键的时候注意加上一个默认值。
models.DO_NOTHING: 删除关联数据,引发错误IntegrityError。
models.SET: 删除关联数据
a.与之关联的值设置为指定值,设置:models.SET(值)
b.与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
def func():
return 10
class MyModel(models.Model):
user = models.ForeignKey(
to="User",
to_field="id",
on_delete=models.SET(func)
)
db_constraint 是否在数据库中创建外键约束,默认为True
🌈OneToOneField
一对一字段
通常一对一字段用来扩展已有字段
- 字段参数
to 设置要关联的表
to_field 设置要关联的字段
on_delete 同Foreign字段
🌈ManyToManyField
用于表示多对多的关联关系,在数据库中通过第三张表来建立关联关系。
- 字段参数
to 设置要关联的表
related_name 同ForeignKey字段
related_query_name 同ForeignKey字段
symmetrical 仅用于多对多自关联时,指定内部是否创建反向操作的字段,默认为True。
举个例子🌰
class Person(models.Model):
name = models.CharField(max_length=16)
friends = models.ManyToManyField("self")
🌸此时person对象就没有person_set属性
class Person(models.Model):
name = models.CharField(max_length=16)
friends = models.ManyToManyField("self", symmetrical=False)
through 在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名。
through_fields 设置关联字段
db_table 默认创建第三张表时,数据库中表的名称
🌈元信息
ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:
- db_table ORM在数据库中的表名默认是app_类名,可以通过db_table重写表名
- index_together 联合索引
- ordering 指定默认按什么字段排序。只有设置了该属性,我们查询到的结果才可以被reverse()
三、单表操作
1、前言
orm使用方式:orm操作可以使用类实例化,obj.save的方式,也可以使用create( )的形式。
QuerySet数据类型介绍
⚠️只要是queryset对象就可以无限的点queryset方法,比如```models.User.object.filter( ).filter( ).count( )
QuerySet与惰性机制
所谓惰性机制:publish.obj,all( )或者.filter( )等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。
QuerySet特点:可迭代的;可切片;惰性计算和缓存机制
def queryset(request):
books=models.Book.objects.all()[:10] #切片 应用分页
books = models.Book.objects.all()[::2]
book= models.Book.objects.all()[6] #索引
print(book.title)
for obj in books: #可迭代
print(obj.title)
books=models.Book.objects.all(). #惰性计算--->等于一个生成器,不应用books不会执行任何SQL操作
# query_set缓存机制1次数据库查询结果query_set都会对应一块缓存,再次使用该query_set时,不会发生新的SQL操作;
#这样减小了频繁操作数据库给数据库带来的压力;
authors=models.Author.objects.all()
for author in authors:
print(author.name)
print('-------------------------------------')
models.Author.objects.filter(id=1).update(name='陈某')
for author in authors:
print(author.name)
#但是有时候取出来的数据量太大会撑爆缓存,可以使用迭代器优雅得解决这个问题;
models.Publish.objects.all().iterator()
return HttpResponse('OK')
拿出前面准备好的表:
from django.db import models
class Book(models.Model):
id=models.AutoField(primary_key=True)
title=models.CharField(max_length=32)
state=models.BooleanField()
pub_date=models.DateField()
price=models.DecimalField(max_digits=8,decimal_places=2)
publish=models.CharField(max_length=32)
🌸 接下来使用单独的py文件测试ORM操作,在test.py中需要配置的参数如下:
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test.settings")
import django
django.setup()
from app import models 🐷这句话必须在下面
2、添加表记录(新增数据的三种方式)
-
方法一
基于create创建(create)
表.objects.create()
🐟create方法的返回值book_obj就是插入book表中python葵花宝典这本书籍记录对象
book_obj = Book.objects.create(title="python葵花宝典",state=True,price=100,publish="苹果出版社",pub_date="2019-12-12")
-
方法二
基于对象的绑定方法创建(save)也就是类实例化
obj=类(属性=xx) obj.save()
book_obj = Book(title="python葵花宝典",state=True,price=100,publish="苹果出版社",pub_date="2012-12-12")
book_obj.save()
-
方法三
自动识别时间格式字符串进行传参
from datetime import datetime
ctime = datetime.now()
models.Book.objects.create(title="python葵花宝典",state=True,price=100,publish="苹果出版社",pub_date=ctime)
3、查询表记录
- 基本查询
查询API | 作用 |
---|---|
all( ) | 查询所有结果 |
filter(**kwargs) | 包含了与所给筛选条件相匹配的对象 |
get(**kwargs) | 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误 |
exclude(**kwargs) | 包含了与所给筛选条件不匹配的对象 |
order_by(*field) | 对查询结果排序 |
reverse( ) | 对查询结果反向排序,前面要先有排序才能反向 |
count( ) | 返回数据库中匹配查询(QuerySet)的对象数量 |
first( ) | 返回第一条记录 |
last( ) | 返回最后一条记录 |
exists( ) | 如果QuerySet包含数据,就返回True,否则返回False |
values(*field) | 返回一个ValueQuerySet—一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列 |
values_list(*field) | 它与values( )非常相似,它返回的是一个元组序列,values返回的是一个字典序列 |
distinct( ) | 从返回结果中剔除重复记录,去重的对象必须是完全相同的数据才能去重 |
例子们🌰:
🍃 filter(**kwargs):
models.User.objects.filter(name='wpr',age=18) ——查出来是<QuerySet []>
models.User.objects.filter(name='wpr',age=23) ——查出来是<QuerySet [<User: User object>, <User: User object>]>
🐷⚠️:filter里面可以放多个限制条件,但多个条件之间是and
🍃 exclude(**kwargs):
models.User.objects.exclude(name='wpr')
🍃 order_by(*field):
models.User.objects.order_by('-age') ——可以在排序的字段前面加一个减号,就是降序
models.User.objects.order_by('age') ——默认是升序
🍃 reverse():
models.User.objects.order_by('age').reverse() ——要先有排序才能反向
🍃 count():
models.User.objects.count()
models.User.objects.all().count()
🍃 first():
models.User.objects.all().first()
models.User.objects.all()[0] ——不支持负数的索引取值
🍃 last():
models.User.objects.all().last()
🍃 exists():
models.User.objects.all().exists()
models.User.objects.filter(name='wpr',age=3).exists()
🍃 values(*field):
models.User.objects.values('name') ——🐷列表套字典[{},{},{}]
🍃 values_list(*field):
models.User.objects.value_list('name','age') ——🐷列表套元组[(),(),()]
🍃 distinct():
models.User.objects.values('name','age').distinct() ——🐷去重的对象必须是完全相同的数据才能去重
必须完全一样才可以去重(意味着带了id就没有意义了)
models.Book.objects.all().values('name').distinct() ——先查一个重复的值再去重
- 基于双下划线的模糊查询
------- 数字查询 -------
查询价格为100或200或300的书籍 models.Book.objects.filter(price__in=[100,200,300])
查询年龄大于44岁的用户 models.User.objects.filter(age__gt=44)
查询价格小于100的书籍 models.Book.objects.filter(price__lt=100)
查询年龄大于等于44的用户 models.User.objects.filter(age__gte=44)
查询年龄小于等于44的用户 models.User.objects.filter(age__lte=44)
查询价格再100到200之间的书籍 models.Book.objects.filter(price__range=[100,200])
查询年龄是否为空(0:is not null | 1:is null) models.User.objects.filter(age__isnull=0|1)
------- 字符串查询 -------
查询名字中包含字母n的用户 models.User.objects.filter(name__contains='n')
查询名字中包含字母n的用户并且忽略大小写 models.User.objects.filter(name__icontains='n')
查询书籍中以py开头的书籍 models.Book.objects.filter(title__startswith='py')
查询书籍中以n结尾的用户 models.User.objects.filter(name__endswith='n')
查询名字满足某个正则表达式的用户 models.User.objects.filter(name__regex='正则表达式')
------- 日期 -------
查询注册时间是在2017年的用户 models.User.objects.filter(register_time__year=2017)
🌸 tips:返回queryset对象的方法
all ( )
filter ( )
exclude ( )
order_by ( )
reverse ( )
distinct ( )
values ( ) 返回一个可迭代的字典序列
values_list ( ) 返回一个可迭代的元祖序列
4、删除表记录
- 基于对象
user_obj = models.User.objects.filter(name='json').first()
user_obj.delete()
删除方法就是delete(),它运行时立即删除对象而不返回任何值,例如user_obj.delete()
。
- 基于queryset
models.User.objects.filter(name='wpr').delete()
一次性删除多个对象,每个QuerySet都有一个delete()方法,它一次性删除QuerySet中所有的对象。例如,下面的代码将删除pub_date是2005年的Entry对象:
Entry.objects.filter(pub_date__year=2005).delete()
在Django删除对象时,会模仿SQL约束ON DELETE CASCADE的行为,换句话说,删除一个对象时也会删除与他相关联的外键对象,例如:
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
要注意的是:delete()方法是QuerySet上的方法,但并不适用于Manager本身。这是一种保护机制,是为了避免意外地调用Entry.objects.delete()方法导致所有的记录被误删除。如果你确认要删除所有的对象,那么你必须显式地调用Entry.objects.all().delete()
如果不想级联删除,可以设置为:
pubHouse = models.ForeignKey(to='Publisher', on_delete=models.SET_NULL, blank=True, null=True)
5、修改表记录
- 基于对象,先获取用户对象,然后赋值,最后保存
user_obj = models.User.objects.filter(name='wpr').first()
user_obj.age = 17
user_obj.save()
-
基于queryset,queryset直接更新
update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录,update()方法会返回一个整型值,表示受影响的记录的条数。
models.User.objects.filter(name='wpr').update(age=77)
三、多表操作
1、Django多表ORM设计规则
(1)关联的表之间建议建立外键,但可以取消关联关系(db_constraint=False)
(2)关联表之间的外键字段建议采用对应类名的全小写
(3) 采用关联表的主键或对象均能进行操作
2、表与表之间的关系
-
一对一(OneToOneField):一对一字段无论建在哪张关系表里都可以,但是推荐建在查询频率比较高的那张表中
-
一对多(ForeignKey):一对多字段建在多的那一方
-
多对多(ManyToManyField):多对多字段无论建在哪张关系表里都可以,但是推荐建在查询频率比较高的那张表中
多对多:add() 添加 | set() 修改 | remove() 不能接收可迭代对象 | clear() 清空,不用传参 -
外键关系
publish = models.ForeignKey(to='Publish',to_field='title')
to 是设置要关联的表
to_field 是设置要关联的表的字段,不设置默认关联id
on_delete 是当删除关联表中的数据时,当前表与其关联的行的行为。在django2.0中要加models.CASCADE
db_constraint 是否在数据库中创建外键约束,默认为True
3、创建模型
【作者Author】:一个作者有姓名,作者详情和作者之间是一对一关系(one-to-one)
【作者详情AuthorDetail】:包含年龄、电话和信息
【出版社Publish】:出版商有名称,地址
【书籍Book】:书籍有书名、价格和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)
from django.db import models
class Author(models.Model):
name=models.CharField( max_length=32)
# 与AuthorDetail建立一对一的关系
author_detail=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE)
class AuthorDetail(models.Model):
age=models.IntegerField()
telephone=models.BigIntegerField()
info=models.CharField( max_length=64)
class Publish(models.Model):
name=models.CharField( max_length=32)
address=models.CharField( max_length=32)
class Book(models.Model):
name = models.CharField( max_length=32)
price=models.DecimalField(max_digits=5,decimal_places=2)
publish_date=models.DateField()
# 与Publish建立一对多的关系,外键字段建立在多的一方
publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)
# 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
authors=models.ManyToManyField(to='Author',)
4、一对多关系
-
规则
(1)关系中“多”依赖于“一”;
(2)Django 1.x外键关联默认有级联删除,2.x需要手动明确外键的级联删除(on_delete=models.CASCADE)
① 增
🌸方式一:外键为关联对象,这里外键是实际表的字段
先有出版社,才有书籍
publish = Publish.objects.create(name="南方出版社",address="上海)
Book.objects.create(name='人间失格',price=88.88,publish_date='2018-8-8',publish=publish)
🌸 方式二:外键字段为关联对象主键,这里外键不是实际表的字段
id = Publish.objects.create(name="北方出版社", address="北京").id
Book.objects.create(name='活着', price=37.70, publish_date='2043-8-8', publish_id=id)
② 删
删除出版社,默认有级联删除,出版社出版的数据全会被删除
Publish.objects.first().delete()
③ 改
🌸方式一:save()
book_obj=models.Book.objects.filter(pk=1).first()
book_obj.publish=new_publish_obj
book_obj.save()
🌸方式二:update()
models.Book.objects.filter(pk=1).update(publish_id=2)
或 models.Book.objects.filter(pk=1).update(publish_id=publish_obj)
5、一对一关系
-
规则
通过外键所在表决定依赖关系
① 增
🌸遵循操作顺序
author_detail = AuthorDetail.objects.create(age=18,telephone=1232331231,ingo="真帅)
Author.objects.create(name='wpr',author_detail=author_detail)
② 删
🌸拥有级联删除
AuthorDetail.objects.first().delete()
③ 改
一般不考虑该关联字段
6、多对多关系
-
规则
(1) 多对多关系存在关系表,关系表建议采用ManyToManyField字段处理
(2) 需要手动创建关系表时,在字段中明确through与through_field值
① 增 add( )
为书籍添加作者的主键或对象们
book.author.add(*args)
② 删 remove( )
删除书籍已有作者的主键或对象们
book.author.remove(*args)
③ 改 set( ) | 清空clear( )
清空并添加作者的主键或对象/设置作者的主键或对象形式的列表
book.authoe.clear()
book.author.set([*args])
🐷tips:add(),set(),remove()都可以支持传多个数字或对象,set必须接收一个可迭代对象
7、跨表查询规则
-
正向反向的概念
关联字段在你当前这张表查询另一张表叫正向
关联字段不在你当前这张表查询另一张表叫反向 -
正向查询按字段名进行跨表查询
-
反向查询按表名小写进行跨表查询
8、基于对象的跨表查询(子查询)
在跨表查询的规则上,跨表查询的结果为多条数据时需要在字段后添加_set;还有如果返回的是None,只要加个all( )。
-
一对一查询
(1) 查询得到作者对象author_obj=Author.object.first()
(2) 基于作者对象跨表获取作者详情对象,正向通过字段名author_detailauthor_detail_obj=author_obj.author_detail
(3) 基于作者详情对象跨表获取作者对象,反向通过表名小写authorauthor_obj=author_detail_obj.author
-
一对多查询
(1) 查询得到书籍对象book_obj=Book.objects.first()
(2) 获取出版社对象,正向通过字段名publishpublish_obj=book_obj.publish
(3) 获取书籍对象们,反向通过表名小写book,多条数据添加_set,如果返回的结果是None,加个all()book_list_obj=publish_obj.book_set.all()
-
多对多查询
(1) 查询得到书籍对象book_obj=Book.objects.first()
(2) 获取作者对象们,正向通过字段名authorauthor_list_obj=book_obj.author
(3) 基于作者对象获取书籍对象们,反向通过表名小写book,多条数据添加_set
author = Author.objects.first()
book_list_obj=author.book_set
🐷⚠️tips:如果在ForeignKey()和ManyToManyField的定义中设置related_name的值来复写FOO_set的名称。
例如:在Article model中做一下更改
publish = ForeignKey(Book,related_name='bookList')
那么在“查询人民出版社出版过的所有书籍”时结果如下:
publish=Publish.objects.get(name="人民出版社")
book_list=publish.bookList.all() 🌸与人民出版社关联的所有书籍对象集合,这里使用all代替了_set
- 多级跨表查询
某作者出版的第一本书的出版社名字
author.book_set.first().publish.name
9、基于双下划线的跨表查询(联表查询)
Django还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,他能自动确认SQL JOIN联系。要做跨关系查询,就使用两个下划线来连接模型(model)间关联字段的名称,直到最终连接到想要的model为止。
正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表。
满足跨表查询规则。
filter方法与values方法支持__查询规则。
- 一对多查询
(1) 正向查询,按字段名:publish
queryResult=Book.objects.filter(publish__name="南方出版社").values_list("title","price")
(2)反向查询,按表名小写:book
queryResult=Publish.objects.filter(name="北方出版社").values_list("book__title","book__price")
- 多对多查询
(1) 正向查询,按字段名:authors
queryResult=Book.objects.filter(authors__name="wpr").values_list("title)
(2)反向查询,按表名小写:book
queryResult=Author.objects.filter(name="wpr").values_list("book_list","book_price")
- 一对一查询
(1) 正向查询
Author.objects.filter(name="wpr").values("author detail__telephone")
(2) 反向查询
AuthorDetail.objects.filter(author__name="wpr").values("telephone")
- 两表关联
查询所有小于18岁作者的名字与实际年龄
authors_dic=Author.objects.filter(author_detail__id__gt=0).values('name','author_detail__age')
- 多表关联(连续跨表)
查询出版社在上海的出版过的所有书的作者姓名、作者电话、具体出版社名的相关信息
info_dic=Book.objects.filter(publish__address__contains="上海").value('author__name','author__author_detail__telephone','publish__name')
queryset对象.query能够查看内部对应的sql语句
all()
filter()
values()
value_list()
order_by()
reverse()
distinct()
exclude() 取反
count() 计数
exists() 布尔值
get() 数据对象本身
first()
last()
🌸 related_name
反向查询时,如果定义了related_name,则用related_name替换表名,例如 publish=ForeignKey(Blog,related_name='bookList')
那么在“查询人民出版社出版过的所有书籍的名字与价格(一对多)”,反向查询,不再按表名:book,而是related_name:bookList
queryResult=Publish.objects.filter(name="人民出版社").values_list("boolList__title","bookList__price")
🐷tips:配置文件配置参数查看所有orm操作内部的sql语句
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
10、聚合查询与分组查询
(1)聚合(aggregate)
from django.db.models import Max,Min,Sum,Count,Avg
aggregate(*args,**kwargs)
举个例子🌰:计算所有图书的平均价格
from django.db.models import Avg
Book.objects.all().aggregate(Avg('price'))
>>> {'price__avg': 34.35} # 自动生成
aggregate()是QuerySet的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名字是聚合值的标识符,值是计算出来的聚合值。键的名字是按照字段和聚合函数的名称自动生成出来的。如果想要为聚合值指定一个名称,可以向聚合子句提供它。
Book.objects.aggregate(average_price=Avg('price'))
>>> {'average_price':34.35}
如果想生成不止一个聚合,你可以想aggregate()子句中添加另一个参数,所以如果想知道所有图书价格的最大值和最小值,可以这样查询:
from django.db.models import Avg, Max, Min
Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
>>> {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
(2)分组(group by)
annotate():依据前面的基表
group_concat():特地用在分组
concat():所有分组
🐷tips:字符串拼接要用到concat和value
- 单表分组查询
emp表:
id | name | age | salary | depends |
---|---|---|---|---|
1 | wpr | 27 | 1000 | 销售部 |
2 | wpp | 30 | 3000 | 人事部 |
3 | www | all( ) | 5000 | 人事部 |
查询每一个部门名称以及对应的员工数
sql语句:select dep,Count(*) from emp group by dep;
ORM语句:emp.objects.values("dep").annotate(c=Count("id")
- 多表分组查询
emp表:
id | name | age | salary | dep_id |
---|---|---|---|---|
1 | wpr | 27 | 1000 | 1 |
2 | wpp | 30 | 3000 | 2 |
3 | www | all( ) | 5000 | 2 |
dep表:
id | name |
---|---|
1 | 销售部 |
2 | 人事部 |
emp-dep表:
id | name | age | salary | dep_id | id | name |
---|---|---|---|---|---|---|
1 | wpr | 27 | 1000 | 1 | 1 | 销售部 |
2 | wpp | 30 | 3000 | 2 | 2 | 人事部 |
3 | www | all( ) | 5000 | 2 | 2 | 人事部 |
查询每一个部门名称以及对应的员工数
sql语句:select dep.name,Count(*) from emp left join dep on emp.dep_id=dep.id group by dep.id
ORM语句dep.objetcs.values("id").annotate(c=Count("emp")).values("name","c")
class Emp(models.Model):
name=models.CharField(max_length=32)
age=models.IntegerField()
salary=models.DecimalField(max_digits=8,decimal_places=2)
dep=models.CharField(max_length=32)
province=models.CharField(max_length=32)
annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
总结:跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。
11、F查询与Q查询
from django.db.models import F,Q
(1)F查询
对两个字段的值做比较,Django提供F()来做这样的比较。F()的实例可以在查询中引用字段,来比较同一个model实例中两个不同字段的值。
F可以取到表中某个字段对应的值来当作我的筛选条件,而不是自定义常量的条件,实现了动态比较的效果。
Django支持F()对象之间以及F()对象和常数之间的加减乘除和取模的操作。
🌸 比较两个不同字段的值
查询评论数大于收藏数的书籍
from django.db.models import F
Book.objects.filter(commnetNum__lt=F('keepNum'))
🌸 取到某个字段的值当作筛选条件
字符串拼接要用到 concat和value
将所有商品的名字后面加一个爆款后缀
from django.db.models.functions import Concat
from django.db.models import Value
models.Product.objects.update(name=Concat(F('name'),Value('爆款')))
🌸 通过字段名获取可以直接做运算的查询结果
from Django.db.models import F
案例一:将id为1的结果年龄增加1
models.User.objects.filter(id=1).update(age=F('age')+1)
案例二:查询id是年龄1/4的结果
models.User.objects.filter(id=F('age')/4)
(2)Q查询
- filter()等方法中的关键字参数查询都是一起进行'AND'与的关系,如果要执行更复杂的查询(例如or或者not)的关系查询数据,可以使用Q对象。
🌸 默认是and关系
models.Product.objects.filter(Q(name='变形金刚'),Q(price=999.999))
🌸 与普通过滤条件混合使用,完成逻辑运算方式的查询
from Django.db.models import Q
与[&] models.User.objects.filter(Q(id=1)&Q(age=10))
或[|] models.User.objects.filter(Q(id=1)|Q(id=2))
非[~] models.User.objects.filter(~Q(id=1))
当一个操作符在两个Q对象上使用时,它产生一个新的Q对象
bookList=Book.objects.filter(Q(authors__name="wpr")|Q(authors__name="wpp"))
等同于下面的SQL WHERE子句
where name="wpr" OR name="wpp"
同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:
bookList=Book.objects.filter(Q(authors__name="wpr") & ~Q(publishDate__year=2017)).values_list("title")
- 查询函数可以混合使用Q对象和关键字参数,所有提供给查询函数的参数(关键字参数或Q对象)都将'AND'在一起,但是,如果出现Q对象,它必须位于所有关键字参数的前面,例如:
bookList=Book.objects.filter(Q(publishDate__year=2019) | Q(publishDate__year=2020),title__icontains="python" )
- Q查询进阶操作——可以传入字符串
先实例化一个Q对象
q=Q()
q.connector='or'
q.children.append(('name','wpr'))
Q对象支持直接放在filter括号内
models.User.objects.filter(q) q对象默认也是and关系
12、事务(上下文管理)
定义:将多个sql语句操作变成原子性操作,要么同时成功,有一个失败则里面回滚到原来的状态,保证数据的完整性和一致性(NoSQL数据库对于事务则是部分支持)
事务的ACID:A(原子性) C(一致性) I(隔离性) D(持久性)
from django.db import transaction
with transaction.atomic():
这里写多个数据库操作
print('其他逻辑代码')
13、浅谈ORM查询性能
- 普通查询
obj_list=models.Love.objects.all()
for row in obj_list: #for循环10次发送10次数据库查询请求
print(row.b.name)
这种查询方式第一次发送查询请求,每for循环一次也会发送查询请求,频繁进行IO操作。
-
select_related:结果为对象,注意query_set类型的对象都有该方法
原理:查询时主动完成连表形成一张大表,for循环时不用额外发请求。
应用场景:节省硬盘空间,数据量少的时候使用,相当于只做了一次数据库查询
obj_list=models.Love.objects.all().select_related('b')
for row in obj_list:
print(row.b.name)
- prefetch_related:结果都是对象
原理:select_related虽然好,但是做连表操作依然会影响查询性能,所以出现了prefetch_related,prefetch_related不做连表查询,多次单表查询外键表,去重之后显示,2次单表查询(有几个外键做1+N次单表查询)。
应用场景:效率高,数据量大的时候适用
obj_list=models.Love.objects.all().prefetch_related('b')
for obj in obj_list:
print(obj.b.name)
- update()和对象.save()修改方式的性能比较
update():
models.Book.objects.filter(id=1).update(price=3)
执行结果:
(0.000) BEGIN; args=None
(0.000) UPDATE "app01_book" SET "price" = '3.000' WHERE "app01_book"."id" = 1; args=('3.000', 1)
(0.000) SELECT "app01_book"."id", "app01_book"."title", "app01_book"."price", "app01_book"."date", "app01_book"."publish_id", "app01_book"."classify_id" FROM "app01_book" WHERE "app01_book"."id" = 1; args=(1,)
save():
book_obj=models.Book.objects.get(id=1)
book_obj.price=5
book_obj.save()
执行结果:
(0.000) BEGIN; args=None
(0.000) UPDATE "app01_book" SET "title" = '人间失格', "price" = '5.000', "date" = '1370-09-09', "publish_id" = 4, "classify_id" = 3 WHERE "app01_book"."id" = 1; args=('人间失格', '5.000', '1370-09-09', 4, 3, 1)
总结:update()比obj.save()性能好。
网友评论