1. 程式人生 > 實用技巧 >【8】Flask 資料庫

【8】Flask 資料庫

資料庫,顧名思義是儲存資料的倉庫,常見的管理資料庫的軟體被稱為資料庫管理系統(DBMS, Database Management System), 常見的DBMS有 MySQL、PostgreSQL、SQLite、MongoDB。這些常見的DBMS我們可以把他們理解為專門負責搬運資料的管理的資料的程式。

1 什麼是ORM?

物件關係對映(英語:(Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping),是一種程式技術,用於實現面向物件程式語言裡不同型別系統的資料之間的轉換 。 ORM是“物件-關係-對映”的簡稱。在web應用開發中ORM把底層的SQL資料實體轉化成高層的Python物件。只需要通過Python程式碼即可完成資料庫操作。

2 為什麼要有ORM?

在web應用裡使用原生的SQL語句操作資料庫固然能達到處理儲存資料的需求,但是會存在以下三類問題:

  • 手動編寫SQL語句比較複雜耗時(當然因人而異,如果熱衷於原生sql,並不影響開發),並且檢視函式中寫大量SQL語句會降低程式碼的易讀性。
  • 比較容易出現安全問題,如SQL注入。
  • 對於不同的DBMS,需要使用不同的Python介面庫,語法各不相同,很難有標準化的程式碼流程。

使用ORM可以很大程度上解決這些問題,在python中,ORM把底層的SQL資料實體轉化成高層的Python物件。這樣的好處是,你甚至不需要了解SQL,只需要操作Python物件的即可完成資料庫操作。

使用ORM的優勢:

  • 提升開發效率。從高層物件轉換成原生SQL會犧牲一些效能,但這微不足道的效能犧牲換取的是巨大開發效率提升。
  • 可移植性好。它實現了資料庫模型與DEMS的解耦,即資料模型的設計不需要依賴於特定的資料庫,通過簡單的配置就可以輕鬆更換資料庫。通常一個orm支援很多的DEMS,如1MySQL、PostgreSQL、Oracle、SQLite等,這極大的減輕了開發人員的工作量,不需要面對因資料庫變更而導致的無效勞動。

3 如何在Flask應用ORM?

選擇ORM框架時,在Flask中更推薦使用Flask的擴充套件元件Flask-SQLchemy 。Python實現的ORM有SQLAlchemy、Peewee、PonyORM等,其中SQLAlchemy是Python社群使用最廣泛的ORM之一,Flask-SQLchemy正是基於SQLchemy。

3.1 連線資料庫:

首先切入到虛擬環境 ,安裝我們的 Flask-SQLchemy

pip install flask-sqlalchemy
pip install pymysql

這裡DBMSmysql資料庫為例, 連線資料庫

例項

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
HOST = '127.0.0.1'
PORT = '3306'
DATABASE_NAME = '01_db'
USERNAME = 'root'
PASSWORD = 'root'

DB_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOST}:{PORT}/{DATABASE_NAME}?charset=utf8mb4"

app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']= False


db = SQLAlchemy(app)

解讀:

1flask_sqlalchemy模組中匯入SQLAlchemy

from flask_sqlalchemy import SQLAlchemy

2 app物件通過變數SQLALCHEMY_DATABASE_URI載入配置好的URI(統一資源識別符號),URI內包含了各種用於連線資料庫的資訊,指向一個具體的庫。

常用資料庫的URI格式

HOST = '127.0.0.1'  # ip
PORT = '3306'		# 埠
USERNAME = 'root'	# 資料庫賬號
PASSWORD = 'root'   # 密碼
DATABASE_NAME = '01_db'  # 具體的一個庫名
DB_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOST}:{PORT}/{DATABASE_NAME}?charset=utf8mb4"

app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI

3 SQLALCHEMY_TRACK_MODIFICATIONS這個配置變數決定是否追蹤物件的修改,這用於FLask- SQLALchemy的事件通知系統。這個配置鍵預設值為None,如果沒有特殊需要我們把它設定為Flase, 避免造成一些沒必要的效能浪費。

app.config['SQLALCHEMY_TRACK_MODIFICATIONS']= False

4 SQLAlchemy類傳入app類,引用app配置定位到具體的資料庫,並且例項化出db物件,這個db物件代表我們的資料庫,並且通過這個物件操作我們的ORM

db = SQLAlchemy(app)

3.2 資料庫模型

3.2.1 什麼是資料庫模型?

繼承了db.Model的python類,並且這個python類對映到資料庫為一個表,這個python類稱之為資料庫模型。每個資料庫模型都對應著資料庫中的一個表。

