Flask Web Development —— 数据库(中)

七、关系

关系数据库经过使用关系在不一样的表中创建链接。图像5-1的关系图表达了用户和用户角色之间的简单关系。这个角色和用户是一对多关系,由于一个角色能够从属于多个用户,而一个用户只能拥有一个角色。python

示例5-3的模型类展现了图像5-1中表达的一对多关系。git

示例5-3. hello.py:关系github

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

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

就像图像5-1中看到的那样,关系经过使用外键来链接两行。添加给User模型的role_id列被定义为外键,且创建关系。db.ForeignKey()的参数roles.id指定的列应该理解为在roles表的行中持有id值的列。sql

添加到Role模型的users属性表现了关系的面向对象的观点。给定Role类的实例,users属性会返回一组链接到该角色的用户。指定给db.relationship()的第一个参数代表模型中关系的另外一边。若是类还未定义,这个模型能够做为字符串提供。shell

注意:以前在segmentdefault中遇到的问题,后来粗略阅读了SQLAlchemy的源码。ForeignKey类的column接收三种类型的参数,一种是“模型名.属性名”;一种是“表名.列名”,最后一种没看明白,下次试着用一下。数据库

db.relationship()backref参数经过给User模型增长role属性来定义反向关系。这个属性能够替代role_id访问Role模型,是做为对象而不是外键。flask

大多数状况下db.relationship()能够定位本身的外键关系,可是有时候不能肯定哪一个列被用做外键。例如,若是User模型有两个或更多列被定义为Role的外键,SQLAlchemy将不知道使用两个中的哪个。每当外键配置模棱两可的时候,就必须使用额外参数db.relationship()。表格5-4列出一些经常使用配置选项用于定义关系。session

表格5-4. 经常使用SQLAlchemy关系选项app

建议:若是你有克隆在GitHub上的应用程序,你如今能够运行git checkout 5a来切换到这个版本的应用程序。函数

除了一对多关系还有其余种类关系。一对一关系能够表述为前面描述的一对多关系,只要将db.relationship()中的uselist选项设置为False,“多”就变为“一”了。多对一关系也可表示为将表反转后的一对多关系,或表示为外键和db.relationship()定义在“多”那边。最复杂的关系类型,多对多,须要一个被称做关联表的额外表。你将在第十二章学习多对多关系。

八、数据库操做

根据图像5-1的数据库图,模型已经彻底配置完且准备好使用。学习怎样使用模型的最好方式就是使用Python shell。如下部分将介绍最多见的数据库操做。

8.一、建立表

首先要作的第一件事情就是指示Flask-SQLAlchemy基于模型类建立数据库。db.create_all()函数会完成这些:

(venv) $ python hello.py shell 
>>> from hello import db
>>> db.create_all()

若是你检查应用程序目录,你会发现名为data.sqlite的新文件,SQLite数据库名在配置中给出。若是数据库已存在db.create_all()函数不会从新建立或更新数据库表。这会很是的不方便当模型被修改且更改须要应用到现有的数据库时。更新现有的数据库表的蛮力解决方案是先删除旧的表:

>>> db.drop_all()
>>> db.create_all()

不幸的是,这种方法有个不受欢迎的反作用就是摧毁旧的数据库中的全部数据。更新数据库问题的解决方案会在这章快结束的时候介绍。

8.二、插入行

下面的示例会建立新的角色和用户:

>>> 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会话没有任何联系。数据库会话也叫事务

数据库会话在数据库一致性上是很是有用的。提交操做会原子性地将全部添加到会话中的对象写入数据库。若是在写入的过程发生错误,会将整个会话丢弃。若是你老是在一个会话提交相关修改,你必须保证避免因部分更新致使的数据库不一致的状况。

注:数据库会话也能够回滚。若是调用db.session.rollback(),任何添加到数据库会话中的对象都会恢复到它们曾经在数据库中的状态。

8.三、修改行

数据库会话中的add()方法一样能够用于更新模型。继续在同一shell会话中,下面的示例重命名“Admin”角色为“Administrator”:

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

注意:不过貌似咱们在作更新操做的时候都不使用db.session.add(),而是直接使用db.session.commit()来提交事务。

8.四、删除行

数据库会话一样有delete()方法。下面的示例从数据库中删除“Moderator”角色:

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

注意删除,和插入更新同样,都是在数据库会话提交后执行。

8.五、返回行

Flask-SQLAlchemy为每一个模型类建立一个query对象。最基本的查询模型是返回对应的表的所有内容:

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

使用过滤器能够配置查询对象去执行更具体的数据库搜索。下面的例子查找全部被分配“User”角色的用户:

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

对于给定的查询还能够检查SQLAlchemy生成的原生SQL查询,并将查询对象转换为一个字符串:

>>> 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 FROM users WHERE :param_1 = users.role_id'

若是你退出shell会话,在前面的示例中建立的对象将不能做为Python对象而存在,但可继续做为行记录存在各自的数据库表中。若是你开始一个全新的shell会话,你必须从它们的数据库行中从新建立Python对象。下面的示例执行查询来加载名字为“User”的用户角色。

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

过滤器如filter_by()经过query对象来调用,且返回通过提炼后的query。多个过滤器能够依次调用直到须要的查询配置结束为止。

表格5-5展现一些查询中经常使用的过滤器。完整的列表参阅SQLAlchemy文档

表格5-5.经常使用SQLAlchemy查询过滤器

在须要的过滤器已经所有运用于query后,调用all()会触发query执行并返回一组结果,可是除了all()之外还有其余方式能够触发执行。表格5-6.展现其余查询执行方法。

表格5-6.经常使用SQLAlchemy查询执行器

关系的原理相似于查询。下面的示例从两边查询角色和用户之间的一对多关系:

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

此处的user_role.users查询有点小问题。当user_role.users表达式在内部调用all()时经过隐式查询执行来返回用户的列表。由于查询对象是隐藏的,是不可能经过附加查询过滤器进一步提取出来。在这个特定的例子中,它多是用于按字母排列顺序返回用户列表。在示例5-4中,被lazy = 'dynamic'参数修改过的关系配置的查询是不会自动执行的。

示例5-4. app/models.py:动态关系

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

用这种方式配置关系,user_roles.user查询尚未执行,因此能够给它增长过滤器:

>>> user_role.users.order_by(User.username).all()
[<User u'david'>, <User u'susan'>]
>>> user_role.users.count()
2
相关文章
相关标签/搜索