MongoEngine
介绍
MongoEngine 是一个从对象(Object)到文档(Document)的映射器,基于 MongoDB。
安装方式:python -m pip install -U mongoengine
MongoEngine 的底层基于 PyMongo driver,适配 MongoDB 多种版本。
MongoEngine 有一个特点,它跟 Django ORM 非常像,如果你有后者的使用经验,将会感到很熟悉。
连接 MongoDB
通过以下方法连接 MongoDB 数据库:
from mongoengine import *
connect('tumblelog')
其中:
- 默认是 MongoDB 运行在本地的场景,因此只需要提供一个数据库名
- 如果 MongoDB 是远程访问的,需要特别注意远程访问的安全性问题,我从新闻中看到有很多攻击事件,都是攻击者通过扫描器,扫描公网上暴露的 MongoDB 数据库,轻松窃取到数据
如果要远程访问:
from mongoengine import *
connect(host='mongodb://192.168.0.1:27017/db_name')
多数据库
MongoEngine 支持同时连接多个 MongoEngine 数据库:
connect(alias='user-db-alias', db='user-db')
connect(alias='book-db-alias', db='book-db')
connect(alias='users-books-db-alias', db='users-books-db')
class User(Document):
name = StringField()
meta = {'db_alias': 'user-db-alias'}
class Book(Document):
name = StringField()
meta = {'db_alias': 'book-db-alias'}
class AuthorBooks(Document):
author = ReferenceField(User)
book = ReferenceField(Book)
meta = {'db_alias': 'users-books-db-alias'}
定义 Document
在 MongoDB 中,可粗略认为 Document 相当于 RDBMS 中的 row(行)。document 存储于 collections 中,是 shchemaless 的。
定义一个 Document:
class User(Document):
email = StringField(required=True)
first_name = StringField(max_length=50)
last_name = StringField(max_length=50)
其中:
- Document 包名:mongoengine.document
- fields 包名:mongoengine.fields
需要注意的是,MongoDB 是一个 NoSQL 数据库,是 Schema 无关的,这里定义的 Schema 并不会传给 MongoDB。定义的 Schema 也是有约束力的,但是只限于框架之上的应用层。
形成对比的是,在 SQL ORM 中,这里的 Schema 将会严格映射到数据库表中。
Document 间的引用关系:
class Post(Document):
title = StringField(max_length=120, required=True)
author = ReferenceField(User)
DynamicDocument
DynamicDocument 比 Document 扩充的地方在于,对于 DynamicDocument,除了设置声明字段外,也可以对未声明的字段进行复赋值,更加灵活:
from mongoengine import *
class Page(DynamicDocument):
title = StringField(max_length=200, required=True)
# Create a new page and add tags
>>> page = Page(title='Using MongoEngine')
>>> page.tags = ['mongodb', 'mongoengine']
>>> page.save()
>>> Page.objects(tags='mongoengine').count()
>>> 1
EmbeddedDocument
围绕 Document 有很多灵活的用法,这里先简单介绍 EmbeddedDocument。这种 Document 的作用是,它自身不单独向数据库中存,而是作为其它的 Document 的字段,随对方一起存。这样就形成了只有单独实体,但数据库中没有单独 Collection。示例:
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
class Post(Document):
title = StringField(max_length=120, required=True)
author = ReferenceField(User)
tags = ListField(StringField(max_length=30))
comments = ListField(EmbeddedDocumentField(Comment))
CASCADE
对于引用关系而言(在关系型数据库中称为外键),删除表的时候需要考虑一个问题,那些通过外键引用它的其它表该怎么处理呢?
CASCADE 就是一种处理方式,即把我(本表)删除的时候,把那些对我有引用的数据也一并删除了吧。
MongoEngine 中也支持这种方式,通过 reverse_delete_rule 参数:
class Post(Document):
title = StringField(max_length=120, required=True)
author = ReferenceField(User, reverse_delete_rule=CASCADE)
tags = ListField(StringField(max_length=30))
comments = ListField(EmbeddedDocumentField(Comment))
Field
Field 是 Document 中的字段,MongoEngine 支持多种字段类型。
field 默认都是可不传的,如果需要强制赋值,需要指定 required 为 True。
支持的 Field 类型:
- BinaryField
- BooleanField
- ComplexDateTimeField
- DateTimeField
- DecimalField
- DictField
- DynamicField
- EmailField
- EmbeddedDocumentField
- EmbeddedDocumentListField
- EnumField
- FileField
- FloatField
- GenericEmbeddedDocumentField
- GenericReferenceField
- GenericLazyReferenceField
- GeoPointField
- ImageField
- IntField
- ListField
- LongField
- MapField
- ObjectIdField
- ReferenceField
- LazyReferenceField
- SequenceField
- SortedListField
- StringField
- URLField
- UUIDField
- PointField
- LineStringField
- PolygonField
- MultiPointField
- MultiLineStringField
- MultiPolygonField
Reference fields
实现对其它 Document 的引用。在第一个参数中传入类名:
class User(Document):
name = StringField()
class Page(Document):
content = StringField()
author = ReferenceField(User)
john = User(name="John Smith")
john.save()
post = Page(content="Test Page")
post.author = john
post.save()
自我引用的话,第一个参数传 'self':
class Employee(Document):
name = StringField()
boss = ReferenceField('self')
profile_page = ReferenceField('ProfilePage')
class ProfilePage(Document):
content = StringField()
引用删除关系
默认 MongoDB 是不校验一致性的,不会进行关联删除(CASCADE)。
MongoEngine 单独添加了关联删除的支持,开启方式:
class ProfilePage(Document):
employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)
当 Employee 删除的时候,指向它的所有 ProfilePage 也会一并被删除。除了 CASCADE 外,可取值有:
取值 | 作用 |
---|---|
mongoengine.DO_NOTHING | 什么也不做。
会导致数据库不一致。 与被删除 Document 存咋关联关系的其它 Document 没有关联删除。 |
mongoengine.DENY | 对于一个 Document,如果有其它 Document 对其有引用,不允许删除 |
mongoengine.NULLIFY | 将对其有引用的 Document 对应字段置空 |
mongoengine.CASCADE | 关联删除 |
mongoengine.PULL | 多对多场景下,从 ListField 中删除对应的 ReferenceField |
ListField
MongoDB 支持存储列表,参数中要在给出一个 Field,表示列表中数据的类型:
class Page(Document):
tags = ListField(StringField(max_length=50))
多对多关系
Reference fields 和 ListField 结合使用,可实现多对多关系:
class User(Document):
name = StringField()
class Page(Document):
content = StringField()
authors = ListField(ReferenceField(User))
bob = User(name="Bob Jones").save()
john = User(name="John Smith").save()
Page(content="Test Page", authors=[bob, john]).save()
Page(content="Another Page", authors=[john]).save()
# Find all pages Bob authored
Page.objects(authors__in=[bob])
# Find all pages that both Bob and John have authored
Page.objects(authors__all=[bob, john])
# Remove Bob from the authors for a page.
Page.objects(id='...').update_one(pull__authors=bob)
# Add John to the authors for a page.
Page.objects(id='...').update_one(push__authors=john)
DateTimeField
date_modified = DateTimeField(
default=datetime.datetime.utcnow)
添加数据
保存
简单保存:
ross = User(email='ross@example.com', first_name='Ross', last_name='Lawley').save()
更新
对有 id 的对象,进行 save,会更新数据。
有 id 的对象包括:
- 上面通过 save 新创建的
- 从数据库中请求回来的
访问数据
遍历
for post in TextPost.objects:
print(post.content)
TextPost.objects 跟 Django 实在是太像了。
过滤
for post in Post.objects(tags='mongodb'):
print(post.title)
计数
num_posts = Post.objects(tags='mongodb').count()
print('Found {} posts with tag "mongodb"'.format(num_posts))