如何操作数据库

来源:互联网 发布:社会与经济发展数据库 编辑:程序博客网 时间:2024/06/04 19:39

数据库操作

创建表

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

(venv) $ python hello.py shell>>> from hello import db>>>db.creat_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属性,它们已经赋值了

数据库会话db.session和之前介绍的Flask session对象没有关系,数据库会话也称为事务

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

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

修改行

在数据库会话上调用add()方法也能更新模型,继续之前shell会话中进行操作,把Admin角色重命名为”Administrator”:

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

删除与插入和更新一样,提交数据库会话后才会执行

查询行

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

>>> Role.query.all()[<Role u'Administrator'>, <Role u'Moderator'>, <Role u'User'>, <Role u'john'>, <Role u'susan'>, <Role u'david'>]>>> 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))

如果退出了shell会话,前面这些例子中创建的对象就不会以Python对象的形式存在,而是作为各自数据库中的行,如果打开了一个新的shell会话,就要从数据库中读取行,再重新创建Python对象,下例发起了一个查询,加载名为’User’的用户角色:

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

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

下表是可在query对象上调用的常用过滤器,完整列表去官方文档看

过滤器 说明 filter() 把过滤器添加到原查询上,返回一个新查询 filter_by() 打等值过滤器添加到原查询上,返回一个新查询 limit() 使用指定的值限制原查询返回的结果数量,返回一个新查询 offset() 偏移原查询返回的结果,返回一个新查询 order_by() 根据指定条件对原查询结果进行排序,返回一个新查询 group_by() 根据指定条件对原查询结果进行分组,返回一个新查询

在查询上应用指定的过滤器后,通过调用all()执行查询,以列表的形式返回结果,除了all()以外,还有其他方法能触发查询执行,下表是方法:

方法 说明 all() 以列表形式返回查询的所有结果 first() 返回查询的第一个结果,如果没有结果则返回None first_or_404() 返回查询的第一个结果,如果没有结果,则中止请求,返回404错误响应 get() 返回指定主键对应的行,如果没有对应的行,则返回None get_or_404() 返回指定主键对应的行,如果没找到指定的主键,则中止请求,返回404错误响应 count() 返回查询结果的数量 paginate 返回一个paginate对象,它包含指定范围内的结果

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

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

这个例子中的user_role.users查询有个小问题,执行user_role.users表达式时,隐含的查询会调用all()返回一个用户列表,query对象是隐藏的,因此无法指定更精确的查询过滤器,就这个特定示例而言,返回一个按照字母顺序排序的用户列表可能更好
下例中,我们修改了关系的设置,加入了lazy=’dynamic’参数,从而禁止自动执行查询

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

这样配置关系之后,user_role.users会返回一个尚未执行的查询,因此可以添加过滤器:

>>> user_role.users.order_by(User.username).all()[<User u'david'>,<User u'susan'>]>>> user_role.users.count()2

在视图函数中操作数据库

数据库操作可以直接在视图函数中进行,示例如下:

@app.route('/', methods=['GET', 'POST'])def index():    form = NameForm()    if form.validate_on_submit():        user = User.query.filter_by(username=form.name.data).first()        if user is None:            user = User(username=form.name.data)            db.session.add(user)            session['known'] = False        else:            session['known'] = True        session['name'] = form.name.data        form.name.data = ''        return redirect(url_for('index')),         form=form, name=session.get('name'),         known=session.get('known', False))

在这个修改后的版本中,提交表单后,程序会使用filter_by()查询过滤器在数据库中查找提交的名字,变量known被写入用户会话中,因此重定向之后,可以把数据传给模板,用来显示自定义的欢迎消息,要想让程序正常运行,必须按照之前的方法,在Python shell中创建数据库表

下面是对应的模板新版本,使用known参数在欢迎消息中加入了第二行,从而对已知用户和新用户显示不同的内容

# index.html{% extends 'base.html' %}{% import "bootstrap/wtf.html" as wtf %}{% block page_content %}<div class="page-header">   <h1>Hello, {% if name %} {{ name }}{% else %}Stringer{% endif %}!!</h1>   {% if not known %}   <p>Pleased to meet you!</p>   {% else %}   <p>Happy to see you again!</p>   {% endif %}</div>{{ wtf.quick_form(form) }}{% endblock %}

集成Python shell

每次启动shell会话都要导入库实例和模型,这很繁琐,我们可以做些配置,让Flask-Script的shell命令自动导入特定的对象

若想把对象添加到导入列表中,我们要为shell命令注册一个make_context回调函数,如下:

# 为shell命令添加一个上下文from flask_script import Shelldef 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

>>> app<Flask 'hello'>>>> db<SQLAlchemy engine='sqlite:///D:\\flasky\\env\\data.sqlite'>>>> User<class '__main__.User'>>>>

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

在开发程序的过程中,会发现有时需要修改数据库模型,而且修改之后还需要更新数据库

仅当数据库表不存在时,Flask-SQLAlchemy才会根据模型进行创建,因此更新表的唯一方式就是先删除旧表,不过这么做会丢失数据库中的所有数据

更好的方法是使用数据库迁移框架, 源码版本控制工具可以跟踪源码文件的变化,类似的,数据库迁移框架能跟踪数据库模式的变化,然后增量式的把变化应用到数据库中

SQLAlchemy的主力开发人员编写了一个迁移框架Alembic,除了直接使用Alembic之外,Flask程序还可以使用Flask-Migrate拓展,这个拓展对Alembic做了轻量级包装,并集成到Flask-Script中

创建迁移仓库

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

(venv) % pip install flask-Migrate

然后配置

from flask_migrate import Migrate, MigrateCommand# ...migrate = Migrate(app, db)manager.add_command('db', MigrateCommand)# ...

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

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

(env) PS D:\flasky\env> python hello.py db initCreating directory D:\flasky\env\migrations ... doneCreating directory D:\flasky\env\migrations\versions ... doneGenerating D:\flasky\env\migrations\alembic.ini ... doneGenerating D:\flasky\env\migrations\env.py ... doneGenerating D:\flasky\env\migrations\env.pyc ... doneGenerating D:\flasky\env\migrations\README ... doneGenerating D:\flasky\env\migrations\script.py.mako ... donePlease edit configuration/connection/logging settings in 'D:\\flasky\\env\\migrations\\alembic.ini' before proceeding.

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

数据库迁移仓库中的文件要和程序的其他文件一起纳入版本控制

创建迁移脚本

在Alembic中,数据库迁移用迁移脚本表示,脚本中有两个函数,分别是upgrade()downgrade()upgrade()函数把迁移中的改动应用到数据库中,downgrade()函数则将改动删除,Alembic具有增加和删除改动的能力,因此数据库可重设到修改历史的任意一点

我们可以使用revision命令手动创建Alembic迁移,也可使用migrate命令自动创建,手动创建的迁移只是个骨架,upgrade()downgrade()都是空的,开发者要使用Alembic提供的Operations对象指令实现具体操作,自动创建的迁移会根据模型定义和数据库当前状态之间的差异生成upgrade()downgrade()函数的内容

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

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

(env) PS D:\flasky\env> python hello.py db migrate -m 'initial migration'

更新数据库

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

(env) PS D:\flasky\env> python hello.py db upgradeINFO  [alembic.runtime.migration] Context impl SQLiteImpl.INFO  [alembic.runtime.migration] Will assume non-transactional DDL.

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

0 0