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 进行增删改查。