ODM操作MongoDB

一、ODM的选择和安装

1.1、MongoDB中的ODM

MongoEngine

  • 使用最为广泛的ODM
  • http://mongoengine.org/

uMongo

  • 支持sync/async特性的ODM
  • https://umongo.readthedocs.io/en/latest/

1.2、MongoEngine的安装

  • MongoDB版本支持情况:v3.4、v3.6、v4.0
  • python版本支持情况:python3.6+
  • 安装:pip install mongoengine

1.3、连接到MongoDB

  • 方式一,使用默认配置:connect('students')

  • 方式二,指定主机地址和端口

    # 连接方式1
    connect('students', host='*', port=27017)
    # 连接方式2
    connect('students', host='mongodb://localhost/students')
    

连接到多个数据库

  • 建立连接
    connect(alias='db1', db='test')
    connect(alias='db2', db='test-temp')
    
  • 断开连接:disconnect(alias='db1')

二、MongoEngine模型介绍

2.1、ODM模型

示例代码

from mongoengine import Document

class User(Document):
	email = StringField(required=True)
	first_name = StringField(max_length=50)
	last_name = StringField(max_length=50)

2.2、常见数据类型

ODM操作MongoDB_第1张图片

2.3、数据类型通用参数

  • db_field:文档中的field/域/列名称
  • required:是否为必填项
  • default:默认值
  • unique:唯一性约束
  • choices:可选择的列表
  • primary_key:是否为文档的主键,默认为False

2.4、类属性meta常见配置项

  • 类属性,其配置项为python的dict(字典)

  • 示例代码

    class User(Document):
    	username = StringField()
    	meta = {}
    

常见配置项

  • db_alias:指定文档所在的数据库(逻辑库)
  • collection:指定文档所在的集合
  • ordering:指定文档的默认排序规则
  • indexes:指定文档的索引规则

2.5、文档的嵌套模型

学生信息数据字典
ODM操作MongoDB_第2张图片

文档的嵌套场景

  • 情况一,数组-简单数据类型:{'grades': [76, 51, 84]}

    模型解决方案

    from mongoengine import ListField, IntField, Document
    
    class Student(Document):
    	grades = ListField(IntField())
    
  • 情况二,数组-文档:{'grades': [{'score': 76}, {'score': 51}]}

    from mongoengine import Document, EmbeddedDocument, EmbeddedDocumentField,IntField,ListField
    
    # 自定义类型
    class CourseGrade(EmbeddedDocument):
    	score = IntField()
    
    class Student(Document):
    	grades = ListField(EmbeddedDocumentField(CourseGrade))
    
  • 情况三,单个文档:{'grade': {'course_name': '语文', 'score': 76}}

    from mongoengine import (
    	Document, EmbeddedDocument, EmbeddedDocumentField,
    	IntField, StringField
    )
    	
    	# 自定义类型
    	class CourseGrade(EmbeddedDocument):
    		course_name = StringField()
    		score = IntField()
    	
    	class Student(Document):
    		grades = EmbeddedDocumentField(CourseGrade)
    

示例

from enum import Enum

from mongoengine import (
	Document, connect, StringField,
	EnumField, IntField, EmbeddedDocument,
	EmbeddedDocumentField, ListField
)

# 连接到test数据库
connect('test', host='localhost', port=27017)


class SexEnum(Enum):
	MAN = '男'
	WOMEN = '女'


class CourseGrade(EmbeddedDocument):
	"""成绩信息(科目、老师、成绩)-被嵌套的文档"""
	course_name = StringField(max_length=64, required=True, verbose_name='科目')
	teacher = StringField(max_length=16, verbose_name='老师')
	score = IntField(min_value=0, max_value=150, required=True, verbose_name='成绩')

	def __repr__(self):
		return f'CourseGrade({self.course_name}, {self.score})'

	def __str__(self):
		return self.__repr__()


class Student(Document):
	"""学生信息"""
	# verbose_name 自定义参数,用于显示定义域的名称
	stu_no = IntField(required=True, unique=True, verbose_name="学号")
	stu_name = StringField(required=True, max_length=16, verbose_name='姓名')
	sex = EnumField(enum=SexEnum, verbose_name='性别')
	class_name = StringField(max_length=10, verbose_name='班级')
	address = StringField(max_length=255, verbose_name='家庭住址')
	phone_no = StringField(max_length=11, verbose_name='电话号码')
	age = IntField(min_value=0, max_value=150, verbose_name='年龄')
	grades = ListField(EmbeddedDocumentField(CourseGrade), verbose_name='成绩数组')

	meta = {
		# 指定文档的集合
		'collection': 'students',
		# 指定排序,可以指定多个域。例如:'age':根据年龄升序,'-age':根据年龄倒叙
		'ordering': ['-age']
	}

	def __repr__(self):
		return f'Student({self.stu_no}, {self.stu_name})'

	def __str__(self):
		return self.__repr__()


