Flask 的 ORM 模型 - 概述
在词条使用 Python 操作 MySQL 数据库中,通过 SQL 语句访问数据库,繁琐易错。本小节介绍了用于简化访问数据库的 ORM 模型,ORM 模型定义了关系数据库和对象的映射关系,使得访问数据库的代码简单清晰、易于维护。
1. 问题的产生
访问关系数据库的传统方式是:拼接 SQL 语句。例如,向数据库中插入一条数据,根据要插入的数据拼接一条 SQL INSERT 语句,下面的 Python 程序使用 SQL 语句向数据库中插入一条学生的信息:
sno = '20201916'
name = '张三'
age = 20
gender = 'male'
sql = 'INSERT INTO students(sno, name, age, gender) VALUES("%s", "%s", %d, "%s")' % (sno, name, age, gender)
rows = cursor.execute(sql)
在第 5 行,Python 程序使用字符串运算符 % 根据参数 sno、name、age 和 gender 最终生成一条 SQL 语句:
INSERT INTO students(sno, name, age, gender) VALUES("", "张三", 20, "male");
随着项目越来越大,通过拼接 SQL 语句访问数据库存在如下的问题:
1. 繁琐易错
在上面的例子中,第 5 行代码用于拼接 INSERT 语句,INSERT 语句需要插入 4 个字段,该行代码较长,无法在一行显示。在实际的软件开发中,INSERT 语句可能需要插入 10 个以上的字段,那么拼接 INSERT 语句的代码则非常的繁琐易错。
下面的 SQL 语句来自于一个实际的项目:
sql = "INSERT INTO Flights(FlightID, AircraftModel, RegisterID, Direction, ExpectApronTime, RunwayID, ApronID, AirwayID, TaxiwayTimes, AirwayTimes, Rank) VALUES('%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', %d)" % (flightID, aircraftModel, registerID, direction, expectApronTime, runwayID, apronID, airwayID, taxiwayTimes, airwayTimes, rank)
要插入的数据包含有 11 个字段,造成 SQL 语句非常的冗长,需要在多行中才能完全显示,程序的可读性极差。
2. SQL 语句重复利用率低
越复杂的 SQL 语句条件越多、代码越长,在实际的项目中,会出现很多很相近的 SQL 语句。
3. Web 安全漏洞
直接使用 SQL 语句存在有 Web 安全漏洞的问题:通过把 SQL 命令插入到页面请求的查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令。
下面的 SQL 语句根据页面请求中的用户名和密码查询数据库:
username = 从页面请求中获取用户名
password = 从页面请求中获取密码
sql = 'select * from users where username = "%s" and password = "%s"' % (username, password)
在第 3 行的 SELECT 语句中,where 条件进行权限检查,只有 username 和 password 与数据库表 users 中的数据匹配时,才返回有效数据,因此,只有用户输入正确的用户名和密码才可以获取数据。
这条 SQL 语句存在有安全漏洞,假设用户在页面中输入的用户名为 admin"# (共 7 个字符,前 5 个字符是 admin,后面 2 个字符是 " 和 #),密码为 123456,则最终拼接的 SQL 语句如下:
select * from users where username = "admin"#" and password = "123456"
在 SQL 中,# 是行注释,因此上述 SQL 语句相当于:
select * from users where username = "admin"
只要数据库表 users 中有 admin 这条记录,执行该条 SQL 语句就会返回数据,这样对 password 的检查就彻底失效了。
2. 对象 - 关系映射 (ORM)
随着面向对象的软件开发方法发展,出现了对象 - 关系映射 (Object Relation Mapping) 模型,简称为 ORM,ORM 通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。
ORM 描述的对象关系映射如上图图所示:
- 关系数据库中的表对应于面向对象中的类;
- 关系数据库中的数据行(记录)对应于面向对象中的对象;
- 关系数据库中的字段对应于面向对象中的属性。
假设关系数据库中存在一张表 Students,包括 sno、name 和 age 等字段,使用如下 SQL 语句进行创建:
CREATE TABLE students(
sno VARCHAR(255),
name VARCHAR(255),
age INT
);
在 ORM 模型中,存在一个类 Student 与关系数据库中的表 students 相对应,代码如下所示:
class Student:
def __init__(self, sno, name, age):
self.sno = sno
self.name = name
self.age = age
tom = Student('1918001', 'tom', 12)
在第 7 行,程序通过类 Student 实例化生成一个对象 student。在这个具体的例子中,对象和数据库之间映射如下表所示:
关系数据库中的概念 | 面向对象中的概念 |
---|---|
表 students | 类 Student |
表 students 中的一条记录 | 对象 tom |
字段 sno、name 和 age | 属性 sno、name 和 age |
3. SQLAlchemy 简介
SQLAlchemy 是 Python 中一个通过 ORM 操作数据库的框架。SQLAlchemy 对象关系映射器提供了一种方法,用于将用户定义的 Python 类与数据库表相关联,并将这些类实例与其对应表中的行相关联。SQLAlchemy 可以让开发者使用类和对象的方式操作数据库,从而从繁琐的 sql 语句中解脱出来。
SQLAlchemy 的架构如下所示:
在 SQLAlchemy 的核心架构中,Schema / Types 定义了类到表之间的映射规则。DBAPI 是访问关系数据库的底层接口,底层接口仍然通过 SQL 语句访问关系数据库。SQLAlchemy 支持多种关系数据库 (Oracle, Postgresql, Mysql),Dialect 根据用户的配置,调用不同的数据库底层访问 API,并执行对应的 SQL 语句。
4. 使用 SQLAlchemy 完成映射
本小节讲解在 Flask 中使用 SQLAlchemy 完成表与对象的映射,分为如下步骤:
4.1 安装相关库
$ pip3 install flask
$ pip3 install pymysql
$ pip3 install SQLAlchemy
$ pip3 install flask-sqlalchemy
4.2 创建数据库
在 mysql 数据库中执行如下 SQL 脚本 db.sql:
DROP DATABASE IF EXISTS school;
CREATE DATABASE school;
USE school;
CREATE TABLE students(
sno INT,
name VARCHAR(255),
age INT,
PRIMARY KEY(sno)
);
INSERT students(sno, name, age) VALUES(1, 'tom', 11);
INSERT students(sno, name, age) VALUES(2, 'jerry', 12);
INSERT students(sno, name, age) VALUES(3, 'mike', 13);
首先,如果存在数据库 school 则删除,然后建立一个新的、空的数据库 school;然后,创建表 students;最后,向数据库的表 students 中插入 3 条记录用于测试。
4.3 创建 SQLAlchemy 对象
创建文件 db.py
,创建 SQLAlchemy 对象,如下所示:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
user = 'root'
password = '123456'
database = 'school'
uri = 'mysql+pymysql://%s:%s@localhost:3306/%s' % (user, password, database)
app.config['SQLALCHEMY_DATABASE_URI'] = uri
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
首先引入库 flask 和库 flask_sqlalchemy;然后对 SQLAlchemy 进行配置,设置如下参数:
参数 | 值 |
---|---|
user | 访问数据库的用户,假设是 root |
password | 访问数据库的密码,假设是 123456 |
database | 数据库名称 |
uri | SQLAlchemy 连接数据库的字符串 |
在第 10 行,对 SQLAlchemy 进行配置,SQLALCHEMY_DATABASE_URI 配置的是连接数据库的字符串,在这个例子中,该字符串为:
mysql+pymysql://root:123456@localhost:3306/school
该字符串包含有数据库类型、用户名、密码、数据库名等信息,含义如下:
字符串 | 含义 |
---|---|
mysql+pymysql | 数据库类型是 mysql,使用 pymysql 作为访问 mysql 的底层 API |
root | 访问数据库的用户 |
123456 | 访问数据库的密码 |
school | 数据库名称 |
最后,在第 13 行,创建 SQLAlchemy 对象 db。
4.3 建立类与表之间的映射
最核心的工作是建立类与表之间的映射,代码如下:
class Student(db.Model):
__tablename__ = 'students'
sno = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
age = db.Column(db.Integer)
建立表和类的映射关系:在第 1 行,创建类 Student 继承于 db.Model,表示类 Student 用于映射数据库中的表;在第 2 行,设定 __tablename__ 为 students,表示将类 Student 映射到数据库中的表 students。
建立属性和字段的映射关系:在第 3 行,映射 sno 到表 students 的字段 sno,类型为整数 (db.Integer),primary_key=True 表示该字段是主键;在第 4 行,映射 name 到表 students 的字段 name,类型为整数 (db.String); 在第 5 行,映射 age 到表 students 的字段 age,类型为整数 (db.Integer)。
4.4 使用面向对象的语法访问数据库
使用 ORM 模型定义了关系数据库和对象的映射关系后,可以使用面向对象的语法访问数据库,如下所示:
students = Student.query.all()
for student in students:
print(student.sno, student.name, student.age)
在第 1 行,类 Student.query.all () 返回所有的学生,相当于使用 SQL 语句 “SELECT * from students” 查询所有的学生;在第 3 行,通过 student.sno、student.name
、student.age 即可访问数据库中一条记录的相关字段。
程序运行输出如下:
1 tom 11
2 jerry 12
3 mike 13
在 4.2 小节中,使用 INSERT 语句插入了 3 条测试数据,因此输出显示了这 3 条数据。
4. 源代码下载
5. 小结
本节介绍 ORM 模型的相关概念,使用思维导图概括如下:
本节通过一个实例讲解了如何在 Flask 中建立面向对象与关系数据库映射关系,在下一个小节中通过一个更完整的实例讲解如何使用 ORM 进行增删改查。