今天闲来无事简单谈下ORM操作。
一:前言
1.什么是ORM
ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。
2.为什么会出现ORM思想
先从项目中数据流存储形式这个角度说起.简单拿MVC这种分层模式.来说. Model作为数据承载实体. 在用户界面层和业务逻辑层之间数据实现面向对象OO形式传递. 当我们需要通过Control层分发请求把数据持久化时我们会发现. 内存中的面向对象的OO如何持久化成关系型数据中存储一条实际数据记录呢?
面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的. 两者之间是不匹配的.而ORM作为项目中间件形式实现数据在不同场景下数据关系映射. 对象关系映射(Object Relational Mapping,简称ORM)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术.ORM就是这样而来的。
3.优缺点
优点:
第一:隐藏了数据访问细节,“封闭”的通用数据库交互,ORM的核心。他使得我们的通用数据库交互变得简单易行,并且完全不用考虑该死的SQL语句。快速开发,由此而来。
第二:ORM使我们构造固化数据结构变得简单易行。在ORM年表的史前时代,我们需要将我们的对象模型转化为一条一条的SQL语句,通过直连或是DB helper在关系数据库构造我们的数据库体系。而现在,基本上所有的ORM框架都提供了通过对象模型构造关系数据库结构的功能。
缺点:
第一:无可避免的,自动化意味着映射和关联管理,代价是牺牲性能(早期,这是所有不喜欢ORM人的共同点)。现在的各种ORM框架都在尝试使用各种方法来减轻这块(LazyLoad,Cache),效果还是很显著的。
第二:面向对象的查询语言(X-QL)作为一种数据库与对象之间的过渡,虽然隐藏了数据层面的业务抽象,但并不能完全的屏蔽掉数据库层的设计,并且无疑将增加学习成本。
第三:对于复杂查询,ORM仍然力不从心。虽然可以实现,但是不值的。视图可以解决大部分calculated column,case ,group,having,order by, exists,但是查询条件(a and b and not c and (d or d))。
世上没有驴是不吃草的(又想好又想巧,买个老驴不吃草),任何优势的背后都隐藏着缺点,这是不可避免的。问题在于,我们是否能容忍缺点。
4.常用的ORM框架
(1)Hibernate全自动需要些hql语句。
(2)iBATIS半自动自己写sql语句,可操作性强,小巧。
(3)EclipseLink 一个可扩展的支持JPA的ORM框架,供强大的缓存功能,缓存支持集群。
(4)Apache OJB等等....
二:Django连接MySQL
1.创建数据库 (注意设置 数据的字符编码)
由于Django自带的orm是data_first类型的ORM,使用前必须先创建数据库
1 create database db1 default character set utf8 collate utf8_general_ci;
2.修改project中的settings.py文件中设置 连接 MySQL数据库(Django默认使用的是sqllite数据库)
1 DATABASES = { 2 'default': { 3 'ENGINE': 'django.db.backends.mysql', 4 'NAME':'db1', 5 'USER': 'root', 6 'PASSWORD': '', 7 'HOST': 'localhost', 8 'PORT': '3306', 9 } 10 }
扩展:查看orm操作执行的原生SQL语句在project中的settings.py文件增加
1 LOGGING = { 2 'version': 1, 3 'disable_existing_loggers': False, 4 'handlers': { 5 'console':{ 6 'level':'DEBUG', 7 'class':'logging.StreamHandler', 8 }, 9 }, 10 'loggers': { 11 'django.db.backends': { 12 'handlers': ['console'], 13 'propagate': True, 14 'level':'DEBUG', 15 }, 16 } 17 }
3.修改project 中的__init__py 文件设置 Django默认连接MySQL的方
import pymysql pymysql.install_as_MySQLdb()
4.setings文件注册APP
1 INSTALLED_APPS = [ 2 'django.contrib.admin', 3 'django.contrib.auth', 4 'django.contrib.contenttypes', 5 'django.contrib.sessions', 6 'django.contrib.messages', 7 'django.contrib.staticfiles', 8 'app01.apps.App01Config', 9 10 ]
5.models.py创建表
1 from django.db import models 2 class UserType(models.Model): 3 """ 4 用户类型 5 """ 6 title = models.CharField(max_length=32) 7 8 9 class UserInfo(models.Model): 10 """ 11 用户表 12 """ 13 name = models.CharField(max_length=32) 14 age = models.IntegerField() 15 ut = models.ForeignKey('UserType',on_delete=models.CASCADE)
6.进行数据迁移
6.1在winds cmd或者Linux shell的项目的manage.py目录下执行
python manage.py makemigrations python manage.py migrate
扩展:修改表之后常见报错
这个报错:因为表创建好之后,新增字段没有设置默认值,或者原来表中字段设置了不能为空参数,修改后的表结构和目前的数据冲突导致;
三:modles.py创建表
ORM字段介绍
Django提供了很多字段类型,比如URL/Email/IP/ 但是mysql数据没有这些类型,这类型存储到数据库上本质是字符串数据类型,其主要目的是为了封装底层SQL语句;
1.字符串类(以下都是在数据库中本质都是字符串数据类型,此类字段只是在Django自带的admin中生效)
1 name=models.CharField(max_length=32) 2 3 4 EmailField(CharField): 5 IPAddressField(Field) 6 URLField(CharField) 7 SlugField(CharField) 8 UUIDField(Field) 9 FilePathField(Field) 10 FileField(Field) 11 ImageField(FileField) 12 CommaSeparatedIntegerField(CharField)
扩展
models.CharField 对应的是MySQL的varchar数据类型
char 和 varchar的区别 :
char和varchar的共同点是存储数据的长度,不能 超过max_length限制,
不同点是varchar根据数据实际长度存储,char按指定max_length()存储数据;所有前者更节省硬盘空间;
2.时间字段
models.DateTimeField(null=True)
date=models.DateField()
3.数字字段
(max_digits=30,decimal_places=10)总长度30小数位 10位)
1 数字: 2 num = models.IntegerField() 3 num = models.FloatField() 浮点 4 price=models.DecimalField(max_digits=8,decimal_places=3) 精确浮点
4.枚举字段
1 choice=( 2 (1,'男人'), 3 (2,'女人'), 4 5 ) 6 lovely=models.IntegerField(choices=choice) #枚举类型
扩展
在数据库存储枚举类型,比外键有什么优势?
1.无需连表查询性能低,省硬盘空间(选项不固定时用外键)
2.在modle文件里不能动态增加(选项一成不变用Django的choice)
3.ForeignKey用于动态选项。
其他字段
1 db_index = True 表示设置索引 2 unique(唯一的意思) = True 设置唯一索引 3 4 联合唯一索引 5 class Meta: 6 unique_together = ( 7 ('email','ctime'), 8 ) 9 联合索引(不做限制) 10 index_together = ( 11 ('email','ctime'), 12 ) 13 14 ManyToManyField(RelatedField) #多对多操作
字段参数介绍
1.数据库级别生效
1 AutoField(Field) 2 - int自增列,必须填入参数 primary_key=True 3 4 BigAutoField(AutoField) 5 - bigint自增列,必须填入参数 primary_key=True 6 7 注:当model中如果没有自增列,则自动会创建一个列名为id的列 8 from django.db import models 9 10 class UserInfo(models.Model): 11 # 自动创建一个列名为id的且为自增的整数列 12 username = models.CharField(max_length=32) 13 14 class Group(models.Model): 15 # 自定义自增列 16 nid = models.AutoField(primary_key=True) 17 name = models.CharField(max_length=32) 18 19 SmallIntegerField(IntegerField): 20 - 小整数 -32768 ~ 32767 21 22 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 23 - 正小整数 0 ~ 32767 24 IntegerField(Field) 25 - 整数列(有符号的) -2147483648 ~ 2147483647 26 27 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 28 - 正整数 0 ~ 2147483647 29 30 BigIntegerField(IntegerField): 31 - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 32 33 自定义无符号整数字段 34 35 class UnsignedIntegerField(models.IntegerField): 36 def db_type(self, connection): 37 return 'integer UNSIGNED' 38 39 PS: 返回值为字段在数据库中的属性,Django字段默认的值为: 40 'AutoField': 'integer AUTO_INCREMENT', 41 'BigAutoField': 'bigint AUTO_INCREMENT', 42 'BinaryField': 'longblob', 43 'BooleanField': 'bool', 44 'CharField': 'varchar(%(max_length)s)', 45 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 46 'DateField': 'date', 47 'DateTimeField': 'datetime', 48 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 49 'DurationField': 'bigint', 50 'FileField': 'varchar(%(max_length)s)', 51 'FilePathField': 'varchar(%(max_length)s)', 52 'FloatField': 'double precision', 53 'IntegerField': 'integer', 54 'BigIntegerField': 'bigint', 55 'IPAddressField': 'char(15)', 56 'GenericIPAddressField': 'char(39)', 57 'NullBooleanField': 'bool', 58 'OneToOneField': 'integer', 59 'PositiveIntegerField': 'integer UNSIGNED', 60 'PositiveSmallIntegerField': 'smallint UNSIGNED', 61 'SlugField': 'varchar(%(max_length)s)', 62 'SmallIntegerField': 'smallint', 63 'TextField': 'longtext', 64 'TimeField': 'time', 65 'UUIDField': 'char(32)', 66 67 BooleanField(Field) 68 - 布尔值类型 69 70 NullBooleanField(Field): 71 - 可以为空的布尔值 72 73 CharField(Field) 74 - 字符类型 75 - 必须提供max_length参数, max_length表示字符长度 76 77 TextField(Field) 78 - 文本类型 79 80 EmailField(CharField): 81 - 字符串类型,Django Admin以及ModelForm中提供验证机制 82 83 IPAddressField(Field) 84 - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 85 86 GenericIPAddressField(Field) 87 - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 88 - 参数: 89 protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6" 90 unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both" 91 92 URLField(CharField) 93 - 字符串类型,Django Admin以及ModelForm中提供验证 URL 94 95 SlugField(CharField) 96 - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) 97 98 CommaSeparatedIntegerField(CharField) 99 - 字符串类型,格式必须为逗号分割的数字 100 101 UUIDField(Field) 102 - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 103 104 FilePathField(Field) 105 - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 106 - 参数: 107 path, 文件夹路径 108 match=None, 正则匹配 109 recursive=False, 递归下面的文件夹 110 allow_files=True, 允许文件 111 allow_folders=False, 允许文件夹 112 113 FileField(Field) 114 - 字符串,路径保存在数据库,文件上传到指定目录 115 - 参数: 116 upload_to = "" 上传文件的保存路径 117 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage 118 119 ImageField(FileField) 120 - 字符串,路径保存在数据库,文件上传到指定目录 121 - 参数: 122 upload_to = "" 上传文件的保存路径 123 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage 124 width_field=None, 上传图片的高度保存的数据库字段名(字符串) 125 height_field=None 上传图片的宽度保存的数据库字段名(字符串) 126 127 DateTimeField(DateField) 128 - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] 129 130 DateField(DateTimeCheckMixin, Field) 131 - 日期格式 YYYY-MM-DD 132 133 TimeField(DateTimeCheckMixin, Field) 134 - 时间格式 HH:MM[:ss[.uuuuuu]] 135 136 DurationField(Field) 137 - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 138 139 FloatField(Field) 140 - 浮点型 141 142 DecimalField(Field) 143 - 10进制小数 144 - 参数: 145 max_digits,小数总长度 146 decimal_places,小数位长度 147 148 BinaryField(Field) 149 - 二进制类型
2.Django admin级别生效
针对 dango_admin生效的参数(正则匹配)(使用Django admin就需要关心以下参数!!))
1 blanke (是否为空) 2 editable=False 是否允许编辑 3 4 help_text="提示信息"提示信息 5 choices=choice 提供下拉框 6 error_messages="错误信息" 错误信息 7 8 validators 自定义错误验证(列表类型),从而定制想要的验证规则 9 from django.core.validators import RegexValidator 10 from django.core.validators import EmailValidator,URLValidator,DecimalValidator, 11 MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator 12 如: 13 test = models.CharField( 14 max_length=32, 15 error_messages={ 16 'c1': '优先错信息1', 17 'c2': '优先错信息2', 18 'c3': '优先错信息3', 19 }, 20 validators=[ 21 RegexValidator(regex='root_d+', message='错误了', code='c1'), 22 RegexValidator(regex='root_112233d+', message='又错误了', code='c2'), 23 EmailValidator(message='又错误了', code='c3'), ]
四:ORM连表操作
五:ORM F和Q操作
当一般的查询语句已经无法满足我们的需求时,Django为我们提供了F和Q复杂查询语句。假设场景一:对数据库中所有人的年龄加一岁,你该怎么做?场景二:我要查询一个名字叫xxx,年龄是18岁,或者名字是yyy,年龄是19岁的人,你该怎么写你的ORM语句?
一.F查询
1 from app01 import models 2 3 from django.db.models import F 4 models.UserInfo.objects.all().update(age=F("age")+1) 只要一刷新数据库所有人的年龄均加一岁
就这样一条简单的语句就完成了对表中所人的年龄更新,是不是很方便!F查询专门对对象中某列值的操作,不可使用__双下划线!
二.Q查询
Q查询可以组合使用 “&”, “|” 操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象,Q对象可以用 “~” 操作符放在前面表示否定,也可允许否定与不否定形式的组合。Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面。
1 from django.db.models import Q 2 models.UserInfo.objects.filter(Q(id=1)) 3 models.UserInfo.objects.filter(Q(id=1) | Q(id=2)) 4 models.UserInfo.objects.filter(Q(id=1) & Q(id=2))
1 from django.db.models import Q 2 3 con = Q() 4 q1 = Q() 5 q1.connector = "AND" 6 q1.children.append(("email", "123@qq.com")) 7 q1.children.append(("password", "abc123")) 8 9 q2 = Q() 10 q2.connector = "AND" 11 q2.children.append(("username", "abc")) 12 q2.children.append(("password", "xyz123")) 13 14 con.add(q1, "OR") 15 con.add(q2, "OR") 16 17 obj = models.UserInfo.objects.filter(con).first()
上面的例子就是一个典型的复杂查询,通过将Q对象实例化来然后增加各个条件之间的关系,而且这种写法用在你不知道用户到底会传入多少个参数的时候很方便!
三.extra
extra内部的有:
extra内部的有:
select select_params
where params
tables
order_by
1 models.UserInfo.objects.extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
extra是针对复杂的SQL语句
select select_params
1 v = models.UserInfo.objects.all().extra(select={ 2 'n': "select count(1) from app01_userinfo WHERE id>%s" 3 }, 4 select_params=[1,] # 有多个% 内部就有多个参数 5 ) 6 print(v) 7 for item in v: 8 print(item.id,item.name,item.n)# 这里使用n
where params
1 models.UserInfo.objects.extra( 2 where=["id=1","name='aaa'"] 3 ) 4 models.UserInfo.objects.extra( 5 where=["id=1 or id=%s ","name=%s"], 6 params=[1,"aaa"] 7 )
tables
1 models.UserInfo.objects.extra( 2 tables=['app01_usertype'], 3 ) 4 # """select * from app01_userinfo,app01_usertype"""
总结:
1 # a. 映射 2 # select 3 # select_params=None 4 # select 此处 from 表 5 6 # b. 条件 7 # where=None 8 # params=None, 9 # select * from 表 where 此处 10 11 # c. 表 12 # tables 13 # select * from 表,此处 14 15 # c. 排序 16 # order_by=None 17 # select * from 表 order by 此处
1 models.UserInfo.objects.extra( 2 select={'newid':'select count(1) from app01_usertype where id>%s'}, 3 select_params=[1,], 4 where = ['age>%s'], 5 params=[18,], 6 order_by=['-age'], 7 tables=['app01_usertype'] 8 )
最终的SQL:
1 select 2 app01_userinfo.id, 3 (select count(1) from app01_usertype where id>1) as newid 4 from app01_userinfo,app01_usertype 5 where 6 app01_userinfo.age > 18 7 order by 8 app01_userinfo.age desc
原生SQL
Django内部提供了写原生SQL的方法
在setting中配置
connection.cursor()默认是default数据库
cursor = connections['db2'].cursor() 可以定义自己的数据库
1 from django.db import connection, connections 2 3 cursor = connection.cursor() #默认 connection=default数据库 4 cursor = connections['db2'].cursor() 5 6 cursor.execute("""SELECT * from auth_user where id = %s""", [1])# 写原生SQL 7 8 row = cursor.fetchone() 9 row = cursor.fetchall()
六:model多对多操作
在数据库表中的多对多,有两种方式:
1.自定义第三张表
1 #创建两张表男生表和女生表 2 class Boy(models.Model): 3 name = models.CharField(max_length=32) 4 5 class Girl(models.Model): 6 nick = models.CharField(max_length=32) 7 #创建男生和女生关系表 8 class Love(models.Model): 9 b = models.ForeignKey('Boy', on_delete=models.CASCADE,) 10 g = models.ForeignKey('Girl', on_delete=models.CASCADE,)
1 #多对多 2 objs = { 3 models.Boy(name='小韩'), 4 models.Boy(name='小全'), 5 models.Boy(name='小你'), 6 models.Boy(name='小及'), 7 models.Boy(name='小可'), 8 } 9 models.Boy.objects.bulk_create(objs,5) 10 objss = { 11 models.Girl(nick='大四'), 12 models.Girl(nick='大五'), 13 models.Girl(nick='大六'), 14 models.Girl(nick='大七'), 15 } 16 models.Girl.objects.bulk_create(objss,5) 17 18 models.Love.objects.create(b_id=1,g_id=1), 19 models.Love.objects.create(b_id=1,g_id=4), 20 models.Love.objects.create(b_id=2,g_id=4), 21 models.Love.objects.create(b_id=2,g_id=2), 22 23 #和小韩有关联的女孩子 24 obj = models.Boy.objects.filter(name='小韩').first() 25 love_list = obj.love_set.all() 26 for row in love_list: 27 print(row.g.nick) 28 29 30 love_list = models.Love.objects.filter(b__name='小韩') 31 for row in love_list: 32 print(row.g.nick) 33 34 love_list = models.Love.objects.filter(b__name='小韩').values('g__nick') 35 for item in love_list: 36 print(item['g__nick']) 37 38 39 love_list = models.Love.objects.filter(b__name='小韩').select_related('g') 40 for obj in love_list: 41 print(obj.g.nick)
2.使用models中自带的ManytoManyFiled自动创建第三张表
1 #创建两张表男生表和女生表 2 class Boy(models.Model): 3 name = models.CharField(max_length=32) 4 # m = models.ManyToManyField('Girl') 5 6 class Girl(models.Model): 7 nick = models.CharField(max_length=32) 8 # g = models.ManyToManyField('Boy') 9
我们重点讨论第二中方式,有一点可以确认的是:
- 使用多对多自动创建后,会创建一张第三张表,三张表中会将操作的前两张表中的ID做对应
- 在没有做ManyToMany 对应的那张表中,会有一列
xx_set
的数据,其中xx为正向的表名,并且为小写
3.多对多之增删改查
1 #增 2 obj = models.Boy.objects.filter(name='小韩').first() 3 print(obj.id,obj.name) 4 obj.m.add(2) 5 obj.m.add(2,4) 6 obj.m.add(*[3,]) 7 #删 8 obj.m.remove(2) 9 obj.m.remove(2,3) 10 obj.m.remove(*[1,]) 11 #重置 12 obj.m.set([1,]) #填可迭代的对象 13 14 # 获取 15 q = obj.m.all() 16 #[Gril对象] 17 print(q) 18 19 obj = models.Boy.objects.filter(name='小韩').first() 20 girl_list = obj.m.all() 21 22 obj = models.Boy.objects.filter(name='小韩').first() 23 girl_list = obj.m.filter(nick='大四') 24 25 obj = models.Boy.objects.filter(name='小韩').first() 26 obj.m.clear()