第四章 資料庫
-
- 一、 Flask架構中的資料庫
- 二、DATABASE Migrate資料庫遷移
- 三、Flask-SQLAlchemy設定
-
- 1、設定SQLITE資料庫引擎uri
- 2、初始化DB對象及遷移對象
- 四、資料庫模型(類似關系型資料庫表結構)
- 五、生成遷移庫
- 六、第一個資料庫遷移
- 七、資料庫更新和降級
- 八、資料庫表關系
- 九、玩樂時間
在實際網站開發中,開發者在本地進行開發,測試成功後再把新版本的功能釋出的正式應用環境。不斷更新更新的過程,怎麼能快速的在本地及正式環境中同步資料呢?那就需要下邊的知識——資料庫遷移技術。
一、 Flask架構中的資料庫
其實在FLASK中,是沒有自帶資料庫功能,但是,它可以與各種流行的資料庫軟體輕易結合,它把這種選擇留給開發人員,這正是Flask的靈活支援。
雖然可供選擇的資料庫很多,比如關系型資料:mysql、PostgreSQL、mssql、oracle等,還有非關系型資料庫。但是,本示例還是選用輕巧的關系型資料庫sqlite(沒有資料庫引擎)。如果要把此應用釋出到生産環境,僅需要把資料庫引擎切換到其他類型的資料庫。
第三章,我們就引進了幾個flask的擴充庫。這次,為了更好的管理資料庫操作,需要一個第三方的庫flask-sqlalchemy。
介紹下ORM
ORM 全稱 Object Relational Mapping, 翻譯過來叫對象關系映射。簡單的說,ORM 将資料庫中的表與面向對象語言中的類建立了一種對應關系。這樣,我們要操作資料庫,資料庫中的表或者表中的一條記錄就可以直接通過操作類、對象、方法或者類執行個體來完成。
SQLAlchemy 是Python 社群最知名的 ORM 工具之一,為高效和高性能的資料庫通路設計,實作了完整的企業級持久模型。它可以适用各種資料庫。
安裝flask-sqlalchemy。 注意:先要激活虛拟環境再安裝。
pip install flask-sqlalchemy
二、DATABASE Migrate資料庫遷移
個人了解:這裡的遷移不是僅指資料庫檔案的整體遷移(備份及還原、附加資料庫等),也包含微小資料結構改變操作。
實際應該是所有資料庫操作動作對應的語言腳本,通過這些腳本可以快速重複同樣的操作。
很多的資料教程會提到怎麼建立、使用資料庫操作,但是當APP應用的資料結構變化或擴充後的相關操作,教程很少會提及。
尤其關系型資料庫的關系結構複雜,一旦結構改變,相應的資料就需要對應改變。
安裝flask-migrate
pip install flask-migrate 注意:先要激活虛拟環境再安裝。
三、Flask-SQLAlchemy設定
1、設定SQLITE資料庫引擎uri
如前邊的章節介紹一樣,把此處的設定内容,增添到config.py檔案中。
path.dirname:去掉路徑中的檔案名稱。
os.path.abspath:擷取絕對路徑。
file:目前檔案的路徑。
import os
basedir = os.path.abspath(os.path.dirname(__file__)) #__file__用來獲得子產品所在的路徑.
class Config(object):
# ...
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \ #擷取環境變量為DATABASE_URL值(windows系統中環境變量的path參數)
'sqlite:///' + os.path.join(basedir, 'app.db') #拼接一個sqlite接口引擎
SQLALCHEMY_TRACK_MODIFICATIONS = False #設定為False後,不會記錄資料的變更記錄。
2、初始化DB對象及遷移對象
app/init.py
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app) #建立DB引擎
migrate = Migrate(app, db) #建立Migratey引擎
from app import routes, models #models是下邊将建立的子產品
Tip:更多的FLASK擴充也會像db、migrate這兩個一樣的初始化。
四、資料庫模型(類似關系型資料庫表結構)
使用者表:id、username、email、password_hash。
為什麼hash化password。主要是如果直接把密碼明文存儲,一旦資料庫被黑客攻陷,那這對使用者來說是災難性的。是以,通過hash化密碼,可以提升安全性。具體的講解放到後邊的章節。
建立一個子產品:app/models.py,增加兩個類(表結構):
這裡請注意:如果完全按照教程中的代碼,會遇到一個坑:在送出的時候,背景語句會報錯,提示sqlite資料庫中沒有user表。是以,先用db.create_all()啟動。
db.Column的參數說明:
primary_key: 如果設為 True,這列就是表的主鍵;
unique:如果設為 True,這列不允許出現重複的值;
index:如果設為 True,為這列建立索引,提升查詢效率;
nullable:如果設為 True,這列允許使用空值;如果設為 False,這列不允許使用空值;
default:為這列定義預設值.
app/models.py:
from app import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
def __repr__(self):
return '<User {}>'.format(self.username)
五、生成遷移庫
上邊,我們已經建立一個資料表結構。但是,随着開發的進一步發展,可能會增加字段或删除一些項目。
Alembic(适用Flask-Migrate的遷移架構,Flask作者所作)可以不用重新建構表而改變schema(結構)。
Alembic包含一個遷移庫,這個庫有一個由這些遷移腳本組成目錄。隻要資料庫結構一改變,記錄這些變動的遷移腳本将添加到遷移庫中。
類似第一章運作flask run 一樣:
flask db init
另外用Pycharm配置後直接運作:
初始化後flask自動生成的語句如下:
Creating directory /home/miguel/microblog/migrations ... done
Creating directory /home/miguel/microblog/migrations/versions ... done
Generating /home/miguel/microblog/migrations/alembic.ini ... done
Generating /home/miguel/microblog/migrations/env.py ... done
Generating /home/miguel/microblog/migrations/README ... done
Generating /home/miguel/microblog/migrations/script.py.mako ... done
Please edit configuration/connection/logging settings in
'/home/miguel/microblog/migrations/alembic.ini' before proceeding.
六、第一個資料庫遷移
上邊建立遷移庫,現在我們開始建立一個資料的遷移。
Alembic 會自動檢測資料庫model與實際資料庫裡的變動差異。由于先前沒有建立過user的schema,遷移腳步會自動增加建立一個完整的user的腳本。
-m “users table”:增加migrate的簡短描述。
flask db migrate -m "users table"
#運作結果
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'user'
INFO [alembic.autogenerate.compare] Detected added index 'ix_user_email' on '['email']'
INFO [alembic.autogenerate.compare] Detected added index 'ix_user_username' on '['username']'
Generating
#e517276bb1c2是系統自動生成的遷移版本的版本号 /home/miguel/microblog/migrations/versions/e517276bb1c2_users_table.py ... done
雖然運作了migrate,但是,此文法不能影響實際資料庫的資料,它僅僅是生成了遷移腳本。
如果要通過遷移改變實際資料庫的資料,需要用到如下兩個函數
upgrade()和downgrade()。
upgrade():運用遷移腳本;
downgrade():移除目前腳本,可回溯到以往的曆史節點。
七、資料庫更新和降級
假設你開發一個APP應用,先是在本地進行開發。如果model發生了變化,在沒有遷移功能情況下,你就需要在開發環境中修改model,同時在生産環境中修改model。
有了遷移功能,在開發環境中,我們就先執行migrate,然後審查這些變動能否正常運作。最後運作upgrade來更新這些變動。
如果發現有問題,可以運作downgrade回到先前的某個更新節點。
flask db upgrade
#運作結果
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> e517276bb1c2, users table
Tip:Flask-SQLAlchemy采用蛇形命名規則(用下劃線連接配接多個單詞)來命名資料庫表名稱。
例如上邊的User模型,則會在資料庫中建立一個user表。
如果要改變這預設的表名稱,可以在模型類增加一個屬性__tablename__,并指派你所期望的名稱。
八、資料庫表關系
在關系資料庫中,可以在“一”表中建立一個變量Post,用來建構user與Post的對應關系。
from datetime import datetime
from app import db #db是上邊建立
from flask_login import UserMixin
#注意添加UserMixin,否則在送出注冊的時候報錯:User沒有active這個屬性。
class User(db.Model,UserMixin):
db.create_all() #這個語句是教程中沒有,必須添加
id = db.Column(db.Integer, primary_key=True) #db.Column定義列字段:第一個參數字段類型,第二個是主鍵
username = db.Column(db.String(64), index=True, unique=True) #unique代表唯一性
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
#relationship生成一個關系,posts不是一個資料庫的字段,僅是一個視圖關系
#在一對多的模式下,第一個參數是代表多方的類(表)名稱
#第二個參數backref,将向post類中添加一個author屬性,進而定義反向關系。這一屬性可替代user_id 通路user模型,此時擷取的是模型對象,而不是外鍵的值(類似表的一行或一個執行個體)。
#u = User.query.get(1)
#author使用的例子:p = Post(body='my first post!', author=u)
posts = db.relationship('Post', backref='author', lazy='dynamic')
#__repr__主要是自定義一種輸出格式,重組print的列印效果。另外一種__str__是更上一層的輸出格式。
#終端使用者顯示使用__str__,而程式員在開發期間則使用底層的__repr__來顯示
def __repr__(self):
return '<User {}>'.format(self.username)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Post {}>'.format(self.body)
九、玩樂時間
下邊的執行個體是在python自帶的shell中運作。
>>> from app import db
>>> from app.models import User, Post
>>> u = User(username='john', email='[email protected]')
>>> db.session.add(u) #先建立回話後才可以進行資料庫操作:新增執行個體
>>> db.session.commit() #執行操作
>>> u = User(username='susan', email='[email protected]')
>>> db.session.add(u)
>>> db.session.commit()
>>> users = User.query.all() #查詢user所有記錄
>>> users
[<User john>, <User susan>]
>>> for u in users:
... print(u.id, u.username)
...
1 john
2 susan
>>> u = User.query.get(1) #查詢主鍵是1的記錄
>>> u
<User john>
>>> u = User.query.get(1)
>>> p = Post(body='my first post!', author=u) #按照author指派POST
>>> db.session.add(p)
>>> db.session.commit()
>>> # get all posts written by a user
>>> u = User.query.get(1)
>>> u
<User john>
>>> posts = u.posts.all()
>>> posts
[<Post my first post!>]
>>> # same, but with a user that has no posts
>>> u = User.query.get(2)
>>> u
<User susan>
>>> u.posts.all()
[]
>>> # print post author and body for all posts
>>> posts = Post.query.all()
>>> for p in posts:
... print(p.id, p.author.username, p.body)
...
1 john my first post!
# get all users in reverse alphabetical order
>>> User.query.order_by(User.username.desc()).all()
[<User susan>, <User john>]
>>> users = User.query.all()
>>> for u in users:
... db.session.delete(u)
...
>>> posts = Post.query.all()
>>> for p in posts:
... db.session.delete(p)
...
>>> db.session.commit()