当前位置: 首页 > 工具软件 > Pony ORM > 使用案例 >

PonyORM教程 5 钩子函数和实体方法扩展

井洲
2023-12-01

钩子函数

pony中有6个钩子函数,可以在对应的时间发生时被触发.最类似的行为就是数据库中的触发器

  • after_delete 在数据库中删除实体实例后调用。
  • after_insert 在将行插入数据库后调用。
  • after_update 在数据库中更新实例后调用。
  • before_delete 在删除数据库中的实体实例之前调用。
  • before_insert 在将新创建的对象插入数据库之前,仅对其调用。
  • before_update 在更新数据库中的实例之前调用实体实例。

下面以实例演示一下:

有2个类Solider(战士)和Gun(枪).逻辑是这样的:

  • 每招募一名战士,就给他发一杆枪
  • 每支枪发下去的时候,会在铭牌上刻上“xxx的xx武器”的字样

我们打算使用2个钩子函数来完成这个功能

  • 在初始化Solider实例之后(保存之前),Solider实例是否有gun属性,如果没有,创建一个。
  • 在Solider实例保存之后,修改Solider实例对应的Gun实例的sign属性(刻字)
class Solider(db.Entity):
    """士兵"""
    name = Required(str, max_len=40)  # 名字
    gun = Optional("Gun", column="gun_id", nullable=True, default=None)  # 武器

    @classmethod
    @db_session
    def create(cls, **kwargs) -> db.Entity:
        instance = cls(**kwargs)
        return instance

    @db_session
    def before_insert(self):
        """
        :return:
        """
        if self.gun is None:
            gun = Gun()
            gun.flush()
            self.gun = gun

    @db_session
    def after_insert(self):
        """
        :return:
        """
        self.gun.sign = "{}的{}".format(self.name, self.gun.model)


class Gun(db.Entity):
    """枪"""
    model = Optional(str, max_len=40, default="AK-47")  # 型号
    sign = Optional(str, max_len=128, nullable=True, default=None)  # 铭牌
    solider = Optional(Solider, nullable=True, default=None)  # 士兵


if __name__ == "__main__":
	solider = Solider.create(name="Tom")
    print(solider.to_dict())
    print(solider.gun.to_dict())

输出结果

Connected to pydev debugger (build 192.6603.34)
{'id': 1, 'name': 'Tom', 'gun': 1}
{'id': 1, 'model': 'AK-47', 'sign': 'Tom的AK-47', 'solider': 1}

Process finished with exit code 0

可以看到,我们仅仅执行了一个create方法创建实例。整个业务的过程大致如下:

  1. 类方法create创建了一个Solider实例,传递了一个name参数。
  2. 在保存Solider实例到数据库之前钩子函数before_insert被唤醒,修改了Solider实例,添加了一个新的Gun对象。
  3. Solider和Gun对象被保存。
  4. 钩子函数after_insert被唤醒,修改了刚刚保存Solider实例的gun属性指向的Gun对象的sign属性。

这样,我们就可以创建一个实例的逻辑只写在create函数里,在before_insert和after_insert完成其他逻辑,实际业务,经常用来说关联对象的更新和实例合法性检查等。并且。所有的钩子函数都是原子性的,也就是说,只要调用的函数链中有一个进行了回滚操作或者抛出异常。整个函数链的操作将全部被取消。这大大的简化了我们平时进行数据库事务操作的复杂性。我们举个例子进行说明:

我们修改Gun.sign,加上一个unique=True的字段约束(铭牌上的签名必须唯一).

class Gun(db.Entity):
    """枪"""
    model = Optional(str, max_len=40, default="AK-47")  # 型号
    sign = Optional(str, max_len=128, unique=True,  default="")  # 铭牌
    solider = Optional(Solider, nullable=True, default=None)  # 士兵

然后再执行如下代码:

