文章目录
一、前言
Django框架本身自带数据库操作功能,即ORM框架,其实我们是可以使用pymysql模块执行原生的sql语句去操作数据库,但是缺点如下:
- sql语句一般比较复杂,并且维护困难;
- sql语句的安全性无法得到保障,可能会有sql注入的风险;
- 数据库的创建、数据表的生成、数据备份以及数据库的迁移非常麻烦;
- sql语句性能无法保障;
ORM:Object Relational Mapping(关系对象映射)
ORM优势:
根据对接的数据库引擎翻译成对应的sql语句,所以我们不用关注使用的是MySQL还是Oracle等,我们只需要修改数据库配置即可;
二、数据库配置
指定在全局配置文件settings.py中的DATABASES字典里配置需要连接的数据库信息
DATABASES = {
# 指定数据库的别名/标签
# 指定的是django默认使用的数据库
'default': {
# 指定当前使用的数据库引擎
# django.db.backends.mysql、oracle、sqlite3
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django_test', # 数据库
'USER': 'root', # 用户名
'PASSWORD': '123456789', # 密码
'PORT': 3306, # 端口
'HOST': '127.0.0.1' # 主机ip
}
}
然后在settings.py所在目录下的__init__py文件中设置Django默认连接MySQL的方式
import pymysql
pymysql.install_as_MySQLdb()
三、数据表创建
1、单表创建
-
一般在子应用models.py中定义模型类(相当于数据库中的一张表);
-
必须继承Model或者Model的子类;
-
在模型类中定义类属性(必须得为Field子类)相当于数据表中字段;
-
字段参数,常见字段如下:
字段 描述 AutoField 主键字段:primary_key=True CharField 字符串,对应的是MySQL的varchar数据类型 IntegerField/BigIntergerField 整型:int FloatField 浮点 DecimalField 精确浮点 EmailField 邮件,底层仍然是VarChar(254) DateField/DateTimeField 日期 BooleanField 布尔值类型 TextField 长文本 FileField 文件
我们来看实际怎么创建数据表:
1)先在子应用models.py中编写模型类
from django.db import models
class People(models.Model):
# CharField等价于varchar
# 属性名就是字段名,
name = models.CharField(max_length=50)
# IntegerField等价于int
age = models.IntegerField()
# BooleanField等价于bool
gender = models.BooleanField()
class Projects(models.Model):
# 在一个模型类中仅仅只能为一个字段指定primary_key=True
# 一旦在模型类中的某个字段上指定了primary_key=True,那么ORM框架就不会自动创建名称为id的主键,否则会自动生成AutoField的id
id = models.IntegerField(primary_key=True, verbose_name='项目主键', help_text='项目主键')
# a.CharField类型必须指定max_length参数(改字段的最大字节数),其他是可选项:verbose_name和help_text是字段释义
# b.如果需要给一个字段添加唯一约束,unique=True(默认为False)
name = models.CharField(max_length=20, verbose_name='项目名称', help_text='项目名称', unique=True)
leader = models.CharField(max_length=10, verbose_name='项目负责人', help_text='项目负责人')
# c.使用default指定默认值(如果指定默认值后,在创建记录时,改字段传递,会使用默认值)
is_execute = models.BooleanField(verbose_name='是否启动项目', help_text='是否启动项目', default=True)
# d.null=True指定前端创建数据时,可以指定该字段为null,默认为null=False,DRF进行反序列化器输入时才有效
# null :针对数据库,如果 null=True, 表示数据库的该字段可以为空,即在Null字段显示为YES。
# e.blank=True指定前端创建数据时,可以指定该字段为空字符串,默认为blank=False,DRF进行反序列化器输入时才有效
# blank :针对表单,如果 blank=True,表示你的表单填写该字段时可以不填,但是对数据库来说,没有任何影响
desc = models.TextField(verbose_name='项目描述信息', help_text='项目描述信息',
null=True, blank=True, default='')
# f.在DateTimeField、DateField等字段中,指定auto_now_add=True,在创建一条记录时,会自动将创建记录时的时间作为该字段的值,后续在更新数据时,就不再修改
# g.在DateTimeField、DateField等字段中,指定auto_now=True,在更新一条记录时,会自动将更新记录时的时间作为该字段的值
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间', help_text='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间', help_text='更新时间')
# h.可以在任意一个模型类中创建Meta内部类,用于修改数据库的元数据信息
class Meta:
# i.db_table指定创建的数据表名称
db_table = 'tb_projects'
# 为当前数据表设置中文描述信息
verbose_name = '项目表'
verbose_name_plural = '项目表'
ordering = ['id']
def __str__(self):
return f"Projects({self.name})"
2)命令行内执行命令
#命令行执行
#根据app下的migrations目录中的记录,检测当前model层代码是否发生变化?
python manage.py makemigrations 子应用名(如果不指定子应用名,会把所有子应用生成迁移脚本)
#把orm代码转换成sql语句去数据库执行
python manage.py migrate 子应用名
然后可以用可视化工具连接数据库,即可看到数据库生成该表。
2、关联表创建
表与表之间的关系可分为以下三种:
- 一对一: 一个人对应一个身份证号码,数据字段设置 unique。
- 可以在任何一个模型类使用OneToOneField
- 一对多: 一个家庭有多个人,一般通过外键来实现。
- 需要在“多”的那个模型中使用ForeignKey
- ForeignKey第一个参数为必传参数,指定需要关联的父表模型类(有2种方式):
- 方式一:直接使用父表模型类的引用
- 方式二:可以使用’子应用名称.父表模型类名’(推荐)
- 使用on_delete指定级联删除策略:
- CASCADE: 当父表数据删除时,相对应的从表数据会被自动删除
- SET_NULL:当父表数据删除时,相对应的从表数据会被自动设置为null值
- PROTECT:当父表数据删除时,如果有相对应的从表数据会抛出异常
- SET_DEFAULT: 当父表数据删除时,相对应的从表数据会被自动设置为默认值,还需要额外指定default=True
- 多对多: 一个学生有多门课程,一个课程有很多学生,一般通过第三个表来实现关联。
- 可以在任何一个模型类使用ManyToManyField
下面来看一个示例:
from django.db import models
class Interfaces(models.Model):
id = models.AutoField(primary_key=True, verbose_name='id主键', help_text='id主键')
name = models.CharField(verbose_name='接口名称', help_text='接口名称', max_length=20, unique=True)
tester = models.CharField(verbose_name='测试人员', help_text='测试人员', max_length=10)
# 第一个参数:使用'子应用名称.父表模型类名'
# CASCADE: 当父表数据删除时,相对应的从表数据会被自动删除
projects = models.ForeignKey('projects.Projects', on_delete=models.CASCADE,
verbose_name='所属项目', help_text='所属项目')
class Meta:
db_table = 'tb_interfaces'
verbose_name = '接口表'
verbose_name_plural = '接口表'
ordering = ['id']
总结:以上就完成了tb_projects和tb_interfaces表就完成了关联,tb_interfaces表中的project_id是tb_projects的主键,所以tb_projects是父表,tb_interfaces是从表,后面的例子还会用到这2张表;
四、ORM增删改查
1、新增数据(C)
(1)创建单表数据
方式一:
obj = Projects(name='xxx测试项目', leader='小李')
obj.save()
- 直接使用模型类(字段名1=值1, 字段名2=值2, …),来创建模型类实例
- 必须模型实例调用save()方法,才会执行sql语句
方式二:
obj = Projects.objects.create(name='xxx22', leader='小王')
- 使用模型类.objects返回manager对象
- 使用manager对象.create(字段名1=值1, 字段名2=值2, …),来创建模型类实例
- 无需使用模型实例调用save()方法,会自动执行sql语句
(2)创建从表数据
创建从表数据的关键是:外键对应的父表如何传递?主要有以下两种方式:
方式一:
# 主表
project_obj = Projects.objects.get(name='测试项目')
interface_obj = Interfaces.objects.create(name='测试项目-登录接口', tester='珍惜', projects=project_obj)
- 先获取父表模型对象;
- 将获取的父表模型对象以外键字段名作为参数来传递,这样就会把name为测试项目的id传递过去;
方式二:
project_obj = Projects.objects.get(name='在线图书项目')
interface_obj = Interfaces.objects.create(name='在线图书项目-注册接口', tester='珍惜', projects_id=project_obj.id)
- 先获取父表模型对象,进而获取父表数据的id值;
- 将父表数据的主键id值以外键名_id作为参数来传递;
2、读取数据(R)
(1)读取多条数据
- 使用模型类.objects.all(),会将当前模型类对应的数据表中的所有数据读取出来;
- 模型类.objects.all(),返回QuerySet对象(查询集对象);
- QuerySet对象,类似于列表,具有惰性查询的特性(在‘用’数据时,才会执行sql语句);
qs = Projects.objects.all()
(2)读取单条数据
方式一:使用模型类.objects.get(条件1=值1)
obj = Projects.objects.get(id=1)
- 如果使用指定条件查询的记录数量为0,会抛出异常;
- 如果使用指定条件查询的记录数量超过1,也会抛出异常;
- 最好使用具有唯一约束的条件去查询;
- 如果使用指定条件查询的记录数量为1,会返回这条记录对应的模型实例对象,可以使用模型对象.字段名去获取相应的字段值
方式二:使用模型类.objects.filter(条件1=值1)
qs = Projects.objects.filter(id=1)
# ORM框架中,会给每一个模型类中的主键设置一个别名(pk),所以当id是主键时上下两个语句是等价的
# qs = Projects.objects.filter(pk=1)
- 如果使用指定条件查询的记录数量为0,会返回空的QuerySet对象;
- 如果使用指定条件查询的记录数量超过1,将符合条件的模型对象包裹到QuerySet对象中返回;
- QuerySet对象,类似于列表,有如下特性:
- 支持通过数值(正整数)索引取值;
- 支持切片操作(正整数);
- 获取第一个模型对象:QuerySet对象.first();
- 获取最后一个模型对象:QuerySet对象.last();
- 获取长度:len(QuerySet对象)、QuerySet对象.count();
- 判断查询集是否为空:QuerySet对象.exists();
- 支持迭代操作(for循环,每次循环返回模型对象);
单独说下常用的filter方法
- 1)字段名__查询类型=具体值
- 2)字段名__exact=具体值,缩写形式为:字段名=具体值
- 3)字段名__gt:大于、字段名__gte:大于等于
- 4)字段名__lt:小于、字段名_lte:小于等于
- 5)contains:包含
- 6)startswith:以xxx开头
- 7)endswith:以xxx结尾
- 8)isnull:是否为null
- 9)一般在查询类型前添加“i”,代表忽略大小写
另外,还有个exclude方法:反向查询,filter方法支持的所有查询类型,都支持;
(3)读取关联表数据
获取父表数据
获取接口表中接口id为1的项目数据
interface_obj = Interfaces.objects.get(id=1)
# 返回父表模型对象
interface_obj.projects
返回结果是projects模型类
获取从表数据
获取项目id为1的所有接口数据
project_obj = Projects.objects.get(id=1)
project_obj.interfaces_set.all()
- 默认可以通过从表模型类名小写_set,返回manager对象,可以进一步使用filter进行过滤
- 如果在从表模型类的外键字段指定了related_name参数,那么会使用related_name指定参数替换interfaces_set作为名称;
带关联表参数查询数据
如果想要通过父表参数来获取从表数据、或者通过从表参数获取父表数据,可以使用关联查询语句:
关联字段名称__关联模型类中的字段名称__查询类型
Interfaces.objects.filter(projects__name__contains='xxx')
Projects.objects.filter(interfaces__name__contains='登录')
逻辑关系
与关系:
# 方式一:在同一个filter方法内部,添加多个关键字参数,那么每个条件为“与”的关系
qs = Projects.objects.filter(name__contains='2', leader='keyou')
# 方式二:可以多次调用filter方法,那么filter方法的条件为“与”的关系 --- QuerySet链式调用特性
qs = Projects.objects.filter(name__contains='2').filter(leader='keyou')
或关系:
# 可以使用Q查询,实现逻辑关系,多个Q对象之间如果使用“|”,那么为“或”关系
qs = Projects.objects.filter(Q(name__contains='2') | Q(leader='多喝热水'))
排序
# 可以使用QuerySet对象(manager对象).order_by('字段名1', '字段名2', '-字段名3')
# 默认为ASC升序,可以在字段名称前添加“-”,那么为DESC降序
Projects.objects.filter(Q(name__contains='2') | Q(leader='多喝热水')).order_by('-name', 'leader')
3、更新数据(U)
# 方式一:一条数据
project_obj = Projects.objects.get(id=1)
project_obj.name = '在线图书项目(一期)'
project_obj.leader = '不语'
# 必须调用save方法才会执行sql语句,并且默认进行完整更新
# 可以在save方法中设置update_fields参数(序列类型),指定需要更新的字段名称(字符串)
project_obj.save()
# 方式二:多条数据
# Projects.objects.filter: QuerySet
# 可以在QuerySet对象.update(字段名称='字段值'),返回修改成功的值,无需调用save方法
qs = Projects.objects.filter(name__contains='2').update(leader='珍惜')
4、删除数据(D)
# 方式一:一条数据
project_obj = Projects.objects.get(id=1)
project_obj.delete()
# 方式二:多条数据
obj = Projects.objects.filter(name__contains='2').delete()
5、聚合运算
# a.可以使用QuerySet对象.aggregate(聚合函数('字段名'))方法,返回字典数据
# b.返回的字典数据中key为字段名__聚合函数名小写
# c.可以使用关键字参数形式,那么返回的字典数据中key为关键字参数名
qs = Projects.objects.filter(name__contains='2').aggregate(Count('id'))
6、分组查询
# a.可以使用Manager对象.values('父表主键id').annotate(聚合函数('从表模型类名小写'))
# b.会自动连接两张表,然后使用外键字段作为分组条件
Projects.objects.values('id').annotate(Count('interfaces'))
7、QuerySet对象特性
查询集QuerySet对象有什么特性?
- 支持链式调用:可以在查询集上多次调用filter、exclude方法
- 惰性查询:仅仅在使用数据时才会执行sql语句,为了提升数据库读写性能;(会执行sql语句的场景:len()、.count()、通过索引取值、print、for)