Flask:使用ORM框架操作数据库

在Flask中并没有限制我们使用哪种方式来操作数据库,如果使用原生SQL的方式来操作数据库,随着项目代码量的提升会发现有大量操作数据库的冗余代码,以及大量SQL语句与逻辑代码杂糅在一起不利于核心代码的读写,并且SQL语句的利用率不高。当更换数据库产品时有些特定SQL语句需要重写,而使用ORM框架能很好的避免上述问题。

ORM

ORM(Object Relational Mapping)中文翻译过来叫作对象关系映射,它把一个类映射成数据库里的一张表而属性映射成数据库表的列,每一个实例对象对应数据库表里的一行数据。通过它我们可以直接使用面向对象的方式来编写程序而不再直接书写原生的SQL语句,而且大部分ORM框架支持多种数据库只需很少的配置即可完成数据库产品的更换,在Python中常见的ORM框架有SQLAlchemy、Django Model、PeeWee等。

Flask-SQLAlchemy扩展

SQLAlchemy是目前很流行的ORM框架,Flask-SQLAlchemy扩展可以帮助我们在Flask中方便的使用SQLAlchemy

安装Flask-SQLAlchemy扩展,假如你没有安装SQLAlchemy会一并安装上

1
pip install flask-sqlalchemy

数据模型

模型是对数据抽象并提供通用访问接口的一种方式,一个模型类对应于数据库中的一张表。

定义模型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///test.db"

db = SQLAlchemy(app)

# 分类
class Category(db.Model):
__tablename__ = "t_category"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))

db.create_all()

代码说明: 所有模型必须继承至 db.Model,使用 __tablename__ 属性指定数据库中表名,如果不指定表名默认为类名。模型类的属性对应于数据表中的列,通过 db.Column 类的实例来描述,最后 db.create_all() 会帮助我们创建表结构

连接数据库的信息通过app.config[“SQLALCHEMY_DATABASE_URI”]来指定,常见数据库的连接信息如下

数据库连接地址
SQLitesqlite:///文件名
MySQLmysql+pymysql://用户名:密码@主机:3306/数据库名
Oracleoracle+cx_oracle://用户名:密码@主机:1521/SID名
SQL Servermssql+pymssql://用户名:密码@主机:端口/数据库名

注: 上面有些URL中的协议部分有加号需要安装指定模块,如MySQL需要安装PyMySQL模块如果不书写加号部分会默认使用MySQLDB进行连接

描述列时常用的数据类型

类型说明
db.Integer整型
db.Float浮点型
db.String变长字符串
db.Date日期类型
db.DateTime日期时间类型
db.Text大文本
db.Boolean布尔型

描述列时还可以添加选项,常见如下

选项名说明
primary_key设置主键
default设置默认值
unique设置是否唯一
nullable设置是否允许为空
index设置是否添加索引

注: SQLAlchemy要求每个模型必须定义主键默认对主键进行自增,默认映射到数据库中的列名为自定义类的属性名称,如果想修改该值需要在db.Column()第一个参数指定

模型之间的关联

模型之间可以互相关联使得相关联的数据能够很容易的从数据库中取出,模型关联映射到数据库中为主外键关系。

一对多/多对一

这是最常见的一种关系,比如说一个分类下面有多篇文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 分类
class Category(db.Model):
__tablename__ = "t_category"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
posts = db.relationship("Post", backref="category")

# 文章
class Post(db.Model):
__tablename__ = "t_post"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(50))
content = db.Column(db.Text)
category_id = db.Column(db.Integer, db.ForeignKey("t_category.id"))

代码说明: 在多的一端通过 db.ForeignKey 定义外键指向一的一端的主键列,该参数值格式为:表名.列名;通过 db.relationship 指定关系,第一个参数填写有关系的模型类,当该模型类还未定义可以先填写字符串底层会通过反射来获取,通过backref参数向有关系的一端添加一个可以引用自己的属性,该语句也可以定义在关系中的任意一方但必须指定的关系要明确。

一对一

常见的场景有一个人只有一个身份证,只需要把一对多关系中多的一端的外键指定为唯一值即可

1
2
3
4
5
6
7
8
9
10
11
12
13
# 人
class People(db.Model):
__tablename__ = "t_people"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15))
codes = db.relationship("Code", backref="people")

# 身份证
class Code(db.Model):
__tablename__ = "t_code"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(18))
p_id = db.Column(db.Integer, db.ForeignKey("t_people.id"), unique=True)

多对多

常见的场景有一个用户对应多个角色,而一个角色同时也能对应多个用户,在关系型数据库中一般通过中间表的形式体现

