Web开发之旅-Flask操作数据库详解

上一篇文章介绍了Flask操作数据库的整个过程,但是关于数据库还有很多的内容没有提及,作为Web开发三大内容之一,而且是其他两块内容的基础,必须详细了解掌握。

一、连接数据库

前面讲到Flask可以使用各种各样的数据库,但是使用数据库之前需要先配置数据库的连接,前一篇文章介绍了使用sqlite的配置方法,sqlite不需要单独安装就可以使用,在小型应用中很实用,但是在大型应用中就无法满足需求了,这篇文章详细介绍Flask使用MySQL的配置,在服务器部署那篇文章介绍了Centos安装配置数据库的方法,Windows和Mac安装方法可以自行搜索安装,并配置好用户及数据库。

其实配置数据库连接只需要配置Flask实例app的SQLALCHEMY_DATABASE_URI参数就可以了,配置方法是直接修改app.config这个字典相应键的值,可以先看看config这个字典有哪些键:

Flask操作数据库是通过SQLAlchemy这个ORM来实现的,把SQLZLCHEMY开头的提出来:

必须配置的参数就是SQLALCHEMY_DATABASE_URI,不同的数据库使用不同的URI:

这里会有一个小坑,因为python连接数据库除了需要驱动,其实就是一个库,所以需要单独安装并明确指出,所以连接mysql需要先安装驱动:

$ pip install pymysql

URI则需要使用+号来指定数据库驱动:

mysql+pymysql://username:password@localhost/mydatabase

sqlite未指定驱动是因为安装python时就有了sqlite的驱动,系统默认使用,sqlite的URI实际上就是一个文件的位置,这个文件存放的就是数据库的内容,首先使用os库获取了当前文件的绝对路径,然后连接一个文件名就得到了数据库文件的绝对路径。

二、建立数据库模型

ORM是将python的对象和数据库表映射起来,新安装的数据库中是没有表的,我们需要先建立python的类,这个类被称为数据库模型Model,然后将这个类映射成数据库的表,使用flask-migrate同步到数据库中生成对应的表,这样就可以进行数据库的增删查改了,如果数据库中的表已经建好了,那么就需要根据表的内容来建立Model,想要修改表的结构同样使用flask-migrate就可以了。

以博客文章为例:

1.字段

首先需要继承db.Model这个类,__tablename__是指定这张表的名称,不是必须指定的,如果不指定这张表名就是这个类名,通常表名使用复数形式更符合习惯。db.Column()是实例化一个表的字段,简单的讲就是表的一列,规定这一列的名称、数据类型及其他属性,id这个字段很重要,primary_key指明它是这张表的主键,其他键的内容都和它完全关联,通过主键可以找到表中任意的一行,而且它是自动增长且不重复,不需要我们操作,db.Integer()是指定字段的数据类型,关于SQLAlchemy的数据类型可以参照下表:

db.Column的其他参数

三、数据库关系

这是关系数据库的最大特点,通过关系将各个表之间关联起来,减少了数据的冗余并且方便查询,关系分为一对多和多对多。

1.一对多

这个关系很好理解,例如:一个网站有很多用户,那么就会有一个User表,存放着用户的各种信息,用户会发表文章,所有文章的信息都会放在Post表中,每篇文章的作者是一个用户,每个用户会发多篇文章,这就是一对多,使用这种关系将Post表和User表关联起来,我们就可以通过一篇文章查到作者的所有信息,也可以通过用户查到他的所有文章,那是如何实现的呢?

第一步,在Post表中添加一个字段,这个字段通过外键关联到User表:

user_id这个字段就是外键,在db.Column实例化的时候传入db.ForeignKey,db.ForeignKey实例化的参数就是关联表的字段,为什么是users.id?users很好理解,它是User的表名,为什么是id字段呢?回头看看上篇文章讲的关系数据库的第三范式!

第二步,在User表中添加一个关系:

posts就是一个关系,db.relationship()的第一个参数是指向了关联的表,注意这里使用的类名称,实例化一个关系还有其他的参数:

这里提到了一对一的关系,例如User表的列数太多的时候,一次查询返回的数据量太大,但是我们并不需要那么多的信息,就可以将表进行拆分,将user的主要信息放在主表中,user的一些其他信息放在附表中,然后在主表中添加一个外键关联到附表中,使用的时候和一对多一样,只需要将附表的关系userlist参数设为False就可以了。

2.多对多

多对多关系比一对多稍稍复杂一点,但是理解起来并不难,比如用户写了一篇文章,文章会提取一些标签,这些标签方便后期的分类和检索,一篇文章会有多个标签,同时会有多篇文章可能使用相同的标签,标签和文章就是一个多对多的关系,标签需要新建一个模型Tag,文章就是之前的Post。

如何处理多对多的关系?

处理多对多关系其实就是增加一张表posts_tags,这张表有两个字段,分别对应到Tag表和Post表,这样就分解成了两个一对多,从Tag到posts_tags是一对多,Post到posts_tags也是。

第一步,新建一张标签和文章的关联表:

这里新建表没有使用继承db.Model的方式,而是用db.Table()生成,db.Table比db.Model更底层,db.Model提供的对象包装方式可以轻松读取某行记录,而我们不需要读取posts_tags某行记录,因为它每一行存放了两个数字,分别是Post和Tag的id,单独读取某行记录是没有意义的,所以使用db.Table来创建表。'posts_tags','post_id','tag_id'分别是表名和两个字段名,因为它对Post和Tag都是多的那一方,所以需要生成两个外键,分别指向posts.id和tags.id。