if __name__ == "__main__":
    # db.drop_table(table_name="solider", if_exists=True, with_all_data=True)  # 删除表,演示实体声明时用于快速清除旧表
    # db.drop_table(table_name="gun", if_exists=True, with_all_data=True)  # 删除表,演示实体声明时用于快速清除旧表
    db.generate_mapping(create_tables=True)  # 生成实体,表和映射关系
    # with db_session(sql_debug=True):
    #     solider = Solider(name="张三")
    try:
        solider = Solider.create(name="Tom")
    except Exception as e:
        ms = "执行异常,错误原因: {}".format(e.args[1])
        print(ms)
    finally:
        with db_session:
            for solider in select(x for x in Solider):
                print(solider.to_dict())
            for gun in select(x for x in Gun):
                print(gun.to_dict())

我们在数据库已有名为Tom的Solider对象后,再次添加一个name=Tom的Solider对象,由于Solider的定义中 ,name是不检查唯一性的,所以这个阶段的Solider和Gun对象写入数据库没有问题。但是在函数链的最后一步骤,after_insert这个函数需要修改刚刚生成的Solider对象对应的Gun对象的sign属性。sign属性有唯一性约束( unique=True),这会导致执行出错。pony检测到出错后,直接rollback到函数链的开始处,刚才的Solider和Gun对象的插入操作作废。这一系列操作,完全不用我们操心,pony默认处理的就很好。
执行结果
插入Solider实例时,抛出了对象重复的错误(Duplicate entry),接下来我们查询数据库中的目标,可以看到Solider和Gun对象的插入都取消了。

Connected to pydev debugger (build 192.6603.34)
执行异常,错误原因: Duplicate entry 'Tom的AK-47' for key 'sign'
{'id': 1, 'name': 'Tom', 'gun': 1}
{'id': 1, 'model': 'AK-47', 'sign': 'Tom的AK-47', 'solider': 1}

Process finished with exit code 0

灵活运用6个钩子函数,你可以在保持主业务代码清晰的同时(主函数写主业务代码),实现强大的辅助功能(钩子函数写辅助业务代码),pony用自己的事务嵌套功能在后台保证了整个函数链涉及的所有数据库操作的原子性。这是一个非常强大的功能。请用好他。唯一一个需要提醒的是,处理业务逻辑,小心避免递归环路

扩展实体方法

由于pony在实现类继承的时候采用的是单表继承的模式,所以你就没法采用写一个公共的基类,然后其他类来继承这个公共的基类进行方法扩展了。基于这种情况,pony实际上是采用多继承的方式来实现实体方法的扩展的。我们下面用实例进行说明.

  • EntityMethods是我们扩展实体方法的载体。
  • Car和Truck实体类,打算使用EntityMethods扩展的实体方法
class EntityMethods(object):
    """实体方法扩展类"""

    @classmethod
    @db_session
    def create(cls, **kwargs) -> db.Entity:
        """
        创建一个实例对象
        :param kwargs:
        :return:
        """
        entity = cls(**kwargs)
        entity.flush()
        return entity

    def digest(self) -> str:
        ms = "my model is {}, i am a {}.".format(self.model, self.__class__.__name__)
        return ms


class Car(db.Entity, EntityMethods):
    model = Required(str, max_len=40)


class Truck(db.Entity, EntityMethods):
    model = Required(str, max_len=40)


if __name__ == "__main__":
    db.generate_mapping(create_tables=True)  # 生成实体,表和映射关系
    car = Car.create(model="BMW i351")
    truck = Truck.create(model="东风 141")
    print(car.digest())
    print(truck.digest())
    pass

执行结果

Connected to pydev debugger (build 192.6603.34)
my model is BMW i351, i am a Car.
my model is 东风 141, i am a Truck.

Process finished with exit code 0

至此,ponyorm的基本使用方法介绍完毕,希望大家玩的开心,再见。??????

索引

  1. PonyORM教程1 连接,声明和查询
  2. PonyORM教程 2 实体关系
  3. PonyORM教程 3 实体继承
  4. PonyORM教程 4 高级定义和连接查询
  5. PonyORM教程 5 钩子函数和实体方法扩展
 类似资料: