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 進行增刪改查。