Flask Web开发--狗书笔记005--第五章 数据库

SQL数据库

想必大家比较熟悉,就不再重复说明了,只摘录我觉得有价值的点。

表中还可以有称为外键的列,引用同一个表或者是不同表中某行的主键。

行之间的这种联系称之为关系,这是关系型数据库的基础。

关系型数据库比较高效,可以避免重复。

NoSQL数据库

一般使用集合代替表,使用文档代替记录。

造成数据重复,但重复数据可以提升查询速度。

使用SQL还是NoSQL

sql数据库擅长于高效而且紧凑的形式存储结构化的数据。这种数据库需要花费大量精力保证数据的一致性。NoSQL数据库放宽了对这种一致性的要求,从而获得了性能上的优势。
对于中小型程序来时,SQL和NoSQL数据库都是很好的选择,而且性能相当。

Python数据库框架

Flask并不限制我们使用何种类型的数据库包,因此MySQL、Postgres、SQLite、Redis、MongoDB或者CouchDB都是很好的选择。

还有一些数据库抽象层的代码包可以供我们选择,例如SQLALchemy和MongoEngine。我们可以使用这些抽象包直接处理高等级的Python对象,而不用处理如表、文档或者查询语言此类的数据库实体。

在选择数据库框架时,我们需要考虑考虑很多因素:

易用性

如果直接比较数据库引擎和数据库抽象层,显然后者取胜。抽象层,也称为对象关系映射(ORM)或者对象文档映射(ODM),在用户不知不觉的情况下把高层的面向对象的操作转化为底层的数据库指令。

性能

ORM或者ODM把对象业务转成数据库业务会有一定的损耗,大多数情况下,这种性能的降低微不足道,但是也不一定是如此。
一般情况下,ORM和ODM对于生产率的提升源源超过了这一丁点的性能降低,所以性能降低这个理由不足以说服用户完全放弃ORM和ODM。
真正的关键点在于如何选择一个能直接操作底层数据库的抽象层,以防特定的操作需要直接使用数据库原生指令优化。

可移植性

选择数据库时,必须考虑其是否能在你的开发平台和生产平台中使用。例如,如果你打算利用云平台托管程序,就要知道这个云服务提供了哪些数据库可供选择。

可移植性还针对ORM和ODM,尽管有些框架只为一种数据库引擎提供抽象层,但是其他框架可能做了更高级的抽象,它们支持不同的数据库引擎,而且都使用相同的面向对象接口。SQLAlchemy ORM就是一个很好的栗子,它支持很多关系型数据库引擎,包括流行的MySQL、Postgres和SQLite。

Flask集成度

使用集成了Flask的框架可以简化配置和操作,所以专门为Flask开发的扩展是我们的首选。

本书中作者选用的数据库框架是Flask-SQLAlchemy,这个Flask扩展包已经包装了SQLAlchemy框架。

使用Flask-SQLAlchemy管理数据库

Flask-SQLAlchemy是一个Flask扩展,简化了在Flask程序中使用SQLAlchemy的操作。SQLAlchemy是一个很强大的关系型数据库框架,支持多种数据库后台。既提供了高层ORM,也提供了数据库原生SQL的低层功能。

安装:

pip install flask-sqlalchemy

在Flask-SQLAlchemy中,数据库使用URL指定。最流行的数据库引擎采用的数据库URL格式如下表:
这里写图片描述
SQLite 数据库不需要使用服务器,因此不用指定hostname、username和password。URL中的database是硬盘上文件的文件名。

程序中使用的数据库URL必须保存到Flask配置对象的SQLALCHEMY_DATABASE_URI键中。配置对象中还有一个很有用的选项,即SQLALCHEMY_COMMIT_ON_TEARDOWN键,将其设置为True时,每次请求结束后都会自动提交数据库中的变动。
更多的配置项的作用可以参阅Flask-ALchemy的文档。

我们举一个栗子,配置一个简单的SQLite数据库

from flask.ext.sqlalchemy import SQLAlchemy

basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+os.path.join(basedir,'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)

db对象是SQLAlchemy类的实例,表示程序使用的数据库,同时还获得了Flask-SQLAlchemy提供的所有功能。

定义模型

模型这个术语指的是程序使用的持久化实体。在ORM中,模型一般是一个Python类,类中的属性对应数据库表中的列。

Flask-SQLAlchemy创建的数据库实例为模型提供了一个基类以及一系列辅助类和辅助函数,可用于定义模型的结构。

栗子,在hello.py中定义Role和User模型,

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(64),unique=True)

    def __repr__(self):
        return '<Role %r>' %self.name

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer,primary_key=True)
    username = db.Column(db.String(64),unique=True,index=True)

    def __repr__(self):
        return '<User %r>' %self.username

我们使用tablename 来定义在数据库中使用的表名,如果不定义的话,就会生成一个默认的名字,这个默认的名字就不会遵守使用复数形式进行命名的规定。