class Grade(Document):
	"""学生成绩"""
	# verbose_name 自定义参数,用于显示定义域的名称
	stu_no = IntField(required=True, verbose_name="学号")
	stu_name = StringField(required=True, max_length=16, verbose_name='姓名')
	sex = EnumField(enum=SexEnum, verbose_name='性别')
	class_name = StringField(max_length=10, verbose_name='班级')
	address = StringField(max_length=255, verbose_name='家庭住址')
	phone_no = StringField(max_length=11, verbose_name='电话号码')
	age = IntField(min_value=0, max_value=150, verbose_name='年龄')
	grade = EmbeddedDocumentField(CourseGrade, verbose_name='成绩')

	meta = {
		# 指定文档的集合
		'collection': 'grades',
		# 指定排序,可以指定多个域。例如:'age':根据年龄升序,'-age':根据年龄倒叙
		'ordering': ['-age']
	}

	def __repr__(self):
		return f'Grade({self.stu_no}, {self.stu_name})'

	def __str__(self):
		return self.__repr__()

三、查询数据

结果集QuerySet的获取:User.objects,User是模型对象

结果集上的常用方法

  • all():查询所有文档
  • filter():按照条件查询
  • count():满足条件的文档数
  • sum()/average():求和/求平均数
  • order_by():排序
  • .skip().limit():分页

3.1、单个文档查询

  • first():没有文档则返回None,User.objects.first()
  • get(**kwargs)
    多个文档时,异常:MultipleObjectsReturned
    没有文档时,异常:DoesNotExist
    仅有一个文档时:返回ODM对象

示例

from learn_odm.school_models import Student


class LearnMongoDBEngine:

	def get_one_student(self):
		"""查询一个学生信息"""
		return Student.objects.first()

	def get_student_by_id(self, pk: str):
		"""根据学生的id查询"""
		return Student.objects.get(id=pk)

3.2、条件查询

比较运算符
ODM操作MongoDB_第3张图片

在MongoEngine中使用双下划线(__)分割。比如:age__gt=12,表示年龄大于12的学生信息。

MongoEngine中的字符串查询

i表示不区分大小写
ODM操作MongoDB_第4张图片
多个条件组合查询

  • Q函数的使用:from mongoengine.queryset.visitor import Q

  • 多个条件同时满足:Student.objects.filter(Q(key1=value1) & Q(key2=value2))

  • 多个条件部分满足:Student.objects.filter(Q(key1=value1) | Q(key2=value2))

示例

from learn_odm.school_models import Student, Grade,SexEnum
from mongoengine.queryset.visitor import Q


class LearnMongoDBEngine:

	def get_student_all(self):
		"""获取所有学生信息"""
		# result = Student.objects()
		result = Student.objects.all()
		return result

	def get_student_01(self):
		"""获取大于12岁的学生信息"""
		return Student.objects.filter(age__gt=12)

	def get_grade_01(self):
		"""查询大于等于60分的学生成绩信息"""
		return Grade.objects.filter(grade__score__gte=60)

	def get_student_02(self):
		"""获取所有姓李的学生"""
		return Student.objects.filter(stu_name__startswith='李')

	def get_student_03(self):
		"""查询年龄在9~12之间(含)的学生信息"""
		# SELECT * FROM school_student_info WHERE age BETWEEN 9 AND 12;
		# db.students.find({'age': {'$gte': 9, '$lte': 12}})
		# 写法1
		return Student.objects.filter(age__gte=9, age__lte=12)
		# 写法2
		# return Student.objects.filter(Q(age__gte=9) & Q(age__lte=12))

	def get_student_04(self):
		"""查询所有12岁以上的男生和9岁以下的女生"""
		return Student.objects.filter(Q(age__gt=12, sex=SexEnum.MAN) | Q(age__lt=9, sex=SexEnum.WOMEN))

3.3、聚合统计

  • 满足条件的文档数:User.objects.count(),所有的结果集都可以使用
  • 求和/平均数:User.objects.filter().sum(field) / User.objects.filter().average(field)

示例

class LearnMongoDBEngine:
	def get_student_05(self):
		"""查询所有12岁以上的男生和9岁以下的女生总数"""
		return Student.objects.filter(Q(age__gt=12, sex=SexEnum.MAN) | Q(age__lt=9, sex=SexEnum.WOMEN)).count()

	def get_grade_02(self):
		"""统计语文成绩的平均分,张三的语数英成绩总分"""
		queryset = Grade.objects.filter(grade__course_name='语文')
		avg_score = queryset.average('grade.score')
		sum_score = Grade.objects.filter(stu_name='张三').sum('grade.score')
		return avg_score, sum_score

