1. 程式人生 > 實用技巧 >flask-sqlalchemy擴充套件

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

mysqlclientpymysql 都是用於mysql訪問的依賴包, 前者由C語言實現的, 而後者由python實現, 前者的執行效率比後者更高, 但前者在windows系統中相容性較差, 工作中建議優先前者。

文件

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'] = True
View 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_app
View 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 和 資料庫查詢快取)