您好,登錄后才能下訂單哦!
全文約 7991 字,讀完可能需要 12 分鐘。
環境 :Ubuntu 15.10 64-bit
SQLAlchemy是Python的ORM框架,它的理念是:數據庫的量級和性能重要于對象集合,而對象集合的抽象又重要于表和行。
直接通過pip安裝:
$ pip install sqlalchemy
打開Python,測試是否安裝成功:
>>> import sqlalchemy>>> sqlalchemy.__version__'1.0.9'
首先以SQLite為例,因為它比較簡單。
from sqlalchemy import create_engine, MetaDataengine = create_engine('sqlite:///foo.db', echo=True)metadata = MetaData(engine)
參數 sqlite:///foo.db 解釋為:
sqlite://<nohostname>/<path>
其中foo.db是相對路徑。也可寫成:
sqlite:///./foo.db
SQLAlchemy缺省使用Python內建的sqlite3模塊來連接或創建SQLite數據庫。執行完create_engine后,可以發現當前目錄多了foo.db文件,不妨用sqlite打開看看。
$ sqlite3 foo.dbSQLite version 3.8.11.1 2015-07-29 20:00:57Enter ".help" for usage hints.sqlite> .tables
注意這里用的是sqlite3而非sqlite,因為foo.db是經由Python內建的sqlite3模塊創建的。
再來看看連接MySQL時怎么創建引擎。 本文后續示例全部基于MySQL,這是與官方文檔不同的地方。 先在MySQL里創建一個測試數據庫:sa_test,后續示例都將基于這個數據庫。
mysql> CREATE DATABASE sa_test DEFAULT CHARACTER SET UTF8;
from sqlalchemy import create_engine, MetaDataengine = create_engine('mysql+mysqldb://root:******@localhost/sa_test', echo=True)metadata = MetaData(engine)
這里的參數看上去就比較復雜了,完整的格式為:
dialect+driver://username:password@host:port/database
這里driver用了mysqldb,詳見: MySQLdb:Python操作MySQL數據庫
引擎配置的詳細信息可參考官方文檔: Engine Configuration
前面在創建MetaData時綁定了引擎:
metadata = MetaData(engine)
當然也可以不綁定。綁定的好處是,后續很多調用 (比如 MetaData.create_all(),Table.create(),等等)就不用指定引擎了。
創建兩張表,user和address,address表里有一個user id的外鍵。 注意:表名沒有像官方文檔及很多人推薦的那樣使用復數形式,個人偏好而已,詳細討論請見StackOverflow的這個問題: Table NamingDilemma: Singular vs. Plural Names
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, ForeignKeyengine = create_engine('mysql+mysqldb://root:******@localhost/sa_test', echo=True)metadata = MetaData(engine)
user_table = Table('user', metadata, Column('id', Integer, primary_key=True), Column('name', String(50)), Column('fullname', String(100)) )address_table = Table('address', metadata, Column('id', Integer, primary_key=True), Column('user_id', None, ForeignKey('user.id')), Column('email', String(128), nullable=False) )metadata.create_all()
執行完metadata.create_all()這一句,兩張表就創建好了,可以在MySQL里立即查看。
MetaData.create all()可以多次調用,不會報錯,它在內部會檢查表是否已經創建。 因為MetaData創建時已經綁定了引擎,所以此處create all()就不必再指定了,否則得寫成:
metadata.create_all(engine)
創建引擎時,echo參數為True,程序運行時便有很多調試信息打印出來。在這些調試信息中,可以看到如下兩條MySQL的CREATE TABLE語句:
CREATE TABLE user ( id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(50), fullname VARCHAR(100), PRIMARY KEY (id))CREATE TABLE address ( id INTEGER NOT NULL AUTO_INCREMENT, user_id INTEGER, email VARCHAR(128) NOT NULL, PRIMARY KEY (id), FOREIGN KEY(user_id) REFERENCES user (id))
除了metadata.create_all(),Table自己也有create方法:
create(bind=None, checkfirst=False)
參數bind一般就是指引擎。 參數checkfirst表示是否檢查表已經存在。為True時,若表已經存在,不報錯,只是什么也不做;為False時,若表已經存在,則將引發異常。 使用這個方法來創建這兩張表:
user_table.create(checkfirst=True)address_table.create(checkfirst=True)
這里忽略了bind參數,因為創建MetaData對象時已經綁定了引擎,而創建表對象時又傳入了metadata,所以順藤摸瓜,表自己是知道引擎的。 如果調整一下表的創建順序,就會報錯,因為address表里有一個user表的外鍵,而這時候user表還沒創建呢。所以,還是建議使用MetaData.create_all()吧,畢竟它也會檢查表是否已經存在。
表創建好了,一般也就不動了。所以實際應用時,往往表都已經存在,并不需要創建,只需把它們"導入"進來即可,這時就得使用autoload參數。
from sqlalchemy import create_engine, MetaData, Tableengine = create_engine('mysql+mysqldb://root:******@localhost/sa_test', echo=False)metadata = MetaData(engine)user_table = Table('user', metadata, autoload=True)print 'user' in metadata.tablesprint [c.name for c in user_table.columns]address_table = Table('address', metadata, autoload=True)print 'address' in metadata.tables
輸出:
True['id', 'name', 'fullname']True
如果MetaData沒有綁定引擎,則另需指定autoload_with參數:
user_table = Table('user', metadata, autoload=True, autoload_with=engine)
如果被反射的表外鍵引用了另一個表,那么被引用的表也會一并被反射。比如只反射address表,user表也一并被反射了。
from sqlalchemy import create_engine, MetaData, Tableengine = create_engine('mysql+mysqldb://root:******@localhost/sa_test', echo=False)metadata = MetaData(engine)address_table = Table('address', metadata, autoload=True)print 'user' in metadata.tablesprint 'address' in metadata.tables
輸出:
TrueTrue
插入數據之前,必須要有表對象,不管是新創建的,還是通過反射導入的。
要往表里插數據,先創建一個Insert對象:
ins = user_table.insert()print ins
打印這個Insert對象,可以看到它所對應的SQL語句:
INSERT INTO user (id, name, fullname) VALUES (%s, %s, %s)
如果連接的數據庫不是MySQL而是SQLite,那輸出可能就是下面這樣:
INSERT INTO user (id, name, fullname) VALUES (?, ?, ?)
可見SQLAlchemy幫我們封裝了不同數據庫之間語法的差異。 如果MetaData創建時沒有綁定引擎,那么輸出會略有不同:
INSERT INTO "user" (id, name, fullname) VALUES (:id, :name, :fullname)
這時SQLAlchemy還不知道具體的數據庫語法,表名加了引號("user"),列名也改用為:id之類一般性的格式。 此外,這條INSERT語句列出了user表里的每一列,而id在插入時一般是不需要指定的,可以通過Insert.values()方法加以限制:
ins = ins.values(name='adam', fullname='Adam Gu')print ins
限制后,id列已經沒有了:
INSERT INTO user (name, fullname) VALUES (%s, %s)
可見values()方法限制了INSERT語句所包含的列。但是我們指定的name和fullname的值并沒有打印出來,這兩個值保存在Insert對象里,只有等到執行時才會用到。
我們一直在說的引擎,可以理解成一個數據庫連接對象的倉庫,通過連接對象可以往數據庫發送具體的SQL語句。調用引擎的connect()方法可以獲取一個連接:
conn = engine.connect()
現在把前面的Insert對象丟給它來執行:
result = conn.execute(ins)
由調試信息可見具體的INSERT語句:
INSERT INTO user (name, fullname) VALUES (%s, %s)('adam', 'Adam Gu')COMMIT
返回值result是一個ResultProxy對象,ResultProxy是對DB- API中cursor的封裝。插入語句的結果并不常用,但是查詢語句肯定是要用到它的。 不妨在MySQL里看一下剛插入的數據。
mysql> select * from user;+----+------+----------+| id | name | fullname |+----+------+----------+| 1 | adam | Adam Gu |+----+------+----------+1 row in set (0.00 sec)
還記得前面的Insert對象使用values()方法來限制列嗎?
ins = ins.values(name='adam', fullname='Adam Gu')
這種方式其實不利于Insert對象的復用,更好的做法是把參數通過execute()方法傳進去:
ins = user_table.insert()conn.execute(ins, name='adam', fullname='Adam Gu')
Insert對象本身還是會包含所有列,最終INSERT語句里的列由execute()的參數決定。由調試信息可見具體的INSERT語句:
INSERT INTO user (name, fullname) VALUES (%s, %s)('adam', 'Adam Gu')COMMIT
一次插入多條記錄也很簡單,只要傳一個字典列表(每個字典的鍵必須一致)給execute()即可。
conn.execute(address_table.insert(), [ { 'user_id': 1, 'email': 'sprinfall@gmail.com' }, { 'user_id': 1, 'email': 'sprinfall@hotmail.com' }, ])
調試信息里具體的INSERT語句:
INSERT INTO address (user_id, email) VALUES (%s, %s)((1, 'sprinfall@gmail.com'), (1, 'sprinfall@hotmail.com'))COMMIT
在MySQL里看一下插入的地址:
mysql> select * from address;+----+---------+-----------------------+| id | user_id | email |+----+---------+-----------------------+| 1 | 1 | sprinfall@gmail.com || 2 | 1 | sprinfall@hotmail.com |+----+---------+-----------------------+2 rows in set (0.00 sec)
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。