3.4、排序

排序规则

  • -:倒叙排列
  • (+):正序排序,默认就是正序
  • 示例代码:Student.objects().order_by('field1', '-field2')
class LearnMongoDBEngine:

	def get_grade_03(self):
		"""统计语文成绩的最高分和最低分"""
		queryset = Grade.objects.filter(grade__course_name='语文')
		max_score = queryset.order_by('-grade.score').first().grade.score
		min_score = queryset.order_by('grade.score').first().grade.score
		return max_score, min_score

3.5、分页处理

  • 方式一,切片方式:User.objects.all()[10:15]
  • 方式二,.skip().limit():User.objects.skip(10).limit(5)
class LearnMongoDBEngine:

	def paginate(self, page: int = 1, page_size: int = 5):
		"""
		分页处理
		:param page: 当前第几页
		:param page_size: 每页多少条数据
		:return:
		"""
		# 方式1
		start = (page - 1) * page_size
		end = start + page_size
		queryset = Student.objects.all()[start:end]
		# 方式2
		queryset1 = Student.objects.skip(start).limit(page_size)
		return queryset, queryset1

四、新增数据

步骤

  • 第一步,构造ODM模型类对象:user=User(username='cjw')
  • 第二步,验证数据:user.validate()
  • 第三步,保存数据:user.save()

模型中的验证器

  • 内置的验证器,如max_length, min_value

  • 自定义验证器

    import re
    
    from mongoengine import StringField
    from mongoengine.errors import ValidationError
    
    def phone_required(value):
    	pattern = r'^1[0-9][10]$'
    	if not re.search(pattern, value):
    		raise ValidationError('请输入正确的手机号')
    phone_no = StringField(validation=phone_required)
    

使用create()方法

  • 参考代码:User.objects.create(**kwargs)

示例

class LearnMongoDBEngine:
	def add_one_student(self):
		"""新增一个学生信息"""
		student = Student(
			stu_no=random.randint(3000, 9999999),
			stu_name=self.gd.name(),
			sex=random.choice([SexEnum.MAN, SexEnum.WOMEN]),
			class_name='三年级四班',
			address=self.gd.address(),
			phone_no=self.gd.phone(),
			age=random.randint(1, 20),
			grades=[
				CourseGrade(course_name='语文', teacher=self.gd.name(), score=random.randint(1, 100)),
				CourseGrade(course_name='数学', teacher=self.gd.name(), score=random.randint(1, 100)),
				CourseGrade(course_name='英语', teacher=self.gd.name(), score=random.randint(1, 100)),
			]
		)
		print(student, student.grades)
		result = student.save()
		return result

	def add_one_student_create(self):
		"""使用create方法,新增一个学生信息"""
		result = Student.objects.create(
			stu_no=random.randint(3000, 9999999),
			stu_name=self.gd.name(),
			sex=random.choice([SexEnum.MAN, SexEnum.WOMEN]),
			class_name='三年级四班',
			address=self.gd.address(),
			phone_no=self.gd.phone(),
			age=random.randint(1, 20),
			grades=[
				CourseGrade(course_name='语文', teacher=self.gd.name(), score=random.randint(1, 100)),
				CourseGrade(course_name='数学', teacher=self.gd.name(), score=random.randint(1, 100)),
				CourseGrade(course_name='英语', teacher=self.gd.name(), score=random.randint(1, 100)),
			]
		)
		return result

五、修改和删除数据

5.1、修改数据

一般先过滤数据,再修改

  • 修改一条数据:User.objects.filter().update_one()
  • 批量修改数据:User.objects.filter().update()

ODM操作MongoDB_第5张图片

5.2、删除数据

  • 示例代码:User.objects.filter().delete()

示例

class LearnMongoDBEngine:
	def update_one(self):
		"""修改一条数据"""
		queryset = Student.objects.filter(stu_no=5219980)
		# result = queryset.update_one(stu_name=self.gd.name(), phone_no=self.gd.phone())
		result = queryset.update_one(stu_name=self.gd.name(), unset__phone_no=True)
		return

	def update_one_1(self):
		"""修改一条数据"""
		stu = Student.objects.filter(stu_no=5219980).first()
		if stu:
			stu.stu_name = self.gd.name()
			result = stu.save()
			print(result)

	def update_many(self):
		"""9岁的学生年龄+1"""
		queryset = Student.objects.filter(age=9)
		queryset.update(inc__age=1)

	def delete_data(self):
		"""删除学号大于3000的学号"""
		queryset = Student.objects.filter(stu_no__gte=3000)
		print(f'删除的学生人数:{queryset.count()}')
		res = queryset.delete()
		print(f'删除的结果:{res}')

你可能感兴趣的:(python操作三大数据库,python,mongodb)