Python 資料庫操作 SQLAlchemy

image
程式在執行過程中所有的的資料都儲存在記憶體 (RAM) 中,「RAM 是易失性儲存器,系統掉電後 RAM 中的所有資料將全部丟失」。在大多數情況下我們希望程式執行中產生的資料能夠長久的儲存,此時我們就需要將資料儲存到磁碟上,無論是儲存到本地磁碟,還是通過網路儲存到伺服器上,最終都會將資料寫入磁碟檔案。將資料儲存在磁碟中我們需要面對一個數據格式的問題,此時就需要引入資料庫操作。
資料庫是專門用於資料的集中儲存和查詢的軟體,它便於程式儲存和讀取資料,且能夠通過特定的條件查詢指定的資料。
資料庫原理請參考 如果有人問你資料庫的原理,叫他看這篇文章 。
Python 的標準資料庫介面為 Python DB-API,它為程式設計人員提供了完善的資料庫應用標稱介面。但是使用 Python DB-API 需要開發人員自行去拼接 SQL,並把 SQL 做成模板。此時全靠程式設計人員來保證系統的安全性,完全有人來保證系統的安全性,不可避免的會出現錯誤,為了減少人為原因產生的錯誤 ORM 框架應運而生。
ORM 即 Object-Relational Mapping,把關係資料庫的表結構對映到物件上面。負責這個轉換過程的就是 ORM 框架
Python 中的 ORM 框架主要有 SQLObject、Storm、Django's ORM、peewee、SQLALchemy。每種 ORM 框架都有各自的特點和相應的應用範圍,本文主要介紹 SALALchemy,若你對其他框架感興趣請自行搜尋相關內容。
SQLAlchemy 簡介
SQLAlchemy 是一個功能強大的開源 Python ORM 工具包。它提供了 “一個知名企業級的持久化模式的,專為高效率和高效能的資料庫訪問設計的,改編成一個簡單的 Python 域語言的完整套件”。它採用了資料對映模式(像 Java 中的 Hibernate)而不是 Active Record 模式(像Ruby on Rails 的 ORM)。
SQLAlchemy 官網 。
SQLAlchemy 的優缺點:
優點:
- 企業級 API,使得程式碼有健壯性和適應性。
- 靈活的設計,使得能輕鬆完成複雜的資料查詢。
缺點:
- 工作單元概念不常見。
- 重量級 API,導致長學習曲線。
SQLAlchemy 應用
以下是一段使用 SQLAlchemy 操作 SQLite 資料庫的程式碼
# -*- coding:utf-8 -*- from sqlalchemy import ( create_engine, Column, Integer, String, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker engine = create_engine('sqlite:///./sqlalchemy.sqlite', echo=True) Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String) fullname = Column(String) nickname = Column(String) def __repr__(self): return "<User(name='%s', fullname='%s', nickname='%s')>" % (self.name, self.fullname, self.nickname) db_session = sessionmaker(bind=engine) session = db_session() Base.metadata.create_all(engine) user1 = User(name='ed', fullname='Ed Jones', nickname='edsnickname') user2 = User(name='wendy', fullname='Wendy Williams', nickname='windy') user3 = User(name='mary', fullname='Mary Contrary', nickname='mary') session.add(user1) session.add(user2) session.add(user3) session.commit() user = session.query(User).filter(User.id<2).all() print(user) user = session.query(User).filter(User.id<=5).all() print(user) user1.name = 'admin' session.merge(user1) user4 = User(name='fred', fullname='Fred Flintstone', nickname='freddy') session.merge(user4) session.query(User).filter(User.id==2).update({'name':'test'}) user = session.query(User).filter(User.id<=5).all() print(user)
在以上程式碼中我們完成了一下工作:
- 連線到資料庫「本次我們使用的是 SQLite 資料庫」。
- 建立資料庫表並將其對映到 Python 類上。
- 建立資料例項,並將其儲存到資料庫中。
- 對儲存在資料庫中的資料進行讀取和修改。
匯入 SQLAlchemy 模組並連線到 SQLite 資料庫
SQLAlchemy 通過 create_engine 函式來建立資料庫連線。create_engine 函式的第一個引數是資料了 URL,第二個引數 echo 設定為 True 表示在程式的執行過程中我們可以在控制檯看到操作所涉及到的 SQL 語句。
在本次示例中我們使用的資料庫是 SQLite,你也可以使用其他資料庫。只有在除錯狀態下將 echo 設定為 True,在生產環境請將 echo 設定為 false 或省略 echo 引數。
engine = create_engine('sqlite:///./sqlalchemy.sqlite', echo=True)
create_engine 返回的是一個 Engine 例項,它指向資料庫的一些核心介面。SQLAlchemy會根據你選擇的資料庫配置而呼叫對應的 DB-API。
create_engine 函式並會不真正建立資料庫的 DB-API 連線,當呼叫 Engine.execute() 或 Engine.connect() 方法時才會建立連線。大多數情況下我們無需關注 Engine,SQLAlchemy 會幫我們處理。
建立資料庫表
將 python 類對映到資料庫表上,這個 Python 類需要時一個指定基類的子類,這個基類應當含有ORM對映中相關的類和表的資訊。這個基類可以通過 declarative_base 方法來建立。
Base = declarative_base()
在這個示例中使用 Base 基類建立了一個 User 的類來作為資料庫表。
class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String) fullname = Column(String) nickname = Column(String) def __repr__(self): return "<User(name='%s', fullname='%s', nickname='%s')>" % (self.name, self.fullname, self.nickname)
在 User 類中我們定義了需要對映到資料庫表上的屬性,主要包括表名稱、列的型別和名稱等。這個類至少應包含一個名為 tablename 的屬性來給出資料庫表的名稱,及至少一個給出表的主鍵「primary key」的列。在 User 類中我們定義了表名稱為 users,定義了 id、name、fullname、nickname 四列資料,並設定 id 為表的主鍵。
建立完成 User 類後,實際在 SQLite 資料庫中並不存在該表,此時需要使用 declarative 基類的 Metadata.create_all 在資料庫中建立 users 表,在 create_all 方法中我們需要傳入引數 Engine。
通過 Metadata.create_all 傳入的 Engine 引數,SQLAlchemy 自動實現對資料庫的連線。
Base.metadata.create_all(engine)
metadata.create_all 方法執行完成後在 SQLite 資料庫即可查到名稱為 users 的資料表。
儲存資料例項到資料庫中
將資料儲存到資料庫中,我們需要 User 的例項和用於操作資料的 session。
session 是 ORM 資料的介面,可以通過 session 來操作資料庫中的資料。
使用已經定義完成的 User 類將資料例項化。
user1 = User(name='ed', fullname='Ed Jones', nickname='edsnickname') user2 = User(name='wendy', fullname='Wendy Williams', nickname='windy') user3 = User(name='mary', fullname='Mary Contrary', nickname='mary')
獲取 session 首先需要使用 sessionmaker 來得到 session 的工廠類,然後通過工廠類來獲取 session。
db_session = sessionmaker(bind=engine) session = db_session()
session 通過 Engine 與資料庫進行關聯。session 建立完成後並不會立即開啟與資料庫的連線,只有當我們第一使用 session 是,才會從 Engine 維護的連線池中取出一個連線來操作資料庫,這個連線在我們關閉 session 時會被釋放。
獲取 session 後可以通過 add 和 commit 方法將資料儲存到資料庫中。
session.add(user1) session.add(user2) session.add(user3) session.commit()
對資料庫中的資料進行查詢和修改
SQLAlchemy 通過 query 來對資料進行查詢,可以通過 filter 方法對查詢結果進行篩選。
user = session.query(User).filter(User.id<2).all() print(user) user = session.query(User).filter(User.id<=5).all() print(user)
以上程式碼通過 query 獲取資料庫中所有 User 資料,然後通過 filter 方法篩選出 id 小於 2 和 id 小於等於 5 的資料。
資料庫的修改可以通過 merge 和 update 來實現
user1.name = 'admin' session.merge(user1) user4 = User(name='fred', fullname='Fred Flintstone', nickname='freddy') session.merge(user4) session.query(User).filter(User.id==2).update({'name':'test'}) user = session.query(User).filter(User.id<=5).all() print(user)
使用 merge 修改資料,當資料中存在該資料時修改,不存在是將當前資料插入資料庫中。
程式碼執行結果
以上示例程式碼的執行結果如下
2019-02-16 21:45:23,919 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2019-02-16 21:45:23,919 INFO sqlalchemy.engine.base.Engine () 2019-02-16 21:45:23,919 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2019-02-16 21:45:23,919 INFO sqlalchemy.engine.base.Engine () 2019-02-16 21:45:23,920 INFO sqlalchemy.engine.base.Engine PRAGMA table_info("users") 2019-02-16 21:45:23,920 INFO sqlalchemy.engine.base.Engine () 2019-02-16 21:45:23,921 INFO sqlalchemy.engine.base.Engine CREATE TABLE users ( id INTEGER NOT NULL, name VARCHAR, fullname VARCHAR, nickname VARCHAR, PRIMARY KEY (id) ) 2019-02-16 21:45:23,921 INFO sqlalchemy.engine.base.Engine () 2019-02-16 21:45:23,922 INFO sqlalchemy.engine.base.Engine COMMIT 2019-02-16 21:45:23,924 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 2019-02-16 21:45:23,925 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, fullname, nickname) VALUES (?, ?, ?) 2019-02-16 21:45:23,925 INFO sqlalchemy.engine.base.Engine ('ed', 'Ed Jones', 'edsnickname') 2019-02-16 21:45:23,926 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, fullname, nickname) VALUES (?, ?, ?) 2019-02-16 21:45:23,926 INFO sqlalchemy.engine.base.Engine ('wendy', 'Wendy Williams', 'windy') 2019-02-16 21:45:23,926 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, fullname, nickname) VALUES (?, ?, ?) 2019-02-16 21:45:23,926 INFO sqlalchemy.engine.base.Engine ('mary', 'Mary Contrary', 'mary') 2019-02-16 21:45:23,927 INFO sqlalchemy.engine.base.Engine COMMIT 2019-02-16 21:45:23,929 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 2019-02-16 21:45:23,929 INFO sqlalchemy.engine.base.Engine SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users WHERE users.id < ? 2019-02-16 21:45:23,929 INFO sqlalchemy.engine.base.Engine (2,) [<User(name='ed', fullname='Ed Jones', nickname='edsnickname')>] 2019-02-16 21:45:23,931 INFO sqlalchemy.engine.base.Engine SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users WHERE users.id <= ? 2019-02-16 21:45:23,931 INFO sqlalchemy.engine.base.Engine (5,) [<User(name='ed', fullname='Ed Jones', nickname='edsnickname')>, <User(name='wendy', fullname='Wendy Williams', nickname='windy')>, <User(name='mary', fullname='Mary Contrary', nickname='mary')>] 2019-02-16 21:45:23,932 INFO sqlalchemy.engine.base.Engine UPDATE users SET name=? WHERE users.id = ? 2019-02-16 21:45:23,932 INFO sqlalchemy.engine.base.Engine ('admin', 1) 2019-02-16 21:45:23,933 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, fullname, nickname) VALUES (?, ?, ?) 2019-02-16 21:45:23,933 INFO sqlalchemy.engine.base.Engine ('fred', 'Fred Flintstone', 'freddy') 2019-02-16 21:45:23,934 INFO sqlalchemy.engine.base.Engine UPDATE users SET name=? WHERE users.id = ? 2019-02-16 21:45:23,934 INFO sqlalchemy.engine.base.Engine ('test', 2) 2019-02-16 21:45:23,935 INFO sqlalchemy.engine.base.Engine SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users WHERE users.id <= ? 2019-02-16 21:45:23,935 INFO sqlalchemy.engine.base.Engine (5,) [<User(name='admin', fullname='Ed Jones', nickname='edsnickname')>, <User(name='test', fullname='Wendy Williams', nickname='windy')>, <User(name='mary', fullname='Mary Contrary', nickname='mary')>, <User(name='fred', fullname='Fred Flintstone', nickname='freddy')>]
由於我們設定 create_engine 中 echo 為 True,因此在執行結果中包含了 SQLAlchemy 列印的 SQL 語句,我們可以取消 crete_engine 中的 echo
engine = create_engine('sqlite:///./sqlalchemy.sqlite')
此時的執行結果如下:
[<User(name='ed', fullname='Ed Jones', nickname='edsnickname')>] [<User(name='ed', fullname='Ed Jones', nickname='edsnickname')>, <User(name='wendy', fullname='Wendy Williams', nickname='windy')>, <User(name='mary', fullname='Mary Contrary', nickname='mary')>] [<User(name='admin', fullname='Ed Jones', nickname='edsnickname')>, <User(name='test', fullname='Wendy Williams', nickname='windy')>, <User(name='mary', fullname='Mary Contrary', nickname='mary')>, <User(name='fred', fullname='Fred Flintstone', nickname='freddy')>]
當取消 echo 後,程式執行結果中原有的 SQL 語句消失。
本文只是對 SQLAlchemy 的使用進行簡單的介紹,SQLAlchemy 本身還有很多特性和運用方法我們可以共同探討。