笔者给出了一些可用的列类型以及在模型中使用的Python类型:(有的可能不再适用,读者可以用的时候自行百度…)
这里写图片描述

db.Column 中其余的参数指定属性的配置选项,下面列出了一些可用选项:
这里写图片描述

注意:Flask-SQLAlchemy要求每个模型都要定义主键,这一列经常命名为id。虽然没有强制要求,但是这两个模型都定义了一个repr()方法,返回一个具有可读性的字符串表示模型,可以在调试和测试时使用。

关系

一对多的关系在模型类中的表示方法:

class Role(db.Model):
    #...
    user = db.relationship('User',backref='role')  


class User(db.Model):
    # ...
    role_id = db.Column(db.Integer,db.ForeignKey('roles.id'))

添加到Role模型中的users属性代表了这个关系额面向对象视角。对于一个Role类的实例,其users属性将返回与角色相关联的用户组成的列表。db.relationship()的第一个参数表名这个关系的另一端是哪个模型。如果模型尚未定义,可使用字符串形式指定。

db.relationship()中的backref参数向User模型中添加了一个role属性,从而定义反向关系。这一属性可替代role_id访问Role模型,此时获取的是模型对象,而不是外键的值。

笔者给出了一个常用的SQLAlchemy关系选项表:
这里写图片描述
这里写图片描述

数据库操作

创建表

首先我们想要Flask-SQLAlchemy根据模型类创建数据库表。方法是使用db.create_all()函数:

>>> from hello import db
/home/python/.virtualenvs/Flask_py/local/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py:839: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.
  'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
>>> db
<SQLAlchemy engine=sqlite:////home/python/Desktop/demo/blog/data.sqlite>
>>> db.create_all()

可能会有一些警告信息,执行完毕之后,查看程序目录,会发现新建了一个名字为data.sqlite的文件。这个SQLite数据库文件的名字就是在配置中指定的。如果数据库表已经存在于数据库中,那么db.create_all()不会重新创建或者更新这个表。

如果我们修改模型之后要把改动应用到现有的数据库中,这一个特性会带来不便。
更新现有的数据库的简单粗暴的方式是先删除旧表再创建:

db.drop_all()
db.create_all()

这种方法的副作用是数据库中原有的数据都销毁了……

插入行

下面这段代码创建了一些角色和用户:

>>> from hello import Role,User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> user_role = Role(name='User')
>>> user_john = User(username = 'john',role = admin_role)
>>> user_susan = User(username = 'susan',role = user_role)
>>> user_david = User(username = 'david',role = user_role)

模型的构造函数接收的参数是使用关键字参数指定的模型属性初始值,注意,role属性也可使用,虽然它不是真正的数据库列,但却是一对多关系的高级表示。这些新建对象的id属性并没有明确设定,因为主键是由Flask-SQLAlchemy管理的,现在这些主键只是存在于Python中,还没有写入数据库。因此id尚未赋值:

>>> print(admin_role.id)
None
>>> print(mod_role.id)
None
>>> print(user_role.id)
None

通过数据库会话管理对数据库所做的改动,在Flask-SQLAlchemy中,会话由db.session表示。准备把对象写入数据库之前,先要将其添加到会话中:

>>> db.session.add(admin_role)
>>> db.session.add(mod_role)
>>> db.session.add(user_role)
>>> db.session.add(user_john)
>>> db.session.add(user_susan)
>>> db.session.add(user_david)

或者也可以简写为:

db.session.add_all([admin_role,mod_role,user_role,user_john,user_susan,user_david])

为了把对象写入数据库,我们要调用commit()方法提交会话:

db.session.commit()

再次查看id属性,现在它们已经被赋值了:

>>> print(admin_role.id)
1
>>> print(mod_role.id)
2
>>> print(user_role.id)
3

注意:数据库的会话db.session和Flask 里面的上下文session没有关系。数据库会话也叫做事务。

数据库会话能保证数据库的一致性。提交操作使用原子方式把会话中的对象全部写入数据库。如果在写入会话的过程中发生了错误,真个会话都会失效。如果你始终把相关改动放在会话中提交,就能避免因为部分更新导致的数据库不一致性。

数据库会话也可以回滚,调用db.session.rollback()之后,添加到数据库会话中的所有对象都会还原到它们在数据库时的状态。

修改行

在数据库上调用add()方法也能更新模型,我们继续在之前的shell会话中进行操作,下面这个例子把“Admin”角色重新命名为“Administrator”

>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()

删除行

数据库会话还有个delete()方法。下面这个例子把”Moderator”角色从数据库中删除:

>>> db.session.delete(mod_role)
>>> db.session.commit()

注意:删除和更新一样,提交数据库会话后才会执行。

查询行

Flask-SQLAlchemy 为每个模型类都提供了query对象。最基本的模型查询是取回对应表中的所有记录:

