Django框架之模型层 单表操作
一、ORM介绍
orm:对象关系映射
对象关系映射简单说:就是通过操作对象的方式,去操作数据库。不需要写大量原生的sql语句就可以完成数据的增删改查
映射关系:
类 | 数据库中的表 |
---|---|
对象 | 表单记录 |
对象获取属性 | 记录的某个字段对应的值 |
优点: 能够让一个不会数据库操作的人 也能够简单快捷去使用数据库
缺点: 由于封装程度太高 可能会导致程序的执行效率偏低,有时候 结合项目需求 可能需要你手写sql语句
注意事项:django的orm不会自动帮你创建库,库需要你自己手动创建,表会自动帮你创建 你只需要书写符合django orm语法的代码即可,去应用下所在的models.py中书写类
二、模型层
模型层:应用下的models.py
。
在该文件先写表类继承于odels.Model
,然后去定义属性字段
这里写的类,会到数据库中去创建响应的表(类名),每一个设置的属性,就是一个类中的字段
关于数据库的连接配置请参考:002 Django框架及ORM的基本使用
如果想打印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',
},
}
}
三、django测试环境搭建
在应用下的tests.py
中
import os
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Django_Stu_Pro.settings")
'''写下面这两句话才可以运行'''
import django
django.setup()
# 你就可以在下面测试django任何的py文件
四、单表操作
创建表:
class Signle(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2) # 长度8位,两位小数
publish_date = models.DateField()
4.1 增删改
# 增: 推荐方式
models.User.objects.create(usr=my_usr, pwd=my_pwd)
# 删: 推荐方式
models.User.objects.filter(usr=my_usr).delete()
# 改: 推荐方式
models.User.objects.filter(usr=my_usr).update(pwd=new_pwd)
4.2 批量插入数据
如果现在有需求,需要通过ORM插入大量的数据,但这时候如果依然使用create
方式去插入,效率会极低,因为每一次create
都会去走一次数据库。因此我们介绍另一种方式,通过创建对象,然后将所有的对象一次性的插入到数据库中。
- bulk_create
# 批量插入:(推荐使用)
models.UserInfo.objects.bulk_create([....])
# 往书籍表中插入大量数据
'''第一种方式'''
# for i in range(1000): # 这种插入方式 效率极低
# models.UserInfo.objects.create(user=f"{i}",password=f"{i}")
'''第二种方式 批量插入 (推荐使用)'''
user_list = []
for i in range(1000): # 时间还是很短 两者差距很大
user_list.append(models.UserInfo(user=f"{i}",password=f"{i}"))
print(len(user_list))
models.UserInfo.objects.bulk_create(user_list) # 批量插入数据
4.3 单表查询之必知必会13条
- all()
- filter()
- get()
- first()
- last()
- exclude()
- values()
- values_list()
- count()
- distinct()
- order_by()
- reverse()
- exists()
'''单表查询:必知必会13条'''
# 1. all() 查询所有 返回:QuerySet对象
res = models.Signle.objects.all() # 惰性查询
# 2.filter()条件查询 返回:QuerySet对象
res = models.Signle.objects.filter(pk=2)
# 3.get()条件查询 返回对象
res = models.Signle.objects.get(pk=2)
# 4.first() 拿第一个 返回对象
res = models.Signle.objects.first()
# 5.last() 最后一个 返回对象
res = models.Signle.objects.last()
# 6.exclude() 除此之外 返回:QuerySet对象
res = models.Signle.objects.exclude(pk=2)
# 7.values() 查询所有该字段值 返回:QuerySet对象, 列表套字典
res = models.Signle.objects.values("name","price")
# 8.values_list() 查询所有该字段值 返回:QuerySet对象, 列表套元祖
res = models.Signle.objects.values_list("name","price")
# 9.count() 统计数据的个数 返回:个数
res = models.Signle.objects.count()
# 10.distinct() 去重,必须完全一样,先查询在去重 返回:QuerySet对象
res = models.Signle.objects.values("name").distinct()
# 11.order_by() 排序 返回:QuerySet对象
res = models.Signle.objects.order_by("price") # 默认升序
res = models.Signle.objects.order_by("-price") # 加负号就是降序
# 12.reverse() 前面必须是先结果排序才可以反转 返回:QuerySet对象
res = models.Signle.objects.order_by("price").reverse()
# 13.exists() 判断存在 返回:布尔值
res = models.Signle.objects.filter(pk=1).exists()
4.4 神奇的双下划线模糊查询
# 查询价格大于200的书籍
res = models.Signle.objects.filter(price__gt=200)
# 查询价格小于200的书籍
res = models.Signle.objects.filter(price__lt=200)
# 查询价格大于或者等于200的书籍
res = models.Signle.objects.filter(price__gte=200)
# 查询价格小于或者等于200的书籍
res = models.Signle.objects.filter(price__lte=200)
# 查询价格是 200 或123.23 或 666.66
res = models.Signle.objects.filter(price__in=[200,123.23,666.66])
# 查询价格在200 到700之间的书籍
res = models.Signle.objects.filter(price__range=[200,666.66]) # 顾头不顾尾
# 查询书籍名称中包含p的
res = models.Signle.objects.filter(name__contains='p') # 区分大小写
res = models.Signle.objects.filter(name__icontains='p') # 忽略大小写
# 查询以什么开头
res = models.Signle.objects.filter(name__startswith="p")
# 查询以什么结尾
res = models.Signle.objects.filter(name__endswith="子")
# 查询 哪年的记录
res = models.Signle.objects.filter(publish_date__year=2019)
# 查询 哪月的记录
res = models.Signle.objects.filter(publish_date__month=10)
# 查询 哪天的记录
res = models.Signle.objects.filter(publish_date__day=24)
4.5 聚合查询和分组查询
# 导入聚合函数
from django.db.models import Max,Min,Count,Sum,Avg
4.5.1 聚合查询(利用聚合函数)
aggregate()
是QuerySet
的一个终止子句,意思是说,它返回一个包含一些键值对的字典。
键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。
示例:
from django.db.models import Avg, Sum, Max, Min, Count
models.Book.objects.all().aggregate(Avg("price"))
{'price__avg': 13.233333}
如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
models.Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 13.233333}
如果你希望生成不止一个聚合,你可以向aggregate()
子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
models.Book.objects.all().aggregate(Avg("price"), Max("price"), Min("price"))
{'price__avg': 13.233333, 'price__max': Decimal('19.90'), 'price__min': Decimal('9.90')}
4.5.2 分组查询
我们在这里先复习一下SQL语句的分组。
假设现在有一张公司职员表:
我们使用原生SQL语句,按照部分分组求平均工资:
select dept,AVG(salary) from employee group by dept;
ORM查询:
from django.db.models import Avg
Employee.objects.values("dept").annotate(avg=Avg("salary").values(dept, "avg")
这里需要注意的是annotate分组依据就是他前面的值,
如果前面没有特点的字段,则默认按照ID分组,
这里有dept字段,所以按照dept字段分组
连表查询的分组:
SQL查询:
select dept.name,AVG(salary) from employee inner join dept on (employee.dept_id=dept.id) group by dept_id;
ORM查询:
from django.db.models import Avg
models.Dept.objects.annotate(avg=Avg("employee__salary")).values("name", "avg")
示例:
# 1.统计每一本书的作者个数
res = models.Book.objects.annotate(author_num = Count("author")).values("author_num")
print(res)
# 2.统计出每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(price_min = Min("book__price")).values("price_min")
print(res)
# 3.统计不止一个作者的图书
# filter() having 过滤条件
res = models.Book.objects.annotate(author_num = Count("author"))\
.filter(author_num__gt=1).values("author_num")
print(res)
# 4.查询各个作者出的书的总价格
res = models.Author.objects.annotate(price_sum = Sum("book__price")).values("price_sum")
print(res)
4.5.3 总结
value里面的参数对应的是sql语句中的select要查找显示的字段,
filter里面的参数相当于where或者having里面的筛选条件
annotate本身表示group by的作用,前面找寻分组依据,内部放置显示可能用到的聚合运算式,后面跟filter来增加限制条件,最后的value来表示分组后想要查找的字段值
4.6 F与Q查询
from django.db.models import F, Q
4.6.1 F查询
在上面所有的例子中,我们构造的过滤器都只是将字段值与某个我们自己设定的常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
# 1.查询出卖出数大于库存数的商品
res = models.BookRecord.objects.filter(maichu__gt=F("kucun")).first()
print(res.book.name)
# 2.将所有的书的价格 全部提高100块
res = models.Book.objects.update(price = F("price")+100)
from django.db.models.functions import Concat
# 3.了解 尝试着将所有的书的名字后面都加上 爆款
res = models.Book.objects.update( name=Concat(F("name"),Value("(爆款)")) )
4.6.2 Q查询
filter()
等方法中逗号隔开的条件是与的关系。 如果你需要执行更复杂的查询(例如OR
语句),你可以使用Q对象
。
# 1.查询书籍名称是 python从入门到放弃(爆款) 或者价格是 212 的书
res = models.Book.objects.filter(Q(name="python从入门到放弃(爆款)")|Q(price=212))
print(res)
# 2.查询书籍名称 不是 python从入门到放弃(爆款) 书
res = models.Book.objects.filter(~Q(name="python从入门到放弃(爆款)"))
print(res)
# Q查询进阶用法 用Q产生对象 然后再使用
q=Q()
q.connector="or"
q.children.append(("name__contains", 'python'))
res = models.Book.objects.filter(q)
print(res)
4.7 only与defer
defer和only互为反关系
-
only:
only是只查该字段 直接将结果封装到返回给你的对象中 点该字段 不需要再走数据库 但如果你点了不是括号内的字段 就会频繁的去走数据库查询
-
defer
defer是查除了该字段的 所有字段 直接将结果封装到返回给你的对象中 点该其他字段 不需要再走数据库 但如果你点了不是括号内的字段 就会频繁的去走数据库查询
# only
res = models.Book.objects.only('name') # 只查name字段
for r in res:
# print(r.name) # 返回name
print(r.price) # 查询的结果没有,因此要每次都去查
# defer
res = models.Book.objects.defer("name") # 查除了name字段之外的字段
for r in res:
# print(r.name) # 查询的结果没有,因此要每次都去查
print(r.price)
五、事务
事务的定义:将多个sql语句操作变成原子性操作,要么同时成功,有一个失败则里面回滚到原来的状态,保证数据的完整性和一致性(NoSQL数据库对于事务则是部分支持)
# 事务
# 1. 创建一条订单数据
# 2. 去产品表 将卖出数+1, 库存数-1
from django.db.models import F
from django.db import transaction
# 开启事务处理
try:
with transaction.atomic():
# 创建一条订单数据
models.Order.objects.create(num="110110111", product_id=1, count=1)
# 能执行成功
models.Product.objects.filter(id=1).update(kucun=F("kucun")-1, maichu=F("maichu")+1)
except Exception as e:
print(e)
5.1 开启事务的第二种方法
@transaction.atomic
def post(self, request):
# 开启事务
sid = transaction.savepoint()
# 回滚
transaction.savepoint_rollback(sid)
# 提交
transaction.savepoint_commit(sid)
5.2 乐观锁与悲观锁
乐观锁与悲观锁都是一种思想的实现。
比如:操作库存,下单前,都需要去查询库存数。由于并发的问题可能会导致查询数据不正确,因为又可以A在查数据库存的时候,B用户下单了。因此需要解救这个问题
悲观锁就是在查数据的时候就锁住,因为怕出错。这样肯定不会出错。但是效率就不高了。
乐观锁就是在操作数据库的时候通过逻辑来进行判断,操作失败就会滚。