上篇介绍的多对多关系是两个模型是之间的多对多关系,关联表联接的是两个明确的实体,还有些情况下只有一个模型,与自己之间存在多对多关系。比如用户之间的关注。表示用户关注其他用户时,只有用户一个实体,没有
第二个实体。如果关系中的两侧都在同一个表中, 这种关系称为自引用关系。
在关注中, 关系的左侧是用户实体,可以称为“关注者”;关系的右侧也是用户实体,但这些是“被关注者”。从概
念上来看,自引用关系和普通关系没什么区别,只是不易理解。下图 是自引用关系的数据库图解,表示用户之间的关注。
本例的关联表是 follows,其中每一行都表示一个用户关注了另一个用户。图中左边表示的一对多关系把用户和 follows 表中的一组记录联系起来,用户是关注者。图中右边表示的一对多关系把用户和 follows 表中的一组记录联系起来,用户是被关注者。
<span style="font-size:18px;">app/models/user.py:关注关联表的模型实现 class Follow(db.Model): __tablename__ = 'follows' follower_id = db.Column(db.Integer, db.ForeignKey('users.id'),primary_key=True) followed_id = db.Column(db.Integer, db.ForeignKey('users.id'),primary_key=True) timestamp = db.Column(db.DateTime, default=datetime.utcnow)</span>
SQLAlchemy 不能直接使用这个关联表,因为如果这么做程序就无法访问其中的自定义字段。相反地, 要把这个多对多关系的左右两侧拆分成两个基本的一对多关系,而且要定义成标准的关系。代码如下 所示。
<span style="font-size:18px;">app/models/user.py:使用两个一对多关系实现的多对多关系</span><p><span style="font-size:18px;"> </span></p> class User(UserMixin, db.Model): # ... followed = db.relationship('Follow',foreign_keys=[Follow.follower_id], backref=db.backref('follower', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') followers = db.relationship('Follow',foreign_keys=[Follow.followed_id], backref=db.backref('followed', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan')
在这段代码中, followed 和 followers 关系都定义为单独的一对多关系。注意,为了消除外键间的歧义, 定义关系时必须使用可选参数 foreign_keys 指定的外键。而且,db.backref() 参数并不是指定这两个关系之间的引用关系,而是回引 Follow 模型。回引中的 lazy 参数指定为 joined。这个 lazy 模式可以实现立即从联结查询中加载相关对
象。例如,如果某个用户关注了 100 个用户,调用 user.followed.all() 后会返回一个列表,其中包含 100 个 Follow 实例,每一个实例的 follower 和 followed 回引属性都指向相应的用户。设定为 lazy=‘joined‘ 模式,就可在一次数据库查询中完成这些操作。如果把lazy 设为默认值 select,那么首次访问 follower 和 followed 属性时才会加载对应的用户,
而且每个属性都需要一个单独的查询, 这就意味着获取全部被关注用户时需要增加 100 次额外的数据库查询。
这两个关系中, User 一侧设定的 lazy 参数作用不一样。 lazy 参数都在“一”这一侧设定,返回的结果是“ 多”这一侧中的记录。上述代码使用的是 dynamic,因此关系属性不会直接返回记录,而是返回查询对象,所以在执行查询之前还可以添加额外的过滤器。
cascade 参数配置在父对象上执行的操作对相关对象的影响。比如,层叠选项可设定为:将用户添加到数据库会话后, 要自动把所有关系的对象都添加到会话中。层叠选项的默认值能满足大多数情况的需求, 但对这个多对多关系来说却不合用。删除对象时,默认的层叠行为是把对象联接的所有相关对象的外键设为空值。 但在关联表中,删除记录后正确的行为应该是把指向该记录的实体也删除, 因为这样能有效销毁联接。这就是层叠选项值delete-orphan 的作用。
cascade 参数的值是一组由逗号分隔的层叠选项,这看起来可能让人有点困惑,但 all 表示除了 delete-orphan 之外的所有层叠选项。设为 all,delete-orphan 的意思是启用所有默认层叠选项,而且还要删除孤儿记录。