>>> Role.query.all()
[<Role u'Administrator'>, <Role u'User'>]
>>> User.query.all()
[<User u'john'>, <User u'susan'>, <User u'david'>]

使用过滤器可以配置query对象进行更加精确的查询。下面这个例子查找角色为“User”的所有用户:

>>> User.query.filter_by(role=user_role).all()
[<User u'susan'>, <User u'david'>]

若要查看SQLAlchemy为查询生成的原生SQL查询语句,只需要把query对象转换成字符串:

>>> str(User.query.filter_by(role = user_role))
'SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id \nFROM users \nWHERE ? = users.role_id' 

如果你退出了shell会话,前面这些例子中创建的对象就不会以Python对象的形式存在,而是作为各自数据库表中的行。如果你打开了一个新的shell会话,就要从数据库中读取行,再重新创建Python对象。

下面这个例子发起了一个查询,加载名字为”User”的用户角色:

user_role = Role.query.filter_by(name='User').first()

filter_by()等过滤器可在query对象上调用,返回一个更精确的query对象,多个过滤器可以一起调用,直到获得所需的结果。

下面是常用的SQLAlchemy查询过滤器:
这里写图片描述
在查询上应用指定的过滤器之后,通过调用all()执行查询,以列表的形式返回结果。除了all()之外,还可以有其他的查询方法:
这里写图片描述

关系和查询的处理方式类似,下面这个例子分别从关系的两端查询角色和用户之间的一对多关系:

>>> users = user_role.user
>>> users
[<User u'susan'>, <User u'david'>]
>>> users[0].role
<Role u'User'>

执行user_role.user表达式的时候,隐含的查询会调用all()返回一个用户列表。query对象是隐藏的,因此无法指定更精确的查询过滤器。就这个特定示例而言,返回一个按照字母顺序排序的用户列表可能更好。

在下面的示例中,我们修改了关系的设置,加入了lazy=’dynamic’参数,从而禁止自动执行查询。

这里写图片描述

这样配置关系之后,user_role.users会返回一个尚未执行的查询,因此可以在其上添加过滤器:
这里写图片描述

在视图函数中操作数据库

集成Python shell

每次启动shell会话都要导入数据库实例和模型,这真是份枯燥的工作。为了避免一直重复导入,我们可以做一些配置,让Flask-Script的shell命令自动导入特定的对象。
若是想把对象添加到到导入列表中,我们要为shell命令注册一个make_context回调函数,如示例所示:
hello.py 为shell命令添加一个上下文

from flask.ext.script import Shell
def make_shell_context():
    return dict(app = app,db=db,User=User,Role=Role)
manager.add_command('shell',Shell(make_context=make_shell_context))

make_shell_context()函数注册了程序、数据库实例以及模型,因此这些对象能直接导入shell.

使用Flask-Migrate实现数据库迁移

数据库迁移能够跟踪数据库模式的变化,然后增量的把变化应用到数据库中。
SQLAlchemy的主力开发人员编写了一个框架,称为Alembic。除了直接使用Alembic之外,Flask程序还可以使用Flask-Migrate扩展。这个扩展对Alembic做了轻量级的包装,并且集成到了Flask-Script中,所有的操作都通过Flask-Script命令完成。

创建迁移仓库

首先,我们要在虚拟环境中安装Flask-Migrate:

pip install flask-migrate

配置Flask-Migrate:

from flask.ext.migrate import Migrate, MigrateCommand
...
migrate = Migrate(app,db)
manager.add_command('db',MigrateCommand)

为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到Flask-Script的manager对象上。
在上面这个例子中,MigrateCommand类使用db命令附加。

在维护数据库迁移之前,要使用init子命令创建迁移仓库:

python hello.py db init

这个命令会创建migrations文件夹,所有的迁移脚本都存放在其中。

注意:数据库迁移仓库中的文件要和其他文件一起纳入版本控制。

创建迁移脚本

数据库迁移用迁移脚本表示,脚本中有两个函数,分别是upgrade()和downgrade()。upgrade()函数把迁移中的改动应用到数据库中,downgrade()函数则是将改动删除。

自动创建的迁移不一定总是正确的,有可能会漏掉一些细节。自动生成迁移脚本后一定要进行检查。

migrate子命令用来自动创建迁移脚本:

python hello.py db migrate -m 'initial migration'

更新数据库

检查并修正好迁移脚本之后,我们可以使用db upgrade命令把迁移应用到数据库中:

python hello.py db upgrade

对于第一个迁移来说,其作用和调用db.create_all()方法一样,但是在后续的迁移过程中,upgrate命令能把改动应用到数据库,而且不影响其中保存的数据。

第5章完结撒花。(本来可以快点的,发了个票圈一直在回复消息……自拍误人……)

猜你喜欢

转载自blog.csdn.net/enjolras_fuu/article/details/80155291
今日推荐