chapter 2 - chapter 3 - chapter 4 - 源碼
概念剖析-flask數據庫操作
數據庫分類
SQL數據庫
基于關系模型,用表來模擬不同的實體,列定義代表所屬實體的數據屬性,每個表有個特殊的列稱為 主鍵,其值是各行的唯一標識符,不能重復,表中還有 外鍵,引用同一表或不同表的主鍵,這種聯系稱為 關系。 特點:支持聯結操作,數據存儲高效,數據一致性好,避免重復,但是設計比較復雜。常見:MySQL, Oracal, SQLiteNoSQL數據庫
使用 集合 代替表,使用 文檔 代替記錄,特點數據重復查詢效率高,一致性差。常見:MongoDB
SQL數據庫
# 表 roles 表 users
# ------------------ ------------------
# id : 主鍵 id : 主鍵
# name username
# password
# role_id : 外鍵
# ------------------ ------------------
NoSQL 數據庫
# users
# -----------------
# id
# username
# password
# role: 有大量重復
# ------------------
python數據庫框架
- 數據引擎的Python包 大多數的數據庫引擎都有對應的Python包。
- 數據庫抽象層 如
SQLAlchemy
和MongoEngine
通過ORM(Object-Relational Mapper)
或者ODM(Object-Document Mapper)
將處理表、文檔或查詢語言等數據庫實體操作轉換成為高層的Python對象的操作,極大地提升開發效率。- 數據庫框架評價指標:易用性,性能,可移植性。Flask集成度。本書使用的是 Flask-SQLAlchemy
使用 Flask-SQLAlchemy
管理數據庫
- 安裝
pip install flask-sqlalchemy
- 通過URL指定數據庫
數據庫引擎 | URL |
---|---|
MYSQL | mysql://uername:password@hostname/database |
SQLite(Unix) | sqlite:///absolute/path/to/database |
SQLite(Win) | sqlite:///c:/absolute/path/to/database |
設置 SQLlite 數據庫的鏈接
hello.py
文件, 設置app.config['SQLALCHEMY_DATABASE_URI']
,app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']
## 設置 SQLite 數據庫 URI
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir,'data.sqlite')
## 每次請求提交后,自動提交數據庫的修改
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
## 獲取數據庫對象
db = SQLAlchemy(app)
定義模型
模型表示程序使用的持久化實體,在
ORM
中,模型一般是一個 Python 類,類的屬性對應數據表中的列,類的操作會在底層自動轉換成為對應的數據庫操作。
hello.py
中,定義 Role 和 User 模型
# 定 Role 模型
class Role(db.Model):
""" database table class Role """
# 表名,一般采用 復數 形式
__tablename__ = 'roles'
# 類變量即數據表的字段,由 db.Column創建
# primary_key = True 定義主鍵
# unique = True 不允許出現重復的值
id = db.Column(db.Integer, primary_key = True )
name = db.Column(db.String(64), unique = True )
# 返回表示模型的字符串,供調試和測試使用
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
""" database table class User """
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True )
username = db.Column(db.String(64), unique = True, index=True )
def __repr__(self):
return '<User %r>' % self.username
創建模型之間的關系
hello.py
文件中,User中定義外鍵 role_id
的同時定義了關系,可以在關系的另一端Role
中定義關系,添加反向引用。
class User(db.Model):
...
# 創建外鏈,同時創建了關系,引用 表 roles 的 id 字段
role_id = db.Column(db.Integer, db.ForeignKey( 'roles.id' ) )
...
class Role(db.Model):
...
# backref 在關系的另一個模型中,添加反向引用
# 添加到 Role 中的 users 屬性代表了關系的面向對象視角,
# 將返回與角色相關聯的用戶的列表,第一個參數 用字符串表示關系另一端的模型
# backref='role' 向User類添加了 role 屬性, role_id 返回的是外鍵的值,
# role返回的是模型Role的對象
users = db.relationship('User', backref='role')
...
數據庫操作
與git
的操作十分相似
-
創建數據表
db.create_all()
創建sqlite
數據庫文件data.sqlite
,改動模型后,更新數據庫時只能刪除舊表db.drop_all()
,然后重新db.create_all()
創建,但是會導致原有數據的丟失。 -
插入記錄 只需要調用模型的關鍵字參數形式的構造函數,如
admin_role = Role(name='Admin)'
,主鍵id
由Flask-SQLAlchemy
管理,不需要明確設置 -
同步模型改動到數據庫 使用
db.session
數據庫事務對象管理對數據庫的改動,如添加記錄到數據庫db.session.add(admin_role)
-
提交操作
db.session.commit()
-
修改記錄 首先修改模型對象屬性值,然后
db.session.add(), db.session.commit()
-
刪除記錄 首先
db.session.delete( model obj)
然后提交到倉庫db.session.commit()
hello.py
文件添加
# 數據庫對象的創建及初始化
def Create_database():
# 創建數據庫文件及表,
# ? 程序如何識別所有需要創建數據表的對象 ?
db.create_all()
# 插入行
admin_role = Role(name='Admin')
mod_role = Role(name='Moderator')
user_role = Role(name='User')
user_john = User( username='john', role = admin_role )
user_susan = User( username='susan', role = user_role )
user_david = User( username='david', role = user_role )
# 添加到會話
db.session.add( admin_role )
db.session.add( mod_role )
db.session.add( user_role )
db.session.add( user_john )
db.session.add( user_susan )
db.session.add( user_david )
# db.session.add_all( [admin_role, mod_role, user_role, user_john , user_susan, user_david] )
# 提交到數據庫
db.session.commit()
# db.session.rollback() 將添加到數據庫會話中的所有對象還原到他們在數據庫中的狀態,相當于git中的checkout
# 刪除數據
# db.session.delete(mod_role)
# db.session.commit()
數據庫的查詢query
對象
SQLALchemy-查詢篇
Python SQLAlchemy基本操作和常用技巧
Flask-SQLAlchemy
為每個模型類都提供一個query
對象,
而 過濾器 在query
上調用,返回更加精確的query
對象 ,過濾器返回query
對象
利用關系創建的模型對象1 *
User.query.filter_by(role=user_role).all()
,role是關系*添加的模型對象,str( User.query.filter_by(role=user_role))
查詢ORM自動生成的查詢語句
利用關系創建的模型對象2user_role.users
默認情況下返回的是query
對象的查詢結果,設置lazy = dynamic
可以得到查詢對象
cmd 中 shell 方式執行查找:
>>> python hello.py shell
>>> from hello import db, Role, User
>>> User.query.all()
[<User 'john'>, <User 'susan'>, <User 'david'>]
git add. git commit -m "sqlalchemy first demo"
視圖函數中調用數據庫
hello.py
文件
# 路由 index
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
# 查找用戶信息
user = User.query.filter_by( username=form.name.data ).first()
# 記錄新用戶
if user is None:
user = User( username = form.name.data)
# add 到 session
db.session.add(user)
session['known'] = False
else:
session['known'] = True
session['name'] = form.name.data
return redirect( url_for('index'))
return render_template('index.html', form=form, name=session.get('name'), known=session.get('known', False))
git add. git commit -m "sqlalchemy in view "
, git tag 5a
集成 Python shell
可以通過Shell 來調試模塊,據說,文檔
每次啟動 shell 都要導入數據庫實例和模型,通過設置讓Flask-Script
的shell
自動導入特定的對象
from flask_script import shell
# 創建 shell 的上下文環境
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role)
# 配置 manager 的命令行
manager.add_command("shell", Shell(make_context = make_shell_context))
數據庫表的修改
開發過程中有時候需要修改數據庫模型,并更新到數據庫,需要用到數據庫遷移框架,推薦
Alembic
,及Flask - Migrate
擴展(封裝了Alembic
),所有的操作通過Flask-Script
完成
- 安裝
pip install flask-migrate
-
hello.py
文件修改
from flask_migrate import Migrate, MigrateCommand
...
# 創建數據庫遷移對象
Migrate(app, db)
# 配置 flask_script 命令
manager.add('db', MigrateCommand)
創建遷移倉庫
>>> python hello.py db init
,會創建migrations
文件夾,放置所有的遷移腳本,遷移倉庫中的要進行版本控制-
數據庫的遷移用 遷移腳本 表示,腳本中有兩個函數
upgrade()
和downgrade()
前者應用修改,后者刪除修改,數據庫可以重置到修改歷史的任一點。-
revision
手動創建遷移,upgrade()
和downgrade()
都是空的,開發者需要使用Operations
對象指令實現具體操作 -
migrate
命令自動創建,會根據模型定義和數據庫當前狀態之間的差異自動生成upgrade()
和downgrade()
的內容
-
此處沒有修改數據庫,只是刪除數據庫
data.sqlite
, 自動創建遷移腳本>>> python hello.py db migrate -m "initial migration"
, 在$project-dir \ migrations \ versions
出現文件ab116a6e32ed_initial_migration.py
內容如下:
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('roles',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=64), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=64), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_users_username'), table_name='users')
op.drop_table('users')
op.drop_table('roles')
# ### end Alembic commands ###
- 應用遷移命令,
>>>python hello.py db upgrade
輸出Running upgrade -> ab116a6e32ed, initial migrateion
, 刪除遷移>>>python hello.py db downgrade
輸出Running downgrade ab116a6e32ed -> , initial migrateion
PROBELM 前文說道,遷移
而不是刪除表格,重新創建表格
能夠保存數據庫中的數據,那么如何指定新列于舊列的對應關系,及其他更精細的操作?
git add. git commit "flask database demo"
, git tag 5b
附錄
常用的SQLAlchemy 的列類型
類型名稱 | python類型 | 描述 |
---|---|---|
Integer | int | 常規整形,通常為32位 |
SmallInteger | int | 短整形,通常為16位 |
BigInteger | int或long | 精度不受限整形 |
Float | float | 浮點數 |
Numeric | decimal.Decimal | 定點數 |
String | str | 可變長度字符串 |
Text | str | 可變長度字符串,適合大量文本 |
Unicode | unicode | 可變長度Unicode字符串 |
Boolean | bool | 布爾型 |
Date | datetime.date | 日期類型 |
Time | datetime.time | 時間類型 |
Interval | datetime.timedelta | 時間間隔 |
Enum | str | 字符列表 |
PickleType | 任意Python對象 | 自動Pickle序列化 |
LargeBinary | str | 二進 |
常用的 SQLAlchemy 的列選項
可選參數 | 描述 |
---|---|
primary_key | 如果設置為True,則為該列表的主鍵 |
unique | 如果設置為True,該列不允許相同值 |
index | 如果設置為True,為該列創建索引,查詢效率會更高 |
nullable | 如果設置為True,該列允許為空。如果設置為False,該列不允許空值 |
default | 定義該列的默認值 |
常用的 SQLAlchemy 的關系選項
選項名 | 說明 |
---|---|
backref | 在關系的另一個模型中添加反向引用 |
primaryjoin | 明確指定另個模型之間使用的關系的聯結條件 |
lazy | 指定如何加載相關記錄,可選項有 select( 首次訪問按需加載 ),immediate( 源對象加載后立即加載 ),joined( 加載記錄且使用聯結 ),subquery( 立即加載,使用子查詢 ),noload( 永不加載 ),dynamic( 不加載記錄,提供加載記錄的查詢 ) |
uselist | 默認為真,對應一對多,返回列表,若為 False 對應一對一,返回標量值 |
order_by | 關系中記錄的排序方式 |
secondary | 指定 多對多 關系中關系表的名字 |
secondaryjoin | 指定 多對多 關系中 二級聯結 條件 |
常用的 SQLAlchemy 查詢過濾器
過濾器 | 說明 |
---|---|
filter() |
將過濾器添加到原查詢上,返回新查詢 |
filter_by() |
將等值過濾器添加到原查詢上,返回新查詢 |
limit() |
使用指定的值限制返回的結果數量,返回新查詢 |
offset() |
偏移原查詢的結果,返回新查詢 |
oredr_by() |
采用指定條件對原查詢的結果排序,返回新查詢 |
group_by |
采用指定條件對原查詢結果進行分組,返回新查詢 |
常用的 query 對象的操作
操作 | 說明 |
---|---|
all() |
列表形式返回所有查詢結果 |
first() |
返回查詢結果的第一個,如果沒有返回None
|
first_or_404() |
返回查詢結果的第一個,如果沒有終止請求,返回404 |
get() |
返回主鍵對應的行,如果沒有返回 None
|
get_or_404() |
返回主鍵對應的行,如果沒有,終止請求,返回404 |
count() |
返回查詢結果的數量 |
paginate() |
paginate (為書或者手稿標頁數),返回一個paginate 對象,包含指定范圍的結果 |