3.2.2 資料庫模型例項:

class UserInfo(db.Model):
    __tablename__ = 'user_info'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    username = db.Column(db.String(20),nullable=False)
  • __tablename__
    

    可以直接指定表名(推薦使用)。如果沒有寫

    __tablename__
    

    指定表名,此類名可以自動轉化為表名(不推薦使用)。

    • 類名自動轉化表名的方式為User--> user# 單個單詞轉換為小寫
      UserInfo--> user_info# 多個單詞轉換為小寫並使用下劃線分隔
    • 如UserInfo類在沒有__tablename__指定表名時候,UserInfo類會自動對映到資料庫的表名為user_info
  • db.Column
    

    類例項化表示欄位(表示資料庫中的列),該類例項化出的物件被一個變數接受,該變量表示欄位名。該類例項化時傳入的引數表示欄位的約束。

    • 如:id = db.Column(db.Integer,primary_key=True,autoincrement=True)表示該表內id欄位為主鍵並且自動增長。

3.2.3 常用的欄位型別表:

欄位 說明 對映到資料庫對應型別
Integer 整數 int型別
String 字串,String類內可選擇length引數的值用於設定最大字元個數 varchar型別
Text 用於儲存較長的Unicode文字,,理論上可以儲存65535個位元組 text型別
Date 日期,儲存Pythondatetime.date 物件 date型別
Time 時間,儲存Pythondatetime.time 物件 time型別
DateTime 時間和日期,儲存Pythondatetime 物件 datetime型別
Float 浮點型別 float型別
Double 雙精度浮點型別,比浮點型別小數位精度更高。 double型別,佔據64位。
Boolean 布林值 tinyint型別
Enum 列舉型別 enum型別

**3.2.4 **Column常用引數表:

約束 說明
primary_key 如果設為True,該列就是表的主鍵
unique 如果設為True,該列每個值唯一,也就是該欄位不允許出現重複值
index 如果設為True,為這列建立索引,用於提升查詢效率
nullable 如果設為True,這列允許使用空值,反之則不允許使用空值。
server_default 為這列定義預設值, 預設值只支援字串,其他型別需要db.text()方法指定
default 為這列定義預設值,但是該約束並不會真正對映到表結構中,該約束只會在ORM層面實現(不推薦使用)
comment 該欄位的註釋
name 可以使用該引數直接指定欄位名
autoincrement 設定這個欄位為自動增長的。
server_default常用配置
配置預設值型別 程式碼
更新datatime時間 server_default = db.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
當前的datatime時間 server_default = db.text("CURRENT_TIMESTAMP")
數字 server_default=“數字”
布林 server_default=db.text('True') / server_default=db.text('False')/ server_default='數字'

3.2.5 將寫好的模型對映到資料庫。

class UserInfo(db.Model):
    __tablename__ = 'user_info'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    username = db.Column(db.String(20),nullable=False)
    
db.create_all()

如果你已經定義好了一個繼承db.Model的類,把這個類稱之為模型。想把這個模型對映到資料庫中,也就是在資料庫中建立這個模型所描述的一張表,使用db.create_all()可以實現把繼承了該db.model的所有模型建立到資料庫中。檢視資料庫的時候會發現多了一張user_info表。

3.2.6 更新模型

如果要更新一個模型,並且想把這個新的模型對映到資料庫中,直接使用db.create_all()會無效,因為原來已經存在了這張表,為了解決這個問題可以先db.drop_all()刪除該庫下的所有繼承了db.model的模型表,然後再db.create_all()使得繼承了db.model的所有模型表對映到資料庫中,從而建立更新的表。這種方式的原理是先刪除資料庫中原來所有的模型表,然後在新建所有需要對映的模型表,這種方式的弊端是它把資料庫中原有的資料都銷燬了。

為了解決這種更新模型導致刪除掉原來的資料的弊端。下一章介紹一種更好的方式用於更新資料庫。

3.3 資料庫操作

3.3.1 增

模型表 對映到資料中

class School(db.Model):
    __tablename__ = "school"
    id = db.Column(db.Integer,primary_key=True,nullable=False,autoincrement=True,comment="ID")
    name = db.Column(db.String(30),nullable=False,server_default='',comment="學校名稱")
    area = db.Column(db.String(30),nullable=False,server_default='',comment="所屬地區")
    score = db.Column(db.Integer,nullable=False,server_default='600',comment="錄取分數線")
    def __repr__(self):
        return "<School(name:{})>".format(self.name)

db.create_all()

例項3.3.1.1: 新增例項

新增四條記錄對映到資料庫中

