django模型层之ORM

介绍​

我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(如增、删、改、查),而一旦谈到数据的管理操作,就需要用到数据库管理软件,例如mysql、oracle、Microsoft SQL Server等。

如果应用程序需要操作数据(比如将用户注册信息永久存放起来),那么我们需要在应用程序中编写原生sql语句,然后使用pymysql模块远程操作mysql数据库

但是直接编写原生sql语句会存在两方面的问题,严重影响开发效率,如下:

  1. sql语句的执行效率:应用开发程序员需要耗费一大部分精力去优化sql语句
  2.  数据库迁移:针对mysql开发的sql语句无法直接应用到oracle数据库上,一旦需要迁移数据库,便需要考虑跨平台问题

为了解决上述问题,django引入了ORM的概念,ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行

django模型层之ORM_第1张图片

原生SQL与ORM的对应关系示例如下

django模型层之ORM_第2张图片

如此,开发人员既不用再去考虑原生SQL的优化问题,也不用考虑数据库迁移的问题,ORM都帮我们做了优化且支持多种数据库,这极大地提升了我们的开发效率

ORM 中常用字段和参数

常用字段

  • AutoField

自增列,可以将其理解为ID主键字段,注意的是必须填入参数 primary_key=True
当model中如果没有自增列,则自动会创建一个列名为id的列

id = models.AutoField(primary_key=True) 
  • IntegerField

整形字段,围在 -2147483648 to 2147483647。(一般不用它来存手机号(位数也不够),直接用字符串存,)

num = models.IntegerField()  # 可以不指定参数 
  • CharField

字符字段,必须提供max_length参数, max_length表示字符长度。

title = models.CharField(max_length=255)
# 注意:Django中的CharField对应的MySQL数据库中的varchar类型
  • DecimalField

小数字段,必须提供max_digits参数和decimal_places参数

price = models.DecimalField(max_digits=8,decimal_places=2)
  • DateField

日期字段,日期格式  YYYY-MM-DD,相当于Python中的datetime.date()实例。

date = models.DateField(auto_now_add=True) 
  • DateTimeField

日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例。

字段参数

  • verbose_name

给字段命名

  • null

用于表示某个字段可以为空。null=True 

  • unique

如果设置为unique=True 则该字段在此表中必须是唯一的 。

  • db_index

如果db_index=True 则代表着为此字段设置索引

  • default

为该字段设置默认值

  • auto_now_add

配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。

  • auto_now

配置上auto_now=True,每次更新数据记录的时候会更新该字段。

关系字段

  • ForeignKey

外键类型在ORM中用来表示外键关联关系,一般把ForeignKey字段设置在 '一对多'中'多'的一方。
ForeignKey可以和其他表做关联关系同时也可以和自身做关联关系。

参数

to 设置要关联的表

publish = models.ForeignKey(to='Publish')  
# 默认是跟publish表的主键字段做的一对多外键关联

to_field
设置要关联的表的字段
​on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。

  • OneToOneField

通常一对一字段用来扩展已有字段。(通俗的说就是一个人的所有信息不是放在一张表里面的,简单的信息一张表,隐私的信息另一张表,之间通过一对一外键关联)
ps:可以用 ForeignKey(unique=True) 代替

参数

to 设置要关联的表。

author_detail = models.OneToOneField(to='AuthorDetail')

to_field
设置要关联的字段。

on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。

  • ManyToManyField

多对多字段
是虚拟字段,表中不会显式,
作用:
1.告诉orm自动创建第三种表
2.帮助orm跨表查询

authors = models.ManyToManyField(to='Author') 

ORM的使用

按步骤创建表

  • models.py
class Employee(models.Model): # 必须是models.Model的子类
    id=models.AutoField(primary_key=True)

    name=models.CharField(max_length=16)

    gender=models.BooleanField(default=1)

    birth=models.DateField()

    department=models.CharField(max_length=30)

    salary=models.DecimalField(max_digits=10,decimal_places=1)

