Flask - 数据库 - 1

1 学习目标

  1. 能够理解 ORM 工作原理以及其优缺点
  2. 能够写出在 Flask 中连接 MySQL 的配置项格式(IP,端口,数据库)
  3. 能够使用 SQLAlchemy 定义出关系为一对多模型类
  4. 能够使用 SQLAlchemy 的相关函数创建表及删除表
  5. 能够写出的指定模型数据对数据库的增删改代码
  6. 能够写出模型数据按照条件查询的功能逻辑
  7. 能够写出模型数据按照指定数量分页的功能逻辑
  8. 能够写出模型数据按照指定条件排序的功能逻辑

2 ORM

ORM 全拼 Object-Relation Mapping,文意为对象-关系映射,主要实现模型对象到关系数据库数据的映射,比如:把数据库表中每条记录映射为一个模型对象

2.1 ORM图解

优点 :

  • 只需要面向对象编程,不需要面向数据库编写代码
    • 对数据库的操作都转化成对类属性和方法的操作
    • 不用编写各种数据库的sql语句
  • 实现了数据模型与数据库的解耦,屏蔽了不同数据库操作上的差异
    • 不在关注用的是mysqloracle...等
    • 通过简单的配置就可以轻松更换数据库,而不需要修改代码

缺点 :

  • 相比较直接使用SQL语句操作数据库,有性能损失
  • 根据对象的操作转换成SQL语句,根据查询的结果转化成对象,在映射过程中有性能损失

3 Flask-SQLAlchemy安装及设置

  1. SQLALchemy 实际上是对数据库的抽象,让开发者不用直接和 SQL 语句打交道,而是通过 Python 对象来操作数据库,在舍弃一些性能开销的同时,换来的是开发效率的较大提升
  2. SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。
  3. 文档地址:http://docs.jinkan.org/docs/flask-sqlalchemy

3.1 安装

  • 安装 flask-sqlalchemy
pip install flask-sqlalchemy
  • 如果连接的是 mysql 数据库,需要安装 mysqldb
pip install flask-mysqldb
  • 在安装flask-mysqldb的时候可能会报错:mysql_config not found

  • 解决如下,输入以下代码 :
sudo aptitude install libmysqlclient-dev

3.2 数据库连接设置

3.2.1 连接 MySQL 数据库

完整连接 URI 列表请跳转到 SQLAlchemy 下面的文档 (Supported Databases) 。这里给出一些 常见的连接字符串。

  • Postgres:
postgresql://scott:tiger@localhost/mydatabase
  • MySQL:
mysql://scott:tiger@localhost/mydatabase
  • Oracle:
- oracle://scott:tiger@127.0.0.1:1521/sidname
  • SQLite (注意开头的四个斜线):
sqlite:absolute/path/to/foo.db
  • 在 Flask-SQLAlchemy 中,数据库使用URL指定,而且程序使用的数据库必须保存到Flask配置对象的 SQLALCHEMY_DATABASE_URI 键中
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test'

数据库连接url的说明:

mysql://:是协议

root:用户名

mysql:密码

127.0.0.1:数据库所在电脑

3306:数据库端口号

test:数据库名

  • 其他设置:
# 动态追踪修改设置,如未设置只会提示警告
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
#查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
  • 配置完成需要去 MySQL 中创建项目所使用的数据库
mysql -uroot -pmysql
create database test charset utf8;
  • 其他配置
名字备注
SQLALCHEMY_DATABASE_URI用于连接的数据库 URI 。例如:sqlite:tmp/test.dbmysql://username:password@server/db
SQLALCHEMY_BINDS一个映射 binds 到连接 URI 的字典。更多 binds 的信息见用 Binds 操作多个数据库
SQLALCHEMY_ECHO如果设置为Ture, SQLAlchemy 会记录所有 发给 stderr 的语句,这对调试有用。(打印sql语句)
SQLALCHEMY_RECORD_QUERIES可以用于显式地禁用或启用查询记录。查询记录 在调试或测试模式自动启用。更多信息见get_debug_queries()。
SQLALCHEMY_NATIVE_UNICODE可以用于显式禁用原生 unicode 支持。当使用 不合适的指定无编码的数据库默认值时,这对于 一些数据库适配器是必须的(比如 Ubuntu 上 某些版本的 PostgreSQL )。
SQLALCHEMY_POOL_SIZE数据库连接池的大小。默认是引擎默认值(通常 是 5 )
SQLALCHEMY_POOL_TIMEOUT设定连接池的连接超时时间。默认是 10 。
SQLALCHEMY_POOL_RECYCLE多少秒后自动回收连接。这对 MySQL 是必要的, 它默认移除闲置多于 8 小时的连接。注意如果 使用了 MySQL , Flask-SQLALchemy 自动设定 这个值为 2 小时。

