Django框架:模型层
模型层
一、前期准备
1.关于数据库
-
自带的sqlite3数据库对时间字段不够敏感,展示的时候会错乱
-
使用数据库比如MySQL的时候,django的orm不会自动创建库,需要自己创建库
2.测试环境
django默认需要整个项目跑起来才能运行功能函数,如果想要测试某个py文件(主要指模型层models.py)需要准备测试环境
-
测试环境1:pycharm中的python console
但是这个测试环境是命令行式的环境,看起来不够直观
- 测试环境2:自己搭建
(1)打开manage.py文件,复制前四行
(2)并添加两行内容
import django
django.setup()
- 最终test.py中的代码
from django.test import TestCase
# Create your tests here.
import os
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djmodel.settings')
import django
django.setup()
3.查看执行的SQL语句
由于Django的orm底层还是SQL语句,我们可以通过两种方法查看
-
(1)如果我们手上是一个QuerySet对象,那么可以直接点query查看SQL语句
但是不是queryset对象就不能点query
-
(2)直接配置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',
},
}
}
二、模型层常用关键字
1.create
-
该方法有返回值,返回值是实例对象
创建数据并且直接获取当前创建的数据对象
models.User.objects.create(name='duo', age=18)
models.User.objects.create(name='bo', age=22)
res = models.User.objects.create(name='hong',age=35)
print(res) # 用户对象的对象是:hong
2.filter()
- 结果是QuerySet 列表套对象
- 括号内支持多个条件,但是是and关系
res = models.User.objects.filter(name='duo')
print(res)
# <QuerySet <QuerySet [<User: 用户对象的对象是:duo>]>
# 当类中没有双下str方法的时候 res为<QuerySet [<User: User object (1)>]>
3.first() last()
QuerySet支持所用取值,但是只支持正数,并且orm不建议使用索引取值
使用索引取值没有的情况下会报错,但是使用first last方法,没有数据的情况或返回none不会报错
res = models.User.objects.filter()[1]
# res = models.User.objects.filter(pk=100)[0] # 没有数据索引会报错
res1 = models.User.objects.filter(pk=100).first() # first方法不会报错
res2 = models.User.objects.filter().last()
print(res) # 用户对象的对象是:bo
print(res1) # None
print(res2) # 用户对象的对象是:hong
4.update()
-
更新数据
根据筛选条件更新不同的个数
# 批量更新
models.User.objects.filter().update()
# 单个更新
res = models.User.objects.filter(id=1).update(age=22)
print(res) # 1
# sql语句
UPDATE `app01_user` SET `age` = 22 WHERE `app01_user`.`id` = 1'; args=(22, 1)
5.delete()
-
删除数据
根据筛选条件删除不同的个数
models.User.objects.filter().delete() 批量删除
models.User.objects.filter(id=1).delete() 单个删除
6.all()
-
查询所有数据
结果是QuerySet [数据对象1,数据对象2]
与filter不同
models.User.objects.all()
<QuerySet [<User: 用户对象的对象是:duo>, <User: 用户对象的对象是:bo>...]>
7.values()
-
指定字段获取 数据,结果是
<QuerySet [{},{},{}]> 列表套字典
-
all().values()
-
filter.values()
-
values()
models.User.objects.all().values()
models.User.objects.filter(id=1).values()
models.User.objects.values()
print(res) # <QuerySet [{},{},{}]> 列表套字典
8.values_list()
- 根据指定字段获取数据
- 结果是
QuerySet[(),(),(),()]
列表套元组
models.User.objects.values_list('name')
------
<QuerySet [('duo',), ('bo',), ('duoduo',), ('bo',), ('hong',)]>
9.distinct()
- 去重,但是得完全一致
10.order_by()
-
根据指定条件排序,默认升序
在条件前面加个减号- 改为降序
res = models.User.objects.all().order_by('age')
<QuerySet [<对象1>, <对象2>]>
11.get()
-
根据条件筛选数据并直接获取到数据对象
条件不存在拿不到数据,则会报错
models.User.objects.get(id=1)
---
用户对象的对象是:duo
12.exclude()
排除
- 去反操作
models.User.objects.exclude(age=22)
---
<QuerySet [<User: 用户对象的对象是:duoduo>, <User: 用户对象的对象是:hong>]>
13.reverse()
- 颠倒顺序,但是被操作的顺序必须需要提前排序完成
models.User.objects.order_by('age').reverse()
---
<QuerySet [<User: 用户对象的对象是:hong>, <User: 用户对象的对象是:duo>, <User: 用户对象的对象是:bo>, <User: 用户对象的对象是:bo>, <User: 用户对象的对象是:duoduo>]>
14.count()
- 统计结果集中数据的个数
models.User.objects.filter(age=22).count()
---
3
15.exists()
-
判断结果集中是否含有数据,如果有则返回True 没有则返回False
当时在Django中结果已经自带了布尔值,所有
exists()
显得有些多余
models.User.objects.filter(id=1).exists()
---
True
方法特点
- 返回QuerySet对象的方法有
返回QuerySet对象 |
---|
filter() |
exclude() |
order_by() |
reverse() |
distinct() |
all() |
- 特殊的QuerySet
特殊的QuerySet | 作用 |
---|---|
values() |
返回一个可迭代的字典序列 |
values_list() |
返回一个可迭代的元祖序列 |
- 返回具体对象的
返回具体对象的 |
---|
first() |
last() |
get() |
- 返回布尔值的方法有:
返回布尔值 |
---|
exists() |
- 返回数字的方法有
返回数字的方法 |
---|
count() |
三、ORM执行查询sql语句
1.方式一:通过ORM执行sql语句
通过raw
方法执行查询sql语句,只支持查询,但是结果要转换成列表查看
方式1的结果,还是会被封装对象
res = models.User.objects.raw('select * from app01_user')
print(list(res))
------------
[<User: 用户操作的对象是:duo>, <User: 用户操作的对象是:bo>, <User: 用户操作的对象是:duoduo>, <User: 用户操作的对象是:bo>, <User: 用户操作的对象是:hong>]
2.方式二:通过django封装好了的pymysql模块执行sql语句
方式2的结果,会直接是数据值
- 导入connection,产生游标对象cursor
from django.db import connection
cursor = connection.cursor()
cursor.execute('select name from app01_user;')
print(cursor.fetchall())
------------
(('duo',), ('bo',), ('duoduo',), ('bo',), ('hong',))
四、神奇的双下划线方法
- QuerySet可以当做1个列表
- QuerySet对象,是一个生成器
- QuerySet用.all()取值的时候,不会一下子全部取出来,LIMIT21,每次只取21条
对于QuerySet对象,就可以无限制的点QuerySet对象的方法
1.__gt
大于
结果是一个QuerySet对象
- 年龄大于30
res = models.Author.objects.filter(age__gt=30)
print(res)
----------
<QuerySet [<Author: 作家对象:余华>, <Author: 作家对象:莫言>, <Author: 作家对象:东野圭吾>]>
2.__lt
小于
结果是一个QuerySet对象
res = models.Author.objects.filter(age__lt=40)
print(res)
----------
<QuerySet [<Author: 作家对象:路遥>]>
3.__gte
大于等于和__lte
小于等于
"只要是querset对象,就可以无限制的点queryset对象点方法"
# 查询年龄大于18的用户名的名字
res = models.User.objects.filter(age__gt=18).filter(id=2)
print(res) # <QuerySet [<User: 用户操作的对象是:bo>]>
4.__in
成员运算
res = models.Author.objects.filter(age__in=(60, 30, 50))
print(res)
----------
<QuerySet [<Author: 作家对象:余华>, <Author: 作家对象:路遥>, <Author: 作家对象:东野圭吾>]>
5.__range
范围之间查询
range
后元组内填的数据,左右都包含
"range填的数据,左右都包含"
res = models.Author.objects.filter(age__range=(30, 60))
print(res)
6.__contains
和 __icontains
模糊查询
-
__contains
查询字段名中含有某个字母的数据 (区分大小写) -
__icontains
查询字段名中含有某个字母的数据 (不会区分大小写)
res1 = models.Author.objects.filter(author_detail__manner__contains='s')
print(res1)
----------
__contains查询时区分大小写
res2 = models.Author.objects.filter(author_detail__manner__icontains='s')
print(res2)
----------
__icontains查询时候不区分大小写
7.查询时间关键字
__year
年__month
月__day
日__hour
时
res = models.Book.objects.filter(publish_time__year=2022).all()
print(res)
五、ORM外键字段的创建
1.mysql中的外键关系
-
一对多:外键字段建立在多的一方
-
多对多:外键字段统一建在第三张关系表中,在django中不用自己创建,只需创建外键后django会帮我们自动创建一个表出来
-
一对一:建在任何一方都可以,但是应该建立在热数据表也就是查询频率较高的表中
关系的判断采用换位思考原则
2.orm外键字段的创建
- 先创建基表,再创建外键字段
(1)一对多,ORM与MySQL
- orm会自动帮我们把外键字段后加
_id
的后缀,所以我们不用自己添加_id
后缀
(2)多对多
- ORM比MySQL更多变化
- ORM:外键字段可以直接建在某张查询频率较高的表中,在orm内部会自动帮我们创建第三张表,这个外键字段是虚拟字段在表中并不会显示,只是告诉orm帮我们创建第三张关系表
- MySQL:自己创建第三张关系表并创建外键字段
(3)一对一
- 外键字段可以直接建在某张查询频率较高的表中
通过
ForiegnKey
和OneToOne
创建外键的关键字,同步到表中之后,会自动添加_id的后缀而多对多的外键关键字
MangToMany
创建的是虚拟外键,是告诉ORM帮我们创建多对多的关系表
(实例)图书、出版社、作者数据表
"""先创建几张基表,再再几张基表之上去创建表与表之间的关系"""
class Book(models.Model):
"""图书表"""
title = models.CharField(max_length=32, verbose_name='书名')
price = models.DecimalField(max_digits=8, decimal_places=3, verbose_name='价格')
publish_time = models.DateTimeField(auto_now_add=True, verbose_name='出版日期')
"书籍表和出版社表是一对多的关系,也就是一本书对应一个出版社,一个出版社对应多本书,所以外键字段建立在多的那方"
# 与出版社的外键,通过ForeignKey,参数to为建立关系的类名
publish = models.ForeignKey(to='Publisher', on_delete=models.CASCADE)
"CASCADE对应的是及联更新删除,在django1.X版本都是默认及联更新删除,但是在django2以上版本需要自己声明 "
"书籍表与作者表的关系是多对多,一本书可以对应多个作者,一个作者也可以对应多本书,所以外键字段建立在那方都可以,但是书籍表更为常用,所以建立在数据表中"
# 与作者的外键,但是对于Book表来说这是一个虚拟字段,相当于是告诉ORM在MySQL数据库中去建立一张新表书籍与作者的关系表
author = models.ManyToManyField(to='Author')
"多对多的表不需要建立及联更新删除"
def __str__(self):
return f"书籍对象:{self.title}"
class Publisher(models.Model):
"""出版社表"""
name = models.CharField(max_length=32, verbose_name='出版社名')
location = models.CharField(max_length=32, verbose_name='出版社地址')
def __str__(self):
return f"出版社对象:{self.name}"
class Author(models.Model):
"""作者表"""
name = models.CharField(max_length=32, verbose_name='作者名')
age = models.IntegerField(verbose_name='年龄')
"作者表和作者详情表是一对一的关系,一个作者可以对应一个作者详情,一个作者详情对应一个作者,外键建在哪方都可以,但是建立在热数据的表中更好"
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)
def __str__(self):
return f"作家对象:{self.name}"
class AuthorDetail(models.Model):
"""作者详情表"""
phone = models.BigIntegerField(verbose_name='手机号')
manner = models.CharField(max_length=32, verbose_name='写作风格')
def __str__(self):
return f"详情对象:{self.manner}"
3.增删改查
-
一对多
book表
针对一对多,插入数据可以之间填写表中的实际数据
也可以填写表中的虚拟字段(类中的字段名)
一对一与一对多一致
- 多对多
第三张orm自动创建的关系表,在orm中点不出来,想要添加关系得通过 含有多对多外键字段的对象去添加 多对多关系
(1)添加数据
对于一对多关系
- 1.直接插入数据
models.Book.objects.create(title='兄弟', price=999.87, publish_id=3)
models.Book.objects.create()
- 2.插入对象
publisher_obj = models.Publisher.objects.filter(pk=2).first()
models.Book.objects.create(title='红高粱', price=888.68, publish=publisher_obj)
models.Book.objects.filter(pk=10).delete()
models.Book.objects.filter(pk=8).delete()
(2)多对多绑定关系
多对多
- 1.通过对象来添加数据
book_obj = models.Book.objects.filter(pk=3).first()
book_obj.author.add(3,2)
- 2.通过对象来添加对象
book_obj1 = models.Book.objects.filter(pk=4).first()
author_obj1 = models.Author.objects.filter(pk=1).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
book_obj1.author.add(author_obj1,author_obj2)
(3)修改关系
obj.表名小写.set()
中set的参数应该是一个可迭代对象列表或者元组
- 1.参数为数据
book_obj3 = models.Book.objects.filter(pk=3).first()
book_obj3.author.set([3])
- 2.参数为对象
book_obj3 = models.Book.objects.filter(pk=3).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=1).first()
book_obj3.author.set((author_obj2, author_obj1))
(4)删除关系
- 1.参数为数据
book_obj3 = models.Book.objects.filter(pk=3).first()
book_obj3.author.remove(1)
- 2.参数为对象
author1 = models.Author.objects.filter(pk=1).first()
book_obj4 = models.Book.objects.filter(pk=4).first()
book_obj4.author.remove(author1)
六、ORM的跨表查询
- 一个数据库应该尽量只对应一个Django项目,不要出现多个django项目使用同一额数据库,会出现报错
1.mysql中的跨表查询思路
(1)子查询
- 分布操作,将一个查询结果当作另一个表的条件
(2)连表查询
- inner join
- left join
- right join
2.正反向查询的概念
- 概念
正向查询:由外键字段所在的表数据查询关联的表数据
反向查询:没有外键字段的表数据查询关联的表数据
- ORM跨表查询的口诀(重要):
正向查询按外键字段
反向查询按表名小写
3.基于对象的跨表查询(子查询)
相当于mysql中的子查询
-
(1)先根据已知条件获取到一个具体的数据对象
-
(2)基于该数据对象运用正反向查询的口诀完成
1.正向
- 1.查询主键为1的书籍对应的出版社名称
# 先根据条件获取数据对象
book_obj = models.Book.objects.filter(pk=1).first()
# 在判断正反向的概念,由书获取出版社 --》从由主键的表差没主键的表是正向
res = book_obj.publish.name
print(res) # 长江文艺
- 2.查询主键为4的书籍对应的作者姓名
# 先根据条件获取对象
book_obj = models.Book.objects.filter(pk=4).first()
res = book_obj.author.name
print(res)
这个时候点name得到的结果是None,然后只要点all就可以拿到所有点对象,在通过values 获取想要点字段名点值
res = book_obj.author.all().values('name')
print(res) #<QuerySet [{'name': '莫言'}, {'name': '路遥'}]>
- 3.查询余华的电话号码
# 先根据条件获取对象
author_obj = models.Author.objects.filter(name='余华').first()
res = author_obj.author_detail.phone
print(res) #111
2.反向
- 4.查询长江文艺出版过的书籍
# 先根据条件获取数据对象
publish_obj = ls.Publisher.objects.filter(name='长江文艺').first()
# 根据数据对象判断 出版社没有外键字段,所以是反向,所以要点表名小写,然后
res = publish_obj.book_set.all().values('title')
print(res)
--------------
<QuerySet [{'title': '活着'}, {'title': '平凡的世界'}, {'title': '嫌疑人x的献身'}]>
- 5.查询余华写过的书籍
# 先根据条件获取数据对象
author_obj = models.Author.objects.filter(name='余华').first()
# 作者表中没有外键,所以是反向,要点获取表名的小写
res = author_obj.book_set.all().values('title')
print(res)
--------------
<QuerySet [{'title': '活着'}, {'title': '细雨中的呼喊'}, {'title': '兄弟'}]>
- 6.查询电话号码是111的作者姓名
# 先根据条件获取数据对象
author_detail_obj = models.AuthorDetail.objects.filter(phone=111).first()
res = author_detail_obj.author.name
print(res)
# 余华
4.基于上下划线的跨表查询
- 反向的口诀也使用与values或者values_list中使用
一条出结果
1.正向
- 1.查询主键为1的书籍对应的出版社名称
res = models.Book.objects.filter(pk=1).values('publish__name')
print(res) #<QuerySet [{'publish__name': '长江文艺'}]>
- 2.查询主键为4的书籍对应的作者姓名
res = models.Book.objects.filter(pk=4).values('author__name')
print(res)
# <QuerySet [{'author__name': '莫言'}, {'author__name': '路遥'}]>
- 3.查询余华的电话号码
res = models.Author.objects.filter(name='余华').values('author_detail__phone')
print(res)
# <QuerySet [{'author_detail__phone': 111}]>
2.反向
- 4.查询长江文艺出版过的书籍
res = models.Publisher.objects.filter(name='长江文艺').values('book__title')
print(res)
#<QuerySet [{'book__title': '活着'}, {'book__title': '平凡的世界'}, {'book__title': '嫌疑人x的献身'}]>
- 5.查询余华写过的书籍
res = models.Author.objects.filter(name='余华').values('book__title')
print(res)
# <QuerySet [{'book__title': '活着'}, {'book__title': '细雨中的呼喊'}, {'book__title': '兄弟'}]>
- 6.查询电话号码是111的作者姓名
res = models.AuthorDetail.objects.filter(phone=111).values('author__name')
print(res)
5.进阶操作
- 1.查询主键为1的书籍对应的出版社名称
res = models.Publisher.objects.filter(book__pk=1).first().name
print(res) # 长江文艺
res2 = models.Publisher.objects.filter(book__pk=1).values('name')
print(res2) # <QuerySet [{'name': '长江文艺'}]>
- 2.查询主键为4的书籍对应的作者姓名
res = models.Author.objects.filter(book__pk=1).first().name
print(res) # 余华
res1 = models.Author.objects.filter(book__id=4).values('name')
print(res1) # <QuerySet [{'name': '莫言'}, {'name': '路遥'}]>
- 3.查询余华的电话号码
res = models.AuthorDetail.objects.filter(author__name='余华').values('phone')
print(res)
# <QuerySet [{'phone': 111}]>
- 4.查询长江文艺出版过的书籍
res = models.Book.objects.filter(publish__name='长江文艺').first().title
print(res) # 活着
res1 = models.Book.objects.filter(publish__name='长江文艺').values('title')
print(res1)
# <QuerySet [{'title': '活着'}, {'title': '平凡的世界'}, {'title': '嫌疑人x的献身'}]>
- 5.查询余华写过的书籍
res = models.Book.objects.filter(author__name='余华').values('title')
print(res)
# <QuerySet [{'title': '活着'}, {'title': '细雨中的呼喊'}, {'title': '兄弟'}]>
res1 = models.Book.objects.filter(author__name='余华').first().title
print(res1)
# 活着
- 6.查询电话号码是111的作者姓名
res = models.Author.objects.filter(author_detail__phone=111).values('name')
print(res)
# <QuerySet [{'name': '余华'}]>
- 查询主键为4的书籍对应的作者的电话号码
# 通过作者表查询
res = models.Author.objects.filter(book__pk=4).values('author_detail__phone')
print(res)
# 通过书籍表查询
res1 = models.Book.objects.filter(pk=4).values('author__author_detail__phone')
print(res1)
# 通过作者详情表查询
res2 = models.AuthorDetail.objects.filter(author__book__pk=4).values('phone')
print(res2)
------------
<QuerySet [{'author_detail__phone': 222}, {'author_detail__phone': 33}]>
<QuerySet [{'author__author_detail__phone': 222}, {'author__author_detail__phone': 33}]>
<QuerySet [{'phone': 222}, {'phone': 33}]>
- 修改外键字段的增删改查
七、图书管理系统讲解
1.表设计
- 先考虑普通字段,再考虑外键字段
2.网页搭建
- 首页搭建
- 图书列表展示页面
八、 聚合查询
聚合函数:max min sum count avg,一般和分组查询配合使用
# 导入聚合函数
from django.db.models import Max,Min,Sum,Count,Avg
在ORM中指出单独使用聚合函数,通过关键字aggregate
from django.db.models import Max, Min, Sum, Count, Avg
models.Book.objects.aggregate(Max('price'), Count('id'), 最小价格=Min('price'), )
--------
{'最小价格': Decimal('32.450'), 'price__max': Decimal('999.870'), 'id__count': 8}
九、分组查询
通过关键字annotate
,进行分组查询
1.分组依据
-
models后面点什么就是按照什么分组
分组依据:
models.表名.objects.annotate() 按照整张表分组 models.表名.objects.values('字段名').annotate 按照values括号内指定的字段分组
2.例
- 1.查询每一本书的作者
models.Book.objects.annotate(author_sum=Count('author__pk')).values('title','author_sum')
-------
<QuerySet [{'title': '活着', 'author__num': 1}, {'title': '细雨中的呼喊', 'author__num': 1}, {'title': '蛙', 'author__num': 1}, {'title': '平凡的世界', 'author__num': 2}, {'title': '嫌疑人x的献身', 'author__num': 1}, {'title': '白夜行', 'author__num': 1}, {'title': '兄弟', 'author__num': 1}, {'title': '红高粱', 'author__num': 1}]>
- 2.筛选作者数量大于1的书
models.Book.objects.annotate(author__num=Count('author__pk')).filter(author__num__gt=1).values('title')
-----------
<QuerySet [{'title': '平凡的世界'}]>
- 3.查询每个作者的书的总价格
models.Author.objects.annotate(总价=Sum('book__price')).values('name','总价')
-----------
<QuerySet [{'name': '余华', '总价': Decimal('1198.080')}, {'name': '莫言', '总价': Decimal('976.110')}, {'name': '路遥', '总价': Decimal('32.450')}, {'name': '东野圭吾', '总价': Decimal('151.070')}]>
如果执行orm分组查询报错,并且有关键字sql_
show variables like '%mode%'
十、F查询与Q查询
1.F查询
当查询条件不是明确的,也需要从数据库中获取的时候,就需要使用F查询,获取表中指定字段对应的数据作为查询条件
from django.db.models import F
- 1.将所有书的价格减少800
models.Book.objects.update(price=F('price')-800)
-
2.将所有书的名称后面追加爆款
针对字符串类型的数据拼接需要使用模块:
Concat
字符串拼接 和Value
from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(title=Concat(F('title'), Value('新款')))
2.Q查询
可以将多个查询条件的关系做修改,Q查询中间的符号逗号是and,|是or,~是not
res = models.Book.objects.filter(Q(pk=1), Q(price__gt=80))
res2 = models.Book.objects.filter(Q(pk=1)| Q(price__gt=80))
res3 = models.Book.objects.filter(~Q(pk=1)| Q(price__gt=80))
print(res)
print(res2)
print(res3)
<QuerySet [<Book: 书籍对象:活着>]>
<QuerySet [<Book: 书籍对象:活着>, <Book: 书籍对象:细雨中的呼喊>, <Book: 书籍对象:兄弟>, <Book: 书籍对象:红高粱>]>
<QuerySet [<Book: 书籍对象:活着>, <Book: 书籍对象:细雨中的呼喊>, <Book: 书籍对象:蛙>, <Book: 书籍对象:平凡的世界>, <Book: 书籍对象:嫌疑人x的献身>, <Book: 书籍对象:白夜行>, <Book: 书籍对象:兄弟>, <Book: 书籍对象:红高粱>]>
3.Q查询进阶操作
可以使得查询条件的左侧变成字符串
查询条件的左边是字段名/变量名
q_obj = Q() # 1.产生q对象
q_obj.connector = 'or' # 默认多个条件的连接是and可以修改成or
q_obj.children.append(('pk', 1)) # 2.添加查询条件
q_obj.children.append(('price__gt', 100)) # 支持添加多个查询条件
res = models.Book.objects.filter(q_obj) # 查询支持直接添写q对象
res1 = models.Book.objects.filter(q_obj).first() # 查询支持直接添写q对象
print(res)
<QuerySet [<Book: 书籍对象:活着>, <Book: 书籍对象:细雨中的呼喊>, <Book: 书籍对象:兄弟>, <Book: 书籍对象:红高粱>]>
print(res1)
书籍对象:活着
print(res1.author.all())
<QuerySet [<Author: 作家对象:余华>]>
十一、ORM查询优化
1.ORM的查询特点
(1)ORM的查询默认都是惰性查询
当写了一条ORM,在我们不用的时候,ORM就不会启用这条ORM语句进行查询,要用的时候才会去访问数据库
(2)ORM的查询自带分页处理
ORM每次查询的时候,最多21条
(3)查询关键字only与defer (去反操作)
-
only
only
可以将括号内列举的字段封装到数据对象内,当我们通过点点方式获取括号内的字段对应的值时,直接通过封装好的对象获取;当我们没有想获取括号外的字段时,就会通过sql语句进行查询
-
defer
与only相反
defer
可以将括号内列举的字段封装到数据对象内,当我们通过点点方式获取括号内的字段对应的值时,会通过sql语句查询;当我们没有想获取括号外的字段时,则直接获取
(4)sql优化
-
select_related
底层是连表操作对于一对一字段(OneToOneField)和外键字段(ForeignKey,一对多),可以使用
select_related
来对QuerySet进行优化在对QuerySet使用s
elect_related()
函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库
model.XXX.objects.all().select_related('外键字段')
model.XXX.objects.all().select_related('外键字段__外键字段')
-
prefetch_related
底层子查询对于一对一字段(OneToOneField)和一对多字段(ForeignKey),可以使用
prefetch_related()
来进行优化。将
prefetch_related()
括号内填写的一对一、一对多字段,基于子查询将其结果数据封装成对象。
但是两者在使用上的时候差不多
预取prefetch
v.预先载入 n.预先载入
十二、ORM事务操作
1.事务的四大特性 ACID
A:atomicity原子性,一个事务是一个不可分割的工作单位,要么同时成功,要么同时失败
C:consistency一致性,事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
I:isolation隔离性,一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
D:durability持久性,持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
2.SQL相关
(1)关键字
- 开启事务
START TRANSACTION;
- 手动提交事务
COMMIT;
- 取消事务(即回滚)
ROLLBACK;
- 设置保存点
SAVEPOINT
(2)事务的隔离级别
MySQL中,事务有4种隔离级别,分别为READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)和SERIALIZABLE(串行化)。
- READ UNCOMMITTED 未提交读-脏读
事务A读取到了事务已经修改但未提交的数据,这种数据就叫脏数据,是不正确的
- READ COMMITTED 读已提交
不可重复读:对于事务A多次读取同一个数据时,由于其他是事务也在访问这个数据,进行修改且提交,对于事务A,读取同一个数据时,有可能导致数据不一致,叫不可重复读
- REPEATABLE READ 可重复读-幻读
幻读:原因:因为mysql数据库读取数据时,是将数据放入缓存中,当事务B对数据库进行操作:例如删除所有数据且提交时,事务A同样能访问到数据,这就产生了幻读。
问题:解决了可重复读,但是会产生一种问题,错误的读取数据,对于其他事务添加的数据也将访问不到
- SERIALIZABLE 串行化
串行化:事务A和事务B同时访问时,在事务A修改了数据,而没有提交数据时,此时事务B想增加或修改数据时,只能等待事务A的提交,事务B才能够执行。
3.ORM事务相关操作
-
方式一:配置文件数据库添加相关键值对
有效范围:全局有效
"ATOMIC_REQUESTS":True
每次请求涉及的orm操作同属于一个事务
如果报错会自动回滚
-
方式二:通过装饰器配置事务
有效范围:局部有效
from django.db import transaction
@transaction.atomic
def 函数名(request):
orm/sql
-
方式三:with上下文管理
有效范围:代码块有效
将with体代码中的orm或者sql语句支持事务
def 函数名(request):
with transacion.atomic():
orm/sql
十三、ORM模型层字段类型
1.常用字段
字段类型 | 作用 | 特点 |
---|---|---|
AutoField | 自增 | 必须填入参数primary_key=True ,如果没有该自增列django则会自动创建 |
CharField | 字符串类型 | 必须提供提供max_length 参数,来表示字符长度 |
IntergerField | 整形 | 范围:-2147483648 ~ 2147483647 |
BigIntergerField | 长整型 | -9223372036854775808 ~ 9223372036854775807 |
DecimalField | 10进制小数 | 参数:max_digits :最大长度decimal_places :小数点后几位 |
DateField | 日期类型 | 格式:YYYY-MM-DD 年-月-日 |
DateTimeField | 日期类型 | 格式:YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] 年-月-日 时-分-秒 |
BooleanField | 布尔值类型 | 传布尔值自动存0或者1 |
FileField | 文件类型 属于字符串类型 |
传文件对象,自动保存到提前配置好的路径下,并在数据库保存路径 upload_to = "":上传文件的保存路径 storage = None:存储组件,默认django.core.files.storage.FileSystemStorage |
TextField | 文本类型 | 存储大文本 |
EmailField | 邮箱类型 属于字符串类型 |
Django Admin以及ModelForm中提供验证机制 |
ImageField | 图片类型 属于字符串类型 |
路径保存在数据库,文件上传到指定目录 upload_to = "":上传文件的保存路径 storage = None:存储组件,默认django.core.files.storage.FileSystemStorage width_field=None:上传图片的高度保存的数据库字段名(字符串) height_field=None:上传图片的宽度保存的数据库字段名(字符串) |
2.不常用字段
字段 | 类型 | 特点 |
---|---|---|
BigAutoField | bigint自增列 | 必须填入参数 primary_key=True 当model中如果没有自增列,则自动会创建一个列名为id的列 |
SmallIntegerField | 小整数 | 范围:-32768 ~ 32767 |
PositiveSmallIntegerField | 正小整数 | 范围:0 ~ 32767 |
PositiveIntegerField | 正整数 | 范围:0 ~ 2147483647 |
BigIntegerField | 长整型(有符号的) | 范围:-9223372036854775808 ~ 9223372036854775807 |
BooleanField | 布尔值 | 有True和False |
NullBooleanField | 可以为空的布尔值 | 可以为空 |
IPAddressField | IP类型 属于 字符串类型 | Django Admin以及ModelForm中提供验证 IPV4 机制 |
GenericIPAddressField | 属于 字符串类型 | 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 | URL类型 属于 字符串类型 | Django Admin以及ModelForm中提供验证 URL |
SlugField | 属于 字符串类型 | Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) |
CommaSeparatedIntegerField | 属于 字符串类型 | 格式必须为逗号分割的数字 |
UUIDField | 属于 字符串类型 | Django Admin以及ModelForm中提供对UUID格式的验证 |
FilePathField | 文件路径类型 属于 字符串类型 | Django Admin以及ModelForm中提供读取文件夹下文件的功能 path:文件夹路径 match=None:正则匹配 recursive=False:递归下面的文件夹 allow_files=True:允许文件 allow_folders=Fals:允许文件夹 |
TimeField | 时间类型 | HH:MM[:ss[.uuuuuu]] 时分秒 |
DurationField | 长整数 | 时间间隔,数据库中按照bigint存储 ORM中获取的值为datetime.timedelta类型 |
FloatField | 浮点型 | 可以保存小数 |
BinaryField | 二进制型 | 存储二进制 |
3.自定义字段
(1)继承models.Field
(2)使用两个方法__init__
和db_type
class MyCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super().__init__(max_length=max_length, *args, **kwargs) # max_length需要按照关键字参数的方式传进去
def db_type(self, connection):
return 'char(%s)' % self.max_length
十四、常用字段参数
1.常用属性
字段 | sql语句 |
---|---|
null | null=True 该字段可以为空 |
unique | unique=True 该字段是唯一的 |
default | 设置默认值,一般设置可以为null |
db_index | 建立索引字段加快搜索 |
max_digits | 小数最大长度 |
max_length | 最大长度 |
decimal_places | 小数点后面几位数 |
auto_now | 当修改的时候自动保存当前时间 |
auto_now_add | 当创建时自动保存当前时间 |
primary_key | 主键 |
verbose_name | 注释 |
choices | 当某个字段的可能性能够被列举完全的情况下使用 |
class MyCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super().__init__(max_length=max_length, *args, **kwargs) # max_length需要按照关键字参数的方式传进去
def db_type(self, connection):
return 'char(%s)' % self.max_length
# choices 列举可能的完全性
class User(models.Model):
name = models.CharField(max_length=32)
info = MyCharField(max_length=64)
# 提前列举好对应关系
gender_choice = (
(1, '男性'),
(2, '女性'),
(3, '其他'),
)
# 存取的还是数字,但是只要是在gender_choice中有对应关系的,取出来就是男性或者女性
gender = models.IntegerField(choices=gender_choice,null=True)
-
choices
在pycharm的console中演示
>>> from app01 import models
>>> user_obj = models.User.objects.filter(pk=1).first()
>>> user_obj.get_gender_display()
'男性'
通过
obj.get_关联字段_display()
的方式来获取对应关系
2.外键字段参数
外键字段参数 | 作用 |
---|---|
to | 设置要关联的表 |
to_field | 设置要关联的表的字段 |
on_delete | 当删除关联表中的数据时,当前表与其关联的行为 |
-
on delete的参数配置
常用:
models.CASCADE
删除关联数据,与之关联也删除
1、models.CASCADE
级联操作,当主表中被连接的一条数据删除时,从表中所有与之关联的数据同时被删除
2、models.SET_NULL
当主表中的一行数据删除时,从表中所有与之关联的数据的相关字段设置为null,此时注意定义外键时,这个字段必须可以允许为空
3、models.PROTECT
当主表中的一行数据删除时,由于从表中相关字段是受保护的外键,所以都不允许删除
4、models.SET_DEFAULT
当主表中的一行数据删除时,从表中所有相关的数据的关联字段设置为默认值,此时注意定义外键时,这个外键字段应该有一个默认值
5、models.SET()
当主表中的一条数据删除时,从表中所有的关联数据字段设置为SET()中设置的值,与models.SET_DEFAULT相似,只不过此时从表中的相关字段不需要设置default参数
6、models.DO_NOTHING
什么都不做,一切都看数据库级别的约束,注数据库级别的默认约束为RESTRICT,这个约束与django中的models.PROTECT相似
十五、多对多三种创建方式
1.全自动创建
class Book(models.Model):
...
author = models.ManyToManyField(to='Author')
class Author(models.Model):
...
- 优势:自动创建第三章表,并且提供了add,remove,set,clear四种操作关系
- 劣势:第三表扩展性很差,无法创建更多的字段
2.纯手动创建
class Book(models.Model):
...
class Author(models.Model):
...
# 第三张表
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
# 其他更多字段
...
- 优势:第三张表完全由自己创建,扩展性强
- 劣势:编写繁琐,并且不支持add,remove,set,clear四种操作关系,也没有正反向的概念
3.半自动创建
class Book(models.Model):
...
# 在表中创建外键字段,但是要通过参数告诉orm我们已经创建了外键关系的第三章表
author = models.ManyToManyField(to='Author',
trough='Book2Author',
trough_fields=('book','author')
)
class Author(models.Model):
...
# 第三张表
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
# 其他更多字段
...
- 优势:第三张表完全自己创建,扩展性强,正反向的概念可以运用
- 劣势:但是不支持add,remove,set,clear四种操作关系