轉自:http://blog.csdn.net/dupei/article/details/6014488
SQLAlchemy指南(tutorial)
對應版本: | 0.3.4 |
---|
這個入門指導用於SQLAlchemy的快速入門,並便利SQLAlchemy的簡單功能。如果你可以跳過這一部分進入 主文檔 會涉及更多內容。如下的例子全部是在Python交互模式下完成了,並且全部通過了 doctest 測試。
1 安裝
1.1 安裝SQLAlchemy
從 setuptools 安裝是非常簡單的,只要運行如下命令即可:
# easy_install SQLAlchemy
這將會在Python Cheese Shop獲取SQLAlchemy的最新版本並安裝。或者你也可以使用setup.py安裝一個發行包:
# python setup.py install
1.2 安裝一個數據庫API
SQLAlchemy被設計用於操作一個 DBAPI 實現,包括大多數常見的數據庫。如果你有一個支持DBAPI的實現,那麼可以跳入下一節。另外SQLite是一個易於使用的數據庫,可以快速開始,並且他可以使用內存數據庫。如果要使用SQLite,你將會需要:
- pysqlite - SQLite的Python接口
- SQLite函數庫
注意在Windows下並不需要SQLite函數庫,因爲Windows版的pysqlite已經內含了。pysqlite和SQLite可以被安裝到Linux或FreeBSD,通過預編譯或從源碼安裝。
預編譯包的地址爲:
http://initd.org/tracker/pysqlite/wiki/PysqlitePackages
2 快速開始
2.1 導入
SQLAlchemy提供了完整的命名空間,只要導入sqlalchemy即可,無需其子包。爲了方便使用本教程,我們導入所有命名到本地命名空間:
>>> from sqlalchemy import *
2.2 連接到數據庫
導入之後,下一步是連接到需要的數據庫,表現爲(represent)爲一個Engine對象。這個對象處理了連接的管理和特定數據庫的操作。下面,我們連接SQLite基於文件的數據庫”tutorial.db”
>>> db=create_engine("sqlite:///tutorial.db")
創建數據庫引擎的更多信息查看”Database Engines”。
3 SQLAlchemy是兩個庫的包裝
現在已經完成了安裝和連接數據庫,可以開始做點實際的事情了。但首先需要有些解釋。
SQLAlchemy的核心有兩個完全不同的功能,一個在另一個之上工作。一個是 SQL語言構造器 ,另一個是 ORM。SQL語言構造器允許調用 ClauseElements 來構造SQL表達式。這些 ClauseElements 可以在編譯成字符串並綁定到數據庫後用於執行,並返回一個叫做 ResultProxy 的對象,類似於一個結果集對象,但是更象dbapi高版本的 cursor 對象。
ORM是建立在SQL語言構造器之上的工具集,用於將Python對象映射到數據庫的行,提供了一系列接口用於從數據庫中存取對象(行)。在ORM 工作時,在底層調用SQL語言構造器的API,這些通用的操作有些許的不同。不同的是,你不再使用行,而是使用自定義類的對象來操作。另外,數據庫的查詢 方式也不同,ORM的可以生成大多數的SQL查詢,除此之外還可以在類中定義更多操作。
SA功能強大,無與倫比,只是有兩個混合在一起的方法有些複雜。有效的使用SA的方法是先了解這兩種不同的工具集,這是兩個不同的概念,而大家常常 混交SQL語言構造器和ORM。關鍵的不同是,使用cursor形式的結果集時使用的是SQL語言構造器;而使用類實例進行管理時使用的是ORM。
本指南首先介紹SQL語言構造器,首先需要聲明的數據庫信息叫做 table metadata 。本指南包含了一些SQL構造的例子,包括如何有效的使用SQL語言構造器的例子。
4 操作數據庫對象
在SQLAlchemy的核心哲學中表格和類是不同的。因爲如此,SQLAlchemy提供了構造表格的方法(使用表格的元信息table metadata)。所以我們從構造表格的元信息對象和定製操作他們的對象開始。稍後我們可以看到SQLAlchemy的ORM,提供了表格元信息的高層 封裝,允許我們隨心所欲的裝載和保存Python類。
4.1 定義元信息,綁定到引擎
首先,你的表格必須已經在MetaData集合中。我們將要創建簡單(handy)表格的MetaData,並自動連接到引擎(將一個模式(schema)對象連接到引擎成爲綁定binding):
>>> metadata=BoundMetaData(db)
一個構造BoundMetaData對象的等同方法是直接使用引擎URL,這將會幫我們調用 create_engine
>>> metadata=BoundMetaData("sqlite:///tutorial.db")
現在,我們告知metadata關於數據庫中的表格,我們可以使用(issue)CREATE語句來創建表格,並且通過他們來創建和執行SQL語 句,除非需要打開和關閉任何連接。這都是自動完成的。注意這個功能是推薦使用的。SQLAlchemy包含了使用模式進行連接管理和SQL構造的全部功 能,並可以在任何引擎上進行操作。
本教程的目的,是教會大家使用”bound”對象,他可以使得代碼簡單和易讀。
4.2 創建表格
使用metadata作爲基本連接,我們可以創建表格:
>>> users_table=Table('user',metadata, ... Column('user_id',Integer,primary_key=True), ... Column('user_name',String(40)), ... Column('password',String(10)) ... )
有如你看到的,我們剛剛定義了一個叫做users的表格並擁有3個列:user_id作爲主鍵,user_name和password。它現在只是 一個對象而與數據庫中的表格沒有必然聯繫。爲了讓表格生效,我們使用create()方法。有趣的是,我們可以讓SQLAlchemy發送SQL語句到數 據庫時同時顯示SQL語句,只要設置BoundMetaData關聯的Engine的echo選項即可:
>>> metadata.engine.echo=True >>> users_table.create() CREATE TABLE users ( user_id INTEGER NOT NULL, user_name VARCHAR(40), password VARCHAR(10), PRIMARY KEY (user_id) ) ...
或者,如果users表格已經存在了(比如你第二次運行這些例子),在這種情況下你可以跳過create()方法的調用。你設置可以跳過列定義,而是讓SQLAlchemy自動從數據庫裝入定義:
>>> users_table=Table('users',metadata,autoload=True) >>> list(users_table.columns)[0].name 'user_id'
關於表格元信息的文檔在”Database Meda Data”中。
4.3 插入記錄
插入記錄是通過表格對象的insert()方法實現的,這將會定義一個子句對象(clause object)(就是CluseElement)來代理INSERT語句:
>>> i=users_table.insert() >>> i <sqlalchemy.sql._Insert object at 0x...> >>> print i INSERT INTO users (user_id,user_name,password) VALUES (?,?,?)
當我們創建這個插入語句對象時,語句本身也綁定到了Engine,並已經可以執行了。子句對象的execute()方法將會將對象編譯爲特定引擎的SQL方言,並且執行語句:
>>> i.execute(user_name='Mary',password='secure') INSERT INTO users (user_name,password) VALUES (?,?) ['Mary','secure'] COMMIT <sqlalchemy.engine.base.ResultProxy object at 0x...> >>> i.execute({'user_name':'Tom'},{'user_name':'Fred'},{'user_name':'Harry'}) INSERT INTO users (user_name) VALUES (?) [['Tom'],['Fred'],['Harry']] COMMIT <sqlalchemy.engine.base.ResultProxy object at 0x...>
注意VALUES子句會自動調整參數數量。這是因爲ClauseElement的編譯步驟並不依賴於特定的數據庫,執行的參數也是如此。
當構造子句對象時,SQLAlchemy會綁定所有的值到參數。在構造時,參數綁定總是依靠鍵值對。在編譯時,SQLAlchemy會轉換他們到適當的格式,基於DBAPI的參數風格。這在DBAPI中描述的參數位置綁定中同樣工作的很好。
這些文檔繼承於”Inserts”。
4.4 查詢
我們可以檢查users表中已經存在的數據。方法同插入的例子,只是你需要調用表格的select()方法:
>>> s=users_table.select() >>> print s SELECT users.user_id,users.user_name,users.password FROM users >>> r=s.execute() SELECT users.user_id,users.user_name,users.password FROM users []
這時,我們並沒有忽略execute()的返回值。他是一個ResultProxy實例,保存了結果,而行爲非常類似於DBAPI中的cursor對象:
>>> r <sqlalchemy.engine.base.ResultProxy object at 0x...> >>> r.fetchone() (1,u'Mary',u'secure') >>> r.fetchall() [(2,u'Tom',None),(3,u'Fred',None),(4,u'Harry',None)]
查詢條件同Python表達式,使用Column對象。所有表達式中的Column對象都是ClauseElements的實例,例如Select、Insert和Table對象本身:
>>> r=users_table.select(users_table.c.user_name=='Harry').execute() SELECT users.user_id,users.user_name,users.password FROM users WHERE users.user_name=? ['Harry'] >>> row=r.fetchone() >>> print row (4,u'Harry',None)
所幸的是所有標準SQL操作都可以用Python表達式來構造,包括連接(join)、排序(order)、分組(group)、函數 (function)、子查詢(correlated subquery)、聯合(union)等等。關於查詢的文檔”Simple Select”。
4.5 操作記錄
你可以看到,當我們打印記錄時返回的可執行對象,它以元組打印記錄。這些記錄實際上同時支持列表(list)和字典(dict)接口。字典接口允許通過字符串的列名定位字段,或者通過Column對象:
>>> row.keys() ['user_id','user_name','password'] >>> row['user_id'],row[1],row[users_table.c.password] (4,u'Harry',None)
通過Column對象來定位是很方便的,因爲這樣避免了使用列名的方式。
結果集也是支持序列操作的。但是相對於select還有微小的差別,就是允許指定所選的列:
>>> for row in select([user_table.c.user_id,users_table.c.user_name]).execute(): ... print row SELECT users.user_id,users.user_name FROM users [] (1,u'Mary') ... ...
4.6 表間關係
我們可以創建第二個表格,email_addresses,這會引用users表。定義表間的關聯,使用ForeignKey構造。我們將來也會考慮使用表格的CREATE語句:
>>> email_addresses_table=Table('email_addresses',metadata, ... Column('address_id',Integer,primary_key=True), ... Column('email_address',String(100),nullable=False), ... Column('user_id',Integer,ForeignKey('users.user_id'))) >>> email_addresses_table.create() CREATE TABLE email_addresses ( address_id INTEGER NOT NULL, email_address VARCHAR(100) NOT NULL, user_id INTEGER, PRIMARY KEY (address_id), FOREIGN KEY(user_id) REFERENCES users (user_id) ) ...
上面的email_addresses表與表users通過ForeignKey(’users.user_id’)相聯繫。ForeignKey 的構造器需要一個Column對象,或一個字符串代表表明和列名。當使用了字符串參數時,引用的表必須已經存在於相同的MetaData對象中,當然,可 以是另外一個表。
下面可以嘗試插入數據:
>>> email_addresses_table.insert().execute( ... {'email_address':'[email protected]','user_id':2}, ... {'email_address':'[email protected]','user_id':1}) INSERT INTO email_addresses (email_address,user_id) VALUES (?,?) [['[email protected]',2],['[email protected]',1]] COMMIT <sqlalchemy.engine.base.ResultProxy object at 0x...>
在兩個表之間,我們可以使用 join 方法構造一個連接:
>>> r=users_table.join(email_addresses_table).select().execute() SELECT users.user_id, users.user_name, users.password, email_addresses.address_id, email_addresses.email_address, email_addresses.user_id FROM users JOIN email_addresses ON users.user_id=email_addresses.user_id [] >>> print [row for row in r] [(1, u'Mary', u'secure', 2, u'[email protected]', 1), (2,u'Tom', None, 1, u'[email protected]', 2)]
join 方法同時也是 sqlalchemy 命名空間中的一個獨立的函數。連接條件指明瞭表對象給定的外鍵。條件(condition),也可以叫做 “ON 子句” ,可以被明確的指定,例如這個例子我們查詢所有使用郵件地址作爲密碼的用戶:
>>> print join(users_table, email_addresses_table, ... and_(users_table.c.user_id==email_addresses_table.c.user_id, ... users_table.c.password==email_addresses_table.c.email_address) ... ) users JOIN email_addresses ON users.user_id=email_addresses.user_id AND users.password=email_address.email_address
5 使用ORM工作
現在我們已經有了一些表格和SQL操作的知識了,讓我們來看看SQLAlchemy的ORM (Object Relational Mapper) 。使用ORM,你可以將表格(和其他可以查詢的對象)同Python聯繫起來,放入映射集(Mappers)當中。然後你可以執行查詢並返回 對象實例 列表,而不是結果集。 對象實例 也被聯繫到一個叫做 Session 的對象,確保自動跟蹤對象的改變,並可以使用 flush 立即保存結果。
5.1 創建一個映射
一個映射通常對應一個Python類,其核心意圖是,“這個類的對象是用作這個表格的行來存儲的”。讓我們創建一個類叫做 User ,描述了一個用戶對象,並保存到 users 表格。:
>>> class User(object): ... def __repr__(self): ... return "%s(%r,%r)"%( ... self.__class__.__name__,self.user_name,self.password)
這個類是一個新形式(new style)的類(繼承自 object )並且不需要構造器(在需要時默認提供)。我們只實現了一個__repr__ 方法,用於顯示 User 對象的基本信息。注意 __repr__ 方法應用了實例變量 user_name 和password ,這是還沒定義的。我們可選的定義這些屬性,並可以進行處理;SQLAlchemy的 Mapper 的構造器會自動管理這些,而且會自動協調到 users 表格的列名。讓我們創建映射,並觀察這些屬性的定義:
>>> usermapper=mapper(User,users_table) >>> ul=User() >>> print ul.user_name None >>> print ul.password None
函數 mapper 返回新建的 Mapper 實例。這也是我們爲 User 類創建的第一個映射,也就是類的 主映射 。一般來說,不需要保存 usermapper 變量;SA的ORM會自動管理這個映射。
5.2 獲取會話(Session)
創建了一個映射之後,所有對映射的操作都需要一個重要的對象叫做 Session 。所有對象通過映射的載入和保存都 必須 通過 Session 對象,有如對象的工作空間一樣被加載到內存。特定對象在特定時間只能關聯到一個Session 。
缺省時,需要在載入和保存對象之前明確的創建 Session 對象。有多種方法來管理會話,但最簡明的方法是調用create_session()
>>> session=create_session() >>> session <sqlalchemy.orm.session.Session object at 0x...>
5.3 查詢對象
會話對象擁有載入和存儲對象的所有方法,同時也可以查看他們的狀態。會話還提供了查詢數據庫的方便接口,你可以獲取一個 Query 對象:
>>> query=session.query(User) >>> print query.select_by(user_name='Harry') SELECT users.user_name AS users_user_name, users.password AS users_password, users.user_id AS users_user_id FROM users WHERE users.user_name=? ORDER BY users.oid ['Harry'] [User(u'Harry',None)]
對象所有的查詢操作實際上都是通過 Query 的。 Mapper 對象的多種 select 方法也是偷偷的在使用 Query 對象來執行操作。一個 Query 總是聯繫到一個特定的會話上。
讓我們暫時關閉數據庫回顯,並嘗試 Query 的幾個方法。結尾是 _by 的方法主要用於對象的鍵參數。其他的方法允許接受 ClauseElement 對象,使用 Column 對象的Python表達式產生,同樣的方法我們在上一節使用過。使用 ClauseElement 結構來查詢更加冗長,但是更加靈活:
>>> metadata.engine.echo=False >>> print query.select(User.c.user_id==3) [User(u'Fred',None)] >>> print query.get(2) User(u'Tom',None) >>> print query.get_by(user_name='Mary') User(u'Mary',u'secure') >>> print query.selectfirst(User.c.password==None) User(u'Tom',None) >>> print query.count() 4
Note
User類有一個特別的屬性 c ,這個屬性描述了User映射表格對象的列。
User.c.user_name 等同於 users_table.c.user_name ,記得 User 是Python對象,而 users 是 Table 對象。
5.4 修改數據
作爲小經驗,我們看看如何做出修改。首先,創建一個新的用戶”Ed”,然後加入會話:
>>> ed=User() >>> ed.user_name='Ed' >>> ed.password='edspassword' >>> session.save(ed) >>> ed in session True
也可以修改數據庫中的其他對象。使用 Query 對象載入,然後改變:
>>> mary=query.get_by(user_name='Mary') >>> harry=query.get_by(user_name='Harry') >>> mary.password='marysnewpassword' >>> harry.password='harrysnewpassword'
這時,什麼東西都沒有保存到數據庫;我們所有的修改都在內存中。如果這時程序其他部分嘗試修改’Mary’會發生什麼呢?因爲使用相同的會話,所以再次載入’Mary’實際上定位到相同的主鍵’Mary’,並且 返回同一對象實例。這個行爲用在會話中確保數據庫的一致性:
>>> mary2=query.get_by(user_name='Mary') >>> mary is mary2 True
在唯一映射中,單一的會話可以確保安全的操作對象。
如果兩個不同的會話同時操作一個對象,會檢測到併發;SA會使用簡單的併發控制來保存對象,可以選擇使用擁有更強的使用ids的檢查。參考 Mapper Arguments 瞭解更多細節。
5.5 保存
在新建了用戶”ed”並對”Mary”和”Harry”作修改之後,我們再刪除”Fred”
>>> fred=query.get_by(user_name='Fred') >>> session.delete(fred)
然後發送更改到數據庫,使用會話的 flush() 方法。開啓回顯來查看過程:
>>> metadata.engine.echo=True >>> session.flush() BEGIN UPDATE users SET password=? WHERE users.user_id=? ['marysnewpassword',1] UPDATE users SET password=? WHERE users.user_id=? ['harrysnewpassword',4] INSERT INTO users (user_name,password) VALUES (?,?) ['Ed','edspassword'] DELETE FROM users WHERE users.user_id=? [3] COMMIT
5.6 關係
如果一個關係包含其他信息時,例如包含郵件地址的列表,我們可以在使用 relation() 創建 Mapper 時聲明。當然,你還可以對這個關係作很多事情,我們舉幾個簡單的例子。首先使用 users 表,它擁有一個外鍵關係連接到 email_addresses 表。 email_addresses 表中的每一行都有列 user_id 用來引用 users 表中的一行;而且 email_addresses 表中的多行可以引用 users 表中的同一行,這叫一對多關係。
首先,處理 email_addresses 表。我們創建一個新的類 Address 描述了 email_addresses 表中的一行,並且也創建了用於 Address 類的映射對象:
>>> class Address(object): ... def __init__(self,email_address): ... self.email_address=email_address ... def __repr__(self): ... return "%s(%r)"%( ... self.__class__.__name__,self.email_address) >>> mapper(Address, email_addresses_table) <sqlalchemy.orm.mapper.Mapper object at 0x...>
然後,我們通過使用 relation() 創建一個關係連接 User 和 Address 類,並且添加關係到 User 映射,使用add_property 函數:
>>> usermapper.add_property('addresses',relation(Address))
函數 relation() 需要一個類或映射作爲首參數,並且還有很多選項來控制行爲。 User 映射現在給每一個User 實例添加了一個屬性叫 addresses 。SA將會自動檢測這個一對多關係。並且隨後創建 addresses 列表。當新的 User 對象創建時,這個列表爲空 。
讓我們看看數據庫做了什麼。當我們修改映射的配置時,最好清理一下會話,讓所有載入的 User 對象可以重新載入:
>>> session.clear()
我們隨之可以使用 User 對象的 addresses 屬性來象列表一樣處理:
>>> mary=query.get_by(user_name='Mary') SELECT users.user_name AS users_user_name, users.password AS users_password, users.user_id AS users_user_id FROM users WHERE users.user_name=? ORDER BY users.oid LIMIT 1 OFFSET 0 ['Mary'] >>> print [a for a in mary.address] SELECT email_addresses.user_id AS email_address_user_id, email_addresses.address_id AS email_addresses_address_id, email_addresses.email_address AS email_addresses_email_address FROM email_addresses WHERE ?= email_addresses.user_id ORDER BY email_addresses.oid [1] [Address(u'[email protected]')]
向列表添加元素也很簡單。新的 Address 對象將會在調用會話的flush()時保存:
>>> mary.addresses.append(Address('[email protected]')) >>> session.flush() BEGIN INSERT INTO email_addresses (email_address,user_id) VALUEs (?,?) ['[email protected]',1] COMMIT
主文檔中關於使用映射的部分在如下地址:
http://www.sqlalchemy.org/docs/datamapping.myt#datamapping
5.7 事務
你可能已經注意到在上面例子中的 session.flush() ,SQLAlchemy使用 BEGIN 和 COMMIT 來使用數據庫事務。 flush() 方法使用事務來對一些記錄執行一系列的指令。如果希望在 flush() 之外使用更大規模的事務,可以通過 SessionTransaction 對象,其生成使用 session.create_transaction() 。下面將會執行一個非常複雜的 SELECT 語句,進行大量的修改,然後再創建一個有兩個郵箱的用戶,這些都在事務中完成。而且將會在中間使用 flush() 來保存,然後在執行最後的 commit() 時將所有改變寫入數據庫。我們把事務封裝在一個 try/except 語句塊當中確保資源的安全釋放:
>>> transaction=session.create_transaction() >>> try: ... (ed,harry,mary)=session.query(User).select( ... User.c.user_name.in_('Ed','Harry','Mary'), ... order_by=User.c.user_name ... ) ... del mary.address[1] ... harry.addresses.append(Address('[email protected]')) ... session.flush() ... print "***flushed the session***" ... fred=User() ... fred.user_name='fred_again' ... fred.addresses.append(Address('[email protected]')) ... fred.addresses.append(Address('[email protected]')) ... session.save(fred) ... transaction.commit() ... except: ... transaction.rollback() ... raise BEGIN SELECT users.user_name AS users_user_name, users.password AS users_password, users.user_id AS users_user_id FROM users WHERE users.user_name IN (?, ?, ?) ORDER BY users.user_name ['Ed', 'Harry', 'Mary'] SELECT email_addresses.user_id AS email_addresses_user_id, email_addresses.address_id AS email_addresses_address_id, email_addresses.email_address AS email_addresses_email_address FROM email_addresses WHERE ? = email_addresses.user_id ORDER BY email_addresses.oid [4] UPDATE email_addresses SET user_id=? WHERE email_addresses.address_id = ? [None, 3] INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?) ['[email protected]', 4] ***flushed the session*** INSERT INTO users (user_name, password) VALUES (?, ?) ['fred_again', None] INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?) ['[email protected]', 6] INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?) ['[email protected]', 6] COMMIT
對應的主文檔:
http://www.sqlalchemy.org/docs/unitofwork.myt#unitofwork
5.8 下一步
如上已經介紹了一下SQLAlchemy。但是不同的人可能有不同的做事方法,比如定義不同風格的映射的關係,所以還是允許使用原始的SQL來定義表格,還有Engine、SQL語句、數據庫連接等。
鏈接:http://gashero.yeax.com/?p=6#id9