3.2.2 连接其他数据库

完整连接 URI 列表请跳转到 SQLAlchemy 下面的文档 (Supported Databases) 。这里给出一些 常见的连接字符串。

  • Postgres:
postgresql://scott:tiger@localhost/mydatabase
  • MySQL:
mysql://scott:tiger@localhost/mydatabase
  • Oracle:
- oracle://scott:tiger@127.0.0.1:1521/sidname
  • SQLite (注意开头的四个斜线):
sqlite:absolute/path/to/foo.db

3.2.3 常用的SQLAlchemy字段类型

类型名python中类型说明
Integerint普通整数,一般是32位
SmallIntegerint取值范围小的整数,一般是16位
BigIntegerint或long不限制精度的整数
Floatfloat浮点数
Numericdecimal.Decimal普通整数,一般是32位
Stringstr变长字符串
Textstr变长字符串,对较长或不限长度的字符串做了优化
Unicodeunicode变长Unicode字符串
UnicodeTextunicode变长Unicode字符串,对较长或不限长度的字符串做了优化
Booleanbool布尔值
Datedatetime.date时间
Timedatetime.datetime日期和时间
LargeBinarystr二进制文件

3.2.4 常用的SQLAlchemy列选项

选项名说明
primary_key如果为True,代表表的主键
unique如果为True,代表这列不允许出现重复的值
index如果为True,为这列创建索引,提高查询效率
nullable如果为True,允许有空值,如果为False,不允许有空值
default为这列定义默认值

3.2.5 常用的SQLAlchemy关系选项

选项名说明
backref在关系的另一模型中添加反向引用
primary join明确指定两个模型之间使用的联结条件
uselist如果为False,不使用列表,而使用标量值
order_by指定关系中记录的排序方式
secondary指定多对多关系中关系表的名字
secondary join在SQLAlchemy中无法自行决定时,指定多对多关系中的二级联结条件

3.2.6 SQLAlchemy使用操作

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

# 配置数据库连接地址
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://root:mysql@127.0.0.1:3306/test_27"
# 是否追踪数据库的修改
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

@app.route("/")
def index():
    return "index"


if __name__ == '__main__':
    app.run(debug=True)

SQLAlchemy常用配置就这两个:uri 和 track_modifications

4 数据库基本操作

在Flask-SQLAlchemy中,插入、修改、删除操作,均由数据库会话管理。

  • 会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 commit() 方法提交会话。

在 Flask-SQLAlchemy 中,查询操作是通过 query 对象操作数据。

  • 最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。

4.1 添加模型&增删改

4.1.1 准备工作

通过对象操作数据库,那么首先需要创建数据库 test:

create database test charset utf8;

进入看一眼,目前还没有表:

4.1.2 添加模型

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

# 配置数据库连接地址
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://root:mysql@127.0.0.1:3306/test"
# 是否追踪数据库的修改
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)


class Role(db.Model):
    # 指定该模型对应数据库中的表名,如果不指定,表名就为类名小写,即“role”
    __tablename__ = "roles"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(64), unique=True)

    def __repr__(self):
        return "Role: %s %s" % (self.id, self.name)


@app.route("/")
def index():
    return "index"


if __name__ == '__main__':
    db.create_all()
    app.run(debug=True)

代码说明:

__repr__ :类似于__str__,打印Role实例对象的时候会调用__repr__

db.create_all():创建所有继承 db.Model 的模型对应的数据库表

接下来运行此程序,然后查看表:

4.1.3 增加

通过 pycharm 的 terminal 进入 ipython3,输入如下代码:

增加数据,需要新建Role实例对象,并且添加到db.session中,最终还要commit提交才可以

注意

这里的session(会话)跟视图中操作的session不一样,这里他是自己做了一个会话,可以将多个数据库操作一次性添加到会话中,最终commit一次即可

commit之后,role.id竟然有值了,这是因为插入成功之后,数据库有了id值,然后SQLAlchemy给数据库的改变反馈到了实例对象role上了,所以role.id有值了

插入成功之后,查询数据库:(插入之前没有数据,插入之后有一条数据)

4.1.4 修改

注意:

