我經常在工作和個人項目中使用Django。但是在最近的工作項目中,我有機會使用Flask和SQLAlchemy。所以我必須學點新東西。
Flask很簡單,因為它是一個微框架,裡面沒有很多東西。然而,理解SQLAlchemy以及如何使用它比我預期的要困難得多。
在本文中,我試圖通過一些示例來展示Django ORM和SQLAlchemy之間的主要區別,這些示例演示了如何在Django中執行某些操作,以及如何在SQLAlchemy中執行相同的操作。如果您出於任何原因試圖切換到SQLAlchemy,我希望它對您有用。
在深入討論示例和Django ORM與SQLAlchemy之間的區別之前,讓我們先從理解事務開始,因為如果您想使用SQLAlchemy編寫任何內容,那麼事務是一個非常重要的概念。
事務
將事務視為確保多個資料庫操作(插入、刪除、……)作為一個組,要麼一起成功,要麼一起失敗的一種方法。
啟動事務時,我們記錄資料庫的當前狀態,然後執行SQL語句。如果所有這些語句都成功,則提交事務。提交之後,所有更改都將持久化到資料庫中,並對其他事務可見。
但是,如果一個或多個語句失敗,我們將捕獲異常並回滾任何成功的語句。
Django和SQLAlchemy中的事務
我們在Django和SQLAlchemy中處理事務的方式是不同的。
在Django中,我們很少考慮事務。因為Django的默認行為是在自動提交模式下運行,這意味著每個SQL語句都被包裝到它自己的事務中,這個事務將根據SQL語句成功與否自動提交或回滾。
但在SQLAlchemy中,我們有Session對象。會話是SQLAlchemy與資料庫交互的方式。它允許您累積多個更改,然後發出commit命令,該命令將自動將所有更改作為一個單元寫入資料庫。這種模式也稱為工作單元:
當提交引發一個異常,你想要處理它,你應該手動回滾事務,這樣你的應用程式才能繼續正常運行:
Django和SQLAlchemy中的原子性
現在,當您了解了Django和SQLAlchemy處理事務的區別之後,您應該會看到這兩種方法的優缺點。
在自動提交模式下運行的優點是,它使得使用這個ORM更容易理解和編寫代碼。缺點是,如果你有多個查詢,其中一個成功,另一個失敗,那麼你的資料庫就有損壞的風險:
解決風險的方法是對資料庫原子進行查詢。原子性意味著您在一個事務中所做的事情作為一個單元進行或失敗。如果代碼塊成功完成,則將更改提交給資料庫。如果出現異常,則回滾更改。
這就是session在SQLAlchemy中的作用。在Django中,我們可以使用原子函數實現原子性:
這兩種Transfer要麼都將被添加到資料庫中,要麼都不添加。
Django和SQLAlchemy中的模型
在Django和SQLAlchemy中定義模型時,您將立即看到的主要區別是,在SQLAlchemy中必須顯式地定義模型。但是在Django中,很多事情都是在底層完成的。
例如,讓我們看看如何在Django和SQLAlchemy中定義具有不同關係的模型。
在Django中:
同樣的事情在SQLAlchemy:
讓我們來討論一下主要的區別。
Django的模型。默認情況下,Model類創建一個自動遞增的整數主鍵。在SQLAlchemy我們必須明確定義:
另一個區別是,在SQLAlchemy中,我們必須自己對關係建模,比如一對多、多對多和一對一。在Django中更簡單,因為它為您處理關係。
例如,如果我們想在SQLAlchemy中創建一對多的關係,我們應該首先定義一個列:
然後聲明兩個模型之間的關係:
注意,當我們定義一個外鍵時,我們指向聊天表的id列。但是當我們定義模型之間的關係時,我們不是指向表,而是指向Chat模型。
backref參數自動聲明反向關係。lazy= ' dynamic '創建了一個動態關係,這意味著當我們訪問chat時。消息,SQLAlchemy將返回一個查詢對象,我們可以進一步過濾:
如果我們不指定lazy參數,默認值將是' select '。它的工作方式不同。當我們首次使用chat.messages時,SQLAlchemy會向資料庫發送一個查詢,獲取所有相關的消息,並返回一個列表:
Django和SQLAlchemy中的查詢
當我們想要在Django中過濾查詢時,我們使用的關鍵字參數的格式是column=value或column_operation =value:
在SQLAlchemy中,我們使用模型表達式進行過濾:
說到連接,在Django中更簡單,因為它為我們處理連接:
在SQLAlchemy我們必須明確:
SQLAlchemy會自動嘗試查找要連接的內容,但是我們可以明確地將其指定為要連接的第二個參數:
大多數情況下,它在不指定第二個參數的情況下也能工作。但是,如果模型之間有很多關係,有時它會以您不希望的方式執行自動連接。所以要小心。
現在讓我們看看如何重用查詢。例如,如果我們需要獲得有未讀消息的對話框(與2個參與者的聊天),該怎麼辦?或者我們想要檢查一個特定的聊天是否是一個對話框,以及它是否有未讀的消息。我們不想在不同的地方重複粘貼代碼。記得擦乾(不要重複)。
在Django中,我們可以為聊天模型創建一個自定義的QuerySet和屬性:
如何使用:
在SQLAlchemy中,我們可以使用混合屬性或方法使代碼可重用:
混合屬性意味著該屬性既可以在類級使用,也可以在實例級使用:
混合方法也可以在實例級和類級使用。不同之處在於,如果需要,我們可以將參數傳遞給方法。
N + 1問題
讓我們來討論一個無論使用Django還是Flask都會遇到的問題:假設您需要從資料庫中提取每個聊天的消息,然後顯示消息文本和關於消息發送者的信息:
為了顯示所有這些信息,我們將向資料庫發送多少個查詢?由於Django比較懶,所以在編寫chat .object .all()時,它只將聊天信息提取到內存中。它不提取關於消息的信息,當然,也不提取關於消息發送者的信息。
因此,如果我們有2個聊天,每個聊天有10條消息,那麼它將需要23個查詢。首先,我們從資料庫中提取所有聊天記錄(1 db請求)。對於每個聊天,我們提取所有消息(另一個2 db請求)。最後,對於每條消息,我們提取其發送者的信息(2個聊天* 10條消息)。1 + 2 + 2 * 10 = 23
我們可以做的是使用prefetch_related函數將需要的信息預加載到內存中:
我們總共只有3個查詢。一個用於聊天,一個用於消息,一個用於用戶(發送者)。
我們可以在SQLAlchemy做類似的事情:
我們使用joinedload將sender預加載到內存中。正如您所看到的,我們沒有預先加載聊天消息。問題是您不能預加載lazy= ' dynamic '關係,因為它產生的是查詢,而不是集合(列表)。因此,在為您的模型定義和使用lazy= ' dynamic '關係時要小心,因為它會導致對資料庫的大量查詢。然而,沒有什麼可以阻止你定義兩個關係到同一個模型:
我建議您使用默認情況下返回列表的關係,但是當您確實需要動態關係來過濾嵌套數據時,您可以輕鬆地將其添加到模型中。
另外,請記住,即使不使用動態關係,您也可以過濾嵌套的關係。contains_eager能幫你做到:
首先,我們加載具有未讀消息的聊天。然後我們預加載聊天-信息的關係。預加載後,此關係將生成未讀消息列表。
結論
我們已經了解了最流行的Python ORM: SQLAlchemy和Django ORM。從我的經驗來看,Django ORM更容易學習和使用,但是SQLAlchemy為您提供了更大的靈活性,而且可能更適合大型應用程式。
如果您是初學者,並且正在嘗試為下一個項目Django或Flask + SQLAlchemy選擇使用什麼,我強烈建議您繼續使用Django。最初,Flask可能看起來很簡單,但是當您開始構建能夠解決實際問題的應用程式時,使用起來就比Django更有挑戰性了。此外,Django的生態系統更大。它有更多的開源庫和教程。
如果您不是初學者,並且正在嘗試構建微服務,那麼Flask + SQLAlchemy可能是更好的選擇。但如果你已經到了那一步,你自己應該就知道如何對框架進行取捨了。