school_01 =School(name="北京大學",area="北京",score=658)  # 例項化模型類作為一條記錄
school_02 =School(name="清華大學",area="北京",score=667)
school_03 =School(name="中山大學",area="廣東",score=645)
school_04 =School(name="復旦大學",area="上海",score=650)

db.session.add(school_01)   # 把新建立的記錄新增到資料庫會話
db.session.add(school_02)
db.session.add(school_03)
db.session.add(school_04)

db.session.commit()  # 提交資料庫會話

提示:資料庫會話db.session和後面介紹的Flasksession物件沒有關係。db.session是資料庫會話也稱為事務。

  1. 例項化模型類建立物件,該物件作為一條記錄,例項化的過程傳入的引數為欄位內容。
  2. 把新建立的記錄新增到資料庫會話。
  3. 提交資料庫會話

檢視資料庫

提示1 :如果add多條記錄可以使用add_all()一次新增包含多條記錄的列表

如:db.session.add_all([school_01,school_02,school_03,school_04])

3.3.2 查

在flask中db.session出的物件呼叫query屬性,可以通過query屬性呼叫各種過濾方法完成查詢。

模型類.<過濾方法>.<查詢方法>

常用過濾器表:

過濾器 說明
filter() 使用指定的規則過濾記錄相當於sql的where約束條件,返回一個新查詢
filter_by() 同filter原理,不同的是查詢的時要使用關鍵字引數,返回一個新查詢
limit() 使用指定的值限制原查詢返回的結果的數量,返回一個新查詢
offset() 偏移原查詢返回的結果,返回一個新查詢
order_by() 根據指定條件對原查詢結構進行排序,返回一個新查詢
group_by() 根據指定條件對原來查詢結構進行分組,返回一個新查詢

例項3.3.2.1: 查詢例項

下面幾個查詢案例需要在例項3.3.1完成的基礎上操作

all()返回一個列表,列表裡存放所有符合條件的記錄

all_school = School.query.all()
print(all_school) 

# 輸出:[<School(name:北京大學)>, <School(name:清華大學)>, <School(name:中山大學)>, <School(name:復旦大學)>]

first()返回符合條件的第一條記錄:

school_01 =School.query.first()
print(school_01)

# 輸出:<School(name:北京大學)>

get()返回指定主鍵值(id欄位)的記錄:

school_01 = School.query.get(1)
print(school_01)

#輸出:<School(name:北京大學)>

filter() 使用指定的規則過濾記錄相當於sql的where約束條件,返回新產生的查詢物件。

beijing_all = School.query.filter(School.area == "北京").all()
beijing_first = School.query.filter(School.area == "北京").first()
print(beijing_all)
print(beijing_first)

# 輸出:[<School(name:北京大學)>, <School(name:清華大學)>]
#       <School(name:北京大學)>

filter_by:同filter()效果一樣,查詢的時候使用關鍵字引數查詢(無法進行多表複雜查詢,不推薦使用)

zhongshan_school = School.query.filter_by(name='中山大學').all()
print(zhongshan_school)

# 輸出:[<School(name:中山大學)>]

db.session.qury(模型類)等價於模型類.query,db.session.qury功能更強大一些,可以進行多表查詢。

fudan_school = School.query.filter(School.name == '復旦大學').first()
print(fudan_school)  
# 輸出:<School(name:復旦大學)>

fudan_school = db.session.query(School).filter(School.name == '復旦大學').first()
print(fudan_school)
# 輸出:<School(name:復旦大學)>

提示:其他的過濾器會在接下來的章節具體根據實際案例講解

3.3.3 改

例項3.3.3.1: 修改例項

修改北京大學的錄取成績

beida = School.query.filter(School.name=='北京大學').first()
beida.score = 630
db.session.commit()

更新一條記錄分為一下幾部:

  1. 找到對應的記錄物件

  2. 修改記錄物件的屬性

  3. 直接呼叫db.session.commit()提交會話

    提示:只有要插入新的記錄或要將現有的記錄新增到會話中時才需要使用add()方法。只是更新現有記錄的時可以修改記錄物件屬性後直接提交會話

3.3.4 刪

例項3.3.4.1: 刪除例項

從資料庫中刪除清華大學相關資訊

qinghua = School.query.filter(School.name=='清華大學').first()
db.session.delete(qinghua)
db.session.commit()

刪除一條記錄分為以下幾步:

  1. 找到對應的記錄物件
  2. 需要呼叫delete()方法在會話中標識需要刪除的記錄,具體是把該記錄物件傳入db.session.delete(記錄物件)實現標識。
  3. 呼叫db.session.commit()提交會話。