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执行

插图1

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

插图2

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

ORM 中常用字段和参数

分类    模型属性类型    mysql数据库类型
自增    AutoField    int
布尔    BooleanField    tinyint
布尔    NullBooleanField    tinyint
字符    CharField    varchar
字符    TextField    longtext
数字    IntegerField    int
数字    DecimalField    decimal
数字    FloatField    double
日期和时间    DateField    date
日期和时间    TimeField    time
日期和时间    DateTimeField    datetime 使用
文件    FileField    varchar  使用
文件    ImageField    varchar

json    JSONField  使用 使用

    'AutoField': 'integer AUTO_INCREMENT',
    'BigAutoField': 'bigint AUTO_INCREMENT',
    'BinaryField': 'longblob',
    'BooleanField': 'bool',
    'CharField': 'varchar(%(max_length)s)',
    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
    'DateField': 'date',
    'DateTimeField': 'datetime',
    'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
    'DurationField': 'bigint',
    'FileField': 'varchar(%(max_length)s)',
    'FilePathField': 'varchar(%(max_length)s)',
    'FloatField': 'double precision',
    'IntegerField': 'integer',
    'BigIntegerField': 'bigint',
    'IPAddressField': 'char(15)',
    'GenericIPAddressField': 'char(39)',
    'NullBooleanField': 'bool',
    'OneToOneField': 'integer',
    'PositiveIntegerField': 'integer UNSIGNED',
    'PositiveSmallIntegerField': 'smallint UNSIGNED',
    'SlugField': 'varchar(%(max_length)s)',
    'SmallIntegerField': 'smallint',
    'TextField': 'longtext',
    'TimeField': 'time',
    'UUIDField': 'char(32)',


 

常用字段

  • 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类型

图片字段,需要 upload_to 参数指定文件保存的位置

    ertificatePath = models.ImageField(verbose_name='证书下载地址', upload_to='ertificate/', default='', null=True, blank=True)
  • 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,具体做法如下

插图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会按照自己的约束将空转换成默认值后,再提交给数据库执行

django项目中删除数据库表时无法再迁移出来的问题

补充: 

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  # 这一句话必须在这下面导入

自定义user表

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值