05.flask-sqlalchemy
安装模块
pip install flask-sqlalchemy
pip install pymysql安装mysql
介绍
ORM介绍
ORM Object Relationship Mapping
模型与表之间的映射
SQLAlchemy
采用写原生sql的方式在代码中会出现大量的sql语句,会出现一些问题:
sql语句重复利用率不高,越复杂的sql语句条件越多,代码越长。会出现很多相近的sql语句
很多sql语句是在业务逻辑中拼出来的,如果有数据库需要更改,就要去修改这些逻辑,这会很容易漏掉对某些sql语句的修改。
写sql时容易忽略web安全问题,给未来造成隐患
ORM,全称Object Relational Mapping,中文叫做对象关系映射,通过ORM可以通过类的方式去操作数据库,而不用再写原生的sql语句。通过把表映射成类,把行作实例,把字段作为属性,ORM在执行对象操作的时候最终还是会把对象的操作转换为数据库原生语句。使用ORM有许多有点:
易用性:使用ORM做数据库的开发可以有效的减少重复sql语句的概率,写出来的模型也更加直观清晰。
性能损耗小:ORM转换成底层数据库操作指令确实会有一些开销。但从实际的情况来看,这种性能损耗很少,只要不是对性能有严格的要求,综合考虑开发效率,代码的阅读性,带来的好处要远大于性能损耗,而且项目越大作用越明显。
设计灵活:可以轻松的写出复杂的查询
可移植性:SQLAlchemy封装了底层的数据库实现,支持多个关系数据库引擎,包括流行的MySQL、PostgreSQL和SQLite。可以非常轻松的切换数据库
连接数据库
创建数据库

定义数据库连接字符串DB_URI
格式:dialect+dricer://username:password@host:port/database
如下:
将定义好的数据库连接字符串DB_URI放到配置文件中
定义SQLAlchemy对象
运行后,出现如下警告:
只需要添加如下即可:

创建模型
通过使用db.Model进行创建模型类。
最小应用
为了创建初始数据库,只需要从交互式 Python shell 中导入 db 对象并且调用 SQLAlchemy.create_all()方法来创建表和数据库:
如下:
这样就会在数据库生成表

如果想要删除,就是用db.drop_all()。
目前数据库已经生成,现在进行创建一些用户:
但是它们还没有真正地写入到数据库中,因此动手确保它们已经写入到数据库中:

访问数据库中的数据也是十分简单的:
返回结果大小控制
all() 返回所有
first() 查询并返回第一条,没有数据为空
one() 查询所有并严格返回一条数据,如果查询到多条数据或没有数据,都会报错
one_or_none 同 one,没有数据会返回None,不会报错,其他一样。
scalar 同 one,但是只返回那条数据的第一个字段。
增删查改
定义模型
添加
添加单个数据
一次性添加多个数据

查询
详细查询方法,见后续
使用filter_by来做条件查询
使用filter来做条件查询
使用get方法查找数据,get方法是根据id来查询的,只会返回一条数据或者None
使用first方法获取结果集中的第一条数据
修改
修改对象,首先在数据库查找数据,然后将数据进行修改,最后做commit操作

删除
删除对象,首先在数据库查找数据,然后将查找到的数据进行删除,最后执行commit操作

表名与列名设置
表名通过
__tablename__进行设置表名称,否则会以类名进行命名。例如UserModel的表名为user_model
列名,通过
db.Column中的name参数进行命名
常用数据类型详解
Integer:整型
Float:浮点类型
Boolean:传递True/False进去
DECIMAL:定点类型,是专门为了解决浮点精度丢失的问题的,在存储相关的字段的时候建议都是要这个数据字段,并且这个类型使用的时候需要传递两个参数,第一个参数使用来标记字段能够存储多少个数字,第二个参数是表示小数点的最大位数
enum:枚举类型。指定某个字段只能是枚举中指定的几个值,不能为其他值,在ORM模型中,使用Enum来作为枚举,示例代码如下:
Date:传递datetime.date()进去。存储时间,只能存储年月日。映射到数据库中是date类型。在python中,可以使用datetime.date来指定
DateTime:传递datetime.datetime()进去。存储时间,可以存储年月日时分秒毫秒等。映射到数据库中也是datetime类型。在python代码中,可以使用"datetime.datetime"来指定,示例:
Time:传递datetime.time()进去。存储时间,可以存储时分秒,映射到数据库中也是time类型。在python代码中,可以使用"datetime.time"来指定,示例:
String:字符类型,使用时需要指定长度,区别于Text类型
Text:文本类型.存储长字符串,一般可以存储6w多个字符。如果超出了这个范围,可以使用LONGTEXT类型,映射到数据库中就是text类型
LONGTEXT:长文本类型
Column常用参数
default:设置某个字段的默认值。默认值可以为一个表达式或者变量,函数
nullable:设置某个字段是否可空,默认值为True
primary_key:设置某个字段为主键
unique:设置某个字段为唯一的字段,不允许有重复值,默认值为True
autoincrement:设置这个字段为自动增长的
name:指定orm模型中某个属性映射到表中的字段名,如果不指定,那么会使用这个属性的名字作为字段名。如果指定了,就会使用指定的这个值作为参数,这个参数也可以当做位置参数,在第1个参数来指定。
从交互式 Python shell 中进行创建:

