十二、数据库-表关系

141 阅读4分钟

关系型数据库一个强大的功能,就是多个表之间可以建立关系,比如文章表中,通常需要保存作者数据,但是我们不需要直接把作者数据放到文章表中,而是文章表中通过外键引用用户表,这种强大的表关系,可以存储非常复杂的数据,并且可以让查询非常迅速,在Flask-SQLAlchemy中,同样也支持表关系的建立,表关系建立的前提,是通过数据库层面的外键实现的,表关系总体来讲可以分为三种,分别是:一对多(多对一)、一对一、多对多,一下分别进行讲解

1、外键

外键是数据库层面的技术,在Flask-SQLAlchemy中支持创建ORM模型的时候就指定外键,创建外键是通过db.ForeginKey()实现的,比如这里我们创建一个article表,这个表有一个author_id字段,通过外键引用user表的id字段,用来保存文章是由谁编写的,article的模型代码如下:

class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(200),nullable=False)
    content = db.Column(db.Text,nullable=False)
    
    author_id = db.Column(db.Integer,db.ForeignKey("user.id"))
    
with app.app_context():
    db.create_all()

以上代码,除了添加常规的title、content属性外,还增加了一个author_id,author_id通过db.ForeignKey("user.id")引用了之前创建的user表中的id字段,这里需要注意,author_id因为引用user表的id字段,所以它的类型必须跟user表的id字段一直,否则会报错

2、一对多关系

我们生活中有很多一对多的例子,如CSDN博客中的一篇文章只能有一个作者,一个作者能发布多篇文章

2.1建立关系

上面通过外键,实际上已经建立起一对多的关系,即一篇文章只能引用一个作者,而一个作者可以被多篇文章引用,但是以上只是建立了一个外键,通过Article的对象还是无法直接获取到author_id引用的那个User对象,为了使操作ORM对象与操作普通Python对象一样,Flask-SQLAlchemy提供了db.relationship来引用外键所指向的那个ORM模型,在以上的Article模型中添加db.relationship,示例代码如下:

class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(200),nullable=False)
    content = db.Column(db.Text,nullable=False)

    author_id = db.Column(db.Integer,db.ForeignKey("user.id"))
    author = db.relationship("User")

我们添加了一个author属性,这个属性通过db.relationship与User模型建立了联系,以后通过Article的实例对象访问author时,如:artile.author,那么Flask-SQLAlchemy会自动根据外键author_id从user表中寻找数据,并形成User模型实例对象,下面通过创建Article对象,并通过访问Article实例对象的author属性来关联User对象,示例代码如下:

@app.route("/article/add")
def article_add():
    user = User.query.first()
    article = Article(title='活着',content='是由余华写的长篇小说',author=user)
    db.session.add(article)
    db.session.commit()

    art = Article.query.filter_by(title="活着").first()
    print(art.author.username)
    return "添加成功"

以上代码首先创建了一个article对象,并添加到数据库中,接下来再从数据库中提取,然后通过art.author.username访问到article对象的用户名

2.2建立双向关系

现在的Article模型可以通过author属性访问到对应的User实例对象,但是User实例无法访问到和其关联的所有Article实例对象,因此为了实现双向关闭绑定,还需要在User模型上添加一个db.relationship类型的article属性,并且在User模型和Article模型双方的db.relationship上都需要添加一个back_populates参数,用于绑定对方访问自己的属性,示例代码如下:

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    username = db.Column(db.String(100),nullable=False)
    password = db.Column(db.String(100),nullable=False)
    articles = db.relationship("Article",back_populates='author')


class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(200),nullable=False)
    content = db.Column(db.Text,nullable=False)

    author_id = db.Column(db.Integer,db.ForeignKey("user.id"))
    author = db.relationship("User",back_populates='articles')

在User端绑定了articles属性后,现在双方都能通过属性直接访问到对方了,示例代码如下

@app.route("/articles")
def articles_a():
    user = User.query.first()
    for article in user.articles:
        print(article.title)
    return "获取文章成功"
2.3 简化关系定义

以上User和Article模型中,通过在两边的db.relationship上传递back_populates参数来实现双向绑定,这种方式有点繁琐,我们还可以通过只在一个模型上定义db.relationship类型属性,并且传递backref参数实现双向绑定,示例代码如下:

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    username = db.Column(db.String(100),nullable=False)
    password = db.Column(db.String(100),nullable=False)
    # articles = db.relationship("Article",back_populates='author')


class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(200),nullable=False)
    content = db.Column(db.Text,nullable=False)

    author_id = db.Column(db.Integer,db.ForeignKey("user.id"))
    author = db.relationship("User",backref='articles')

以上代码中,我们删除了User模型上的articles属性,并且在Article模型上将author属性的db.relationship中的back_populates修改为backref,backref参数的功能更加强大,其可以自动给对方添加db.relationship的属性,但是建议使用back_populates来实现双向绑定