第二步,在Post表中生成关系

只需要在一个模型中添加关系就可以,例如上图添加在Post中,Tag模型不需要任何修改,db.relationship()第一个参数是指向的多对多的另外一个模型,而不是posts_tags这个关联表,posts_tags传给了secondary参数,表示关联情况保存在posts_tags中,backref参数是给Tag模型添加一个属性posts,这样就可以通过tag.posts查询带有该标签的所有文章。大家可能会疑惑为什么Tag模型中未定义关系,这是因为SQLAlchemy隐式的帮我们处理了这个关联关系。

3.自引用

前面说的是两张表之间的关系,还有一种情况是一个模型的某个字段会外键关联到自身,例如:一个用户会关注很多其他用户,同时他也会被其他用户关注,这是一个多对多的关系,但都是User这个模型,如何处理这种自引用的多对多关系呢?

第一步,新建一个关联表:

为什么这个关联表没有使用db.Table,因为我们在里面加了timestamp这个字段用来表示关注时间,所以需要查询这张表获取关注时间,还有一个特殊之处在于follower_id和followed_id两个字段都设为主键,这种情况叫联合主键,通过两个字段的值才能确定是哪一行的数据。follower表示关注者,followed表示被关注者。

第二步,添加关系

这里与之前普通多对多关系不同,添加了两条关系,因为我们把关联表变成可查询的后,SQLAlchemy无法隐式关联了,只能是显式的添加两条关联关系,followed表示用户所有关注的人,followers表示所有关注自己的人,相当于这两个字段之间通过Follow这张表来实现多对多的关系,为了消除外键的歧义,传入了foreign_keys来指定关联Follow中的外键,cascade被称为层叠选项,默认情况下记录被删除后会将相关对象的外键设为空值,而这里需要把指向该记录的实体也删除,这样可以消除孤儿记录(与其他数据都没有关联的记录)

四、数据的迁移操作

模型已经建立好了,但是数据库中还没有相应的表,如果对数据库进行操作就会报错,或者是修改了模型,数据库没有做对应的修改,仍然会导致异常,这里就需要用到上一篇文章中的flask-migrate这个库来将模型的改动同步到数据库中,这个库非常的实用,避免了我们手动的去修改数据库,写好模型后就能自动在数据库中生成对应的表,配置这个库在上一篇文章已经讲到了,这篇文章讲一些细节。

当输入python hello.py db会显示上面这些信息,下面就说说他们都有什么用。

第一次使用时用init初始化一个仓库,然后用migrate把模型生成一个revision,最后使用upgrade把revision同步到数据库,以后有修改模型就先migrate,然后upgrade就可以了。

五、数据库的CRUD

CRUD是操作数据库的最基本操作,使用flask-migrate将数据库同步好以后,我们先在shell里面看看如何操作数据库:

1.将新建的模型加入到上下文

这样在shell中才能使用这些类。

2.增(Create)

user=User(),将User类实例化一个对象,然后修改user的属性username,db.session.add(user)是将要写入数据库的对象添加到会话中,db.session.commit()是将会话中的内容提交到数据库,这样user这个数据就已经写入到数据库中了,注意的问题是如果username设置了unique属性,也就是不能重复的话,就会存在写入时报错,通常会先做检查然后再写入,后面会讲如何操作。tag的操作是直接讲属性的内容当参数传递给了Tag来进行实例化,一条语句搞定,post就涉及到关联的操作了,post.user我们直接讲user对象赋值给它,建立模型的时候User中的posts反向引用backref参数为user,所以可以直接使用post.user,它接受的是一个User对象,这是多到一的情况,post.tags是多到多的情况,它是一个列表,所以使用append方法将Tag对象添加到post.tags中,如果反过来呢?同样的道理,tag.posts.append(post)就可以了,前面的user.posts也是相同的方法,就是“多”的情况就用append,“1”的情况就直接赋值。

2查(Read)

查询应该是数据库操作使用得最多的操作了,先介绍简单的方式,使用模型Model进行查询。

这里返回结果看起来不太友好,User 1我们并知道是谁,能不能返回的是user的username呢?给User类添加一个__repr__方法就可以了,其他模型操作同理。

查询语句排在第一的是模型,也就是想查哪张表就用哪个模型,第二是query,查询的意思,第三是过滤器,是可选的,例如filter_by,第四是查询执行函数,例如all。

过滤器

执行函数

联结查询

如果想得到一个用户所关注的所有用户的所有文章怎么办?

2. select_from(Follow)表示查询从Follow开始

3. filter_by(follower_id=user.id)查询出用户关注的所有用户

4. join(Post, Follow.followed_id == Post.user_id)括号里的内容表示Post表中作者为用户所关注的所有用户的文章,join表示联结。

简化一下可以得到:

关于联结查询大家可以SQL相关内容来帮助理解。

3改(Update)

4删(Delete)

删除也是先通过查询拿到数据,然后调用delete方法删除就OK了。

关于通过视图函数来操作数据库在后面表单操作的文章中介绍,使用方法与这一节shell中操作方法相同。

数据库是很重要的一块内容,强力推荐更深入的学习数据库的基础知识。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180830G0PLWD00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券