一,说明

使用pymongo和mongoengine,就是基本的curd
参考链接:
pymongo 文档: https://www.mongodb.com/zh-cn/docs/languages/python/pymongo-driver/current/read/specify-a-query/
mongoengine 文档:https://docs.mongoengine.org/guide/querying.html
                 https://www.cnblogs.com/zhenyauntg/p/13201826.html
mongo 一对多数据 :https://cloud.tencent.com/developer/ask/sof/101750948
                 https://deepinout.com/mongoengine/mongoengine-questions/217_mongoengine_mongoengine_listfield_within_a_embeddeddocument_throws_typeerror_on_validation.html

二,代码

1,基类

class MongoDbModel(db.Document):
    meta = {
        'abstract': True,
        'allow_inheritance': True
    }
    deleted_at = DateTimeField(default=None, null=True)

    @classmethod
    def connect(cls, db_name=None, username=None, password=None, host='mongodb://localhost:27017'):
        """链接数据库"""
        if not db_name:
            db_name = 'test'
        if not username and not password:
            db.connect(host=host, db=db_name, username=username, password=password)
        else:
            db.connect(host=host, db=db_name)

    @classmethod
    def disconnect(cls):
        """断开数据库"""
        db.disconnect()

    @classmethod
    def mongo_add(cls, **kwargs):
        return cls(**kwargs).save()

    @classmethod
    def find_one(cls, **kwargs):
        return cls.find_many(**kwargs).first()

    @classmethod
    def find_many(cls, **kwargs):
        kwargs['deleted_at'] = None
        return cls.objects(**kwargs)
        # return cls.objects.filter(**kwargs)

    @classmethod
    def find_add_paginate(cls, page, per_page, order_by=None, asc=True, **kwargs):
        skip = (page - 1) * per_page
        kwargs['deleted_at'] = None
        total = cls.find_many(**kwargs).count()
        total_pages = total // per_page + (1 if total % per_page > 0 else 0)
        if order_by:
            if asc:
                items = cls.find_many(**kwargs).order_by(order_by).skip(skip).limit(per_page)
            else:
                items = cls.find_many(**kwargs).order_by(f'-{order_by}').skip(skip).limit(per_page)
        else:
            items = cls.find_many(**kwargs).skip(skip).limit(per_page)
        return total, total_pages, items

    @classmethod
    def add_or_update_many(cls, objects):
        cls_collection = cls._get_collection()
        operations = []
        for obj in objects:
            if obj.id:
                tmp_obj = cls.find_one(id=obj.id)
                if tmp_obj is None:
                    raise NoneException(f'数据[{obj.id}]不存在')
                operations.append(UpdateOne({'_id': obj.id}, {'$set': obj.to_mongo()}))
            else:
                operations.append(InsertOne(obj.to_mongo()))
        if operations:
            result = cls_collection.bulk_write(operations)
            if result.inserted_count or result.matched_count:
                return True
            else:
                return False
        else:
            return False

    @classmethod
    def delete_many_by_objects(cls, objects):
        ids = [obj.id for obj in objects]
        return cls.delete_many_by_ids(ids)

    @classmethod
    def delete_many_by_ids(cls, ids):
        cls_collection = cls._get_collection()
        # result = cls_collection.delete_many({'_id': {'$in': ids}})
        # return result.deleted_count > 0
        cls_collection.update_many({'_id': {'$in': ids}}, {'$set': {'deleted_at': datetime.datetime.now()}})

    @classmethod
    def delete_many_by_condition(cls, **kwargs):
        cls_collection = cls._get_collection()
        filter_dict = {}
        for key, value in kwargs.items():
            field, operator = key.split('__', 1)
            if operator == 'in':
                filter_dict[field] = {'$in': value}
            elif operator == 'gt':
                filter_dict[field] = {'$gt': value}
            elif operator == 'eq':
                filter_dict[field] = value
            elif operator == 'gte':
                filter_dict[field] = {'$gte': value}
            elif operator == 'lt':
                filter_dict[field] = {'$lt': value}
            elif operator == 'lte':
                filter_dict[field] = {'$lte': value}
            elif operator == 'ne':
                filter_dict[field] = {'$ne': value}
            elif operator == 'like':
                filter_dict[field] = {'$regex': f'^{value}$', '$options': 'i'}
            elif operator == 'ilike':
                filter_dict[field] = {'$regex': f'^{value}$', '$options': 'i'}
            elif operator == 'notlike':
                filter_dict[field] = {'$not': {'$regex': f'^{value}$', '$options': 'i'}}
            elif operator == 'notilike':
                filter_dict[field] = {'$not': {'$regex': f'^{value}$', '$options': 'i'}}
            else:
                filter_dict[key] = value
        # result = cls_collection.delete_many(filter_dict)
        # return result.deleted_count > 0
        cls_collection.update_many(filter_dict, {'$set': {'deleted_at': datetime.datetime.now()}})

    @classmethod
    def delete_many_by_dict(cls, **kwargs):
        cls.objects.filter(**kwargs).update(set__deleted_at=datetime.datetime.now())

    def mongo_update(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
        return self.save()

    def mongo_delete(self):
        self.deleted_at = datetime.datetime.now()
        self.save()
        return True

    @classmethod
    def mongo_drop(cls):
        cls.objects.update(set__deleted_at=datetime.datetime.now())
        # cls._get_collection().drop()

    def mongo_close(self):
        self.disconnect()

2,基本使用

继承基类

class User(MongoDbModel):
    user_name = db.StringField(required=True)
    age = db.IntField()

测试

def test_mongodb_user():
    User.connect()
    User.mongo_drop()

    init_data = []
    for i in range(50):
        init_data.append(User(user_name=f'user{i}', age=i))
    User.add_or_update_many(init_data)
    users1 = User.find_one(user_name='user4')
    User.delete_many_by_dict(user_name='user4')
    # print(users1.to_json())
    users1.mongo_delete()
    print('\n')
    pprint.pprint(users1)
    users2 = User.find_many(age__gt=10)
    print('\n')
    pprint.pprint(users2)
    # conditions = {'age__gt': 10, 'user_name__in': ['user11', 'user12']}
    # users3 = User.find_add_paginate(page=1, per_page=10, **conditions)
    users3 = User.find_add_paginate(page=1, per_page=10, age__gt=10)
    users3 = User.find_add_paginate(page=1, per_page=10, order_by='age', asc=False, age__gt=10)
    print('\n')
    pprint.pprint(users3)
    user0 = User.mongo_add(user_name='Tom', age=20)
    user0.age = 21
    user0.mongo_update(age=22)
    user1 = User(user_name='John', age=25)
    User.add_or_update_many([user0, user1])
    user1 = User.find_one(user_name='John')
    user2 = User.mongo_add(user_name='Mike', age=30)

    # 批量按条件删除
    User.delete_many_by_ids([user1.id, user2.id])
    User.delete_many_by_objects([user0, user1, user2])
    conditions = {'age__gt': 10, 'user_name__in': ['user11', 'user12']}
    User.delete_many_by_condition(**conditions)
    # User.delete_many_by_condition(age__gt=10)
    User.mongo_drop()

    User.disconnect()

3, 一对多

注意:

MongoEngine定义EmbeddedDocumentListField时,MongoEngine需要确保相关的EmbeddedDocument类已经被加载和注册。

如果将Student类放到Person类的后面,那么在解析Person类定义时,MongoEngine会尝试解析students字段的类型,但是它可能会发现Student类尚未被加载和注册,从而导致报错

也就是说,写类的时候,将子类,放到父类的上面


A, 子表数据,存储在主表

继承EmbeddedDocument的,不会在数据库中单独存储,而是作为一个字段存储在父文档中
1对多关系,子数据存储在父数据中,父数据通过ReferenceField来引用子数据。1张表
class Student(db.EmbeddedDocument):
    # person_id = db.ReferenceField(Person)
    student_name = db.StringField(required=True)
    age = db.IntField()


class Person(MongoDbModel):
    person_name = db.StringField(required=True)
    age = db.IntField()
    hobby = db.ListField(db.StringField())
    students = db.EmbeddedDocumentListField(Student)
测试
def test_mongodb_person():
    Person.connect()
    Person.mongo_drop()

    init_data = []
    for i in range(50):
        init_data.append(Person(person_name=f'person{i}', age=i, hobby=['reading', 'running'], students=[Student(student_name=f'student{i}', age=i), Student(student_name=f'student{i + 1}', age=i + 1)]))
    Person.add_or_update_many(init_data)
    persons3 = Person.find_add_paginate(page=1, per_page=10, age__gt=10)

    Person.mongo_drop()
    Person.disconnect()


B,子表数据,单独存储在新的一张表

class Dog(MongoDbModel):
    name = db.StringField(required=True)
    age = db.IntField()
    animal = db.ReferenceField('Animal')


class Animal(MongoDbModel):
    name = db.StringField(required=True)
    age = db.IntField()
测试
def test_mongodb_animal():
    Animal.connect()
    Animal.mongo_drop()
    client = Animal._get_db().client
    with client.start_session() as mongo_session:
        with mongo_session.start_transaction():
            try:
                for i in range(50):
                    animal = Animal(name=f'animal{i}', age=i, )
                    animal.save(session=mongo_session)
                    dog = Dog(name=f'dog{i}', age=i, animal=animal)
                    dog.save(session=mongo_session)
            except Exception as e:
                mongo_session.abort_transaction()
                print(e.args[0])
            finally:
                mongo_session.end_session()

    animal1 = Animal.find_one(age=11)

    # 查询子表,需要反向关联
    dogs = Dog.find_many(animal=animal1)
    for dog in dogs:
        print(dog.age)

4, 多对多

.....还没看。