django的orm支持多种数据库,如果想将上述模型转为mysql数据库中的表,需要settings.py中配置

  • settings.py
# 删除\注释掉原来的DATABASES配置项,新增下述配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # 使用mysql数据库
        'NAME': 'db1',          # 要连接的数据库
        'USER': 'root',         # 链接数据库的用于名
        'PASSWORD': '',         # 链接数据库的用于名                  
        'HOST': '127.0.0.1',    # mysql服务监听的ip  
        'PORT': 3306,           # mysql服务监听的端口 
        'CHARSET':'utf8', 
        'ATOMIC_REQUEST': True, #设置为True代表同一个http请求所对应的所有sql都放在一个事务中执行 
                                #(要么所有都成功,要么所有都失败),这是全局性的配置,如果要对某个
                                #http请求放水(然后自定义事务),可以用non_atomic_requests修饰器 
        'OPTIONS': {
            "init_command": "SET storage_engine=INNODB", #设置创建表的存储引擎为INNODB
        }
    }
}

 在链接mysql数据库前,必须事先创建好数据库

mysql> create database db1; # 数据库名必须与settings.py中指定的名字对应上

其实python解释器在运行django程序时,django的orm底层操作数据库的python模块默认是mysqldb而非pymysql,然而对于解释器而言,python2.x解释器支持的操作数据库的模块是mysqldb,而python3.x解释器支持的操作数据库的模块则是pymysql,,毫无疑问,目前我们的django程序都是运行于python3.x解释器下,于是我们需要修改django的orm默认操作数据库的模块为pymysql,具体做法如下

django模型层之ORM_第3张图片

 确保配置文件settings.py中的INSTALLED_APPS中添加我们创建的app名称,django2.x与django1.x处理添加方式不同

# django1.x版本,在下述列表中新增我们的app名字即可
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01',
    # 'app02' # 若有新增的app,依次添加即可
]

# django2.x版本,可能会帮我们自动添加app,只是换了一种添加方式
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config', # 如果默认已经添加了,则无需重复添加
    # 'app02.apps.App02Config', # 若有新增的app,按照规律依次添加即可
]

如果想打印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',
        },
    }
}
# 可以看到sql语句,以及语句执行的之间

# 其他方式:
1.单独查看某个orm转化的sql语句,可以用以下方法
如: 在视图中的orm语句为:
user_obj = User.objects.get(username=username)
print(user_obj.query)

2.查看某个视图内orm转化的sql语句,可以用以下方法
from django.db import connection
def myviews(request)
    ...
    代码块
    ...
    print(connection.queries)
    return HttpResponse('...')
说明: print(connection.queries)会把它所在位置之前的sql语句都打印出来(包括sql执行时间)

 最后在命令行中执行两条数据库迁移命令,即可在指定的数据库db1中创建表 :

python manage.py makemigrations
python manage.py migrate

注意:

1、makemigrations只是生成一个数据库迁移记录的文件,而migrate才是将更改真正提交到数据库执行

2、数据库迁移记录的文件存放于app01下的migrations文件夹里

3、了解:使用命令python manage.py showmigrations可以查看没有执行migrate的文件

补充:

在使用的是django1.x版本时,如果报如下错误

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\base.py
这个路径里的文件(mac或linux请在终端执行pip show django查看django的安装路径,找到base.py即可)

# 注释下述两行内容即可
if version < (1, 3, 3):
     raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)

当我们直接去数据库里查看生成的表时,会发现数据库中的表与orm规定的并不一致,这完全是正常的,事实上,orm的字段约束就是不会全部体现在数据库的表中,比如我们为字段gender设置的默认值default=1,去数据库中查看会发现该字段的default部分为null

