flask-sqlalchemy擴充套件
1、基本使用
1.1、認識flask-sqlalchemy
Flask-SQLAlchemy 是一個為 Flask 應用增加
SQLAlchemy
支援的擴充套件。它致力於簡化在 Flask 中SQLAlchemy
的使用。SQLAlchemy
是目前python中最強大的 ORM框架, 功能全面, 使用簡單。
ORM優缺點
- 優點
- 有語法提示, 省去自己拼寫SQL,保證SQL語法的正確性
- orm提供方言功能(dialect, 可以轉換為多種資料庫的語法), 減少學習成本
- 面向物件, 可讀性強, 開發效率高
- 防止sql注入攻擊
- 搭配資料遷移, 更新資料庫方便
- 缺點
- 需要語法轉換, 效率比原生sql低
- 複雜的查詢往往語法比較複雜 (可以使用原生sql替換)
安裝flask-sqlalchemy
pip install flask-sqlalchemy
flask-sqlalchemy
在安裝/使用過程中, 如果出現ModuleNotFoundError: No module named 'MySQLdb'
錯誤, 則表示缺少mysql依賴包, 可依次嘗試下列兩個方案後重試:方案1: 安裝
mysqlclient
依賴包 (如果失敗再嘗試方案2)
pip install mysqlclient
- 方案2: 安裝
pymysql
依賴包
pip install pymysql
mysqlclient
和pymysql
都是用於mysql訪問的依賴包, 前者由C語言實現的, 而後者由python實現, 前者的執行效率比後者更高, 但前者在windows系統中相容性較差, 工作中建議優先前者。
文件
- 官方文件 https://flask-sqlalchemy.palletsprojects.com/en/2.x/
- 中文翻譯 http://www.pythondoc.com/flask-sqlalchemy/index.html
1.2、元件初始化
1.2.1、基本配置
flask-sqlalchemy
的相關配置也封裝到了flask
的配置項中, 可以通過app.config
屬性 或 配置載入方案 (如config.from_object
) 進行設定- 主要配置
配置項 | 說明 |
SQLALCHEMY_DATABASE_URI | 設定資料庫的連線地址 |
SQLALCHEMY_BINDS | 訪問多個數據庫時, 用於設定資料庫的連線地址 |
SQLALCHEMY_ECHO | 是否列印底層執行的SQL語句 |
SQLALCHEMY_RECORD_QUERIES | 是否記錄執行的查詢語句, 用於慢查詢分析, 除錯模式下自動啟動 |
SQLALCHEMY_TRACK_MODIFICATIONS | 是否追蹤資料庫變化(觸發鉤子函式), 會消耗額外的記憶體 |
SQLALCHEMY_ENGINE_OPTIONS | 設定針對 sqlalchemy 本體的配置項 |
資料庫URI(連線地址)格式: 協議名://使用者名稱:密碼@資料庫IP:埠號/資料庫名
, 如:
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]:3306/test31'
注意點:
- 如果資料庫驅動使用的是 pymysql, 則協議名需要修改為
mysql+pymysql://xxxxxxx
sqlalchemy
支援多種關係型資料庫, 其他資料庫的URI可以查閱 官方文件
程式碼示例:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 設定資料庫連線地址 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]:3306/test31' # 是否追蹤資料庫修改(開啟後會觸發一些鉤子函式) 一般不開啟, 會影響效能 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 是否顯示底層執行的SQL語句 app.config['SQLALCHEMY_ECHO'] = TrueView Code
1.2.2、兩種初始化方式
flask-sqlalchemy
支援兩種元件初始化方式:
- 方式1: 建立元件時, 直接關聯Flask應用
- 方式2: 先建立元件, 延後關聯Flass應用
方式一:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 應用配置 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]:3306/test31' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_ECHO'] = True # 方式1: 初始化元件物件, 直接關聯Flask應用 db = SQLAlchemy(app)View Code
方式二:
from flask import Flask from flask_sqlalchemy import SQLAlchemy # 方式2: 初始化元件物件, 延後關聯Flask應用 db = SQLAlchemy() def create_app(config_type): """工廠函式""" # 建立應用 flask_app = Flask(__name__) # 載入配置 config_class = config_dict[config_type] flask_app.config.from_object(config_class) # 關聯flask應用 db.init_app(app) return flask_appView Code
方式2主要針對的是 動態建立應用 的場景
1.3、構建模型類
flask-sqlalchemy
的關係對映和 Django-orm
類似:
- 類 對應 表
- 類屬性 對應 欄位
- 例項物件 對應 記錄
程式碼示例:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 相關配置 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]:3306/test31' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_ECHO'] = True # 建立元件物件 db = SQLAlchemy(app) # 構建模型類 類->表 類屬性->欄位 例項物件->記錄 class User(db.Model): __tablename__ = 't_user' # 設定表名, 表名預設為類名小寫 id = db.Column(db.Integer, primary_key=True) # 設定主鍵, 預設自增 name = db.Column('username', db.String(20), unique=True) # 設定欄位名 和 唯一約束 age = db.Column(db.Integer, default=10, index=True) # 設定預設值約束 和 索引 if __name__ == '__main__': # 刪除所有繼承自db.Model的表 db.drop_all() # 建立所有繼承自db.Model的表 db.create_all() app.run(debug=True)View Code
注意點:
- 模型類必須繼承 db.Model, 其中 db 指對應的元件物件
- 表名預設為類名小寫, 可以通過
__tablename__
類屬性 進行修改 - 類屬性對應欄位, 必須是通過 db.Column() 建立的物件
- 可以通過
create_all()
和drop_all()
方法 來建立和刪除所有模型類對應的表
常用的欄位型別
型別名 | python接收型別 | mysql生成型別 | 說明 |
Integer | int | int | 整型 |
Float |
float |
float |
浮點型 |
Boolean | bool | tinyint | 整型,只佔一個位元組 |
Text | str | text | 文字型別, 最大64KB |
LongText | str | longtext | 文字型別, 最大4GB |
String | str | varchar | 變長字串, 必須限定長度 |
Date |
datetime.date |
date |
日期 |
DateTime | datetime.datetime | datetime | 日期和時間 |
Time |
datetime.time |
time |
時間 |
常用的欄位選項
選項名 | 說明 |
primary_key | 如果為True,表示該欄位為表的主鍵, 預設自增 |
unique | 如果為True,代表這列設定唯一約束 |
nullable | 如果為False,代表這列設定非空約束 |
default | 為這列設定預設值預設 |
index |
如果為True,為這列建立索引,提高查詢效率 |
注意點: 如果沒有給對應欄位的類屬性設定default
引數, 且新增資料時也沒有給該欄位賦值, 則 sqlalchemy
會給該欄位設定預設值 None
2、資料操作
2.1、增加操作
增加資料主要需要三步操作
- 建立模型物件
模型物件 = 模型類(欄位名=欄位值)
- 將模型物件新增到會話中
元件物件.session.add(模型物件)
- 提交會話
元件物件.session.commit()
程式碼示例:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 相關配置 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]:3306/test31' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_ECHO'] = True # 建立元件物件 db = SQLAlchemy(app) # 構建模型類 class User(db.Model): __tablename__ = 't_user' id = db.Column(db.Integer, primary_key=True) name = db.Column('username', db.String(20), unique=True) age = db.Column(db.Integer, index=True) @app.route('/') def index(): """增加資料""" # 1.建立模型物件 user1 = User(name='zs', age=20) # user1.name = 'zs' # user1.age = 20 # 2.將模型物件新增到會話中 db.session.add(user1) # 新增多條記錄 # db.session.add_all([user1, user2, user3]) # 3.提交會話 (會提交事務) # sqlalchemy會自動建立隱式事務 # 事務失敗會自動回滾 db.session.commit() return "index" if __name__ == '__main__': db.drop_all() db.create_all() app.run(debug=True)View Code
注意點:
- 給模型物件設定資料 可以通過 初始化引數 或者 賦值屬性 兩種方式
session.add(模型物件)
新增單條資料到會話中,session.add_all(列表)
新增多條資料到會話中- 這裡的 會話 並不是 狀態保持機制中的
session
,而是sqlalchemy
的會話。它被設計為 資料操作的執行者, 從SQL角度則可以理解為是一個 加強版的資料庫事務 sqlalchemy
會 自動建立事務, 並將資料操作包含在事務中, 提交會話時就會提交事務- 事務提交失敗會自動回滾
2.2、查詢資料
sqlalachemy
的查詢語法較多, 接下來通過一個案例來進行綜合演練- 案例說明
- 案例中包含一個模型類
User
, 對應users
表, 包含四個欄位:id
(主鍵),name
,email
,age
- 首先執行案例程式碼, 生成測試資料
- 程式啟動後會重置
users
表, 並向其中新增10條使用者資料
- 程式啟動後會重置
- 為了方便展示查詢結果, 建議使用 互動模式 測試查詢語句
- 推薦使用
ipython
包, 相比 python自帶的互動模式 有語法提示 - 安裝包
pip install ipython
- 推薦使用
- 關於輸出結果
- 內建方法
__repr__()
是__str__()
方法 的升級版, 可以修改 print(物件) 和 互動模式下物件 的輸出結果 - 案例中將 模型物件的輸出結果 修改為 輸出模型物件的所有屬性值 (記錄的資料), 以便驗證查詢結果
- 內建方法
- 案例中包含一個模型類
示例程式碼:
# hm_03_資料查詢.py from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 相關配置 app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:[email protected]:3306/test31" app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False app.config["SQLALCHEMY_ECHO"] = False db = SQLAlchemy(app) # 自定義類 繼承db.Model 對應 表 class User(db.Model): __tablename__ = "users" # 表名 預設使用類名的小寫 # 定義類屬性 記錄欄位 id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64)) email = db.Column(db.String(64)) age = db.Column(db.Integer) def __repr__(self): # 自定義 互動模式 & print() 的物件列印 return "(%s, %s, %s, %s)" % (self.id, self.name, self.email, self.age) @app.route('/') def index(): # 查詢所有使用者資料 User.query.all() 返回列表, 元素為模型物件 # 查詢有多少個使用者 User.query.count() # 查詢第1個使用者 User.query.first() 返回模型物件/None # 查詢id為4的使用者[3種方式] # 方式1: 根據id查詢 返回模型物件/None User.query.get(4) # 方式2: 等值過濾器 關鍵字實參設定欄位值 返回BaseQuery物件 # BaseQuery物件可以續接其他過濾器/執行器 如 all/count/first等 User.query.filter_by(id=4).all() # 方式3: 複雜過濾器 引數為比較運算/函式引用等 返回BaseQuery物件 User.query.filter(User.id == 4).first() # 查詢名字結尾字元為g的所有使用者[開始 / 包含] User.query.filter(User.name.endswith("g")).all() User.query.filter(User.name.startswith("w")).all() User.query.filter(User.name.contains("n")).all() User.query.filter(User.name.like("w%n%g")).all() # 模糊查詢 # 查詢名字和郵箱都以li開頭的所有使用者[2種方式] User.query.filter(User.name.startswith('li'), User.email.startswith('li')).all() from sqlalchemy import and_ User.query.filter(and_(User.name.startswith('li'), User.email.startswith('li'))).all() # 查詢age是25 或者 `email`以`itheima.com`結尾的所有使用者 from sqlalchemy import or_ User.query.filter(or_(User.age==25, User.email.endswith("itheima.com"))).all() # 查詢名字不等於wang的所有使用者[2種方式] from sqlalchemy import not_ User.query.filter(not_(User.name == 'wang')).all() User.query.filter(User.name != 'wang').all() # 查詢id為[1, 3, 5, 7, 9]的使用者 User.query.filter(User.id.in_([1, 3, 5, 7, 9])).all() # 所有使用者先按年齡從小到大, 再按id從大到小排序, 取前5個 User.query.order_by(User.age, User.id.desc()).limit(5).all() # 查詢年齡從小到大第2-5位的資料 2 3 4 5 User.query.order_by(User.age).offset(1).limit(4).all() # 分頁查詢, 每頁3個, 查詢第2頁的資料 paginate(頁碼, 每頁條數) pn = User.query.paginate(2, 3) pn.pages 總頁數 pn.page 當前頁碼 pn.items 當前頁的資料 pn.total 總條數 # 查詢每個年齡的人數 select age, count(name) from t_user group by age 分組聚合 from sqlalchemy import func data = db.session.query(User.age, func.count(User.id).label("count")).group_by(User.age).all() for item in data: # print(item[0], item[1]) print(item.age, item.count) # 建議通過label()方法給欄位起別名, 以屬性方式獲取資料 # 只查詢所有人的姓名和郵箱 優化查詢 User.query.all() # 相當於select * from sqlalchemy.orm import load_only data = User.query.options(load_only(User.name, User.email)).all() # flask-sqlalchem的語法 for item in data: print(item.name, item.email) data = db.session.query(User.name, User.email).all() # sqlalchemy本體的語法 for item in data: print(item.name, item.email) return 'index' if __name__ == '__main__': # 刪除所有表 db.drop_all() # 建立所有表 db.create_all() # 新增測試資料 user1 = User(name='wang', email='[email protected]', age=20) user2 = User(name='zhang', email='[email protected]', age=33) user3 = User(name='chen', email='[email protected]', age=23) user4 = User(name='zhou', email='[email protected]', age=29) user5 = User(name='tang', email='[email protected]', age=25) user6 = User(name='wu', email='[email protected]', age=25) user7 = User(name='qian', email='[email protected]', age=23) user8 = User(name='liu', email='[email protected]', age=30) user9 = User(name='li', email='[email protected]', age=28) user10 = User(name='sun', email='[email protected]', age=26) # 一次新增多條資料 db.session.add_all([user1, user2, user3, user4, user5, user6, user7, user8, user9, user10]) db.session.commit() app.run(debug=True)View Code
查詢語法
有兩套查詢語法可以使用:
flask-sqlalchemy
擴充套件 封裝的語法為查詢結果 = 模型類.query[.查詢過濾器].查詢執行器
, 返回的查詢結果中資料單元為對應的 模型物件sqlalchemy
本體 提供的語法為元件物件.session.query(欄位)[.查詢過濾器].查詢執行器
, 返回的資料單元為 類元組物件, 該型別支援 索引、屬性名 以及 別名 三種取值方式- 查詢過濾器非必須, 查詢執行器必須設定
- 除了特殊查詢語句(聯表/聚合等)需要使用
sqlalchemy
本體的語法, 一般使用flask-sqlalchemy
擴充套件 封裝的語法即可
常用的SQLAlchemy查詢執行器
執行器的特點:
- 將整個查詢語句轉換為SQL語句並 執行查詢
- 在查詢語句的末尾設定, 每條查詢語句 只能設定一個執行器
方法 | 說明 |
all() | 返回列表, 元素為所有符合查詢的模型物件 |
count() | 返回查詢結果的數量 |
first() | 返回符合查詢的第一個模型物件,如果未查到,返回None |
first_or_404() | 返回符合查詢的第一個模型物件,如果未查到,返回404 |
get(主鍵) | 返回主鍵對應的模型物件,如不存在,返回None |
get_or_404(主鍵) | 返回指定主鍵對應的模型物件,如不存在,返回404 |
paginate(頁碼, 每頁條數) | 返回一個Paginate物件,它包含分頁查詢的結果 |
常用的SQLAlchemy查詢過濾器
- 過濾器的特點:
- 只負責設定過濾條件, 不會執行查詢(查詢由執行器來完成)
- 允許續接其他過濾器 或 執行器
過濾器 | 說明 |
filter_by(欄位名=值) | 把等值過濾器新增到原查詢上,返回BaseQuery物件 |
filter(函式引用/比較運算) | 把過濾器新增到原查詢上,返回BaseQuery物件 |
limit(限定條數) | 使用指定的值限定原查詢返回的結果,返回BaseQuery物件 |
offset(偏移條數) | 根據指定的值按照原查詢進行偏移查詢,返回BaseQuery物件 |
order_by(排序欄位) | 根據指定條件對原查詢結果進行排序,返回BaseQuery物件 |
group_by(分組欄位) | 根據指定條件對原查詢結果進行分組,返回BaseQuery物件 |
options() | 針對原查詢限定查詢的欄位,返回BaseQuery物件 |
注意點:
- 查詢過濾器返回的都是
BaseQuery
型別物件, 該物件支援鏈式呼叫, 即可以續接其他過濾器 或 執行器 - 如果考慮到效能優化, 應該避免
select *
, 只查詢需求欄位 (select * 會導致資料庫伺服器去獲取&解析&處理目標資料的每個欄位, 對伺服器資源造成浪費, 並且不必要的資料也會佔用更多的 網路IO 和 資料庫查詢快取)