簡介
Flask-SQLAlchemy 使用起來非常有趣,對於基本應用十分容易使用,並且對於大型項目易於擴展。
常見情況下對於只有一個 Flask 應用,所有您需要做的事情就是創建 Flask 應用,選擇加載配置接着創建 SQLAlchemy
對象時候把 Flask 應用傳遞給它作為參數。
基本操作:
pip3 install flask-sqlalchemy
# __init__.py:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
#第一步:在__init__中創建db對象,包含了SQLALchemy的所有操作。
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.debug = True
app.config.from_object('settings.DevelopmentConfig')
from .views import account
app.register_blueprint(account.ac)
# 第二步: 在__init___中將app傳入到db中。
db.init_app(app)
return app
# settings.py:第三步:將連接字符串的定義寫在配置文件中
class BaseConfig(object):
# SESSION_TYPE = 'redis' # session類型為redis
# SESSION_KEY_PREFIX = 'session:' # 保存到session中的值的前綴
# SESSION_PERMANENT = True # 如果設置為False,則關閉瀏覽器session就失效。
# SESSION_USE_SIGNER = False # 是否對發送到瀏覽器上 session:cookie值進行加密
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root@127.0.0.1:3306/userinfo3?charset=utf8"
SQLALCHEMY_POOL_SIZE = 5
SQLALCHEMY_POOL_TIMEOUT = 30
SQLALCHEMY_POOL_RECYCLE = -1
# 追蹤對象的修改並且發送信號
SQLALCHEMY_TRACK_MODIFICATIONS = False
class ProductionConfig(BaseConfig):
pass
class DevelopmentConfig(BaseConfig):
pass
class TestingConfig(BaseConfig):
pass
models:
from sqlalchemy import Column, Integer, String, UniqueConstraint, Index, DATETIME, ForeignKey
from flask2 import db
# 第四步:定義數據庫表
class Users(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(32), unique=True, nullable=False)
class School(db.Model):
__tablename__ = 'school'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(32), unique=True, nullable=False)
# 第五步: 創建離線腳本: drop_create_table.py
"""
Web運行時,flask程序運行起來,用戶通過瀏覽器訪問
離線腳本,自定義的一個py文件+使用flask中定義好的功能
"""
from flask2 import db, create_app,models
app = create_app()
with app.app_context():
# db.drop_all()
db.create_all()
# data = db.session.query(models.Users).all()
# print(data)
# 第六步: 在視圖函數中使用SQLAlchemy:
from .. import models
from flask import Blueprint
from flask2 import db
ac = Blueprint('ac',__name__)
@ac.route('/login',methods=['GET', 'POST'])
def login():
ret = db.session.query(models.Users).all()
print(ret[0].name)
db.session.remove()
return 'login'
Session 的生命周期
首先我們需要知道一個 sqlalchemy session 的生命周期是怎樣的。我們的 web 應用會同時服務多個用戶,因此不同的請求要有不同的 session,不然就會翻車。
session 會在一次請求進來的時候創建出來
session = Session()
在整個請求處理過程中被使用
session.add(some_obj)
session.commit()
在請求處理完畢后被關閉銷毀。
session.close()
當然上面的代碼只是簡略的情形,通常還需要包括 try-except 來處理 session 需要 rollback 之類的情況。
scoped_session 與 registry 模式
scoped_session
就是用在 web 應用這種處理請求的場景下,協助進行 session 維護的。
from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session
engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/userinfo3?charset=utf8",pool_size=2, max_overflow=0)
session_factory = sessionmaker(bind=engine)
session = scoped_session(session_factory )
通常我們在初始化 web 應用時通過scoped_session
函數對原始的Session
工廠進行處理,返回出一個ScopedSession
工廠,在每個請求來的時候就可以通過這個工廠獲得一個 scoped_session 對象。
這實際上實現了一種 registry 模式,它有個中心化的 registry 來保存已經創建的 session,並在你調用ScopedSession
工廠的時候,在 registry 里面找找是不是之前已經為你創建過 session 了,如果有,就直接把這個 session 返回給你,如果沒有,就創建一個新的 session,並注冊到 registry 中以便你下次來要的時候給你。
Thread-Local Storage
上面說到 scoped_session 類似單例模式,我們看似創建了新的 session,實際上拿到的可能是之前創建出來的 session。但我們 web 應用通常要同時處理多個請求,我的請求有沒有可能不小心拿到別人創建的 session 對象呢?
盡管 Python 有着神奇的 GIL,沒法真正的並行地跑線程,但至少還是有線程的概念的,對於不同的請求進來的時候我們通常會在不同的線程中進行處理。Python 里有個概念叫 thread local storage(TLS),即線程本地存儲,它可以作為全局變量一樣使用,但這個數據只在這個線程中有效,與其他的線程是隔離的。
import threading mydata = threading.local() mydata.x = 1
這不正好和剛才 registry 模式的思想差不多嘛。和我們所希望的一樣,scoped_session 也確實使用了 tls 作為 session 的存儲方式,一個線程只能拿到自己創建出來的 session,保證了不同線程不會亂入別人的 session。
使用 tls 還有另外一個好處,由於 session 是跟着線程走的,就算你沒有調用remove()
親手干掉 session,也會由於線程結束,session 也跟着被一起回收掉,不至於泄漏。(但仍建議在必要的時候對資源進行顯式的回收)
還有一個隱蔽的問題,如果我們用了 gevent 來處理並發而不是用多線程,會翻車嗎?答案是不會。gevent 在monkey.patch_all()
的時候,已經悄悄把這個 threading 相關的東西悄悄替換成自己的一套了,thread-local 的東西已經變成了 greenlet-local,不同協程間仍是隔離的,一般不會有問題。
一些思路
如果你是純Flask應用,不涉及任何視圖函數之外的數據庫邏輯,請直接使用flask-sqlalchemy。它封裝的session默認是scoped_session。
如果還有很多外部代碼,一定保證你的session是可控的。 簡單來說就是:
- 用就開,用完就關
- 盡量避免多個函數依賴之間同時使用session
- 使用scoped_session來保證線程安全
關於sqlalchemy的session參照了https://blog.csdn.net/lucyxu107/article/details/82699996,感謝博主