【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
這裡DBMS
以mysql
資料庫為例, 連線資料庫
例項
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)
解讀:
1 從flask_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 | 日期,儲存Python 的datetime.date 物件 |
date型別 |
Time | 時間,儲存Python 的datetime.time 物件 |
time型別 |
DateTime | 時間和日期,儲存Python 的datetime 物件 |
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 :如果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()
更新一條記錄分為一下幾部:
-
找到對應的記錄物件
-
修改記錄物件的屬性
-
直接呼叫
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()
刪除一條記錄分為以下幾步:
- 找到對應的記錄物件
- 需要呼叫
delete()
方法在會話中標識需要刪除的記錄,具體是把該記錄物件傳入db.session.delete(記錄物件)
實現標識。 - 呼叫
db.session.commit()
提交會話。