Python SQLAlchemy ORM教程(1)

由于SQLAlchemy 中文資料比較少,所以根據(jù)官網(wǎng)給的tutorial外加其他大佬寫的中文資料整合以后準(zhǔn)備寫一個SQLAlchemy 系列的基礎(chǔ)入門教程。本系列可能會夾雜一些個人對于python 、SQLAlchemy 以及ORM的理解,如有出錯部分,請指正我。

版本信息:

  • SQLAlchemy 1.2.15
  • Python 3.6+
  • Mac OS 10.14
  • DB基于SQLite

目錄

Python SQLAlchemy ORM教程(2)-使用SQLAlchemy在數(shù)據(jù)庫查詢數(shù)據(jù)
Python SQLAlchemy ORM教程(3)-使用SQLAlchemy在數(shù)據(jù)庫進行復(fù)雜查詢

Python SQLAlchemy ORM教程(4)-使用SQLAlchemy建立表關(guān)系

ORM

所謂ORM(Object Relational Mapping),就是建立其由Python類到數(shù)據(jù)庫表的映射關(guān)系:一個Python實例(instance)對應(yīng)數(shù)據(jù)庫中的一行(row)。這種映射包含兩層含義,一是實現(xiàn)對象和與之關(guān)聯(lián)的的行的狀態(tài)同步,二是將涉及數(shù)據(jù)庫的查詢操作,表達為Python類的相互關(guān)系。

注意ORM和SQLAlchemy的Expression Language不同。后者可以視為對原始SQL的封裝。ORM是基于Expression Language而構(gòu)建的,其抽象層次要高于Expression Language。很多時候我們都是使用ORM,有時需要一些高度定制化的功能時,就需要使用到Expression Language。

Version Check

本教程是基于以下版本,如果你的版本號差距不大,本教程仍然具有參考意義

>>> sqlalchemy.__version__
'1.2.15'

Connecting

SQLite 支持在內(nèi)存中創(chuàng)建數(shù)據(jù)庫,這對于我們學(xué)習(xí)來說非常方便。在SQLAlchemy中,所有數(shù)據(jù)庫都是使用引擎進行鏈接(engine),引擎將鏈接不同數(shù)據(jù)庫的差異磨平,讓你可以非??焖俚倪M行數(shù)據(jù)的鏈接。我們來看一段代碼怎么創(chuàng)建SQLAlchemy的引擎:

>>> from sqlalchemy import create_engine
>>> engine = create_engine('sqlite:///:memory:',echo=True)
>>> engine
Engine(sqlite:///:memory:)

echo 的的作用類似debug,True的時候可以在控制臺看到所有關(guān)于SQL語言的操作,不是必須選項。create_engine會返回engine實例,這是一個鏈接DBAPI的核心接口。
SQLAlchemy 使用了Lazy Connecting ,create_engine返回的engine 實際上并沒有連接到數(shù)據(jù)庫。只有當(dāng)你實際執(zhí)行一次任務(wù)的時候才會連接到數(shù)據(jù)庫。比如執(zhí)行exectue() 或者 connect()。
盡管SQLALchemy,盡可能的磨平了不同數(shù)據(jù)庫之間的操作差異,但是SQLALchemy 本身提供了多樣化的操作,來實現(xiàn)高度的ORM定制,但是本教程僅僅做基礎(chǔ)方面的,關(guān)于Lazy Connecting僅僅知道就行。
關(guān)于更多的連接數(shù)據(jù)庫的方式請參考Engine Configuration

Declaring a Mapping

當(dāng)我們使用ORM的時候,其配置過程主要分為兩個部分:

  1. 理清楚我們的數(shù)據(jù)庫表和表關(guān)系等等內(nèi)容
  2. 將這些數(shù)據(jù)庫的內(nèi)容表達成Python的映射類

在 SQLAlchemy 中Declarative System會幫助你完成一些事情,所有的映射類都需要繼承基類(Base class),而Declarative system的實例可以使用declarative_base() 函數(shù)來創(chuàng)建,這個函數(shù)在 from sqlalchemy.ext.declarative 包內(nèi)。創(chuàng)建實例

>>> from sqlalchemy.ext.declarative import declarative_base
>>> Base = declarative_base()
>>> Base
<class 'sqlalchemy.ext.declarative.api.Base'>

現(xiàn)在我們已經(jīng)有了一個Declarative system的實例了(實現(xiàn)Declarative system的類也叫基類),我們可以基于這個基類來創(chuàng)建我們的映射類了。我們以建立一個User類為例子。從Base派生一個名為User的類,在這個類里面我們可以定義將要映射到數(shù)據(jù)庫的表上的屬性(主要是表的名字,列的類型和名稱等)

>>> from sqlalchemy import Column, Integer, String
>>> class User(Base):
...     __tablename__ = 'users'
...
...     id = Column(Integer, primary_key=True)
...     name = Column(String)
...     fullname = Column(String)
...     password = Column(String)
...
...     def __repr__(self):
...        return "<User(name='%s', fullname='%s', password='%s')>" % (
...                             self.name, self.fullname, self.password)

通過Declarative system生成的類至少應(yīng)該包含一個名為tablename的屬性來給出目標(biāo)表的名稱,以及至少一個Column來給出表的主鍵(Primary Key)。SQLAlchemy不會對于類名和表名之間的關(guān)聯(lián)做任何假設(shè),也不會自動涉及數(shù)據(jù)類型以及約束的轉(zhuǎn)換。一般的你可以自己創(chuàng)建一個模板來建立這些自動轉(zhuǎn)換,這樣可以減少你的很多重復(fù)勞動。
當(dāng)我們的類聲明完成后,Declarative system將會將所有的Column成員替換成為特殊的Python訪問器(accessors),我們稱之為descriptors。這個過程我們稱為instrumentation,經(jīng)過instrumentation的映射類可以讓我們能夠讀寫數(shù)據(jù)庫的表和列。
注意除了這些涉及ORM的映射意外,這些mapping類的其他部分仍然是不變的。

Create a Schema

通過繼承基類創(chuàng)建 User 類以后,我們就有了與表相關(guān)的映射類的信息(此時表并沒有在數(shù)據(jù)庫內(nèi)創(chuàng)建)。我們可以使用__table__屬性來查看

>>> User.__table__ 
Table('users', MetaData(bind=None),
            Column('id', Integer(), table=<users>, primary_key=True, nullable=False),
            Column('name', String(), table=<users>),
            Column('fullname', String(), table=<users>),
            Column('password', String(), table=<users>), schema=None)

額外話題:Classical Mappings

Declarative system 在 SQLAlchemy ORM中不是必須的,但是SQLAlchemy還是非常推薦你使用的,如果你不使用Declarative system,任何獨立的 Python 類都可以通過mapper()函數(shù)來手動映射到任何Table類,更多騷操作可以看官方的文檔。作為新手,還是老老實實使用Declarative system 。

繼續(xù)

如果我們使用Declarative system 的話,Declarative system 會幫我們使用Python的metaclass 類來對我們創(chuàng)建的映射類進行深加工。Declarative system 會創(chuàng)建一個與User類相關(guān)的Table對象,然后構(gòu)造一個Mapper對象與之關(guān)聯(lián),這一步在Classical Mappings中是由程序員手動操作,但是使用Declarative system 后這些將自動完成。

Table Object

Table 對象是 MetaData中的一員。當(dāng)我們使用Declarative system時,這個對象也可以在Declarative base class.metadata屬性中看到??梢钥聪?code>Declarative base class中的Table對象

>>> Base.metadata.sorted_tables
[Table('users', MetaData(bind=None), Column('id', Integer(), table=<users>, primary_key=True, nullable=False), Column('name', String(), table=<users>), Column('fullname', String(), table=<users>), Column('password', String(), table=<users>), schema=None)]

MetaData是我們與數(shù)據(jù)庫打交道的一個接口。在MetaData類內(nèi),儲存了你創(chuàng)建的數(shù)據(jù)庫相關(guān)的所有元數(shù)據(jù)(metaData)。對于我們的SQLite數(shù)據(jù)庫而言,此時還沒有一個名為users的表的存在,我們需要使用MetaData類的方法來發(fā)出CREATE TABLE的命令。下面我們使用MetaData.create_all()指令,將我們上面得到的Engine作為參數(shù)傳入。如果你上面設(shè)置了echo為True的話,應(yīng)該可以看到這一過程中的SQL指令。首先檢查了users表的存在性,如果不存在的話會執(zhí)行表的創(chuàng)建工作。

>>> Base.metadata.create_all(engine)
2018-12-14 14:21:45,329 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
2018-12-14 14:21:45,335 INFO sqlalchemy.engine.base.Engine ()
2018-12-14 14:21:45,336 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
2018-12-14 14:21:45,336 INFO sqlalchemy.engine.base.Engine ()
2018-12-14 14:21:45,337 INFO sqlalchemy.engine.base.Engine PRAGMA table_info("users")
2018-12-14 14:21:45,337 INFO sqlalchemy.engine.base.Engine ()
2018-12-14 14:21:45,338 INFO sqlalchemy.engine.base.Engine
CREATE TABLE users (
    id INTEGER NOT NULL,
    name VARCHAR,
    fullname VARCHAR,
    password VARCHAR,
    PRIMARY KEY (id)
)


2018-12-14 14:21:45,339 INFO sqlalchemy.engine.base.Engine ()
2018-12-14 14:21:45,339 INFO sqlalchemy.engine.base.Engine COMMIT

Create an Instance of the Mapped Class

ok!大部分前期準(zhǔn)備工作已經(jīng)完成,我們現(xiàn)在開始往數(shù)據(jù)庫內(nèi)插入數(shù)據(jù)看看

>>> ed_user = User(name='ed', fullname='Ed Jones', password='edspassword')
>>> ed_user.name
'ed'
>>> ed_user.password
'edspassword'
>>> str(ed_user.id)
'None'

你可以看到,我們并沒有指定id是多少,但是當(dāng)我們想看看id是多少的時候,返回了None,但是我們并沒有在構(gòu)造器內(nèi)填入id的值,SQLAlchemy 官方文檔說,為了防止出現(xiàn)AttributeError的出現(xiàn),我們幫你添加了。

Creating a Session

Session 在SQLAlchemy中是一個非常重要的概念,session可以理解為緩沖區(qū),因為session的存在,所以很多功能得以方便的實現(xiàn),比如Rollback。

我們現(xiàn)在可以和數(shù)據(jù)庫對話了。在SQLAlchemy中對數(shù)據(jù)庫的數(shù)據(jù)CDRU都是通過Session來實現(xiàn)的,Session 和 Engine 是同一級別下的類,相同的重要。我們定義一個Session類來作為生成新的Session的Factory類

>>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker(bind=engine)

sessionmaker 僅僅是session的前置配置,綁定engine不是必須的

>>> Session = sessionmaker()

你仍然可以通過configure來綁定engine

>>> Session.configure(bind=engine)  # once engine is available

然后你可以通過factory 類來創(chuàng)建一個與數(shù)據(jù)庫綁定的session

>>> session = Session()

題外話:對于不同的session你可能有自己的新想法,所以factory類允許你改動配置以后重新創(chuàng)建一個新的session類。

到這一步,engine 仍然沒有連接到數(shù)據(jù)庫,engine在等待你的第一次使用,當(dāng)你連接到數(shù)據(jù)庫以后,Pool會維持你的連接直到你手動關(guān)閉session。

Adding and Updating Objects

我們要開始往數(shù)據(jù)庫寫入東西了。非常簡單,session類內(nèi)置了 add 方法

>>> ed_user = User(name='ed', fullname='Ed Jones', password='edspassword')
>>> session.add(ed_user)

當(dāng)你session.add以后,這個User實例的狀態(tài)為Pending,你的數(shù)據(jù)仍然保存在Pool中,而不是數(shù)據(jù)庫中,這是一個你與數(shù)據(jù)庫中的緩沖地帶,為你的錯誤提供rollback的機會。當(dāng)SQL命令被執(zhí)行后,你的數(shù)據(jù)才會出現(xiàn)在數(shù)據(jù)庫中,這是過程SQLAlchemy稱之為flush。

現(xiàn)在你就可以創(chuàng)建查詢(Query)對象了。我們來查詢下剛剛添加進去的一個User。(更多的查詢,會在后面講)

>>> our_user = session.query(User).filter_by(name='ed').first()
2018-12-14 14:57:27,320 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-12-14 14:57:27,322 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
2018-12-14 14:57:27,322 INFO sqlalchemy.engine.base.Engine ('ed', 'Ed Jones', 'edspassword')
2018-12-14 14:57:27,323 INFO sqlalchemy.engine.base.Engine SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
FROM users
WHERE users.name = ?
 LIMIT ? OFFSET ?
2018-12-14 14:57:27,323 INFO sqlalchemy.engine.base.Engine ('ed', 1, 0)
>>> our_user
<User(name='ed', fullname='Ed Jones', password='edspassword')>

我們已經(jīng)拿回了ed_user,我們可以看到our_usered_user沒什么區(qū)別

>>> id(ed_user)
4419178904
>>> id(our_user)
4419178904
>>> ed_user is our_user
True

我們嘗試下大批量的加入數(shù)據(jù):

>>> session.add_all([
...     User(name='wendy', fullname='Wendy Williams', password='foobar'),
...     User(name='mary', fullname='Mary Contrary', password='xxg527'),
...     User(name='fred', fullname='Fred Flinstone', password='blah')])

要記住,這個時候,我們還沒有執(zhí)行任何SQL語句,當(dāng)我們發(fā)現(xiàn)的密碼太短了,不太安全,我們想進行修改的時候,可以方便的修改

>>> ed_user.password='f8s7ccs'

當(dāng)你修改了大量的數(shù)據(jù)是,你想看看哪些數(shù)據(jù)被修改了,SQLAlchemy已經(jīng)替你想好了

>>> session.dirty
IdentitySet([<User(name='ed', fullname='Ed Jones', password='f8s7ccs')>])

你可以查詢下,新加入的數(shù)據(jù)

>>> session.new  
IdentitySet([<User(name='wendy', fullname='Wendy Williams', password='foobar')>,
<User(name='mary', fullname='Mary Contrary', password='xxg527')>,
<User(name='fred', fullname='Fred Flinstone', password='blah')>])

好了,都沒問題,我們準(zhǔn)備將數(shù)據(jù)放入到數(shù)據(jù)庫了

>>> session.commit()
2018-12-14 15:04:29,162 INFO sqlalchemy.engine.base.Engine UPDATE users SET password=? WHERE users.id = ?
2018-12-14 15:04:29,162 INFO sqlalchemy.engine.base.Engine ('f8s87ccs', 1)
2018-12-14 15:04:29,162 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
2018-12-14 15:04:29,162 INFO sqlalchemy.engine.base.Engine ('wendy', 'Wendy Williams', 'foobar')
2018-12-14 15:04:29,163 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
2018-12-14 15:04:29,163 INFO sqlalchemy.engine.base.Engine ('mary', 'Mary Contrary', 'xxg527')
2018-12-14 15:04:29,163 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
2018-12-14 15:04:29,163 INFO sqlalchemy.engine.base.Engine ('fred', 'Fred Flinstone', 'blah')
2018-12-14 15:04:29,163 INFO sqlalchemy.engine.base.Engine COMMIT

我們來看下 ed_userid 還是不是 None

>> ed_user.id
2018-12-14 15:05:07,938 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-12-14 15:05:07,938 INFO sqlalchemy.engine.base.Engine SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
FROM users
WHERE users.id = ?
2018-12-14 15:05:07,938 INFO sqlalchemy.engine.base.Engine (1,)
1

Reference:
SQLAlchemy ORM教程之一:Create
Object Relational Tutorial

由于偷懶復(fù)制了一些內(nèi)容,如果有侵犯,我馬上刪

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容