標籤:style blog http color io os ar 資料 sp
在一個Web App中,所有資料,包括使用者資訊、發布的日誌、評論等,都儲存在資料庫中。在awesome-python-app中,我們選擇MySQL作為資料庫。
Web App裡面有很多地方都要訪問資料庫。訪問資料庫需要建立資料庫連接、遊標對象,然後執行SQL語句,最後處理異常,清理資源。這些訪問資料庫的代碼如果分散到各個函數中,勢必無法維護,也不利於代碼複用。
此外,在一個Web App中,有多個使用者會同時訪問,系統以多進程或多線程模式來處理每個使用者的請求。假設以多線程為例,每個線程在訪問資料庫時,都必須建立僅屬於自身的串連,對別的線程不可見,否則,就會造成資料庫操作混亂。
所以,我們還要建立一個簡單可靠的資料庫訪問模型,在一個線程中,能既安全又簡單地操作資料庫。
為什麼不選擇SQLAlchemy?SQLAlchemy太龐大,過度地物件導向設計導致API太複雜。
所以我們決定自己設計一個封裝基本的SELECT、INSERT、UPDATE和DELETE操作的db模組:transwarp.db
。
設計db介面
設計底層模組的原則是,根據上層調用者設計簡單易用的API介面,然後,實現模組內部代碼。
假設transwarp.db
模組已經編寫完畢,我們希望以這樣的方式來調用它:
首先,初始化資料庫連接資訊,通過create_engine()
函數:
from transwarp import dbdb.create_engine(user=‘root‘, password=‘password‘, database=‘test‘, host=‘127.0.0.1‘, port=3306)
然後,就可以直接操作SQL了。
如果需要做一個查詢,可以直接調用select()
方法,返回的是list,每一個元素是用dict表示的對應的行:
users = db.select(‘select * from user‘)# users =># [# { "id": 1, "name": "Michael"},# { "id": 2, "name": "Bob"},# { "id": 3, "name": "Adam"}# ]
如果要執行INSERT、UPDATE或DELETE操作,執行update()
方法,返回受影響的行數:
n = db.update(‘insert into user(id, name) values(?, ?)‘, 4, ‘Jack‘)
update()
函數簽名為:
update(sql, *args)
統一用?
作為預留位置,並傳入可變參數來綁定,從根本上避免SQL注入攻擊。
每個select()
或update()
調用,都隱含地自動開啟並關閉了資料庫連接,這樣,上層調用者就完全不必關心資料庫底層串連。
但是,如果要在一個資料庫連接裡執行多個SQL語句怎麼辦?我們用一個with語句實現:
with db.connection(): db.select(‘...‘) db.update(‘...‘) db.update(‘...‘)
如果要在一個資料庫事務中執行多個SQL語句怎麼辦?我們還是用一個with語句實現:
with db.transaction(): db.select(‘...‘) db.update(‘...‘) db.update(‘...‘)
實現db模組
由於模組是全域對象,模組變數是全域唯一變數,所以,有兩個重要的模組變數:
# db.py# 資料庫引擎對象:class _Engine(object): def __init__(self, connect): self._connect = connect def connect(self): return self._connect()engine = None# 持有資料庫連接的內容物件:class _DbCtx(threading.local): def __init__(self): self.connection = None self.transactions = 0 def is_init(self): return not self.connection is None def init(self): self.connection = _LasyConnection() self.transactions = 0 def cleanup(self): self.connection.cleanup() self.connection = None def cursor(self): return self.connection.cursor()_db_ctx = _DbCtx()
由於_db_ctx
是threadlocal
對象,所以,它持有的資料庫連接對於每個線程看到的都是不一樣的。任何一個線程都無法訪問到其他線程持有的資料庫連接。
有了這兩個全域變數,我們繼續實現資料庫連接的上下文,目的是自動擷取和釋放串連:
class _ConnectionCtx(object): def __enter__(self): global _db_ctx self.should_cleanup = False if not _db_ctx.is_init(): _db_ctx.init() self.should_cleanup = True return self def __exit__(self, exctype, excvalue, traceback): global _db_ctx if self.should_cleanup: _db_ctx.cleanup()def connection(): return _ConnectionCtx()
python Day 2 - 編寫資料庫模組