onupdate:在数据更新的时候会调用这个参数指定的值或函数,在第一次插入这条数据的时候,不会用onupdate的值,只会使用default的值,常用的就是"update_time"(每次更新数据的时候都要更新的值)
对artitle的title进行修改,进行观察

现在可以看到update_time已经更新了。
query函数
模型对象。指定查找这个模型中所有的对象
模型中的属性。可以指定只查找某个模型的其中几个属性
聚合函数 :
db.func.count:统计行的数量
db.func.avg:求平均值
db.func.max:求最大值
db.func.min:求最小值
db.func.sum:求和
'db.func'上,其实没有任何聚合函数,但是因为底层做了一些魔术,只要mysql中有聚合函数,都可以通过db.func调用
创建模型
添加数据
通过交互式 Python shell 创建表和数据库:
调用func

filter方法常用过滤条件
过滤是数据库的一个很重要的功能,以下对一些常用的过滤条件进行解释,并且这些过滤条件都是只能通过filter方法实现的。
如果想看底层sql原生语句,在语句末尾不加all(),就可以打印出原生sql语句。
定义一个模型
交互shell生成测试数据:

equals(等于)
not equals(不等于)
like & ilike(不区分大小写)
in(含有)
not in(不含有)
is null
is not null
or
and
注意在使用or和and的时候,需要添加
from sqlalchemy import or_,and_或者使用db.or_、db.and_
filter_by方法常用过滤条件
filter_by与filter区别,filter需要使用类名.列名,filter_by只需要使用列名即可
外键及其四种约束讲解
在MySQL中,外键可以让表之间的关系更加紧密,而SQLAlchemy同样支持外键。通过FpreignKey类来实现,并且可以指定表的外键约束。
外键约束有以下几项:
RESTRICT(restrict):父表数据被删除,会阻止删除
NO ACTION:在MySQL中,同RESTRICT
CASCADE:级联删除
SET NULL:父类数据被删除,子表数据设置为NULL
相关示例代码如下:
ORM层外键
mysql级别的外键,还不够ORM,必须拿到一个表中的外键,然后再去通过这个外键再去另外一张表查找。可这样太麻烦了。SQLAlchemy提供了一个"relationship",这个类可以定义属性,以后再访问相关联的表的时候就可以直接通过属性访问的方式就可以访问得到了。
一对一关系
想要一对一关系,可以把 uselist=False 传给 relationship() 。
如下:
另外,可以通过backref来指定反向访问的属性名称。
如下:
添加数据

一对多关系
继续采用用户(user)与文章(article),关系为一个用户拥有多篇文章。如下:
通过backref来指定反向访问的属性名称。
添加数据

查询
通过user.articles这样进行调用属性,就能够方便的获取相关表的数据。
多对多关系
想要用多对多关系,需要定义一个用于关系的辅助表。对于这个辅助表, 强烈建议不使用模型,而是采用一个实际的表:
如下问题,继续采用用户(user)与文章(article),关系为一个用户拥有多篇文章,一篇文章有一个以上用户。即:
在relationship函数中添加secondary=user_article,即可。其中user_article为中间表。
不需要设置外键了
添加数据

查询
ORM层面删除数据注意事项
ORM层面删除数据,会无视mysql级别的外键约束,直接会对将对应的数据删除,然后将从表中的那个外键设置为NULL,如果想要避免这种行为,应该将从表中的外键的"nullable=False"。
relationship方法中的cascade参数详解
如果将数据库得到外键设置为RESTRICT,那么在ORM层面,删除了父表中的数据,那么从表中的数据将会NULL。如果不想要这种情况发生,那么应该将这个值的nullable=False。
在SQLAlchemy,只要将一个数据添加到session中,和它相关联的数据都可以一起存入到数据库中了。这些是怎么设置的呢?其实是通过relationship的时候 ,有一个关键字参数cascade可以设置这些属性:
save-update:默认选项。在添加一条数据的时候,会把其他和它相关联的数据都添加到数据库中。这种行为就是save-update属性影响的。
delete:表示当删除某一个模型中的数据的时候,是否也删除掉使用relationship和它关联的数据。
delete-orphan:表示当对一个ORM对象解除了父表中的关联对象的时候,自己便会被删除掉。当然如果表中的数据被删除,自己也会被删除。这个选项只能用在一对多上,不能用在多对多以及多对一上。并且还需要在子模型中的relationship中,增加一个single_parent=True的参数。
merge:默认选项。当在使用session.merge,合并一个对象的时候,会将使用了relationship相关联的对象也进行merge操作
expunge:移除操作的时候,会将相关联的对象也进行移除。这个操作只是从session中移除,并不会真正的从数据库中删除。
all:是对save-update,merge,refresh-expire,expunge,delete几种的填写
例如:
三种排序方式详解
创建模型类
添加数据