这里的修改操作也应该通过db.session.add(role)来处理,但是因为上边增加操作已经将role 添加到db.session中了。所以这里可以省略第二步。(其实建议再写一遍,这样更规范)

4.1.5 删除

注意:删除操作是通过db.session.delete()来处理的

4.2 数据库一对多的关系定义

4.2.1 准备工作

接下来我们讨论一下多表的关系:一对多关系

那么就需要增加一个模型类User,Role 与 User 的关系是一对多。

增加User如下:

增加测试数据:

if __name__ == '__main__':
    db.drop_all()
    db.create_all()

    # 插入一条数据
    ro1 = Role(name='admin')
    db.session.add(ro1)
    db.session.commit()
    # 再次插入一条数据
    ro2 = Role(name='user')
    db.session.add(ro2)
    db.session.commit()
    
    # 一次插入多条数据
    us1 = User(name='wang', email='wang@163.com', password='123456', role_id=ro1.id)
    us2 = User(name='zhang', email='zhang@189.com', password='201512', role_id=ro2.id)
    us3 = User(name='chen', email='chen@126.com', password='987654', role_id=ro2.id)
    us4 = User(name='zhou', email='zhou@163.com', password='456789', role_id=ro1.id)
    us5 = User(name='tang', email='tang@itheima.com', password='158104', role_id=ro2.id)
    us6 = User(name='wu', email='wu@gmail.com', password='5623514', role_id=ro2.id)
    us7 = User(name='qian', email='qian@gmail.com', password='1543567', role_id=ro1.id)
    us8 = User(name='liu', email='liu@itheima.com', password='867322', role_id=ro1.id)
    us9 = User(name='li', email='li@163.com', password='4526342', role_id=ro2.id)
    us10 = User(name='sun', email='sun@163.com', password='235523', role_id=ro2.id)
    db.session.add_all([us1, us2, us3, us4, us5, us6, us7, us8, us9, us10])
    db.session.commit()
    
    app.run(debug=True)

问题:

  • 为啥create_all()之前要先drop_all()?

因为执行已经生成过Role表了,现在如果重新创建,肯定会报错,所以先删除所有表,在重新创建

  • 为啥ro1和ro2添加到db.session之后,要立马commit,不能和user一起commit么?

运行,生成表和数据:

4.2.2 关联查询

进入ipython3,我们来看一下一对多查询:

说明:

all 返回两个用户的列表,列表中的每一项是一个role对象;

为啥是Role 1 admin,这是因为__repr__方法就是这样返回的

我们先执行如下代码:

然后获取id3的用户的角色信息:

你会发现这样查询很麻烦,需要先通过id3查询到user,然后在查询user身上的role_id角色id,在通过角色id查询Role表才可以知道。那我们畅想一下:

如何实现畅想呢?给Role添加一个关系属性users:

上图代码说明:users属性关联着User模型类

接下来查询:

那么想查询用户所属角色怎么办?在后边加上一句:backref("role")

users = db.relationship("User", backref="role")

查询:

4.3 数据库的简单查询

4.3.1 常用的SQLAlchemy查询过滤器

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

4.3.2 常用的SQLAlchemy查询执行器

方法说明
all()以列表形式返回查询的所有结果
first()返回查询的第一个结果,如果未查到,返回None
first_or_404()返回查询的第一个结果,如果未查到,返回404
get()返回指定主键对应的行,如不存在,返回None
get_or_404()返回指定主键对应的行,如不存在,返回404
count()返回查询结果的数量
paginate()返回一个Paginate对象,它包含指定范围内的结果

4.3.3 牛刀小试

接下来我们给下边的所有题目做完即可:

查询所有用户数据
查询有多少个用户
查询第1个用户
查询id为4的用户[3种方式]
查询名字结尾字符为g的所有数据[开始/包含]
查询名字不等于wang的所有数据[2种方式]
查询名字和邮箱都以 li 开头的所有数据[2种方式]
查询password是 `123456` 或者 `email` 以 `itheima.com` 结尾的所有数据
查询id为 [1, 3, 5, 7, 9] 的用户列表
查询name为liu的角色数据
查询所有用户数据,并以邮箱排序
每页3个,查询第2页的数据

答案如下: 

In [1]: from test import *                                                                        

In [2]: User.query.all()                                                                          
Out[2]: 
[User: 1 wang,
 User: 2 zhang,
 User: 3 chen,
 User: 4 zhou,
 User: 5 tang,
 User: 6 wu,
 User: 7 qian,
 User: 8 liu,
 User: 9 li,
 User: 10 sun]

