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