【三】模型层ORM

折腾,是对梦想的尊重。

一、ORM简介

  MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,在业务逻辑层和数据库层之间起了桥梁的作用。
  ORM(Object Relational Mapping)是“对象—关系—映射”的简称。

ORM 对象-关系-映射

二、前期准备

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_detail author_detail_obj=author_obj.author_detail
    (3) 基于作者详情对象跨表获取作者对象,反向通过表名小写author author_obj=author_detail_obj.author

  • 一对多查询
    (1) 查询得到书籍对象 book_obj=Book.objects.first()
    (2) 获取出版社对象,正向通过字段名publish publish_obj=book_obj.publish
    (3) 获取书籍对象们,反向通过表名小写book,多条数据添加_set,如果返回的结果是None,加个all() book_list_obj=publish_obj.book_set.all()

  • 多对多查询
    (1) 查询得到书籍对象 book_obj=Book.objects.first()
    (2) 获取作者对象们,正向通过字段名author author_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()性能好。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351