django之模型层(models)
django之模型层(models)
模型层搭建
模型层连接数据库
django默认连接sqlite3,但是这个数据库对时间字段不敏感,所以一般还是选择别的数据库测试。
一般是选择常见的数据库mysql去连接,而这是我们就需要更改一些设置:
# 修改配置文件
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # django后端数据引擎
'NAME': 'navicat_test1', # mysql这个属性用数据库即可
'HOST': '127.0.0.1', # ip
'PORT': 3306,
'USER': 'root', # mysql用户名,当然一般的项目中你都不是root
'PASSWORD': '111',
'CHARSET': 'utf8'
}
}
ps:第一次连接mysql数据库时可能会报错,需要按照提示下载模块,连接mysql需要下载mysqlclient
模型层编写模型类
我们可以通过ORM来使用类和对象映射我们的表和记录,而这些对应表的类就称作模型类,它一开始存储在我们的models.py文件中,由我们自己编写:
from django.db import models
# Create your models here.
class UserInfo(models.Model):
# 字段名 = 字段类型 + 约束条件
id = models.AutoField(primary_key=True) # 主键类型
name = models.CharField(max_length=32) # 字符字段
age = models.IntegerField() # 整型字段
这个类所对应的表,其大致属性为:表名-UserInfo,含三个字段,其中有一个主键自增字段。
我们在终端中运行manage命令(可以通过IDE的tools找到直接运行manage.py的环境)
python38 manage.py makemigrations # 记录数据库相关
python38 manage.py migrate # 将操作同步到数据库
# 第一次会创建很多表、其中一个是当前新增的模型表
# 如果在manage.py task环境下则只需要输入命令即可,而且还会补全
manage.py@jicheng > makemigrations
manage.py@jicheng > migrate
独立测试环境搭建
默认不允许单独测试某个py文件,尤其我们测试模型层时,为了测试模型数据而搭建一整个项目太过麻烦。
我们可以通过pycharm console即时测试(无法保存代码)。
也可以自行搭建:
我们可以在它默认提供的tests.py(或者我们自己在相同位置自己编写一个py文件也可)中编写:
import os
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jicheng.settings') # 添加了环境
import django
django.setup()
-测试代码-
if __name__ == '__main__':
main()
这样我们就可以在tests.py文件中测试代码了。
ORM的基础使用
查看ORM的底层sql
-
orm查询语句一般都会返回一个QuerySet对象,这个对象的query属性记录了这次的sql语句
import os def main(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jicheng.settings') # 添加了环境 import django django.setup() res = models.UserInfo.objects.filter() print(res.query) if __name__ == '__main__': main() # 运行结果: # SELECT `app01_userinfo`.`id`, `app01_userinfo`.`name`, `app01_userinfo`.`age` FROM `app01_userinfo`
注:有些orm方法返回的并不是QuerySet对象,并不能使用这种方法
-
在配置中添加以下代码,那么终端就会在每次数据操作时在终端显示sql语句:
终端打印sql配置
ORM基础方法
基础方法总结
增删改查
方法 | 返回值 |
---|---|
create(字段名=数据) | 刚创建的数据记录对象 |
filter(筛选条件) | QuerySet列表对象 |
filter().update(修改内容) | 受影响的行数 |
filter().delete() | 受影响的行数即各表受影响的行数 |
返回QuerySet对象的方法(大多通过模型类.objects.方法调用)
QuerySet对象形似存储了一个个记录对象的列表,但拥有一些特殊的属性,如query。
方法 | 含义 | 补充 |
---|---|---|
all() | 拿到表中所有记录 | |
filter() | 拿到表中所有符合条件的记录 | 括号中需要写筛选条件 |
exclude() | 拿到表中所有不符合条件的记录 | 括号中需要写筛选条件 |
order_by() | 按照某个字段进行排序 | 括号中需要写字段名称 |
reverse() | 将排序好的字段反转排序 | 只能对排过序的QuerySet对象操作 |
distinct() | 对QuerySet对象进行去重 | 一般搭配value(*field)使用,传入筛选过字段的记录集合QuerySet对象 |
raw(sql) | 执行原生的sql语句 | 注意实际的表名与模型表的类名区别 |
返回特殊QuerySet对象
方法 | 含义 | 返回值 |
---|---|---|
values() | 筛选字段 | <QuerySet[{},{},{}]>内部是字典 |
values_list() | 筛选字段 | <QuerySet[(),(),()]>内部是元组 |
返回具体记录对象
方法 | 含义 | 返回值 |
---|---|---|
create(**kwargs) | 向表中插入记录表 | 刚创建的记录对象 |
QuerySet对象.first() | 拿到结果的第一个记录 | 如果没有返回None |
QuerySet对象.last() | 拿到结果的最后一个记录 | 如果没有返回None |
QuerySet对象.get(筛选条件) | 拿到结果中符合条件的记录 | 如果没有符合条件则报错 |
具体测试
-
模型类.objects.create(字段名=值1,...)
res = models.UserInfo.objects.create(name='jason', age=28) print(res) print(res.name) print(res.age)
-
模型类.objects.filter(筛选条件).update(字段名=值1,...)
res = models.UserInfo.objects.filter(name="jason").update(name='zhang', age=38) print(res) # 1 返回受影响的行数
-
模型类.objects.filter(筛选条件).delete(字段名=值1,...)
res = models.UserInfo.objects.filter().delete() print(res) # (3, {'app01.UserInfo': 3}) 返回受影响的行数和每张表受影响的行数
-
模型类.objects.all()
res = models.UserInfo.objects.all()
-
模型类.objects.filter(筛选条件) (附first())
res = models.UserInfo.objects.filter() res1 = models.UserInfo.objects.filter().first() res2 = models.UserInfo.objects.filter(id=1)
-
模型类.objects.exclude(筛选条件)
res = models.UserInfo.objects.exclude(id=1) # 得到了id不为1的两个记录
-
模型类.objects.order_by(排序依据)
res1 = models.UserInfo.objects.order_by("age") # 按照年龄排序 res2 = models.UserInfo.objects.order_by("-age") # 降序排 res3 = models.UserInfo.objects.order_by("age").reverse() # 降序排
-
模型类.objects.values(字段名)
res = models.UserInfo.objects.all().values("id", "name") # 所有记录的id,name字段 res1 = models.UserInfo.objects.filter(id=1).values("id", "name") res2 = models.UserInfo.objects.exclude(id=1).values("id", "name")
-
模型类.objects.values(字段名).distinct()
res = models.UserInfo.objects.all().values("id", "name").distinct() # 所有字段联合去重
编写原生sql语句
自己编写sql来查询模型类有两种方式,在自己编写时需要注意实际的表名与模型表的类名区别:
-
raw()方法 (不要用)
res = models.User.objects.raw("select * from app01_user;") # 拿到个sql语句列表 for i in res: print(i) # 逐个打印查到的对象
-
游标 (类似pymysql)
from django.db import connection # 导入connection cursor = connection.cursor() cursor.execute('select name from app01_user;') print(cursor.fetchall())
ORM筛选条件
基础筛选
我们在编写filter的筛选条件时,默认是使用=
表示相等,并且多个相等条件的判断默认是与关系。
而实际上,sql中却应该支持我们一些大于、小于等筛选条件,这类方法该如何编写呢:
django中将字段后加上__条件
的方式让关键字参数拥有除等号外的其他含义。
-
大于、小于 gt、lt、gte、lte
res = models.User.objects.filter(age__gt=18) # 年龄字段大于18的用户 res = models.User.objects.filter(age__lt=18) # 小于 res = models.User.objects.filter(age__gte=18) # 大于等于 res = models.User.objects.filter(age__lte=18) # 小于等于
-
成员运算 in
res = res = models.User.objects.filter(age__in=(18,32,34)) # 年龄字段是18,32,34的记录
-
范围 range
res = models.User.objects.filter(age__range=(18, 38)) # 相当于between
-
模糊查询 contains icontains
res = models.User.objects.filter(name__contains="l") # 区分大小写 匹配"%l%" res = models.User.objects.filter(name__contains="l") # 不区分大小写
-
时间匹配
res = models.User.objects.filter(add_time__year=2022) # 年份 res = models.User.objects.filter(add_time__month=12) # 月份
F和Q查询方法
模型表添加字段
在介绍这两个方法之前,我们可以先做一些数据准备,在书籍表中添加两个字段:库存和卖出字段
首先,这个过程需要在模型表中添加两个字段:
store = models.IntegerField(null=True) sale_num = models.IntegerField(default=0)
注:字段中填写了两个参数,一个是null,即允许为空(默认情况下为非空),一个是default默认值,即在记录中这个字段如果没填写则自动填充的值。如果不设置这两个参数,那么在添加这两个字段的时候,因为表中已经有数据了,所以原本记录的这两个字段即要求非空又没有默认值就无法迁移。
然后,涉及到我们的数据迁移,需要执行那两条命令makemigrations migrate
。
我们再在表中手动添加一些数据便于测试即可。
F方法
为了解决这么一类查询问题:查询卖出数量大于库存的书籍
这种将两个字段进行比较的筛选条件,我们在ORM中就需要借助F方法。
from django.db.models import F
res = models.Book.objects.filter(sale_num__gt=F("store")).values("title")
因为我们对字段的数据进行拿取时,默认是通过字段名的方式直接拿的,如果不借助F方法,它在=后是无法被识别到的。
借助F方法后就可以在=后面也能抓取每条记录的数据,用于作为筛选条件等。
Q方法
在筛选条件中,还有一种或、非的关系需要借助Q方法来实现。
尝试解决:查询主键是1或者价格大于2000的书籍名
我们filter中的逗号隔开的条件默认是and的关系,我们可以通过Q方法来让修改这个关系为或、非
from django.db.models import Q
res = models.Book.objects.filter(Q(pk=1) | Q(store__gt=50)).values("title")
# 查询主键不为1的书籍名
res = models.Book.objects.filter(~Q(pk=1)).values("title")
Q方法使用总结:
- 两个条件是或关系
Q(条件1) | Q(条件2)
- 两个条件是非关系
~Q(条件)
Q查询进阶操作
我们可以提前对Q查询做整体的拼接,产生一个Q对象存起来。
q_obj = Q()
q_obj.connector = 'or' # 默认多个条件是and,可以修改为or
q_obj.children.append(("pk", 1)) # children是子条件,用逻辑运算连接
q_obj.children.append(("price__gt", 50)) # 可以添加多个子条件,以元组输入
res = models.Book.objects.filter(q_obj) # q对象可以直接做筛选条件。
print(res)
进阶操作中,添加判断条件可以通过字符串的方式,也就意味着,可以让用户交互,进行筛选条件的选择与添加。
ORM外键
模型表创建外键
建立外键得按照我们三种表关系来建立,有一对多、一对一、多对多的关系。
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.FloatField()
# 建立一对多关系,多条记录的一方建立外键,关键字为ForeignKey
publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
# 建立多对多关系,django中支持将外键建立在双方的一方
author = models.ManyToManyField(to="Author")
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=64)
class Author(models.Model):
name = models.CharField(max_length=32)
# 建立一对一关系,我们一般将其建立在查询频率高的一方
detail = models.OneToOneField(to="AuthorDetail", on_delete=models.CASCADE)
class AuthorDetail(models.Model):
tel = models.CharField(max_length=16)
addr = models.CharField(max_length=64)
需要注意的是:
-
创建一对多关系
和sql语句一样,外键建立到多的那张表上,不同的是,我们可以不讲究关联表和被关联表的建立顺序。字段类为
ForeignKey
在django2.x版本以上,建立一对多关系时需要指定on_delete参数为CASCADE,不加会报错,不过也不一定就是CASCADE,可能为其他实参,这里不展开。
建立外键时,系统会自动加上_id后缀作为字段名。
-
创建多对多关系
sql中是将两张表建立好后,将外键字段创建在第三张表中,而django为我们省去了这一步骤,我们可以在多对多关系双方的一个模型表中直接建立一个虚拟外键,
ManyToManyField
在底层,sql依旧创建了第三张表来存储两表的多对多关系,但是在orm操作中我们就可以将模型表中的外键当做实实在在的联系,因为在查询时,我们感受不到第三张的表的存在。
多对多关系的外键没有on_delete关键字参数。
-
创建一对多关系
一对一的字段类为
OneToOneField
,建议建立在查询频率高的一方。建立一对一关系时需要指定on_delete参数,否则报错。
利用外键设置表关系
在django中,当两张表的记录有外键的关联,它们就拥有了关系,可以在连表、子查询时省去很多麻烦。
-
一对多和一对一
一对多和一对一外键属于实际在表中的外键字段,这种关系可以直接通过对表的外键字段增删改来实现修改。而外键字段默认绑定所绑定的表的主键字段。
# 一对一 Author关联AuthorDetail models.AuthorDetail.objects.create(tel='1111', addr="合肥") models.Author.objects.create(name="leethon", detail_id=1) # 可以为被关联表有的主键字段 res = models.AuthorDetail.objects.filter(pk=2).first() models.Author.objects.filter(name="leethon").update(detail_id=res) # 可以为一个AuthorDetail对象 # 一对多 Publish对Book models.Publish.objects.create(name="上海出版社", addr="上海") res = models.Publish.objects.create(name="西安出版社", addr="西安") # 创建出版社记录,并返回了数据对象 models.Book.objects.create(title='80天环绕寝室', price="99", publish=res) # 可以通过数据对象来关联 models.Book.objects.create(title='进击的养殖鸡', price="99", publish_id=1) # 可以通过被关联表主键关联
一对一关系中需要注意只能一一对应,也就是外键字段有独一性的约束条件
2.多对多
多对多外键属于实际不在模型表中的虚拟字段,多对多关系则需要django提供给我们的方法来实现增删改关系。
拿到设立多对多外键的模型表的对象,用它点出外键属性,可以进行add、set、remove方法,这些方法都是这条记录对象的操作。
# 多对多 Book对Author
## add 添加关系
book_obj = models.Book.objects.filter(pk=1).first() # 拿到book对象
book_obj.author # 这个是所有book记录都有的author外键属性
book_obj.author.add(1, 5) # 直接通过主键数字来添加关系,可以一次添加多个
author_obj = models.Author.objects.filter(pk=7).first() # 拿到author对象
book_obj.author.add(author_obj) # 添加author对象也可以添加关系
## set 设置关系
book_obj.author.set([6,7,8]) # 将原本的关系删除,设置为新关系
book_obj.author.set((author_obj1,author_obj2)) # 参数是可迭代对象,元素可以是author对象
## remove 移除关系
book_obj.author.remove(1) # 通过主键数字来删除关系
book_obj.author.remove(author_obj) # 通过author对象删除关系
ORM跨表查询
建立外键的模型表到其外键关联的模型表,如Book到Publish,这个方向称为正向的表关系,反之则为反向表关系。
我们试着使用不同的方法去查询
- 出版了书籍《霸道总裁爱上谁》的作者的名字。(正向查询:书籍含外键)
- 作者leethon出版的书籍名。(反向查询,书籍含外键)
基于对象的正反向查询
# 正向查询:
# 1. 拿到对象
book_obj = models.Book.objects.filter(title="霸道总裁爱上谁").first()
# 2. 通过对象拿到外键字段
book_obj.author.all().values('name') # 结果形式 <QuerySet [{}, {}, {}]>
# 反向查询:
# 1. 拿到作者对象(没有外键的被关联表)
author_obj = models.Author.objects.filter(name="leethon").first()
# 2. 通过作者对象反向查询时,需要将关联表的名称小写加上_set
author_obj.book_set.all().values("title")
- 正向查询:数据对象可以点出它有的
外键字段
的名称属性 - 反向查询:数据对象可以点出关联它的
表名小写_set
的名称属性
基于字段的正反向查询
# 正向查询
models.Book.objects.filter(title="霸道总裁爱上谁").values("author__name")
# 反向查询
models.Author.objects.filter(name="leethon").values("book__title")
- 正向查询:字段属性可以直接填写它的
外键字段名__被关联表字段名
- 反向查询:字段属性可以直接填写它的
关联表表名__关联表的字段名
基于筛选条件的正反向查询
# 反向查询
models.Author.objects.filter(book__title="霸道总裁爱上谁").values("name")
# 正向查询
models.Book.objects.filter(author__name="leethon").values("title")
- 正向查询:筛选属性可以通过
外键字段名__被关联表字段
直接将被关联表的字段作为判断条件 - 反向查询:筛选属性可以通过
关联表表名__关联表字段
直接将关联表的字段作为判断条件
了解了上述方法,我们可以尝试:
查询出版了书籍《80天环绕寝室》的作者其电话号码是多少?
这个题目中涉及了三张表,用>来表示正向则 book>author>authordetail
# 方法一:全正向
models.Book.objects.filter(title="80天环绕寝室").values("author__detail__tel")
# 方法二:全逆向
models.AuthorDetail.objects.filter(author__book__title="80天环绕寝室").values('tel')
# 方向三:正逆向都掺和点
models.Author.objects.filter(book__title="80天环绕寝室").values('detail__tel')
多对多关系补充
多对多关系字段有三种建立方式,其中第一种我们上文已经介绍了,就是ManyToManyField字段,也体会到它的方便之处,它可以自动的创建第三张关系表,而且在进行关联表和被关联表的相互查询和关系修改时也十分方便,我们可以使用正向方向查询和set\add\remove\clear
等修改关系。
但是全自动建立的外键,它所建立的第三张表,是不可拓展的,如果我们有一些业务逻辑就是在关系表上,我们就无法通过第三张表完成了。
所以我们还需要介绍手动创建的方法,来满足扩展字段的需求。
全手动方法
class Book(models.Model):
title = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
others = models.CharField(max_length=32)
join_time = models.DateField(auto_now_add=True)
-
这种方式几乎和sql建立多对多关系相对应,很明显这个是有很强的拓展性的,我们可以建立字段来存储建立关系的时间等业务逻辑。
但是作为模型表,我们无法通过
set\add\remove\clear
等修改关系,正反向查询的逻辑也比较乱,需要通过中间表转接一次。 -
手动创建优化
django在为手动创建的表增加了一层优化,让我们可以通过ORM的正反向查询直接链接到两张表:
class Book(models.Model):
title = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author', # 被关联表
through='Book2Author', # 关系表
through_fields=('book','author') # 关系表对应外键字段
)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book', on_delete=models.CASCADE)
author = models.ForeignKey(to='Author', on_delete=models.CASCADE)
others = models.CharField(max_length=32)
join_time = models.DateField(auto_now_add=True)
这层优化,需要我们在一张模型表中设置ManyToManyField字段,并且将第三张表的表名和两张表的对应外键告知它。这个操作是在book中设置虚拟外键,让我们仍然可以通过正反向查询直接访问关联的表。但是仍然无法使用set\add\remove\clear
的方法。
聚合函数和分组查询
聚合函数
对应sql中的聚合函数,聚合函数可以在分组前后使用,而ORM中需要将两种情况分开来看,如果在分组前来看,我们需要通过模型表对象.aggregate(聚合函数)
的方式来使用。如:
from django.db.models import Max, Min, Sum, Count, Avg
# 首先这些聚合函数需要从db.models文件中导入
res = models.Book.objects.aggregate(Max('price'), Count('pk'), 最小价格=Min('price'), allPrice=Sum('price'),平均价格=Avg('price'))
print(res) # {'最小价格': 99.0, 'allPrice': 396.0, '平均价格': 99.0, 'price__max': 99.0, 'pk__count': 4}
其中,可以通过关键字传参的方式给字段取别名。
而使用了aggregate意味着整张表会作为一个整体,其单独的数据属性是不能作为我们查询字段的,这个对应了我们sql的严格模式,分组后只能插叙组的数据统合,而不再查看组中某条具体记录。
方法 | 含义 |
---|---|
Max()|Min() | 字段的最大最小值 |
Count() | 记录的数量 |
Sum() | 该字段数据的和 |
Avg() | 平均值 |
Concat() | 字符拼接 |
分组查询
分组后的查询所用的关键字是:annotate
在ORM中,有两种分组依据,一个是与sql一样按照字段分组,一种则是依据外键对应的表来进行分组的。
-
通过字段分组
先按照字段取结果,再使用annotate方法:
表对象.values(分组字段).annotate(聚合函数)
-
通过表来分组
需要先找到分组依据的表对象再使用annotate方法:
表对象.annotate(聚合函数).values(聚合函数别名)
通过几个例子来看一下:
1.统计出每个出版社卖的最便宜的书的价格
分析:分组依据是出版社,目标字段是书的价格
# 按字段分组,先拿目标字段所在表对象即可
res = models.Book.objects.values("publish_id").annotate(Min("price"))
# 按表分组,先拿分组依据表对象
res = models.Publish.objects.annotate(min_price=Min("book__price")).values("name", "min_price")
ps:如果sql_mode配置中含only_group_by,则会导致第二种方式的values中不能添加非表字段,如我们的Publish的name字段就无法显示了。
2.筛选出作者个数大于1的书籍
分析:分组依据是书籍,目标字段是作者_pk(任何一个字段都可以,count的结果都一样)
# 按字段分组
res = models.Author.objects.values("book__title").annotate(author_count=Count("pk"))\
.filter(author_count__gt=1)
# 按表分组
res=models.Book.objects.annotate(author_count=Count('author__pk')).\
filter(author_count__gt=1).values("title", "author_count")
这里的filter就相当于sql中的having,因为它是分组后查询的。
ORM查询优化
在ORM中可以自动的或者手动做一些查询优化,让我们查询的流程或者效率更高。而总体的原理在于,并不是产生queryset对象时执行sql,而是在触发打印、循环、取字段值时触发sql语句的执行。
查询默认惰性查询
惰性查询即,当语法检测到后续没有用我拿的查询结果做任何操作如展示,则不查询。但是每次使用这个结果都会执行一次查询语句。
res = models.Book.objects.filter()
# print(res) # 没有这句则底层查询,可以通过sql配置查看语句验证。
自带分页处理
我们查询sql语句在执行打印操作时,最后默认会加上limit的条件,没有写,则按默认值分页。
ps:如果不是打印相关操作,就不会带分页,如查询结果会for循环处理每一个数据就要保证每个数据被处理,所以就不带分页。
only和defer
-
我们使用values去取字段时,queryset对象结果中的一个个元素是以字典方式存储的。
<queryset [{字段名:值},{},{}]>
-
我们用only(查询字段)或者defer(不查询字段)的方式拿到的queryset对象,内部则会存储具体数据对象。
<queryset [数据对象,数据对象2,数据对象3]>
res = models.Book.objects.only("title", "publish_time")
for data in res:
print(data.title)
# print(data.price)
执行上述代码,如果用数据对象点出only内指定的字段,则不会重复查询,但是如果想用这个数据对象取其他的字段数据,则会再构筑一条sql执行查询。
而defer是指定不进行查询的字段,相当于对字段取反的only操作。
select_related与prefetch_related
这两个操作是针对数据对象取外键操作的查询优化,即当数据对象所包含的字段默认是自己表中的字段(如all()的queryset结果中的数据对象),这样的数据对象取自己的字段不必重复查询,但是取外键链接的字段则需要重复查询。
res = models.Book.objects.all()
for data in res:
print(data.publish.name) # 每次都会重新查询
# 使用select_related
res = models.Book.objects.select_related("publish")
for data in res: # 直接执行连表查询
print(data.publish.name) # 每次循环不会再重复执行
# 使用prifetch_related
res = models.Book.objects.prefetch_related("publish")
for data in res: # 直接执行子查询
print(data.publish.name) # 每次循环不会再重复执行
ORM批量操作数据
我们尝试通过ORM对100000条数据进行创建,这100000条数据就假设存在一个列表中,那么我们应该怎么操作?
下述代码是简单的逻辑实现,但是效率很低。
from apps import models
from xxx import data_list # 假设导进来一个拥有十万条数据的数据列表
# 列表中每个元素的存储形式为{"aaa":v1,"bbb":v2...}
def create_data(request):
for data in data_list:
models.Data.objects.create(aaa=data.get('aaa'),bbb=data.get('bbb'))
...
上述代码就相当于每条数据组织一个sql去插入数据,所以效率低下。
而针对于重复的插入数据操作,ORM中也提供了相应的方法。
from apps import models
from xxx import data_list # 假设导进来一个拥有十万条数据的数据列表
# 列表中每个元素的存储形式为{"aaa":v1,"bbb":v2...}
def create_data(request):
# 循环产生数据对象,并不执行sql语句
data_obj = (models.Data(aaa=data.get('aaa'),bbb=data.get('bbb')) for data in data_list)
# 批量将数据对象插入数据库,此时才执行sql
models.Data.object.bulk_create(data_list)
上述批量插入的方法,效率得到了极大的提升,我们只执行了一句sql,对应insert into 表名 values()
的values后有多个数据值,就可以插入所有数据,至少在ORM中,我们已经做到了优化。
ORM事务操作
sql事务操作直达
ACID
原子性、一致性、隔离性、持续性
sql关键字
start transanction; # 开启事务
rollback; # 回滚
commit;# 确认
savepoint 回滚点名; # 新建回滚点
一些重要概念
- 脏读:未提交读
- 幻读:可重复读
- 不可重复读:提交读
- MVCC多版本控制:解决幻读的问题
ORM事务操作
-
全局开启事务
在配置数据库时即DATABASE的default中,添加一个键值对配置,
"ATOMIC_REQUESTS":TRUE
。每次涉及ORM操作的同属于一个事务。
-
装饰器
局部有效,一般是针对视图函数的,当我们这个视图函数中涉及ORM操作,并且没有执行完时出现报错,那么就会自动触发回滚,返回视图函数开始的状态。
没有添加事务的转账操作,一方已经加钱,另一方全因为错误没有扣钱,这时应该加事务在遇到错误时回滚。
def transfer(request):
from django.db.models import F
models.User.objects.filter(pk=1).update(account=F("account")+30) # 这步已经生效
sdlkfj # 一个错误
models.User.objects.filter(pk=2).update(account=F("account")-30) # 这步没有成功
return HttpResponse("转账成功")
添加事务操作后,就整个视图函数中遇到错误时自动回滚到未执行的状态
from django.db import transanction # 需要导入一个事务模块
@transanction.atomic # 原子装饰器
def transfer(request):
from django.db.models import F
models.User.objects.filter(pk=1).update(account=F("account")+30) # 这步暂时生效
sdlkfj # 一个错误直接回滚,上一步操作作废,回到最初状态。
models.User.objects.filter(pk=2).update(account=F("account")-30)
return HttpResponse("转账成功")
如果没有那个错误,则执行成功,读者可自行尝试。
3.with上下文
with则将原子操作的位置划分的更小,可以是任意一段代码,将其放到with的子代码中即可,在with开始开启事务,在遇到错误时回滚,在执行完毕后确认。
with transaction.atomic():
models.Book.objects.filter(pk=1).update(price=F("price")+30)
。。
models.Book.objects.filter(pk=2).update(price=F("price")-30)
ps:注意在视图函数中,哪怕返回的不是httpresponse对象,对于视图函数本身也是执行完了,会触发事务的确认。
ORM常用字段类型
常用字段 | 字段常见参数 | 字段描述 |
---|---|---|
AutoField | primary_key | 主键字段才会用,可以让其自动创建 |
CharField | max_length | 对应varchar字段,存储有限的字符 |
IntegerField、BigIntergerField | 整型字段 | |
DecimalField | max_digits/decimal_places | 小数字段 |
DateField、DateTimeField | auto_now/auto_now_add | 日期、时间字段 |
BooleanField | orm传入True、False存成1和0 | |
TextField | 存储大量文本 | |
EmailField | 存储邮箱格式数据 | |
FileField | upload_to | orm传入文件对象,会将文件内容保存到配置指定路径,字段存文件路径 |
ForeignKeyField、OneToOneField | to/to_field/on_delete | 实际外键字段,建立一对多和一对一关系 |
ManyToManyField | to/to_field | 虚拟外键字段,建立多对多关系 |
需要说明的是,这些orm字段并非和sql字段一一对应,有些是封装了一些逻辑功能在字段的创建、存储过程中的。
自定义ORM字段
class MyCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super().__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
return 'char(%s)' % self.max_length
class User(models.Model):
name = models.CharField(max_length=32)
info = MyCharField(max_length=64)
ORM常用字段参数
一些基础参数
primary_key | verbose_name | null | default | unique | db_index |
---|---|---|---|---|---|
主键字段 | 字段注释 | 允许为空 | 默认值 | 唯一值 | 添加辅助索引 |
这些参数都在sql中有对应的关系,不必过多说明,这些参数几乎所有字段都可以添加。
字段独有参数
-
max_length:最大长度
是字符类型数据所拥有的参数,对应varchar(64)后面的数字,规定字符类型最大存储字符数。
-
max_digits:小数总共多少位
decimal_places:小数点后面的位数
这两个参数是DecimalField独有的,表示小数位数的存储方式 -
auto_now:每次操作数据自动更新事件
auto_now_add:首次创建自动更新事件后续不自动更新
在时间相关字段的独有参数,设置为True则会自动执行相关功能。 -
choices:当某个字段的可能性能够被列举完全的情况下使用
如性别、学历、工作状态、...等提前以元组形式定义所有结果的对应关系,当我们想存入值,就会对应的存入元组的0号元素。
如果我们想要取出值,则可以选择读取实际存储值,或按映射关系拿到元组的1号元素对应值。
class User(models.Model):
name = models.CharField(max_length=32)
gender_choice = (
(1, '男性'),
(2, '女性'),
)
gender = models.IntergerField(choices=gender_choice,null=True)
user_obj = User.objects.filter(pk=1).first() # 拿到一个对象
user_obj.gender # 直接点显示存储的真实数据
user_obj.get_gender_display() # 通过这个方法拿显示转义后的选项。
- upload_to:文件字段,文件不会存在数据库,而会存储到这个参数所指定的文件目录下,数据库只存储文件路径。
外键相关参数
- to:关联表
- to_field:关联字段(不写默认关联数据主键)
- on_delete:当删除关联表中的数据时,当前表与其关联的行的行为(多对多关系无此参数)。
- related_name:反向查询名(默认为关联表名小写_set)
- related_query_name:反向连表字段名(默认为关联表名小写)
- db_constraint:默认为True,如果为False,则不建立数据库的外键约束,但是ORM层面的关联仍然正常使用
on_delete的参数值:
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相似。这个级联策略只能在没有建立外键约束时才能用 db_constraint=False
ps:主表一般是指在一对一、一对多中一的那一方,当对其进行删除时,另外一张表的所有元素都应该受到影响