调用order_by
order_by:可以指定根据这个表中的某个字段进行排序,如果在后面添加desc(),就能够调用desc()方法进行降序。而升序,sqlalchemy是默认为升序的,或者调用asc()方法。
模型定义时指定默认排序
在模型定义的时候指定默认排序:有些时候,不想每次在查询的时候都指定排序的方式,可以在定义模型的时候就指定排序的方式。
有以下两种方式:
relationship的order_by参数:在指定relationship的时候,传递order_by参数来指定排序的字段
在模型定义中,添加以下代码:
__mapper_args__ = { 'order_by':title, }
即可让文章用标题来进行排序
limit、offset以及切片操作
添加数据

含义为返回从第9行起,后面4行数据,包括第9行。
limit:可以限制每次查询的时候只查询几条数据。
offset:可以限制查找数据的时候过来前面多少条。
切片:可以Query对象使用切片操作,来获取想要的数据
数据查询加载技术
lazy 决定了 SQLAlchemy 什么时候从数据库中加载数据。
'select'(默认值) 就是说 SQLAlchemy 会使用一个标准的 select 语句必要时一次加载数据。'joined'告诉 SQLAlchemy 使用JOIN语句作为父级在同一查询中来加载关系。'subquery'类似'joined',但是 SQLAlchemy 会使用子查询。'dynamic'在有多条数据的时候是特别有用的。不是直接加载这些数据,SQLAlchemy 会返回一个查询对象,在加载数据前您可以过滤(提取)它们。
如下:
group_by和having子句
创建模型
添加数据
group_by
根据某个字段进行分组
示例
统计每个年龄段的人数,sql语句为:
实现:
having
having是对查找结果进一步过滤,在分组的基础上在进行筛选过滤
示例
统计每个年龄段的人数,并筛选出年龄小于18的,sql语句为:
实现:
复杂查询
创建模型类
添加数据

join实现复杂查询
join查询分为两种,
inner join
left join
right join
outer join。
如果想要查询User及其对应的Address,则可以通过以下方式来实现:
tips:join方法配合filter过滤方法一起使用
left join
sql语句:
实现:

right join
sql语句:
实现:

outer join
sql语句:
实现:

subquery实现复杂查询
模型类
添加数据

子查询可以让多个查询变成一个查询,只要查找一次数据库,性能相对来讲更加高效一点,不用写多个sql语句就可以一些复杂的查询了,那么在sqlalchemy中,要实现一个子查询,应该使用以下几个步骤:
1.将子查询按照传统的方式写好查询代码,然后在"query"对象后面执行"subquery"方法,将这个查询变成一个子查询。
2.在子查询中,将以后需要用到的字段通过"label"方法,取个别名
3.在父查询中,如果想要使用子查询的字段,那么可以通过子查询的变量上的"c"属性拿到
使用别名(aliased)
SQLAlchemy 使用 aliased() 方法表示别名,当需要把同一张表连接多次的时候,常常需要用到别名。
text - 直接写sql
在text里写sql语句,并在
filter和order_by中使用
text里可以用
:name传动态参数,并params传值
text里也可以给完整的sql语句,然后传给
from_statement
如果用from_statement中不是给的所有字段,那可用 columns 将值赋给字段
Flask-Script
Flask-Script的作用是可以通过命令行的形式来操作Flask。
安装
三种创建命令:使用@command修饰符、使用@option修饰符、创建command子类
@command修饰符
运行:

@option修饰符
添加参数
运行:

command子类
运行:

子类
db_script.py
manage.py
运行:

tips:执行db_script.py下的init函数
add_command()添加子类,将db_manager映射为db
项目结构重构
为什么进行项目重构?
因为flask项目中,有些变量或者对象会被调用在多个文件中,可能在调用时形成一个死循环。因此,专门将一些经常使用的对象,放在一个文件中,以便调用。
exts.py
将一些配置信息专门放在一个配置文件中。
config.py
app.py
Flask-Migrate
Flask-Migrate是一个为Flask应用处理SQLAlchemy数据库迁移的扩展,使得可以通过Flask的命令行接口或者Flask-Scripts对数据库进行操作。
安装命令
配置flask-migrate
manage.py
使用init命令创建迁移仓库
使用migrate命令将模型映射到文件中
使用upgrade命令将文件数据映射到数据库中
使用downgrade命令回滚迁移中的数据库改动
更多命令
总步骤

最后更新于
这有帮助吗?