In [3]: User.query.count()                                                                        
Out[3]: 10

In [4]: User.query.first()                                                                        
Out[4]: User: 1 wang

In [5]: User.query.get(4)                                                                         
Out[5]: User: 4 zhou

In [6]: User.query.filter_by(id=4).first()                                                        
Out[6]: User: 4 zhou

In [7]: User.query.filter(User.id==4).first()                                                     
Out[7]: User: 4 zhou

In [10]: User.query.filter(User.name.endswith("g")).all()                                         
Out[10]: [User: 1 wang, User: 2 zhang, User: 5 tang]                                                                     

In [11]: User.query.filter(User.name.endswith("g")).all()                                         
Out[11]: [User: 1 wang, User: 2 zhang, User: 5 tang]

In [12]: User.query.filter(User.name.startswith("g")).all()                                       
Out[12]: []

In [13]: User.query.filter(User.name.contains("g")).all()                                         
Out[13]: [User: 1 wang, User: 2 zhang, User: 5 tang]

In [14]: User.query.filter(User.name != "wang").all()                                             
Out[14]: 
[User: 2 zhang,
 User: 3 chen,
 User: 4 zhou,
 User: 5 tang,
 User: 6 wu,
 User: 7 qian,
 User: 8 liu,
 User: 9 li,
 User: 10 sun]

In [17]: from sqlalchemy import not_                                                              

In [18]: User.query.filter(User.name != "wang").all()                                             
Out[18]: 
[User: 2 zhang,
 User: 3 chen,
 User: 4 zhou,
 User: 5 tang,
 User: 6 wu,
 User: 7 qian,
 User: 8 liu,
 User: 9 li,
 User: 10 sun]

In [19]: from sqlalchemy import not_                                                              

In [20]: User.query.filter(not_(User.name == "wang")).all()                                       
Out[20]: 
[User: 2 zhang,
 User: 3 chen,
 User: 4 zhou,
 User: 5 tang,
 User: 6 wu,
 User: 7 qian,
 User: 8 liu,
 User: 9 li,
 User: 10 sun]

In [21]: User.query.filter(User.name.startswith("li"), User.email.startswith("li")).all()         
Out[21]: [User: 9 li, User: 8 liu]

In [22]: from sqlalchemy import and_                                                              

In [23]: User.query.filter(and_(User.name.startswith("li"), User.email.startswith("li"))).all()   
Out[23]: [User: 9 li, User: 8 liu]

In [24]: from sqlalchemy import or_                                                               

In [25]: User.query.filter(or_(User.password == "123456", User.email.endswith("itheima.com"))).all
    ...: ()                                                                                       
Out[25]: [User: 1 wang, User: 5 tang, User: 8 liu]

In [26]: User.query.filter(User.id.in_([1, 3, 5, 7, 9])).all()                                    
Out[26]: [User: 1 wang, User: 3 chen, User: 5 tang, User: 7 qian, User: 9 li]

In [28]: User.query.filter(User.name == "liu").first().role                                       
Out[28]: Role: 1 admin

In [29]: User.query.order_by(User.email).all()                                                    
Out[29]: 
[User: 3 chen,
 User: 9 li,
 User: 8 liu,
 User: 7 qian,
 User: 10 sun,
 User: 5 tang,
 User: 1 wang,
 User: 6 wu,
 User: 2 zhang,
 User: 4 zhou]

In [30]: User.query.order_by(User.email.desc()).all()                                             
Out[30]: 
[User: 4 zhou,
 User: 2 zhang,
 User: 6 wu,
 User: 1 wang,
 User: 5 tang,
 User: 10 sun,
 User: 7 qian,
 User: 8 liu,
 User: 9 li,
 User: 3 chen]

In [31]: User.query.order_by(User.email.asc()).all()                                              
Out[31]: 
[User: 3 chen,
 User: 9 li,
 User: 8 liu,
 User: 7 qian,
 User: 10 sun,
 User: 5 tang,
 User: 1 wang,
 User: 6 wu,
 User: 2 zhang,
 User: 4 zhou]

In [34]: paginate = User.query.paginate(2, 3)                                                     

In [35]: paginate.items                                                                           
Out[35]: [User: 4 zhou, User: 5 tang, User: 6 wu]

In [36]: paginate.pages                                                                           
Out[36]: 4

In [37]: paginate.page                                                                            
Out[37]: 2

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值