05-數據庫
數據庫,顧名思義是儲存數據的倉庫,常見的管理數據庫的軟件被稱為數據庫管理系統(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 = "mysql+pymysql://{username}:{password}@{host}:{port}/{databasename}?charset=utf8mb4"\
.format(username=USERNAME,password=PASSWORD,host=HOST,port=PORT,databasename=DATABASE_NAME)
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 = "mysql+pymysql://{username}:{password}@{host}:{port}/{databasename}?charset=utf8mb4"\
.format(username=USERNAME,password=PASSWORD,host=HOST,port=PORT,databasename=DATABASE_NAME)
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()
提交會話。