方式一: 使用中间模型帮我们建立中间表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 用户
class User(db.Model):
__tablename__ = "t_user"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15))
user_roles = db.relationship("UserRole", backref="user")

# 角色
class Role(db.Model):
__tablename__ = "t_role"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15))
role_users = db.relationship("UserRole", backref="role")

# 关系
class UserRole(db.Model):
__tablename__ = "t_user_role"
user_id = db.Column(db.Integer, db.ForeignKey("t_user.id"), primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey("t_role.id"), primary_key=True)

方式二: 手动创建中间表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
t_user_role = db.Table("t_user_role",
db.Column("user_id", db.Integer, db.ForeignKey("t_user.id"), primary_key=True),
db.Column("role_id", db.Integer, db.ForeignKey("t_role.id"), primary_key=True)
)

# 用户
class User(db.Model):
__tablename__ = "t_user"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15))
roles = db.relationship("Role", backref="users", secondary=t_user_role)

# 角色
class Role(db.Model):
__tablename__ = "t_role"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15))

以上两种方式都能实现多对多关系,我个人更喜欢方式二因为方式一多了一个模型类操作起来更繁琐

表管理

在上面的例子中我们只使用了db.create_all(),他会扫描模型创建所有的表,如果想删除所有表使用如下语句

1
db.drop_all()

当然在实际项目中我们不会在代码中管理表一般会借助工具来生成,如使用Flask-Script扩展写一个命令函数,在Shell中执行上面的代码,更高级的使用Flask-Migrate扩展它不仅能创建表还能进行数据迁移。

操作数据

有了模型类也生成了表,接下来通过模型类对数据进行增删改查

新增

只需构建模型类的实例,添加进数据库会话即可

1
2
c = Category(name="Java")
db.session.add(c)

对于一对多/多对一关联关系

1
2
3
4
5
p = Post()
p.title = "Java入门示例"
p.content = "Hello World ...."
p.category = c
db.session.add(p)

对于多对多关系,下例采用方式二定义的多对多关系,至于方式一定义的关联关系操作与一对多类似

1
2
3
4
5
6
7
8
9
10
11
u = User(name="Harmel")
r1 = Role(name="ROOT")
r2 = Role(name="NONE")
db.session.add(r1)
db.session.add(r2)
u.roles = [r1, r2]
db.session.add(u)

# 也可以采用如下方式
u.roles.append(r1)
u.roles.append(r2)

修改

把查出来的对象更改属性后再次保存即可修改

1
2
3
4
# 根据ID查询
c = Category.query.get(1)
c.name = "Python"
db.session.add(c)

删除

显示删除

1
2
c = Category.query.get(1)
db.session.delete(c)

隐式删除就是在一对一或一对多中配置级联删除后,如果删除一的一端另一端的数据也会同步删除

查询

根据ID查询

1
c = Category.query.get(1)

查询所有

1
categorys = Category.query.all()

查询总记录数

1
count = Category.query.count()

将结果排序

1
2
3
4
5
# 默认是顺序排列
categorys = Category.query.order_by("name").all()

# 倒序排列
categorys = Category.query.order_by("name desc").all()

分页查询

1
2
3
# offset 指定偏移量
# limit 指定查几条
categorys = Category.query.offset(1).limit(2).all()

Flask-SQLAlchemy提供了分页方法

1
2
3
4
5
6
7
8
9
# 第一个参数指定第几页
# 第二个参数指定每页多少条数据
paginate = Category.query.paginate(1, 5)
categorys = paginate.items
page_count = paginate.pages # 总页数
has_prev = paginate.has_prev # 是否有上一页
has_next = paginate.has_next # 是否有下一页
prev_paginate = paginate.prev() # 上一页
next_paginate = paginate.next() # 下一页

等值过滤

1
2
3
4
categorys = Category.query.filter_by(name="Python").all()

# 由于all()返回的是一个集合,如果只想取第一个可以这样
category = Category.query.filter_by(name="Python").first()

模糊查询

1
categorys = Category.query.filter(Category.name.ilike('%o%')).all()

事务

对于每次的增、删、改操作都必须要提交事务

1
db.session.commit()

我们可以配置让其自动提交

1
app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = False

回滚事务

1
db.session.rollback()

  • 本文作者: Harmel
  • 本文链接: http://www.harmel.cn/2018/08/flask-orm.html
  • 版权声明: 文章如无特别说明,则表明该文章为原创文章,如需要转载,请注明出处。
  • 本站说明: 本站使用阿里云服务器,如果您喜欢我的网站,欢迎收藏,能捐赠支持一下就再好不过了。