mysql> desc app01_employee; # 数据库中标签前会带有前缀app01_
+------------+---------------+------+-----+---------+----------------+
| Field      | Type          | Null | Key | Default | Extra          |
+------------+---------------+------+-----+---------+----------------+
| id         | int(11)       | NO   | PRI | NULL    | auto_increment |
| name       | varchar(16)   | NO   |     | NULL    |                |
| gender     | tinyint(1)    | NO   |     | NULL    |                |
| birth      | date          | NO   |     | NULL    |                |
| department | varchar(30)   | NO   |     | NULL    |                |
| salary     | decimal(10,1) | NO   |     | NULL    |                |
+------------+---------------+------+-----+---------+----------------+

虽然数据库没有增加默认值,但是我们在使用orm插入值时,完全为gender字段插入空,orm会按照自己的约束将空转换成默认值后,再提交给数据库执行

补充: 

choices字段参数

1.对于类型性别,假否,婚否的字段,有固定字段数据的字段可以用choices参数
2.节省空间,因为存的是数字

# models.py文件中:
class User(models.Model):
    username = models.CharField(max_length=32)
    age = models.IntegerField()
    choices = (
        (1,'男'),(2,'女'),(3,'其他')
    )
    gender = models.IntegerField(choices=choices)  # 指定choices参数
​
    """
    1 存choice里面罗列的数字与中文对应关系
         print(user_obj.get_gender_display())
            只要是choices字段 在获取数字对应的注释 固定语法
            get_字段名_display()
​
    2 存没有罗列出来的数字
        不会报错 还是展示数字
    """
# views.py文件中
def userlist(request):
    user_obj = models.User.objects.filter(pk=1).first()
    gender = user_obj.get_gender_display()  # 得到对应的性别注释,如'男'

自定义char字段

class MyChar(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

数据库查询优化

orm内所有的语句操作 都是惰性查询:只会在你真正需要数据的时候才会走数据库,如果你单单只写orm语句时不会走数据库的

这样设计的好处 在于 减轻数据库的压力

  • only

当你点击一个是only括号内指定的字段的时候,只走一次数据库查询,

当你点击一个不是only括号内指定的字段的时候不会报错,而是会频繁的走数据库查询

res = models.Book.objects.only('title')
for r in res:
    print(r.price)
  • defer

defer与only是相反的,defer会将不是括号内的所有的字段信息 全部查询出来封装对象中

当你点击了括号内指定的字段,就会频繁的周数据库

当你点击了不是括号内指定的字段,不再走数据库

res1 = models.Book.objects.defer('title')
for r in res1: 
    print(r.price)  # 不在走数据库
    print(r.title)  # 将频繁走数据
  • select_related

select_related:会将括号内外键字段所关联的那张表 直接全部拿过来(可以一次性拿多张表)跟当前表拼接操作
从而降低你跨表查询 数据库的压力

注意:select_related括号只能放外键字段(一对一表关系和一对多表关系可以使用)

res = models.Book.objects.all().select_related('publish')
 for r in res:
     print(r.publish.name)
res = res = models.Book.objects.all().select_related('外键字段1__外键字段2__外键字段3__外键字段4')  # 只要有外键就可以一直连表
  • prefetch_related 

 特点: 不主动连表

res = models.Book.objects.prefetch_related('publish')
for r in res:
    print(r.publish.name)
"""
不主动连表操作(但是内部给你的感觉像是连表操作了)  而是将book表中的publish全部拿出来  在取publish表中将id对应的所有的数据取出
res = models.Book.objects.prefetch_related('publish')
括号内有几个外键字段 就会走几次数据库查询操作    
"""

事务

from django.db import transaction
    
with transaction.atomic():
    """
    数据库操作
    在该代码块中书写的操作 同属于一个事务
    """
    models.Book.objects.create()
    models.Publish.objects.create()
    # 添加书籍和出版社 就是同一个事务 要么一起成功要么一起失败
print('出了 代码块 事务就结束')

单独的py文件测试ORM

import os
​
​
if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day58.settings")
    import django
    django.setup()
    from app01 import models  # 这一句话必须在这下面导入

 

你可能感兴趣的:(Django框架)